chatccc 0.2.231 → 0.2.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.claude/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.cursor/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/README.md +90 -90
- package/agent-prompts/claude_specific.md +45 -45
- package/agent-prompts/codex_specific.md +2 -2
- package/agent-prompts/cursor_specific.md +13 -13
- package/bin/cccagent.mjs +17 -17
- package/im-skills/feishu-skill/receive-send-file.md +63 -63
- package/im-skills/feishu-skill/receive-send-image.md +24 -24
- package/im-skills/feishu-skill/skill.md +3 -3
- package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
- package/im-skills/wechat-file-skill/send-file.mjs +83 -83
- package/im-skills/wechat-file-skill/skill.md +10 -10
- package/im-skills/wechat-image-skill/skill.md +10 -10
- package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
- package/im-skills/wechat-video-skill/send-video.mjs +79 -79
- package/im-skills/wechat-video-skill/skill.md +10 -10
- package/package.json +1 -1
- package/scripts/postinstall-sharp-check.mjs +58 -58
- package/src/__tests__/agent-activity.test.ts +86 -86
- package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
- package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
- package/src/__tests__/builtin-chat-session.test.ts +449 -393
- package/src/__tests__/builtin-cli-json.test.ts +39 -39
- package/src/__tests__/builtin-config.test.ts +26 -26
- package/src/__tests__/builtin-context.test.ts +319 -202
- package/src/__tests__/builtin-file-tools.test.ts +240 -240
- package/src/__tests__/builtin-permissions.test.ts +211 -211
- package/src/__tests__/builtin-session-search.test.ts +228 -0
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/builtin-skills.test.ts +284 -284
- package/src/__tests__/builtin-web-tools.test.ts +220 -220
- package/src/__tests__/card-action-routing.test.ts +18 -18
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/ccc-adapter.test.ts +170 -170
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
- package/src/__tests__/chatgpt-subscription.test.ts +135 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
- package/src/__tests__/claude-adapter.test.ts +614 -614
- package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
- package/src/__tests__/codex-adapter.test.ts +58 -58
- package/src/__tests__/codex-raw-stream-log.test.ts +170 -170
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/crash-logging.test.ts +95 -1
- package/src/__tests__/cursor-adapter.test.ts +268 -268
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +164 -164
- package/src/__tests__/feishu-message-ingress.test.ts +138 -138
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/jsonl-stream.test.ts +79 -79
- package/src/__tests__/package-files.test.ts +24 -24
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/progress-reducer.test.ts +121 -121
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/response-stall.test.ts +49 -49
- package/src/__tests__/restart.test.ts +55 -10
- package/src/__tests__/session.test.ts +199 -199
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/sim-platform.test.ts +16 -16
- package/src/__tests__/startup-lifecycle.test.ts +231 -231
- package/src/__tests__/stop-session.test.ts +34 -34
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/terminal-error.test.ts +54 -54
- package/src/__tests__/terminal-renderer.test.ts +247 -247
- package/src/__tests__/update-command-guard.test.ts +144 -144
- package/src/__tests__/web-ui.test.ts +326 -326
- package/src/adapters/adapter-interface.ts +33 -33
- package/src/adapters/ccc-adapter.ts +141 -141
- package/src/adapters/claude-adapter.ts +620 -620
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +426 -426
- package/src/adapters/cursor-adapter.ts +681 -681
- package/src/adapters/jsonl-stream.ts +157 -157
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/agent-activity.ts +175 -175
- package/src/agent-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +91 -91
- package/src/agent-reload-config-rpc.ts +34 -34
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +6 -0
- package/src/builtin/config.ts +84 -84
- package/src/builtin/context.ts +465 -391
- package/src/builtin/file-log.ts +38 -38
- package/src/builtin/file-tools.ts +34 -0
- package/src/builtin/index.ts +24 -23
- package/src/builtin/proc-tree-kill.ts +61 -61
- package/src/builtin/progress/cards-helpers.ts +76 -76
- package/src/builtin/progress/reducer.ts +113 -113
- package/src/builtin/progress/terminal-renderer.ts +294 -294
- package/src/builtin/progress/view.ts +77 -77
- package/src/builtin/raw-stream-log.ts +124 -124
- package/src/builtin/session-search.ts +363 -0
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- package/src/builtin/skills.ts +205 -205
- package/src/builtin/web-tools.ts +313 -313
- package/src/card-action-routing.ts +14 -14
- package/src/chatgpt-subscription-rpc.ts +27 -27
- package/src/chatgpt-subscription.ts +299 -299
- package/src/chrome-devtools-guard.ts +318 -318
- package/src/codex-reset-actions.ts +184 -184
- package/src/feishu-api.ts +193 -193
- package/src/feishu-message-ingress.ts +195 -195
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -293
- package/src/index.ts +6 -1
- package/src/litellm-proxy.ts +374 -374
- package/src/orchestrator.ts +36 -13
- package/src/platform-adapter.ts +6 -6
- package/src/privacy.ts +118 -118
- package/src/progress/reducer.ts +113 -113
- package/src/progress/terminal-renderer.ts +294 -294
- package/src/progress/view.ts +77 -77
- package/src/response-stall.ts +28 -28
- package/src/runtime-reload.ts +34 -34
- package/src/session-chat-binding.ts +82 -82
- package/src/session-name.ts +8 -8
- package/src/session.ts +201 -201
- package/src/shared-prefix.ts +29 -29
- package/src/shared.ts +44 -0
- package/src/sim-platform.ts +20 -20
- package/src/startup-lifecycle.ts +250 -250
- package/src/stream-state.ts +21 -21
- package/src/terminal-error.ts +129 -129
- package/src/turn-cards.ts +117 -117
- package/src/update-command-guard.ts +165 -165
|
@@ -9,7 +9,7 @@ vi.mock("../adapters/proc-tree-kill.ts", () => ({
|
|
|
9
9
|
}));
|
|
10
10
|
|
|
11
11
|
// mock stream-state 以支持在测试中控制累积长度
|
|
12
|
-
const mockStreamStates = new Map<string, {
|
|
12
|
+
const mockStreamStates = new Map<string, {
|
|
13
13
|
accumulatedContent: string;
|
|
14
14
|
finalReply: string;
|
|
15
15
|
activity?: {
|
|
@@ -22,14 +22,14 @@ const mockStreamStates = new Map<string, {
|
|
|
22
22
|
autoEndedAt?: number;
|
|
23
23
|
turnCount?: number;
|
|
24
24
|
finalReplySentTurn?: number;
|
|
25
|
-
finalReplySentAt?: number;
|
|
26
|
-
terminalError?: {
|
|
27
|
-
kind: "network_timeout" | "network" | "authentication" | "rate_limit" | "provider" | "process" | "resource" | "unknown";
|
|
28
|
-
title: string;
|
|
29
|
-
message: string;
|
|
30
|
-
occurredAt: number;
|
|
31
|
-
};
|
|
32
|
-
}>();
|
|
25
|
+
finalReplySentAt?: number;
|
|
26
|
+
terminalError?: {
|
|
27
|
+
kind: "network_timeout" | "network" | "authentication" | "rate_limit" | "provider" | "process" | "resource" | "unknown";
|
|
28
|
+
title: string;
|
|
29
|
+
message: string;
|
|
30
|
+
occurredAt: number;
|
|
31
|
+
};
|
|
32
|
+
}>();
|
|
33
33
|
vi.mock("../stream-state.ts", () => ({
|
|
34
34
|
readStreamState: async (sid: string) => {
|
|
35
35
|
const state = mockStreamStates.get(sid);
|
|
@@ -40,8 +40,8 @@ vi.mock("../stream-state.ts", () => ({
|
|
|
40
40
|
finalReply: state.finalReply,
|
|
41
41
|
activity: state.activity,
|
|
42
42
|
finalReplySentTurn: state.finalReplySentTurn,
|
|
43
|
-
finalReplySentAt: state.finalReplySentAt,
|
|
44
|
-
terminalError: state.terminalError,
|
|
43
|
+
finalReplySentAt: state.finalReplySentAt,
|
|
44
|
+
terminalError: state.terminalError,
|
|
45
45
|
autoEndedAt: state.autoEndedAt,
|
|
46
46
|
status: state.status ?? "running",
|
|
47
47
|
chunkCount: 0,
|
|
@@ -66,14 +66,14 @@ vi.mock("../stream-state.ts", () => ({
|
|
|
66
66
|
autoEndedAt?: number;
|
|
67
67
|
turnCount?: number;
|
|
68
68
|
finalReplySentTurn?: number;
|
|
69
|
-
finalReplySentAt?: number;
|
|
70
|
-
terminalError?: {
|
|
71
|
-
kind: "network_timeout" | "network" | "authentication" | "rate_limit" | "provider" | "process" | "resource" | "unknown";
|
|
72
|
-
title: string;
|
|
73
|
-
message: string;
|
|
74
|
-
occurredAt: number;
|
|
75
|
-
};
|
|
76
|
-
}) => {
|
|
69
|
+
finalReplySentAt?: number;
|
|
70
|
+
terminalError?: {
|
|
71
|
+
kind: "network_timeout" | "network" | "authentication" | "rate_limit" | "provider" | "process" | "resource" | "unknown";
|
|
72
|
+
title: string;
|
|
73
|
+
message: string;
|
|
74
|
+
occurredAt: number;
|
|
75
|
+
};
|
|
76
|
+
}) => {
|
|
77
77
|
mockStreamStates.set(state.sessionId, {
|
|
78
78
|
accumulatedContent: state.accumulatedContent,
|
|
79
79
|
finalReply: state.finalReply,
|
|
@@ -82,8 +82,8 @@ vi.mock("../stream-state.ts", () => ({
|
|
|
82
82
|
autoEndedAt: state.autoEndedAt,
|
|
83
83
|
turnCount: state.turnCount,
|
|
84
84
|
finalReplySentTurn: state.finalReplySentTurn,
|
|
85
|
-
finalReplySentAt: state.finalReplySentAt,
|
|
86
|
-
terminalError: state.terminalError,
|
|
85
|
+
finalReplySentAt: state.finalReplySentAt,
|
|
86
|
+
terminalError: state.terminalError,
|
|
87
87
|
});
|
|
88
88
|
},
|
|
89
89
|
createEmptyStreamState: (sid: string, cwd: string, tool: string, turnCount: number) => ({
|
|
@@ -466,7 +466,7 @@ describe("runAgentSession process monitor", () => {
|
|
|
466
466
|
vi.useRealTimers();
|
|
467
467
|
});
|
|
468
468
|
|
|
469
|
-
it("marks the turn as error and sends a separate notice when the CLI process disappears", async () => {
|
|
469
|
+
it("marks the turn as error and sends a separate notice when the CLI process disappears", async () => {
|
|
470
470
|
const platform = mockPlatform("feishu");
|
|
471
471
|
setSessionPlatform(platform);
|
|
472
472
|
bindChatToSession("sid-process", "chat-process");
|
|
@@ -522,52 +522,52 @@ describe("runAgentSession process monitor", () => {
|
|
|
522
522
|
"chat-process",
|
|
523
523
|
expect.stringContaining("进程异常结束"),
|
|
524
524
|
);
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
it("persists and sends the root cause when the agent stream fails before producing output", async () => {
|
|
528
|
-
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
529
|
-
const platform = mockPlatform("feishu");
|
|
530
|
-
platform.cardCreate = vi.fn(async () => {
|
|
531
|
-
throw new Error("CardKit unavailable");
|
|
532
|
-
});
|
|
533
|
-
setSessionPlatform(platform);
|
|
534
|
-
bindChatToSession("sid-stream-error", "chat-stream-error");
|
|
535
|
-
recordLastActiveChat("sid-stream-error", "chat-stream-error");
|
|
536
|
-
|
|
537
|
-
const adapter: ToolAdapter = {
|
|
538
|
-
displayName: "CCC Agent",
|
|
539
|
-
sessionDescPrefix: "CCC Session:",
|
|
540
|
-
createSession: async () => ({ sessionId: "sid-stream-error" }),
|
|
541
|
-
getSessionInfo: async (sid) => ({ sessionId: sid, cwd: "/tmp" }),
|
|
542
|
-
closeSession: async () => {},
|
|
543
|
-
prompt: async function* () {
|
|
544
|
-
throw new Error(
|
|
545
|
-
"Failed after 3 attempts. Last error: Cannot connect to API: " +
|
|
546
|
-
"Connect Timeout Error (timeout: 10000ms)",
|
|
547
|
-
);
|
|
548
|
-
},
|
|
549
|
-
};
|
|
550
|
-
_setAdapterForToolForTest("ccc", adapter);
|
|
551
|
-
|
|
552
|
-
const outcome = await runAgentSession(
|
|
553
|
-
"sid-stream-error",
|
|
554
|
-
"prompt",
|
|
555
|
-
platform,
|
|
556
|
-
"chat-stream-error",
|
|
557
|
-
Date.now(),
|
|
558
|
-
"ccc",
|
|
559
|
-
);
|
|
560
|
-
|
|
561
|
-
const state = mockStreamStates.get("sid-stream-error");
|
|
562
|
-
expect(outcome).toBe("error");
|
|
563
|
-
expect(state?.status).toBe("error");
|
|
564
|
-
expect(state?.terminalError?.kind).toBe("network_timeout");
|
|
565
|
-
expect(state?.terminalError?.message).toContain("已重试 3 次");
|
|
566
|
-
expect(platform.sendText).toHaveBeenCalledWith(
|
|
567
|
-
"chat-stream-error",
|
|
568
|
-
expect.stringContaining("网络连接超时"),
|
|
569
|
-
);
|
|
570
|
-
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it("persists and sends the root cause when the agent stream fails before producing output", async () => {
|
|
528
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
529
|
+
const platform = mockPlatform("feishu");
|
|
530
|
+
platform.cardCreate = vi.fn(async () => {
|
|
531
|
+
throw new Error("CardKit unavailable");
|
|
532
|
+
});
|
|
533
|
+
setSessionPlatform(platform);
|
|
534
|
+
bindChatToSession("sid-stream-error", "chat-stream-error");
|
|
535
|
+
recordLastActiveChat("sid-stream-error", "chat-stream-error");
|
|
536
|
+
|
|
537
|
+
const adapter: ToolAdapter = {
|
|
538
|
+
displayName: "CCC Agent",
|
|
539
|
+
sessionDescPrefix: "CCC Session:",
|
|
540
|
+
createSession: async () => ({ sessionId: "sid-stream-error" }),
|
|
541
|
+
getSessionInfo: async (sid) => ({ sessionId: sid, cwd: "/tmp" }),
|
|
542
|
+
closeSession: async () => {},
|
|
543
|
+
prompt: async function* () {
|
|
544
|
+
throw new Error(
|
|
545
|
+
"Failed after 3 attempts. Last error: Cannot connect to API: " +
|
|
546
|
+
"Connect Timeout Error (timeout: 10000ms)",
|
|
547
|
+
);
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
_setAdapterForToolForTest("ccc", adapter);
|
|
551
|
+
|
|
552
|
+
const outcome = await runAgentSession(
|
|
553
|
+
"sid-stream-error",
|
|
554
|
+
"prompt",
|
|
555
|
+
platform,
|
|
556
|
+
"chat-stream-error",
|
|
557
|
+
Date.now(),
|
|
558
|
+
"ccc",
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
const state = mockStreamStates.get("sid-stream-error");
|
|
562
|
+
expect(outcome).toBe("error");
|
|
563
|
+
expect(state?.status).toBe("error");
|
|
564
|
+
expect(state?.terminalError?.kind).toBe("network_timeout");
|
|
565
|
+
expect(state?.terminalError?.message).toContain("已重试 3 次");
|
|
566
|
+
expect(platform.sendText).toHaveBeenCalledWith(
|
|
567
|
+
"chat-stream-error",
|
|
568
|
+
expect.stringContaining("网络连接超时"),
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
571
|
|
|
572
572
|
it("recreates the progress card when the initial CardKit send fails", async () => {
|
|
573
573
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
@@ -907,7 +907,7 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
907
907
|
});
|
|
908
908
|
});
|
|
909
909
|
|
|
910
|
-
it("does not apply the reply-stall timeout while an Agent is compacting context", async () => {
|
|
910
|
+
it("does not apply the reply-stall timeout while an Agent is compacting context", async () => {
|
|
911
911
|
vi.setSystemTime(0);
|
|
912
912
|
_setResponseStallTimeoutForTest(180_000);
|
|
913
913
|
_setResponseStallCheckIntervalForTest(1_000);
|
|
@@ -932,10 +932,10 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
932
932
|
signal?: AbortSignal,
|
|
933
933
|
options?: ToolPromptOptions,
|
|
934
934
|
) {
|
|
935
|
-
options?.onSessionCreated?.(closeSession);
|
|
936
|
-
options?.onProcessStart?.({ pid: 4343 });
|
|
937
|
-
yield { type: "assistant", blocks: [{ type: "agent_status", status: "compacting" }] };
|
|
938
|
-
await new Promise<void>((resolve) => {
|
|
935
|
+
options?.onSessionCreated?.(closeSession);
|
|
936
|
+
options?.onProcessStart?.({ pid: 4343 });
|
|
937
|
+
yield { type: "assistant", blocks: [{ type: "agent_status", status: "compacting" }] };
|
|
938
|
+
await new Promise<void>((resolve) => {
|
|
939
939
|
if (signal?.aborted) {
|
|
940
940
|
resolve();
|
|
941
941
|
return;
|
|
@@ -957,11 +957,11 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
957
957
|
);
|
|
958
958
|
|
|
959
959
|
await vi.waitFor(() => {
|
|
960
|
-
expect(activePrompts.get("sid-starting-stall")).toMatchObject({
|
|
961
|
-
processPid: 4343,
|
|
962
|
-
});
|
|
963
|
-
expect(activePrompts.get("sid-starting-stall")?.responseProgress).toBeUndefined();
|
|
964
|
-
expect(closeSession).toHaveBeenCalledTimes(0);
|
|
960
|
+
expect(activePrompts.get("sid-starting-stall")).toMatchObject({
|
|
961
|
+
processPid: 4343,
|
|
962
|
+
});
|
|
963
|
+
expect(activePrompts.get("sid-starting-stall")?.responseProgress).toBeUndefined();
|
|
964
|
+
expect(closeSession).toHaveBeenCalledTimes(0);
|
|
965
965
|
});
|
|
966
966
|
|
|
967
967
|
await vi.advanceTimersByTimeAsync(181_001);
|
|
@@ -972,17 +972,17 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
972
972
|
.length;
|
|
973
973
|
|
|
974
974
|
// 旧实现不会结束真正的零事件启动;先清理测试会话,避免失败用例悬挂。
|
|
975
|
-
stopSession("sid-starting-stall");
|
|
976
|
-
await vi.advanceTimersByTimeAsync(1_000);
|
|
977
|
-
await runPromise;
|
|
978
|
-
|
|
979
|
-
expect(stateAfterDeadline).toMatchObject({
|
|
980
|
-
status: "running",
|
|
981
|
-
activity: { kind: "compacting" },
|
|
982
|
-
});
|
|
983
|
-
expect(closeCountAfterDeadline).toBe(0);
|
|
984
|
-
expect(treeKillCountAfterDeadline).toBe(0);
|
|
985
|
-
});
|
|
975
|
+
stopSession("sid-starting-stall");
|
|
976
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
977
|
+
await runPromise;
|
|
978
|
+
|
|
979
|
+
expect(stateAfterDeadline).toMatchObject({
|
|
980
|
+
status: "running",
|
|
981
|
+
activity: { kind: "compacting" },
|
|
982
|
+
});
|
|
983
|
+
expect(closeCountAfterDeadline).toBe(0);
|
|
984
|
+
expect(treeKillCountAfterDeadline).toBe(0);
|
|
985
|
+
});
|
|
986
986
|
|
|
987
987
|
it("self-heals a missing trigger-chat binding and gives automatic recovery the normal card lifecycle", async () => {
|
|
988
988
|
vi.setSystemTime(0);
|
|
@@ -1425,7 +1425,7 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
1425
1425
|
expect(receivedPrompts[1]).toContain(recoveryPrompt);
|
|
1426
1426
|
expect(platform.sendText).toHaveBeenCalledWith(
|
|
1427
1427
|
"chat-recovery-limit",
|
|
1428
|
-
"⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。",
|
|
1428
|
+
"⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。",
|
|
1429
1429
|
);
|
|
1430
1430
|
});
|
|
1431
1431
|
|
|
@@ -1627,7 +1627,7 @@ describe("unified display loop activity status", () => {
|
|
|
1627
1627
|
});
|
|
1628
1628
|
});
|
|
1629
1629
|
|
|
1630
|
-
describe("unified display loop terminal card update", () => {
|
|
1630
|
+
describe("unified display loop terminal card update", () => {
|
|
1631
1631
|
beforeEach(() => {
|
|
1632
1632
|
vi.useFakeTimers();
|
|
1633
1633
|
resetState();
|
|
@@ -1641,7 +1641,7 @@ describe("unified display loop terminal card update", () => {
|
|
|
1641
1641
|
vi.useRealTimers();
|
|
1642
1642
|
});
|
|
1643
1643
|
|
|
1644
|
-
it("shows a distinct auto-ended state and sends a warning even with an empty reply", async () => {
|
|
1644
|
+
it("shows a distinct auto-ended state and sends a warning even with an empty reply", async () => {
|
|
1645
1645
|
const platform = mockPlatform("feishu");
|
|
1646
1646
|
setSessionPlatform(platform);
|
|
1647
1647
|
|
|
@@ -1683,115 +1683,115 @@ describe("unified display loop terminal card update", () => {
|
|
|
1683
1683
|
expect(card.header.title.content).toBe("已自动结束 · 3分钟无新内容");
|
|
1684
1684
|
expect(card.header.template).toBe("orange");
|
|
1685
1685
|
expect(platform.sendText).toHaveBeenCalledWith(
|
|
1686
|
-
"chat-auto-ended",
|
|
1687
|
-
"⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。本轮没有可发送的回复内容。",
|
|
1686
|
+
"chat-auto-ended",
|
|
1687
|
+
"⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。本轮没有可发送的回复内容。",
|
|
1688
1688
|
);
|
|
1689
1689
|
expect(displayCards.has("chat-auto-ended")).toBe(false);
|
|
1690
|
-
});
|
|
1691
|
-
|
|
1692
|
-
it("shows the stream root cause in the error card without a duplicate text notice", async () => {
|
|
1693
|
-
const platform = mockPlatform("feishu");
|
|
1694
|
-
setSessionPlatform(platform);
|
|
1695
|
-
|
|
1696
|
-
bindChatToSession("sid-network-error", "chat-network-error");
|
|
1697
|
-
recordLastActiveChat("sid-network-error", "chat-network-error");
|
|
1698
|
-
sessionInfoMap.set("chat-network-error", {
|
|
1699
|
-
sessionId: "sid-network-error",
|
|
1700
|
-
turnCount: 1,
|
|
1701
|
-
lastContextTokens: 0,
|
|
1702
|
-
startTime: 0,
|
|
1703
|
-
tool: "ccc",
|
|
1704
|
-
});
|
|
1705
|
-
displayCards.set("chat-network-error", {
|
|
1706
|
-
cardId: "card-network-error",
|
|
1707
|
-
sequence: 1,
|
|
1708
|
-
cardBusy: false,
|
|
1709
|
-
cardCreatedAt: Date.now(),
|
|
1710
|
-
lastSentContent: "",
|
|
1711
|
-
streamErrorNotified: false,
|
|
1712
|
-
sessionId: "sid-network-error",
|
|
1713
|
-
turnCount: 1,
|
|
1714
|
-
dotCount: 0,
|
|
1715
|
-
});
|
|
1716
|
-
mockStreamStates.set("sid-network-error", {
|
|
1717
|
-
accumulatedContent: "",
|
|
1718
|
-
finalReply: "",
|
|
1719
|
-
status: "error",
|
|
1720
|
-
turnCount: 1,
|
|
1721
|
-
terminalError: {
|
|
1722
|
-
kind: "network_timeout",
|
|
1723
|
-
title: "网络连接超时",
|
|
1724
|
-
message: "连接模型服务失败,已重试 3 次,单次连接等待 10 秒。请检查网络、VPN或模型服务状态后重试。",
|
|
1725
|
-
occurredAt: Date.now(),
|
|
1726
|
-
},
|
|
1727
|
-
});
|
|
1728
|
-
|
|
1729
|
-
startUnifiedDisplayLoop();
|
|
1730
|
-
await vi.advanceTimersByTimeAsync(3_000);
|
|
1731
|
-
|
|
1732
|
-
const payload = vi.mocked(platform.cardUpdate).mock.calls[0]?.[1];
|
|
1733
|
-
const card = JSON.parse(payload as string) as {
|
|
1734
|
-
header: { title: { content: string }; template?: string };
|
|
1735
|
-
body: { elements: Array<{ content?: string }> };
|
|
1736
|
-
};
|
|
1737
|
-
expect(card.header.title.content).toBe("异常结束 · 网络连接超时");
|
|
1738
|
-
expect(card.header.template).toBe("red");
|
|
1739
|
-
expect(card.body.elements[0]?.content).toContain("已重试 3 次");
|
|
1740
|
-
expect(platform.sendText).not.toHaveBeenCalled();
|
|
1741
|
-
expect(mockStreamStates.get("sid-network-error")?.finalReplySentTurn).toBe(1);
|
|
1742
|
-
expect(displayCards.has("chat-network-error")).toBe(false);
|
|
1743
|
-
});
|
|
1744
|
-
|
|
1745
|
-
it("falls back to a root-cause text when the error card cannot be updated", async () => {
|
|
1746
|
-
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
1747
|
-
const platform = mockPlatform("feishu");
|
|
1748
|
-
platform.cardUpdate = vi.fn(async () => {
|
|
1749
|
-
throw new Error("CardKit unavailable");
|
|
1750
|
-
});
|
|
1751
|
-
setSessionPlatform(platform);
|
|
1752
|
-
|
|
1753
|
-
bindChatToSession("sid-error-fallback", "chat-error-fallback");
|
|
1754
|
-
recordLastActiveChat("sid-error-fallback", "chat-error-fallback");
|
|
1755
|
-
sessionInfoMap.set("chat-error-fallback", {
|
|
1756
|
-
sessionId: "sid-error-fallback",
|
|
1757
|
-
turnCount: 1,
|
|
1758
|
-
lastContextTokens: 0,
|
|
1759
|
-
startTime: 0,
|
|
1760
|
-
tool: "ccc",
|
|
1761
|
-
});
|
|
1762
|
-
displayCards.set("chat-error-fallback", {
|
|
1763
|
-
cardId: "card-error-fallback",
|
|
1764
|
-
sequence: 1,
|
|
1765
|
-
cardBusy: false,
|
|
1766
|
-
cardCreatedAt: Date.now(),
|
|
1767
|
-
lastSentContent: "",
|
|
1768
|
-
streamErrorNotified: false,
|
|
1769
|
-
sessionId: "sid-error-fallback",
|
|
1770
|
-
turnCount: 1,
|
|
1771
|
-
dotCount: 0,
|
|
1772
|
-
});
|
|
1773
|
-
mockStreamStates.set("sid-error-fallback", {
|
|
1774
|
-
accumulatedContent: "",
|
|
1775
|
-
finalReply: "",
|
|
1776
|
-
status: "error",
|
|
1777
|
-
turnCount: 1,
|
|
1778
|
-
terminalError: {
|
|
1779
|
-
kind: "network_timeout",
|
|
1780
|
-
title: "网络连接超时",
|
|
1781
|
-
message: "连接模型服务失败,已重试 3 次。",
|
|
1782
|
-
occurredAt: Date.now(),
|
|
1783
|
-
},
|
|
1784
|
-
});
|
|
1785
|
-
|
|
1786
|
-
startUnifiedDisplayLoop();
|
|
1787
|
-
await vi.advanceTimersByTimeAsync(3_000);
|
|
1788
|
-
|
|
1789
|
-
expect(platform.sendText).toHaveBeenCalledWith(
|
|
1790
|
-
"chat-error-fallback",
|
|
1791
|
-
expect.stringContaining("异常结束:网络连接超时"),
|
|
1792
|
-
);
|
|
1793
|
-
expect(displayCards.has("chat-error-fallback")).toBe(false);
|
|
1794
|
-
});
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1692
|
+
it("shows the stream root cause in the error card without a duplicate text notice", async () => {
|
|
1693
|
+
const platform = mockPlatform("feishu");
|
|
1694
|
+
setSessionPlatform(platform);
|
|
1695
|
+
|
|
1696
|
+
bindChatToSession("sid-network-error", "chat-network-error");
|
|
1697
|
+
recordLastActiveChat("sid-network-error", "chat-network-error");
|
|
1698
|
+
sessionInfoMap.set("chat-network-error", {
|
|
1699
|
+
sessionId: "sid-network-error",
|
|
1700
|
+
turnCount: 1,
|
|
1701
|
+
lastContextTokens: 0,
|
|
1702
|
+
startTime: 0,
|
|
1703
|
+
tool: "ccc",
|
|
1704
|
+
});
|
|
1705
|
+
displayCards.set("chat-network-error", {
|
|
1706
|
+
cardId: "card-network-error",
|
|
1707
|
+
sequence: 1,
|
|
1708
|
+
cardBusy: false,
|
|
1709
|
+
cardCreatedAt: Date.now(),
|
|
1710
|
+
lastSentContent: "",
|
|
1711
|
+
streamErrorNotified: false,
|
|
1712
|
+
sessionId: "sid-network-error",
|
|
1713
|
+
turnCount: 1,
|
|
1714
|
+
dotCount: 0,
|
|
1715
|
+
});
|
|
1716
|
+
mockStreamStates.set("sid-network-error", {
|
|
1717
|
+
accumulatedContent: "",
|
|
1718
|
+
finalReply: "",
|
|
1719
|
+
status: "error",
|
|
1720
|
+
turnCount: 1,
|
|
1721
|
+
terminalError: {
|
|
1722
|
+
kind: "network_timeout",
|
|
1723
|
+
title: "网络连接超时",
|
|
1724
|
+
message: "连接模型服务失败,已重试 3 次,单次连接等待 10 秒。请检查网络、VPN或模型服务状态后重试。",
|
|
1725
|
+
occurredAt: Date.now(),
|
|
1726
|
+
},
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
startUnifiedDisplayLoop();
|
|
1730
|
+
await vi.advanceTimersByTimeAsync(3_000);
|
|
1731
|
+
|
|
1732
|
+
const payload = vi.mocked(platform.cardUpdate).mock.calls[0]?.[1];
|
|
1733
|
+
const card = JSON.parse(payload as string) as {
|
|
1734
|
+
header: { title: { content: string }; template?: string };
|
|
1735
|
+
body: { elements: Array<{ content?: string }> };
|
|
1736
|
+
};
|
|
1737
|
+
expect(card.header.title.content).toBe("异常结束 · 网络连接超时");
|
|
1738
|
+
expect(card.header.template).toBe("red");
|
|
1739
|
+
expect(card.body.elements[0]?.content).toContain("已重试 3 次");
|
|
1740
|
+
expect(platform.sendText).not.toHaveBeenCalled();
|
|
1741
|
+
expect(mockStreamStates.get("sid-network-error")?.finalReplySentTurn).toBe(1);
|
|
1742
|
+
expect(displayCards.has("chat-network-error")).toBe(false);
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
it("falls back to a root-cause text when the error card cannot be updated", async () => {
|
|
1746
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
1747
|
+
const platform = mockPlatform("feishu");
|
|
1748
|
+
platform.cardUpdate = vi.fn(async () => {
|
|
1749
|
+
throw new Error("CardKit unavailable");
|
|
1750
|
+
});
|
|
1751
|
+
setSessionPlatform(platform);
|
|
1752
|
+
|
|
1753
|
+
bindChatToSession("sid-error-fallback", "chat-error-fallback");
|
|
1754
|
+
recordLastActiveChat("sid-error-fallback", "chat-error-fallback");
|
|
1755
|
+
sessionInfoMap.set("chat-error-fallback", {
|
|
1756
|
+
sessionId: "sid-error-fallback",
|
|
1757
|
+
turnCount: 1,
|
|
1758
|
+
lastContextTokens: 0,
|
|
1759
|
+
startTime: 0,
|
|
1760
|
+
tool: "ccc",
|
|
1761
|
+
});
|
|
1762
|
+
displayCards.set("chat-error-fallback", {
|
|
1763
|
+
cardId: "card-error-fallback",
|
|
1764
|
+
sequence: 1,
|
|
1765
|
+
cardBusy: false,
|
|
1766
|
+
cardCreatedAt: Date.now(),
|
|
1767
|
+
lastSentContent: "",
|
|
1768
|
+
streamErrorNotified: false,
|
|
1769
|
+
sessionId: "sid-error-fallback",
|
|
1770
|
+
turnCount: 1,
|
|
1771
|
+
dotCount: 0,
|
|
1772
|
+
});
|
|
1773
|
+
mockStreamStates.set("sid-error-fallback", {
|
|
1774
|
+
accumulatedContent: "",
|
|
1775
|
+
finalReply: "",
|
|
1776
|
+
status: "error",
|
|
1777
|
+
turnCount: 1,
|
|
1778
|
+
terminalError: {
|
|
1779
|
+
kind: "network_timeout",
|
|
1780
|
+
title: "网络连接超时",
|
|
1781
|
+
message: "连接模型服务失败,已重试 3 次。",
|
|
1782
|
+
occurredAt: Date.now(),
|
|
1783
|
+
},
|
|
1784
|
+
});
|
|
1785
|
+
|
|
1786
|
+
startUnifiedDisplayLoop();
|
|
1787
|
+
await vi.advanceTimersByTimeAsync(3_000);
|
|
1788
|
+
|
|
1789
|
+
expect(platform.sendText).toHaveBeenCalledWith(
|
|
1790
|
+
"chat-error-fallback",
|
|
1791
|
+
expect.stringContaining("异常结束:网络连接超时"),
|
|
1792
|
+
);
|
|
1793
|
+
expect(displayCards.has("chat-error-fallback")).toBe(false);
|
|
1794
|
+
});
|
|
1795
1795
|
|
|
1796
1796
|
it("does not repeat the same terminal CardKit sequence while the prompt is still active", async () => {
|
|
1797
1797
|
const platform = mockPlatform("feishu");
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
ABD_APPEND_PROMPT,
|
|
5
|
-
applySharedPrefix,
|
|
6
|
-
} from "../shared-prefix.ts";
|
|
7
|
-
|
|
8
|
-
describe("applySharedPrefix", () => {
|
|
9
|
-
it("leaves normal messages unchanged", () => {
|
|
10
|
-
expect(applySharedPrefix("帮我分析")).toEqual({
|
|
11
|
-
matched: false,
|
|
12
|
-
text: "帮我分析",
|
|
13
|
-
body: "帮我分析",
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("removes /abd without requiring a space", () => {
|
|
18
|
-
const result = applySharedPrefix("/abd帮我分析");
|
|
19
|
-
|
|
20
|
-
expect(result.matched).toBe(true);
|
|
21
|
-
expect(result.body).toBe("帮我分析");
|
|
22
|
-
expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("removes whitespace after /abd", () => {
|
|
26
|
-
const result = applySharedPrefix("/abd 帮我分析");
|
|
27
|
-
|
|
28
|
-
expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("keeps the appendix when the user body is empty", () => {
|
|
32
|
-
const result = applySharedPrefix("/abd ");
|
|
33
|
-
|
|
34
|
-
expect(result.text).toBe(`---\n${ABD_APPEND_PROMPT}`);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ABD_APPEND_PROMPT,
|
|
5
|
+
applySharedPrefix,
|
|
6
|
+
} from "../shared-prefix.ts";
|
|
7
|
+
|
|
8
|
+
describe("applySharedPrefix", () => {
|
|
9
|
+
it("leaves normal messages unchanged", () => {
|
|
10
|
+
expect(applySharedPrefix("帮我分析")).toEqual({
|
|
11
|
+
matched: false,
|
|
12
|
+
text: "帮我分析",
|
|
13
|
+
body: "帮我分析",
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("removes /abd without requiring a space", () => {
|
|
18
|
+
const result = applySharedPrefix("/abd帮我分析");
|
|
19
|
+
|
|
20
|
+
expect(result.matched).toBe(true);
|
|
21
|
+
expect(result.body).toBe("帮我分析");
|
|
22
|
+
expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("removes whitespace after /abd", () => {
|
|
26
|
+
const result = applySharedPrefix("/abd 帮我分析");
|
|
27
|
+
|
|
28
|
+
expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("keeps the appendix when the user body is empty", () => {
|
|
32
|
+
const result = applySharedPrefix("/abd ");
|
|
33
|
+
|
|
34
|
+
expect(result.text).toBe(`---\n${ABD_APPEND_PROMPT}`);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -62,21 +62,21 @@ describe("SimulatedPlatform", () => {
|
|
|
62
62
|
await expect(SimulatedPlatform.setChatAvatar("t", "ch1", "claude", "busy")).resolves.toBeUndefined();
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
it("纯函数 extractSessionInfo 正常工作", () => {
|
|
66
|
-
const result = SimulatedPlatform.extractSessionInfo("Claude Code Session: a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
|
67
|
-
expect(result).toEqual({ sessionId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", tool: "claude" });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("pure extractSessionInfo recognizes ccc session ids", () => {
|
|
71
|
-
const result = SimulatedPlatform.extractSessionInfo("CCC Session: session-20260702-121530-a1b2c3");
|
|
72
|
-
expect(result).toEqual({ sessionId: "session-20260702-121530-a1b2c3", tool: "ccc" });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("纯函数 formatDelayNotice 正常工作", () => {
|
|
76
|
-
const notice = SimulatedPlatform.formatDelayNotice(Date.now() - 20 * 60 * 1000, "测试消息");
|
|
77
|
-
expect(notice).toBeDefined();
|
|
78
|
-
expect(notice).toContain("延迟送达");
|
|
79
|
-
expect(notice).not.toContain("因服务离线");
|
|
65
|
+
it("纯函数 extractSessionInfo 正常工作", () => {
|
|
66
|
+
const result = SimulatedPlatform.extractSessionInfo("Claude Code Session: a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
|
67
|
+
expect(result).toEqual({ sessionId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", tool: "claude" });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("pure extractSessionInfo recognizes ccc session ids", () => {
|
|
71
|
+
const result = SimulatedPlatform.extractSessionInfo("CCC Session: session-20260702-121530-a1b2c3");
|
|
72
|
+
expect(result).toEqual({ sessionId: "session-20260702-121530-a1b2c3", tool: "ccc" });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("纯函数 formatDelayNotice 正常工作", () => {
|
|
76
|
+
const notice = SimulatedPlatform.formatDelayNotice(Date.now() - 20 * 60 * 1000, "测试消息");
|
|
77
|
+
expect(notice).toBeDefined();
|
|
78
|
+
expect(notice).toContain("延迟送达");
|
|
79
|
+
expect(notice).not.toContain("因服务离线");
|
|
80
80
|
// 近期消息不触发
|
|
81
81
|
expect(SimulatedPlatform.formatDelayNotice(Date.now(), "test")).toBeNull();
|
|
82
82
|
});
|
|
@@ -90,4 +90,4 @@ describe("SimulatedPlatform", () => {
|
|
|
90
90
|
// 未来时间戳(时钟偏移)不触发
|
|
91
91
|
expect(SimulatedPlatform.formatDelayNotice(now + 60 * 1000, "边界", now)).toBeNull();
|
|
92
92
|
});
|
|
93
|
-
});
|
|
93
|
+
});
|