chatccc 0.2.230 → 0.2.231
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 -76
- 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 +65 -1
- 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 +202 -163
- package/src/__tests__/builtin-permissions.test.ts +211 -211
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- 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 -136
- 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__/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 -110
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/response-stall.test.ts +49 -49
- package/src/__tests__/session.test.ts +11 -16
- 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-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 +24 -18
- package/src/adapters/ccc-adapter.ts +141 -131
- 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 -170
- 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 +9 -2
- package/src/builtin/config.ts +84 -84
- package/src/builtin/context.ts +62 -4
- package/src/builtin/file-log.ts +38 -38
- package/src/builtin/index.ts +83 -19
- 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 -108
- 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-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- 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/litellm-proxy.ts +374 -374
- package/src/platform-adapter.ts +6 -6
- package/src/privacy.ts +118 -118
- package/src/progress/reducer.ts +113 -108
- 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 +12 -11
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/startup-lifecycle.ts +250 -250
- package/src/turn-cards.ts +117 -117
- package/src/update-command-guard.ts +165 -165
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
import { gunzipSync } from "node:zlib";
|
|
2
|
-
import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
|
|
6
|
-
import { describe, expect, it } from "vitest";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
createRawStreamLog,
|
|
10
|
-
sanitizeLogPathSegment,
|
|
11
|
-
} from "../adapters/raw-stream-log.ts";
|
|
12
|
-
|
|
13
|
-
describe("raw stream log", () => {
|
|
14
|
-
it("does nothing when disabled", async () => {
|
|
15
|
-
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
16
|
-
try {
|
|
17
|
-
const log = await createRawStreamLog({
|
|
18
|
-
enabled: false,
|
|
19
|
-
rootDir: root,
|
|
20
|
-
tool: "cursor",
|
|
21
|
-
sessionId: "sid",
|
|
22
|
-
label: "turn",
|
|
23
|
-
maxBytesPerTurn: 1024,
|
|
24
|
-
retentionDays: 7,
|
|
25
|
-
});
|
|
26
|
-
expect(log).toBeNull();
|
|
27
|
-
} finally {
|
|
28
|
-
await rm(root, { recursive: true, force: true });
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("writes gzipped JSONL and can keep the file", async () => {
|
|
33
|
-
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
34
|
-
try {
|
|
35
|
-
const log = await createRawStreamLog({
|
|
36
|
-
enabled: true,
|
|
37
|
-
rootDir: root,
|
|
38
|
-
tool: "cursor",
|
|
39
|
-
sessionId: "sid/unsafe",
|
|
40
|
-
label: "turn:1",
|
|
41
|
-
maxBytesPerTurn: 1024,
|
|
42
|
-
retentionDays: 7,
|
|
43
|
-
});
|
|
44
|
-
expect(log).not.toBeNull();
|
|
45
|
-
log!.writeLine('{"type":"assistant","text":"hello"}');
|
|
46
|
-
await log!.close({ keep: true });
|
|
47
|
-
|
|
48
|
-
const raw = gunzipSync(await readFile(log!.filePath)).toString("utf-8");
|
|
49
|
-
expect(raw).toBe('{"type":"assistant","text":"hello"}\n');
|
|
50
|
-
expect(await stat(log!.filePath)).toBeTruthy();
|
|
51
|
-
} finally {
|
|
52
|
-
await rm(root, { recursive: true, force: true });
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("writes a truncation marker once maxBytesPerTurn is exceeded", async () => {
|
|
57
|
-
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
58
|
-
try {
|
|
59
|
-
const log = await createRawStreamLog({
|
|
60
|
-
enabled: true,
|
|
61
|
-
rootDir: root,
|
|
62
|
-
tool: "cursor",
|
|
63
|
-
sessionId: "sid",
|
|
64
|
-
label: "turn",
|
|
65
|
-
maxBytesPerTurn: 12,
|
|
66
|
-
retentionDays: 7,
|
|
67
|
-
});
|
|
68
|
-
log!.writeLine('{"a":1}');
|
|
69
|
-
log!.writeLine('{"too":"large"}');
|
|
70
|
-
log!.writeLine('{"ignored":true}');
|
|
71
|
-
await log!.close({ keep: true });
|
|
72
|
-
|
|
73
|
-
const raw = gunzipSync(await readFile(log!.filePath)).toString("utf-8");
|
|
74
|
-
expect(raw).toContain('{"a":1}');
|
|
75
|
-
expect(raw).toContain("chatccc_raw_stream_log_truncated");
|
|
76
|
-
expect(raw).not.toContain("ignored");
|
|
77
|
-
} finally {
|
|
78
|
-
await rm(root, { recursive: true, force: true });
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("removes the file when keep is false", async () => {
|
|
83
|
-
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
84
|
-
try {
|
|
85
|
-
const log = await createRawStreamLog({
|
|
86
|
-
enabled: true,
|
|
87
|
-
rootDir: root,
|
|
88
|
-
tool: "cursor",
|
|
89
|
-
sessionId: "sid",
|
|
90
|
-
label: "turn",
|
|
91
|
-
maxBytesPerTurn: 1024,
|
|
92
|
-
retentionDays: 7,
|
|
93
|
-
});
|
|
94
|
-
log!.writeLine('{"type":"result"}');
|
|
95
|
-
await log!.close({ keep: false });
|
|
96
|
-
await expect(stat(log!.filePath)).rejects.toThrow();
|
|
97
|
-
} finally {
|
|
98
|
-
await rm(root, { recursive: true, force: true });
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("sanitizes path segments", () => {
|
|
103
|
-
expect(sanitizeLogPathSegment("../sid:1/2")).toBe("sid_1_2");
|
|
104
|
-
expect(sanitizeLogPathSegment("")).toBe("unknown");
|
|
105
|
-
});
|
|
106
|
-
});
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createRawStreamLog,
|
|
10
|
+
sanitizeLogPathSegment,
|
|
11
|
+
} from "../adapters/raw-stream-log.ts";
|
|
12
|
+
|
|
13
|
+
describe("raw stream log", () => {
|
|
14
|
+
it("does nothing when disabled", async () => {
|
|
15
|
+
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
16
|
+
try {
|
|
17
|
+
const log = await createRawStreamLog({
|
|
18
|
+
enabled: false,
|
|
19
|
+
rootDir: root,
|
|
20
|
+
tool: "cursor",
|
|
21
|
+
sessionId: "sid",
|
|
22
|
+
label: "turn",
|
|
23
|
+
maxBytesPerTurn: 1024,
|
|
24
|
+
retentionDays: 7,
|
|
25
|
+
});
|
|
26
|
+
expect(log).toBeNull();
|
|
27
|
+
} finally {
|
|
28
|
+
await rm(root, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("writes gzipped JSONL and can keep the file", async () => {
|
|
33
|
+
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
34
|
+
try {
|
|
35
|
+
const log = await createRawStreamLog({
|
|
36
|
+
enabled: true,
|
|
37
|
+
rootDir: root,
|
|
38
|
+
tool: "cursor",
|
|
39
|
+
sessionId: "sid/unsafe",
|
|
40
|
+
label: "turn:1",
|
|
41
|
+
maxBytesPerTurn: 1024,
|
|
42
|
+
retentionDays: 7,
|
|
43
|
+
});
|
|
44
|
+
expect(log).not.toBeNull();
|
|
45
|
+
log!.writeLine('{"type":"assistant","text":"hello"}');
|
|
46
|
+
await log!.close({ keep: true });
|
|
47
|
+
|
|
48
|
+
const raw = gunzipSync(await readFile(log!.filePath)).toString("utf-8");
|
|
49
|
+
expect(raw).toBe('{"type":"assistant","text":"hello"}\n');
|
|
50
|
+
expect(await stat(log!.filePath)).toBeTruthy();
|
|
51
|
+
} finally {
|
|
52
|
+
await rm(root, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("writes a truncation marker once maxBytesPerTurn is exceeded", async () => {
|
|
57
|
+
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
58
|
+
try {
|
|
59
|
+
const log = await createRawStreamLog({
|
|
60
|
+
enabled: true,
|
|
61
|
+
rootDir: root,
|
|
62
|
+
tool: "cursor",
|
|
63
|
+
sessionId: "sid",
|
|
64
|
+
label: "turn",
|
|
65
|
+
maxBytesPerTurn: 12,
|
|
66
|
+
retentionDays: 7,
|
|
67
|
+
});
|
|
68
|
+
log!.writeLine('{"a":1}');
|
|
69
|
+
log!.writeLine('{"too":"large"}');
|
|
70
|
+
log!.writeLine('{"ignored":true}');
|
|
71
|
+
await log!.close({ keep: true });
|
|
72
|
+
|
|
73
|
+
const raw = gunzipSync(await readFile(log!.filePath)).toString("utf-8");
|
|
74
|
+
expect(raw).toContain('{"a":1}');
|
|
75
|
+
expect(raw).toContain("chatccc_raw_stream_log_truncated");
|
|
76
|
+
expect(raw).not.toContain("ignored");
|
|
77
|
+
} finally {
|
|
78
|
+
await rm(root, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("removes the file when keep is false", async () => {
|
|
83
|
+
const root = await mkdtemp(join(tmpdir(), "chatccc-raw-log-"));
|
|
84
|
+
try {
|
|
85
|
+
const log = await createRawStreamLog({
|
|
86
|
+
enabled: true,
|
|
87
|
+
rootDir: root,
|
|
88
|
+
tool: "cursor",
|
|
89
|
+
sessionId: "sid",
|
|
90
|
+
label: "turn",
|
|
91
|
+
maxBytesPerTurn: 1024,
|
|
92
|
+
retentionDays: 7,
|
|
93
|
+
});
|
|
94
|
+
log!.writeLine('{"type":"result"}');
|
|
95
|
+
await log!.close({ keep: false });
|
|
96
|
+
await expect(stat(log!.filePath)).rejects.toThrow();
|
|
97
|
+
} finally {
|
|
98
|
+
await rm(root, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("sanitizes path segments", () => {
|
|
103
|
+
expect(sanitizeLogPathSegment("../sid:1/2")).toBe("sid_1_2");
|
|
104
|
+
expect(sanitizeLogPathSegment("")).toBe("unknown");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
hasResponseStalled,
|
|
5
|
-
observeResponseProgress,
|
|
6
|
-
} from "../response-stall.ts";
|
|
7
|
-
|
|
8
|
-
describe("response stall detection", () => {
|
|
9
|
-
it("starts tracking while responding even when the total character count is zero", () => {
|
|
10
|
-
const observation = observeResponseProgress(undefined, true, 0, 1_000);
|
|
11
|
-
|
|
12
|
-
expect(observation).toEqual({
|
|
13
|
-
totalChars: 0,
|
|
14
|
-
unchangedSince: 1_000,
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("auto-ends only after the same character count has lasted three minutes", () => {
|
|
19
|
-
const first = observeResponseProgress(undefined, true, 12, 1_000);
|
|
20
|
-
const unchanged = observeResponseProgress(first, true, 12, 90_000);
|
|
21
|
-
|
|
22
|
-
expect(unchanged).toBe(first);
|
|
23
|
-
expect(hasResponseStalled(unchanged, 180_999, 180_000)).toBe(false);
|
|
24
|
-
expect(hasResponseStalled(unchanged, 181_000, 180_000)).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("restarts the timer whenever the total character count changes", () => {
|
|
28
|
-
const first = observeResponseProgress(undefined, true, 12, 1_000);
|
|
29
|
-
const changed = observeResponseProgress(first, true, 13, 150_000);
|
|
30
|
-
|
|
31
|
-
expect(changed).toEqual({
|
|
32
|
-
totalChars: 13,
|
|
33
|
-
unchangedSince: 150_000,
|
|
34
|
-
});
|
|
35
|
-
expect(hasResponseStalled(changed, 181_000, 180_000)).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("clears tracking outside responding and starts a fresh window after returning", () => {
|
|
39
|
-
const first = observeResponseProgress(undefined, true, 0, 1_000);
|
|
40
|
-
const cleared = observeResponseProgress(first, false, 0, 100_000);
|
|
41
|
-
const resumed = observeResponseProgress(cleared, true, 0, 200_000);
|
|
42
|
-
|
|
43
|
-
expect(cleared).toBeUndefined();
|
|
44
|
-
expect(resumed).toEqual({
|
|
45
|
-
totalChars: 0,
|
|
46
|
-
unchangedSince: 200_000,
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
});
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
hasResponseStalled,
|
|
5
|
+
observeResponseProgress,
|
|
6
|
+
} from "../response-stall.ts";
|
|
7
|
+
|
|
8
|
+
describe("response stall detection", () => {
|
|
9
|
+
it("starts tracking while responding even when the total character count is zero", () => {
|
|
10
|
+
const observation = observeResponseProgress(undefined, true, 0, 1_000);
|
|
11
|
+
|
|
12
|
+
expect(observation).toEqual({
|
|
13
|
+
totalChars: 0,
|
|
14
|
+
unchangedSince: 1_000,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("auto-ends only after the same character count has lasted three minutes", () => {
|
|
19
|
+
const first = observeResponseProgress(undefined, true, 12, 1_000);
|
|
20
|
+
const unchanged = observeResponseProgress(first, true, 12, 90_000);
|
|
21
|
+
|
|
22
|
+
expect(unchanged).toBe(first);
|
|
23
|
+
expect(hasResponseStalled(unchanged, 180_999, 180_000)).toBe(false);
|
|
24
|
+
expect(hasResponseStalled(unchanged, 181_000, 180_000)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("restarts the timer whenever the total character count changes", () => {
|
|
28
|
+
const first = observeResponseProgress(undefined, true, 12, 1_000);
|
|
29
|
+
const changed = observeResponseProgress(first, true, 13, 150_000);
|
|
30
|
+
|
|
31
|
+
expect(changed).toEqual({
|
|
32
|
+
totalChars: 13,
|
|
33
|
+
unchangedSince: 150_000,
|
|
34
|
+
});
|
|
35
|
+
expect(hasResponseStalled(changed, 181_000, 180_000)).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("clears tracking outside responding and starts a fresh window after returning", () => {
|
|
39
|
+
const first = observeResponseProgress(undefined, true, 0, 1_000);
|
|
40
|
+
const cleared = observeResponseProgress(first, false, 0, 100_000);
|
|
41
|
+
const resumed = observeResponseProgress(cleared, true, 0, 200_000);
|
|
42
|
+
|
|
43
|
+
expect(cleared).toBeUndefined();
|
|
44
|
+
expect(resumed).toEqual({
|
|
45
|
+
totalChars: 0,
|
|
46
|
+
unchangedSince: 200_000,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -907,7 +907,7 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
907
907
|
});
|
|
908
908
|
});
|
|
909
909
|
|
|
910
|
-
it("
|
|
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);
|
|
@@ -934,6 +934,7 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
934
934
|
) {
|
|
935
935
|
options?.onSessionCreated?.(closeSession);
|
|
936
936
|
options?.onProcessStart?.({ pid: 4343 });
|
|
937
|
+
yield { type: "assistant", blocks: [{ type: "agent_status", status: "compacting" }] };
|
|
937
938
|
await new Promise<void>((resolve) => {
|
|
938
939
|
if (signal?.aborted) {
|
|
939
940
|
resolve();
|
|
@@ -958,11 +959,8 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
958
959
|
await vi.waitFor(() => {
|
|
959
960
|
expect(activePrompts.get("sid-starting-stall")).toMatchObject({
|
|
960
961
|
processPid: 4343,
|
|
961
|
-
responseProgress: {
|
|
962
|
-
totalChars: 0,
|
|
963
|
-
unchangedSince: expect.any(Number),
|
|
964
|
-
},
|
|
965
962
|
});
|
|
963
|
+
expect(activePrompts.get("sid-starting-stall")?.responseProgress).toBeUndefined();
|
|
966
964
|
expect(closeSession).toHaveBeenCalledTimes(0);
|
|
967
965
|
});
|
|
968
966
|
|
|
@@ -974,19 +972,16 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
974
972
|
.length;
|
|
975
973
|
|
|
976
974
|
// 旧实现不会结束真正的零事件启动;先清理测试会话,避免失败用例悬挂。
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
await vi.advanceTimersByTimeAsync(1_000);
|
|
980
|
-
}
|
|
975
|
+
stopSession("sid-starting-stall");
|
|
976
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
981
977
|
await runPromise;
|
|
982
978
|
|
|
983
979
|
expect(stateAfterDeadline).toMatchObject({
|
|
984
|
-
status: "
|
|
985
|
-
|
|
986
|
-
autoEndedAt: expect.any(Number),
|
|
980
|
+
status: "running",
|
|
981
|
+
activity: { kind: "compacting" },
|
|
987
982
|
});
|
|
988
|
-
expect(closeCountAfterDeadline).toBe(
|
|
989
|
-
expect(treeKillCountAfterDeadline).
|
|
983
|
+
expect(closeCountAfterDeadline).toBe(0);
|
|
984
|
+
expect(treeKillCountAfterDeadline).toBe(0);
|
|
990
985
|
});
|
|
991
986
|
|
|
992
987
|
it("self-heals a missing trigger-chat binding and gives automatic recovery the normal card lifecycle", async () => {
|
|
@@ -1430,7 +1425,7 @@ describe("runAgentSession response stall watchdog", () => {
|
|
|
1430
1425
|
expect(receivedPrompts[1]).toContain(recoveryPrompt);
|
|
1431
1426
|
expect(platform.sendText).toHaveBeenCalledWith(
|
|
1432
1427
|
"chat-recovery-limit",
|
|
1433
|
-
"⚠️ 自动续跑仍连续 3
|
|
1428
|
+
"⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。",
|
|
1434
1429
|
);
|
|
1435
1430
|
});
|
|
1436
1431
|
|
|
@@ -1689,7 +1684,7 @@ describe("unified display loop terminal card update", () => {
|
|
|
1689
1684
|
expect(card.header.template).toBe("orange");
|
|
1690
1685
|
expect(platform.sendText).toHaveBeenCalledWith(
|
|
1691
1686
|
"chat-auto-ended",
|
|
1692
|
-
"⚠️
|
|
1687
|
+
"⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。本轮没有可发送的回复内容。",
|
|
1693
1688
|
);
|
|
1694
1689
|
expect(displayCards.has("chat-auto-ended")).toBe(false);
|
|
1695
1690
|
});
|
|
@@ -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
|
+
});
|