feat: release v0.77.0 with full pending changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
coso
2026-03-03 01:28:44 +08:00
co-authored by Claude Opus 4.6
parent 0f1be80ba7
commit 4fadd09c7c
56 changed files with 3151 additions and 551 deletions
+11
View File
@@ -159,6 +159,11 @@ export interface NovelListRunsRequest {
limit?: number;
}
export interface NovelDeleteCharacterRequest {
project_id: string;
character_id: string;
}
export async function createNovelProject(
request: CreateNovelProjectRequest,
): Promise<NovelProject> {
@@ -236,3 +241,9 @@ export async function listNovelRuns(
): Promise<NovelGenerationRun[]> {
return invoke("novel_list_runs", { request });
}
export async function deleteNovelCharacter(
request: NovelDeleteCharacterRequest,
): Promise<boolean> {
return invoke("novel_delete_character", { request });
}
+12 -1
View File
@@ -259,6 +259,11 @@ export async function resolveProjectRootPath(name: string): Promise<string> {
/** 获取项目列表 */
export async function listProjects(): Promise<Project[]> {
const projects = await invoke<RawProject[]>("workspace_list");
// 防御性编程:确保返回数组
if (!Array.isArray(projects)) {
console.warn("listProjects 返回非数组值:", projects);
return [];
}
return projects.map((project) => normalizeProject(project));
}
@@ -326,7 +331,13 @@ export async function listContents(
projectId: string,
query?: ListContentQuery,
): Promise<ContentListItem[]> {
return invoke("content_list", { projectId, query });
const contents = await invoke<ContentListItem[]>("content_list", { projectId, query });
// 防御性编程:确保返回数组
if (!Array.isArray(contents)) {
console.warn("listContents 返回非数组值:", contents);
return [];
}
return contents;
}
/** 更新内容 */
+4 -4
View File
@@ -3,12 +3,12 @@ import { resolveAppVersion } from "./appVersion";
describe("appVersion", () => {
it("优先返回首个有效版本号", () => {
expect(resolveAppVersion("0.76.1", "0.76.0")).toBe("0.76.1");
expect(resolveAppVersion("unknown", "0.76.0")).toBe("0.76.0");
expect(resolveAppVersion("", " ", "0.76.0")).toBe("0.76.0");
expect(resolveAppVersion("0.77.1", "0.77.0")).toBe("0.77.1");
expect(resolveAppVersion("unknown", "0.77.0")).toBe("0.77.0");
expect(resolveAppVersion("", " ", "0.77.0")).toBe("0.77.0");
});
it("无候选值时回退 package.json 版本", () => {
expect(resolveAppVersion(undefined, null, "unknown")).toBe("0.76.0");
expect(resolveAppVersion(undefined, null, "unknown")).toBe("0.77.0");
});
});
+131 -1
View File
@@ -176,6 +176,89 @@ const defaultMocks: Record<string, any> = {
remove_total: 0,
},
}),
get_server_diagnostics: () => ({
generated_at: new Date().toISOString(),
running: false,
host: "127.0.0.1",
port: 8787,
telemetry_summary: {
total_requests: 0,
successful_requests: 0,
failed_requests: 0,
timeout_requests: 0,
success_rate: 0,
avg_latency_ms: 0,
min_latency_ms: null,
max_latency_ms: null,
total_input_tokens: 0,
total_output_tokens: 0,
total_tokens: 0,
},
capability_routing: {
filter_eval_total: 0,
filter_excluded_total: 0,
filter_excluded_tools_total: 0,
filter_excluded_vision_total: 0,
filter_excluded_context_total: 0,
provider_fallback_total: 0,
model_fallback_total: 0,
all_candidates_excluded_total: 0,
},
response_cache: {
config: {
enabled: true,
ttl_secs: 600,
max_entries: 200,
max_body_bytes: 1048576,
cacheable_status_codes: [200],
},
stats: {
size: 0,
hits: 0,
misses: 0,
evictions: 0,
},
hit_rate_percent: 0,
},
request_dedup: {
config: {
enabled: true,
ttl_secs: 30,
wait_timeout_ms: 15000,
},
stats: {
inflight_size: 0,
completed_size: 0,
check_new_total: 0,
check_in_progress_total: 0,
check_completed_total: 0,
wait_success_total: 0,
wait_timeout_total: 0,
wait_no_result_total: 0,
complete_total: 0,
remove_total: 0,
},
replay_rate_percent: 0,
},
idempotency: {
config: {
enabled: false,
ttl_secs: 86400,
header_name: "Idempotency-Key",
},
stats: {
entries_size: 0,
in_progress_size: 0,
completed_size: 0,
check_new_total: 0,
check_in_progress_total: 0,
check_completed_total: 0,
complete_total: 0,
remove_total: 0,
},
replay_rate_percent: 0,
},
}),
check_server_status: () => ({
running: false,
host: "127.0.0.1",
@@ -752,7 +835,21 @@ const defaultMocks: Record<string, any> = {
clear_logs: () => ({}),
// Test 相关
test_api: () => ({ success: true, status: 200, body: "", time_ms: 0 }),
test_api: () => ({
success: true,
status: 200,
body: "",
time_ms: 0,
response_headers: {
"x-proxycast-request-id": "mock-request-id",
"x-proxycast-cache": "store",
"x-proxycast-dedup": "new",
"x-proxycast-idempotency": "new",
"x-proxycast-requested-provider": "openai",
"x-proxycast-effective-provider": "openai",
"x-proxycast-model": "gpt-4o-mini",
},
}),
// Kiro Credentials 相关
get_kiro_credentials: () => ({ loaded: false }),
@@ -832,8 +929,30 @@ const defaultMocks: Record<string, any> = {
close_screenshot_chat_window: () => ({}),
// Update 相关
get_update_check_settings: () => ({
enabled: true,
check_interval_hours: 24,
show_notification: true,
last_check_timestamp: 0,
skipped_version: null,
remind_later_until: null,
}),
get_update_notification_metrics: () => ({
shown_count: 0,
update_now_count: 0,
remind_later_count: 0,
skip_version_count: 0,
dismiss_count: 0,
update_now_rate: 0,
remind_later_rate: 0,
skip_version_rate: 0,
dismiss_rate: 0,
}),
record_update_notification_action: () => ({}),
download_update: () => ({ success: true }),
skip_update_version: () => ({}),
remind_update_later: () => Math.floor(Date.now() / 1000) + 24 * 3600,
dismiss_update_notification: () => Math.floor(Date.now() / 1000) + 24 * 3600,
close_update_window: () => ({}),
set_update_check_settings: () => ({ success: true }),
test_update_window: () => ({}),
@@ -915,6 +1034,17 @@ const defaultMocks: Record<string, any> = {
previousRootPath: null,
warning: null,
}),
workspace_get_projects_root: () => "/mock/workspace/projects",
workspace_resolve_project_path: (args: any) => `/mock/workspace/projects/${args?.name ?? "untitled"}`,
workspace_create: (args: any) => ({
id: `mock-project-${Date.now()}`,
name: args?.request?.name ?? "Mock Project",
rootPath: args?.request?.rootPath ?? "/mock/workspace/projects/mock-project",
workspaceType: args?.request?.workspaceType ?? "general",
createdAt: Date.now(),
updatedAt: Date.now(),
isArchived: false,
}),
};
/**
+39
View File
@@ -1,5 +1,7 @@
import { afterEach, describe, expect, it } from "vitest";
import {
buildWorkspaceRepairBatchSummary,
buildWorkspaceRepairSummary,
clearWorkspaceRepairHistory,
getWorkspaceRepairHistory,
recordWorkspaceRepair,
@@ -42,4 +44,41 @@ describe("workspaceHealthTelemetry", () => {
expect(history[0].workspace_id).toBe("ws-55");
expect(history[49].workspace_id).toBe("ws-6");
});
it("应生成可复制的自愈摘要", () => {
const summary = buildWorkspaceRepairSummary({
timestamp: "2026-03-02T10:00:00.000Z",
workspace_id: "ws-99",
root_path: "/tmp/ws-99",
source: "agent_chat_page",
});
expect(summary).toContain("ProxyCast Workspace 自愈记录");
expect(summary).toContain("Workspace ID: ws-99");
expect(summary).toContain("来源: agent_chat_page");
expect(summary).toContain("修复后路径: /tmp/ws-99");
});
it("应生成批量自愈摘要", () => {
const summary = buildWorkspaceRepairBatchSummary([
{
timestamp: "2026-03-02T10:00:00.000Z",
workspace_id: "ws-1",
root_path: "/tmp/ws-1",
source: "app_startup",
},
{
timestamp: "2026-03-02T10:02:00.000Z",
workspace_id: "ws-2",
root_path: "/tmp/ws-2",
source: "agent_chat_page",
},
]);
expect(summary).toContain("ProxyCast Workspace 自愈记录(最近)");
expect(summary).toContain("## 记录 1");
expect(summary).toContain("Workspace ID: ws-1");
expect(summary).toContain("## 记录 2");
expect(summary).toContain("Workspace ID: ws-2");
});
});
+32
View File
@@ -50,6 +50,38 @@ export function clearWorkspaceRepairHistory(): void {
localStorage.removeItem(WORKSPACE_REPAIR_HISTORY_KEY);
}
export function buildWorkspaceRepairSummary(
record: WorkspaceRepairRecord,
): string {
return [
"# ProxyCast Workspace 自愈记录",
`- 时间: ${record.timestamp}`,
`- Workspace ID: ${record.workspace_id}`,
`- 来源: ${record.source}`,
`- 修复后路径: ${record.root_path}`,
].join("\n");
}
export function buildWorkspaceRepairBatchSummary(
records: WorkspaceRepairRecord[],
): string {
if (records.length === 0) {
return "# ProxyCast Workspace 自愈记录\n- 暂无记录";
}
const lines: string[] = ["# ProxyCast Workspace 自愈记录(最近)"];
records.forEach((record, index) => {
lines.push(
`\n## 记录 ${index + 1}`,
`- 时间: ${record.timestamp}`,
`- Workspace ID: ${record.workspace_id}`,
`- 来源: ${record.source}`,
`- 修复后路径: ${record.root_path}`,
);
});
return lines.join("\n");
}
function readWorkspaceRepairHistory(): WorkspaceRepairRecord[] {
if (typeof localStorage === "undefined") return [];