chatccc 0.2.252 → 0.2.253

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.
Files changed (280) hide show
  1. package/README.md +1 -1
  2. package/bin/cccagent.mjs +12 -3
  3. package/bin/chatccc.mjs +15 -6
  4. package/deepccc-agent/os-prompts/darwin.md +8 -8
  5. package/deepccc-agent/os-prompts/linux.md +8 -8
  6. package/deepccc-agent/os-prompts/win32.md +11 -11
  7. package/deepccc-agent/package.json +65 -65
  8. package/dist/deepccc-agent/src/cli.js +634 -0
  9. package/dist/deepccc-agent/src/config.js +76 -0
  10. package/dist/deepccc-agent/src/context.js +348 -0
  11. package/dist/deepccc-agent/src/file-log.js +34 -0
  12. package/dist/deepccc-agent/src/file-tools.js +1208 -0
  13. package/dist/deepccc-agent/src/index.js +571 -0
  14. package/dist/deepccc-agent/src/permissions.js +170 -0
  15. package/dist/deepccc-agent/src/privacy.js +124 -0
  16. package/dist/deepccc-agent/src/proc-tree-kill.js +60 -0
  17. package/dist/deepccc-agent/src/progress/cards-helpers.js +70 -0
  18. package/dist/deepccc-agent/src/progress/reducer.js +102 -0
  19. package/dist/deepccc-agent/src/progress/terminal-renderer.js +264 -0
  20. package/dist/deepccc-agent/src/progress/view.js +30 -0
  21. package/dist/deepccc-agent/src/raw-stream-log.js +106 -0
  22. package/dist/deepccc-agent/src/session-search.js +276 -0
  23. package/dist/deepccc-agent/src/session-select.js +23 -0
  24. package/dist/deepccc-agent/src/sigint.js +26 -0
  25. package/dist/deepccc-agent/src/skills.js +178 -0
  26. package/dist/deepccc-agent/src/web-tools.js +246 -0
  27. package/dist/src/adapters/adapter-interface.js +19 -0
  28. package/dist/src/adapters/ccc-adapter.js +112 -0
  29. package/dist/src/adapters/claude-adapter.js +497 -0
  30. package/dist/src/adapters/claude-session-meta-store.js +92 -0
  31. package/dist/src/adapters/codex-adapter.js +279 -0
  32. package/dist/src/adapters/codex-session-meta-store.js +94 -0
  33. package/dist/src/adapters/cursor-adapter.js +491 -0
  34. package/dist/src/adapters/cursor-session-meta-store.js +116 -0
  35. package/dist/src/adapters/jsonl-stream.js +104 -0
  36. package/{src/adapters/proc-tree-kill.ts → dist/src/adapters/proc-tree-kill.js} +94 -97
  37. package/dist/src/adapters/raw-stream-log.js +106 -0
  38. package/dist/src/adapters/resource-monitor.js +113 -0
  39. package/dist/src/agent-activity.js +133 -0
  40. package/dist/src/agent-delegate-task-rpc.js +129 -0
  41. package/dist/src/agent-delegate-task.js +48 -0
  42. package/dist/src/agent-file-rpc.js +152 -0
  43. package/dist/src/agent-image-rpc.js +148 -0
  44. package/dist/src/agent-platform-routing.js +13 -0
  45. package/dist/src/agent-reload-config-rpc.js +23 -0
  46. package/dist/src/agent-rpc-body.js +87 -0
  47. package/dist/src/agent-stop-stuck.js +110 -0
  48. package/dist/src/card-action-routing.js +7 -0
  49. package/dist/src/card-plain-text.js +101 -0
  50. package/dist/src/cardkit.js +158 -0
  51. package/dist/src/cards.js +573 -0
  52. package/dist/src/chatgpt-subscription-rpc.js +18 -0
  53. package/dist/src/chatgpt-subscription.js +199 -0
  54. package/dist/src/chrome-devtools-guard.js +238 -0
  55. package/dist/src/claude-sdk-installer.js +249 -0
  56. package/dist/src/codex-reset-actions.js +143 -0
  57. package/dist/src/config-utils.js +149 -0
  58. package/dist/src/config.js +804 -0
  59. package/dist/src/cursor-usage.js +77 -0
  60. package/dist/src/exit-banner.js +28 -0
  61. package/dist/src/feishu-api.js +1404 -0
  62. package/dist/src/feishu-message-ingress.js +137 -0
  63. package/dist/src/feishu-platform.js +97 -0
  64. package/dist/src/format-message.js +252 -0
  65. package/dist/src/git-command.js +155 -0
  66. package/dist/src/im-skills.js +121 -0
  67. package/dist/src/index.js +833 -0
  68. package/dist/src/litellm-proxy.js +300 -0
  69. package/dist/src/orchestrator.js +2078 -0
  70. package/dist/src/package-root.js +26 -0
  71. package/dist/src/platform-adapter.js +7 -0
  72. package/dist/src/platform-startup.js +6 -0
  73. package/dist/src/privacy.js +100 -0
  74. package/dist/src/progress/reducer.js +102 -0
  75. package/dist/src/progress/terminal-renderer.js +264 -0
  76. package/dist/src/progress/view.js +30 -0
  77. package/dist/src/response-stall.js +14 -0
  78. package/dist/src/runtime-entry.js +13 -0
  79. package/dist/src/runtime-reload.js +19 -0
  80. package/dist/src/session-chat-binding.js +183 -0
  81. package/dist/src/session-name.js +7 -0
  82. package/dist/src/session.js +2144 -0
  83. package/dist/src/shared-prefix.js +16 -0
  84. package/dist/src/shared.js +493 -0
  85. package/dist/src/sim-agent.js +105 -0
  86. package/dist/src/sim-platform.js +142 -0
  87. package/dist/src/sim-store.js +231 -0
  88. package/dist/src/simplify.js +99 -0
  89. package/dist/src/startup-lifecycle.js +209 -0
  90. package/dist/src/stream-state.js +141 -0
  91. package/dist/src/terminal-error.js +100 -0
  92. package/dist/src/trace.js +50 -0
  93. package/dist/src/turn-cards.js +92 -0
  94. package/dist/src/update-command-guard.js +114 -0
  95. package/{src/web-ui.ts → dist/src/web-ui.js} +749 -823
  96. package/dist/src/wechat-platform.js +545 -0
  97. package/package.json +76 -74
  98. package/deepccc-agent/LICENSE +0 -201
  99. package/deepccc-agent/bin/deepccc.mjs +0 -26
  100. package/deepccc-agent/docs/cache-hit-rate-1.jpg +0 -0
  101. package/deepccc-agent/docs/cache-hit-rate-2.jpg +0 -0
  102. package/deepccc-agent/package-lock.json +0 -2027
  103. package/deepccc-agent/src/__tests__/chat-session.test.ts +0 -877
  104. package/deepccc-agent/src/__tests__/cli-json.test.ts +0 -49
  105. package/deepccc-agent/src/__tests__/config.test.ts +0 -34
  106. package/deepccc-agent/src/__tests__/context.test.ts +0 -341
  107. package/deepccc-agent/src/__tests__/file-tools.test.ts +0 -240
  108. package/deepccc-agent/src/__tests__/permissions.test.ts +0 -199
  109. package/deepccc-agent/src/__tests__/privacy.test.ts +0 -318
  110. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +0 -121
  111. package/deepccc-agent/src/__tests__/session-search.test.ts +0 -262
  112. package/deepccc-agent/src/__tests__/session-select.test.ts +0 -116
  113. package/deepccc-agent/src/__tests__/sigint.test.ts +0 -56
  114. package/deepccc-agent/src/__tests__/skills.test.ts +0 -284
  115. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +0 -247
  116. package/deepccc-agent/src/__tests__/web-tools.test.ts +0 -220
  117. package/deepccc-agent/src/cli.ts +0 -682
  118. package/deepccc-agent/src/config.ts +0 -101
  119. package/deepccc-agent/src/context.ts +0 -465
  120. package/deepccc-agent/src/file-log.ts +0 -38
  121. package/deepccc-agent/src/file-tools.ts +0 -1493
  122. package/deepccc-agent/src/index.ts +0 -710
  123. package/deepccc-agent/src/permissions.ts +0 -226
  124. package/deepccc-agent/src/privacy.ts +0 -141
  125. package/deepccc-agent/src/proc-tree-kill.ts +0 -61
  126. package/deepccc-agent/src/progress/cards-helpers.ts +0 -76
  127. package/deepccc-agent/src/progress/reducer.ts +0 -113
  128. package/deepccc-agent/src/progress/terminal-renderer.ts +0 -294
  129. package/deepccc-agent/src/progress/view.ts +0 -77
  130. package/deepccc-agent/src/raw-stream-log.ts +0 -124
  131. package/deepccc-agent/src/session-search.ts +0 -370
  132. package/deepccc-agent/src/session-select.ts +0 -48
  133. package/deepccc-agent/src/sigint.ts +0 -50
  134. package/deepccc-agent/src/skills.ts +0 -205
  135. package/deepccc-agent/src/web-tools.ts +0 -313
  136. package/deepccc-agent/tsconfig.build.json +0 -13
  137. package/deepccc-agent/tsconfig.json +0 -13
  138. package/deepccc-agent/vitest.config.ts +0 -7
  139. package/src/__tests__/adapter-interface.test.ts +0 -152
  140. package/src/__tests__/agent-activity.test.ts +0 -86
  141. package/src/__tests__/agent-delegate-task-rpc.test.ts +0 -165
  142. package/src/__tests__/agent-image-rpc.test.ts +0 -34
  143. package/src/__tests__/agent-platform-routing.test.ts +0 -26
  144. package/src/__tests__/agent-reload-config-rpc.test.ts +0 -99
  145. package/src/__tests__/agent-rpc-body.test.ts +0 -42
  146. package/src/__tests__/builtin-chat-session.test.ts +0 -532
  147. package/src/__tests__/builtin-cli-json.test.ts +0 -39
  148. package/src/__tests__/builtin-config.test.ts +0 -26
  149. package/src/__tests__/builtin-context.test.ts +0 -319
  150. package/src/__tests__/builtin-file-tools.test.ts +0 -240
  151. package/src/__tests__/builtin-permissions.test.ts +0 -219
  152. package/src/__tests__/builtin-session-search.test.ts +0 -262
  153. package/src/__tests__/builtin-session-select.test.ts +0 -116
  154. package/src/__tests__/builtin-sigint.test.ts +0 -56
  155. package/src/__tests__/builtin-skills.test.ts +0 -284
  156. package/src/__tests__/builtin-web-tools.test.ts +0 -220
  157. package/src/__tests__/card-action-routing.test.ts +0 -18
  158. package/src/__tests__/card-plain-text.test.ts +0 -45
  159. package/src/__tests__/cardkit.test.ts +0 -60
  160. package/src/__tests__/cards.test.ts +0 -607
  161. package/src/__tests__/ccc-adapter.test.ts +0 -194
  162. package/src/__tests__/chatgpt-subscription-rpc.test.ts +0 -89
  163. package/src/__tests__/chatgpt-subscription.test.ts +0 -135
  164. package/src/__tests__/chrome-devtools-guard.test.ts +0 -165
  165. package/src/__tests__/claude-adapter.test.ts +0 -614
  166. package/src/__tests__/claude-raw-stream-log.test.ts +0 -96
  167. package/src/__tests__/claude-sdk-installer.test.ts +0 -285
  168. package/src/__tests__/codex-adapter.test.ts +0 -331
  169. package/src/__tests__/codex-raw-stream-log.test.ts +0 -170
  170. package/src/__tests__/codex-reset-actions.test.ts +0 -146
  171. package/src/__tests__/config-reload.test.ts +0 -284
  172. package/src/__tests__/config-sample.test.ts +0 -97
  173. package/src/__tests__/config-utils.test.ts +0 -40
  174. package/src/__tests__/config.test.ts +0 -395
  175. package/src/__tests__/crash-logging.test.ts +0 -360
  176. package/src/__tests__/cursor-adapter.test.ts +0 -890
  177. package/src/__tests__/cursor-session-meta-store.test.ts +0 -212
  178. package/src/__tests__/feishu-api.test.ts +0 -60
  179. package/src/__tests__/feishu-avatar.test.ts +0 -504
  180. package/src/__tests__/feishu-message-ingress.test.ts +0 -138
  181. package/src/__tests__/feishu-platform.test.ts +0 -75
  182. package/src/__tests__/fixtures/codex_simple_text.jsonl +0 -4
  183. package/src/__tests__/fixtures/codex_with_tool.jsonl +0 -6
  184. package/src/__tests__/fixtures/cursor_partial_only.jsonl +0 -5
  185. package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +0 -13
  186. package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +0 -12
  187. package/src/__tests__/format-message.test.ts +0 -316
  188. package/src/__tests__/git-command.test.ts +0 -288
  189. package/src/__tests__/im-skills.test.ts +0 -125
  190. package/src/__tests__/jsonl-stream.test.ts +0 -79
  191. package/src/__tests__/orchestrator.test.ts +0 -1268
  192. package/src/__tests__/package-files.test.ts +0 -24
  193. package/src/__tests__/platform-startup.test.ts +0 -19
  194. package/src/__tests__/privacy.test.ts +0 -198
  195. package/src/__tests__/proc-tree-kill.test.ts +0 -108
  196. package/src/__tests__/progress-reducer.test.ts +0 -121
  197. package/src/__tests__/raw-stream-log.test.ts +0 -106
  198. package/src/__tests__/response-stall.test.ts +0 -49
  199. package/src/__tests__/restart.test.ts +0 -232
  200. package/src/__tests__/session-ccc-config.test.ts +0 -66
  201. package/src/__tests__/session.test.ts +0 -3004
  202. package/src/__tests__/shared-prefix.test.ts +0 -36
  203. package/src/__tests__/sim-agent.test.ts +0 -174
  204. package/src/__tests__/sim-platform.test.ts +0 -93
  205. package/src/__tests__/sim-store.test.ts +0 -214
  206. package/src/__tests__/simplify.test.ts +0 -283
  207. package/src/__tests__/startup-lifecycle.test.ts +0 -231
  208. package/src/__tests__/stop-session.test.ts +0 -162
  209. package/src/__tests__/stream-state.test.ts +0 -164
  210. package/src/__tests__/terminal-error.test.ts +0 -54
  211. package/src/__tests__/terminal-renderer.test.ts +0 -247
  212. package/src/__tests__/update-command-guard.test.ts +0 -144
  213. package/src/__tests__/web-ui.test.ts +0 -438
  214. package/src/__tests__/wechat-platform.test.ts +0 -111
  215. package/src/adapters/adapter-interface.ts +0 -217
  216. package/src/adapters/ccc-adapter.ts +0 -150
  217. package/src/adapters/claude-adapter.ts +0 -673
  218. package/src/adapters/claude-session-meta-store.ts +0 -120
  219. package/src/adapters/codex-adapter.ts +0 -426
  220. package/src/adapters/codex-session-meta-store.ts +0 -131
  221. package/src/adapters/cursor-adapter.ts +0 -681
  222. package/src/adapters/cursor-session-meta-store.ts +0 -154
  223. package/src/adapters/jsonl-stream.ts +0 -157
  224. package/src/adapters/raw-stream-log.ts +0 -124
  225. package/src/adapters/resource-monitor.ts +0 -141
  226. package/src/agent-activity.ts +0 -175
  227. package/src/agent-delegate-task-rpc.ts +0 -153
  228. package/src/agent-delegate-task.ts +0 -91
  229. package/src/agent-file-rpc.ts +0 -172
  230. package/src/agent-image-rpc.ts +0 -168
  231. package/src/agent-platform-routing.ts +0 -28
  232. package/src/agent-reload-config-rpc.ts +0 -34
  233. package/src/agent-rpc-body.ts +0 -92
  234. package/src/agent-stop-stuck.ts +0 -129
  235. package/src/card-action-routing.ts +0 -14
  236. package/src/card-plain-text.ts +0 -108
  237. package/src/cardkit.ts +0 -179
  238. package/src/cards.ts +0 -684
  239. package/src/chatgpt-subscription-rpc.ts +0 -27
  240. package/src/chatgpt-subscription.ts +0 -299
  241. package/src/chrome-devtools-guard.ts +0 -318
  242. package/src/claude-sdk-installer.ts +0 -324
  243. package/src/codex-reset-actions.ts +0 -184
  244. package/src/config-utils.ts +0 -211
  245. package/src/config.ts +0 -1063
  246. package/src/cursor-usage.ts +0 -128
  247. package/src/exit-banner.ts +0 -33
  248. package/src/feishu-api.ts +0 -1616
  249. package/src/feishu-message-ingress.ts +0 -195
  250. package/src/feishu-platform.ts +0 -159
  251. package/src/format-message.ts +0 -293
  252. package/src/git-command.ts +0 -202
  253. package/src/im-skills.ts +0 -149
  254. package/src/index.ts +0 -1089
  255. package/src/litellm-proxy.ts +0 -374
  256. package/src/orchestrator.ts +0 -2543
  257. package/src/platform-adapter.ts +0 -70
  258. package/src/platform-startup.ts +0 -16
  259. package/src/privacy.ts +0 -118
  260. package/src/progress/reducer.ts +0 -113
  261. package/src/progress/terminal-renderer.ts +0 -294
  262. package/src/progress/view.ts +0 -77
  263. package/src/response-stall.ts +0 -28
  264. package/src/runtime-reload.ts +0 -34
  265. package/src/session-chat-binding.ts +0 -292
  266. package/src/session-name.ts +0 -8
  267. package/src/session.ts +0 -2659
  268. package/src/shared-prefix.ts +0 -29
  269. package/src/shared.ts +0 -552
  270. package/src/sim-agent.ts +0 -167
  271. package/src/sim-platform.ts +0 -177
  272. package/src/sim-store.ts +0 -317
  273. package/src/simplify.ts +0 -120
  274. package/src/startup-lifecycle.ts +0 -250
  275. package/src/stream-state.ts +0 -177
  276. package/src/terminal-error.ts +0 -129
  277. package/src/trace.ts +0 -51
  278. package/src/turn-cards.ts +0 -118
  279. package/src/update-command-guard.ts +0 -165
  280. package/src/wechat-platform.ts +0 -680
@@ -1,283 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from "vitest";
2
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
- const TEST_DATA_DIR = await mkdtemp(join(tmpdir(), "chatccc-simplify-test-"));
7
- vi.mock("../config.ts", async () => {
8
- const actual = await vi.importActual<typeof import("../config.ts")>("../config.ts");
9
- return {
10
- ...actual,
11
- PROJECT_ROOT: TEST_DATA_DIR,
12
- ts: () => "test-ts",
13
- };
14
- });
15
-
16
- let simplifyToolUse: (name: string, input: unknown) => string | null;
17
- let simplifyToolResult: (name: string, toolUseId: string, isError: boolean, toolCallInput?: unknown) => string | null;
18
- let reloadSimplifyConfig: () => void;
19
-
20
- beforeEach(async () => {
21
- vi.resetModules();
22
- try {
23
- await rm(join(TEST_DATA_DIR, "simplify.json"), { force: true });
24
- } catch {}
25
- const mod = await import("../simplify.ts");
26
- simplifyToolUse = mod.simplifyToolUse;
27
- simplifyToolResult = mod.simplifyToolResult;
28
- reloadSimplifyConfig = mod.reloadSimplifyConfig;
29
- });
30
-
31
- afterAll(async () => {
32
- try {
33
- await rm(TEST_DATA_DIR, { recursive: true, force: true });
34
- } catch {}
35
- });
36
-
37
- describe("simplifyToolUse", () => {
38
- it("无 simplify.json 时返回 null(回退默认格式化)", () => {
39
- reloadSimplifyConfig();
40
- expect(simplifyToolUse("Read", { file_path: "/tmp/x.ts" })).toBeNull();
41
- });
42
-
43
- it("有规则时按模板格式化 tool_use", async () => {
44
- await writeFile(
45
- join(TEST_DATA_DIR, "simplify.json"),
46
- JSON.stringify({
47
- tool_use: {
48
- Read: { template: "📖 **Read** {file_path}", maxLength: 300 },
49
- },
50
- }),
51
- "utf-8",
52
- );
53
- reloadSimplifyConfig();
54
-
55
- expect(simplifyToolUse("Read", { file_path: "/home/user/project/src/index.ts" }))
56
- .toBe("📖 **Read** /home/user/project/src/index.ts");
57
- });
58
-
59
- it("无对应工具规则时返回 null", async () => {
60
- await writeFile(
61
- join(TEST_DATA_DIR, "simplify.json"),
62
- JSON.stringify({
63
- tool_use: {
64
- Read: { template: "📖 **Read** {file_path}", maxLength: 300 },
65
- },
66
- }),
67
- "utf-8",
68
- );
69
- reloadSimplifyConfig();
70
-
71
- expect(simplifyToolUse("Write", { file_path: "/tmp/x.ts" })).toBeNull();
72
- });
73
-
74
- it("模板中未匹配的占位符保留原文", async () => {
75
- await writeFile(
76
- join(TEST_DATA_DIR, "simplify.json"),
77
- JSON.stringify({
78
- tool_use: {
79
- Read: { template: "📖 **Read** {file_path} {unknown_field}", maxLength: 300 },
80
- },
81
- }),
82
- "utf-8",
83
- );
84
- reloadSimplifyConfig();
85
-
86
- expect(simplifyToolUse("Read", { file_path: "/tmp/x.ts" }))
87
- .toBe("📖 **Read** /tmp/x.ts {unknown_field}");
88
- });
89
-
90
- it("超过 maxLength 时截断", async () => {
91
- await writeFile(
92
- join(TEST_DATA_DIR, "simplify.json"),
93
- JSON.stringify({
94
- tool_use: {
95
- Bash: { template: "🖥️ **Bash** {command}", maxLength: 20 },
96
- },
97
- }),
98
- "utf-8",
99
- );
100
- reloadSimplifyConfig();
101
-
102
- const result = simplifyToolUse("Bash", { command: "echo hello world this is a long command" });
103
- expect(result).toBeTruthy();
104
- // 被截断了:长度不超过 maxLength + 3("..." 后缀)
105
- expect(result!.length).toBeLessThanOrEqual(23);
106
- expect(result!.endsWith("...")).toBe(true);
107
- });
108
-
109
- it("多字段模板", async () => {
110
- await writeFile(
111
- join(TEST_DATA_DIR, "simplify.json"),
112
- JSON.stringify({
113
- tool_use: {
114
- Grep: { template: "🔎 **Grep** {pattern} in {path}", maxLength: 300 },
115
- },
116
- }),
117
- "utf-8",
118
- );
119
- reloadSimplifyConfig();
120
-
121
- expect(simplifyToolUse("Grep", { pattern: "TODO", path: "src/" }))
122
- .toBe("🔎 **Grep** TODO in src/");
123
- });
124
-
125
- it("input 为 null 时不抛异常", async () => {
126
- await writeFile(
127
- join(TEST_DATA_DIR, "simplify.json"),
128
- JSON.stringify({
129
- tool_use: {
130
- TodoWrite: { template: "✅ **TodoWrite**", maxLength: 200 },
131
- },
132
- }),
133
- "utf-8",
134
- );
135
- reloadSimplifyConfig();
136
-
137
- expect(simplifyToolUse("TodoWrite", null)).toBe("✅ **TodoWrite**");
138
- });
139
- });
140
-
141
- describe("simplifyToolResult", () => {
142
- it("无 simplify.json 时返回 null", () => {
143
- reloadSimplifyConfig();
144
- expect(simplifyToolResult("Read", "abc123def456", false)).toBeNull();
145
- });
146
-
147
- it("有规则时按模板格式化 tool_result", async () => {
148
- await writeFile(
149
- join(TEST_DATA_DIR, "simplify.json"),
150
- JSON.stringify({
151
- tool_result: {
152
- Read: { template: "✅ *{id}*: 已读取", maxLength: 200 },
153
- },
154
- }),
155
- "utf-8",
156
- );
157
- reloadSimplifyConfig();
158
-
159
- expect(simplifyToolResult("Read", "abc123def456", false))
160
- .toBe("✅ *def456*: 已读取");
161
- });
162
-
163
- it("isError 时结果前加 ❌", async () => {
164
- await writeFile(
165
- join(TEST_DATA_DIR, "simplify.json"),
166
- JSON.stringify({
167
- tool_result: {
168
- Read: { template: "✅ *{id}*: 已读取", maxLength: 200 },
169
- },
170
- }),
171
- "utf-8",
172
- );
173
- reloadSimplifyConfig();
174
-
175
- expect(simplifyToolResult("Read", "abc123def456", true))
176
- .toBe("❌ ✅ *def456*: 已读取");
177
- });
178
-
179
- it("tool_result 模板可使用 tool_use 的输入字段", async () => {
180
- await writeFile(
181
- join(TEST_DATA_DIR, "simplify.json"),
182
- JSON.stringify({
183
- tool_result: {
184
- Write: { template: "✅ *{id}*: 已写入 {file_path}", maxLength: 200 },
185
- },
186
- }),
187
- "utf-8",
188
- );
189
- reloadSimplifyConfig();
190
-
191
- expect(simplifyToolResult("Write", "abc123def456", false, { file_path: "/tmp/out.txt" }))
192
- .toBe("✅ *def456*: 已写入 /tmp/out.txt");
193
- });
194
-
195
- it("无对应工具规则时返回 null", async () => {
196
- await writeFile(
197
- join(TEST_DATA_DIR, "simplify.json"),
198
- JSON.stringify({
199
- tool_result: {
200
- Read: { template: "✅ *{id}*: 已读取", maxLength: 200 },
201
- },
202
- }),
203
- "utf-8",
204
- );
205
- reloadSimplifyConfig();
206
-
207
- expect(simplifyToolResult("Write", "abc123def456", false)).toBeNull();
208
- });
209
- });
210
-
211
- describe("accumulateBlockContent with simplification", () => {
212
- it("tool_use 使用简化规则,tool_result 回退默认格式", async () => {
213
- // 动态 import session 以使用当前 mock 的简单化模块
214
- const { accumulateBlockContent } = await import("../session.ts");
215
-
216
- await writeFile(
217
- join(TEST_DATA_DIR, "simplify.json"),
218
- JSON.stringify({
219
- tool_use: {
220
- Read: { template: "📖 **Read** {file_path}", maxLength: 300 },
221
- },
222
- }),
223
- "utf-8",
224
- );
225
- reloadSimplifyConfig();
226
-
227
- const state = { accumulatedContent: "", finalText: "", finalCompleteText: "", chunkCount: 0 };
228
- const toolCallMap = new Map<string, { name: string; input: unknown }>();
229
-
230
- accumulateBlockContent(
231
- { type: "tool_use", id: "toolu_001", name: "Read", input: { file_path: "/src/app.ts" } },
232
- state,
233
- toolCallMap,
234
- );
235
-
236
- expect(state.accumulatedContent).toBe("\n\n📖 **Read** /src/app.ts\n");
237
- });
238
-
239
- it("无简化规则时回退默认 tool_use 格式", async () => {
240
- const { accumulateBlockContent: acb } = await import("../session.ts");
241
-
242
- reloadSimplifyConfig(); // 无 simplify.json
243
-
244
- const state = { accumulatedContent: "", finalText: "", finalCompleteText: "", chunkCount: 0 };
245
- const toolCallMap = new Map<string, { name: string; input: unknown }>();
246
-
247
- acb(
248
- { type: "tool_use", id: "toolu_001", name: "Read", input: { file_path: "/src/app.ts" } },
249
- state,
250
- toolCallMap,
251
- );
252
-
253
- expect(state.accumulatedContent).toContain("📖 **Read**");
254
- expect(state.accumulatedContent).toContain('{"file_path":"/src/app.ts"}');
255
- });
256
-
257
- it("tool_result 使用简化规则", async () => {
258
- const { accumulateBlockContent: acb } = await import("../session.ts");
259
-
260
- await writeFile(
261
- join(TEST_DATA_DIR, "simplify.json"),
262
- JSON.stringify({
263
- tool_result: {
264
- Read: { template: "✅ *{id}*: 已读取 {file_path}", maxLength: 200 },
265
- },
266
- }),
267
- "utf-8",
268
- );
269
- reloadSimplifyConfig();
270
-
271
- const state = { accumulatedContent: "", finalText: "", finalCompleteText: "", chunkCount: 0 };
272
- const toolCallMap = new Map<string, { name: string; input: unknown }>();
273
- toolCallMap.set("toolu_001", { name: "Read", input: { file_path: "/src/app.ts" } });
274
-
275
- acb(
276
- { type: "tool_result", tool_use_id: "toolu_001", content: "file content here..." },
277
- state,
278
- toolCallMap,
279
- );
280
-
281
- expect(state.accumulatedContent).toBe("✅ *lu_001*: 已读取 /src/app.ts\n");
282
- });
283
- });
@@ -1,231 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- import {
4
- INTERNAL_RESTART_ENV_VAR,
5
- buildWebUiUrl,
6
- createServiceLifecycleGuard,
7
- createInternalRestartEnv,
8
- openWebUiInDefaultBrowser,
9
- shouldAutoOpenWebUi,
10
- } from "../startup-lifecycle.ts";
11
-
12
- describe("ChatCCC startup lifecycle", () => {
13
- it("opens the Web UI by default for a direct start", () => {
14
- expect(shouldAutoOpenWebUi({ env: {} })).toBe(true);
15
- expect(shouldAutoOpenWebUi({ env: {}, openOnStart: true })).toBe(true);
16
- });
17
-
18
- it("respects the persisted openOnStart preference", () => {
19
- expect(shouldAutoOpenWebUi({ env: {}, openOnStart: false })).toBe(false);
20
- });
21
-
22
- it("never opens the Web UI for an internal restart", () => {
23
- expect(shouldAutoOpenWebUi({
24
- env: { [INTERNAL_RESTART_ENV_VAR]: "1" },
25
- openOnStart: true,
26
- })).toBe(false);
27
- });
28
-
29
- it("marks internal restart children without losing the inherited environment", () => {
30
- expect(createInternalRestartEnv({ PATH: "test-path", CUSTOM: "value" })).toEqual({
31
- PATH: "test-path",
32
- CUSTOM: "value",
33
- [INTERNAL_RESTART_ENV_VAR]: "1",
34
- });
35
- });
36
-
37
- it("uses localhost and the configured port for the Web UI URL", () => {
38
- expect(buildWebUiUrl(18080)).toBe("http://localhost:18080/");
39
- expect(buildWebUiUrl(18081)).toBe("http://localhost:18081/");
40
- });
41
-
42
- it("opens the configured URL in the Windows default browser", () => {
43
- const child = {
44
- on: vi.fn().mockReturnThis(),
45
- unref: vi.fn(),
46
- };
47
- const spawnImpl = vi.fn(() => child as never);
48
-
49
- expect(openWebUiInDefaultBrowser(18081, {
50
- platform: "win32",
51
- spawnImpl,
52
- })).toBe(true);
53
- expect(spawnImpl).toHaveBeenCalledWith(
54
- "cmd.exe",
55
- ["/c", "start", "", "http://localhost:18081/"],
56
- {
57
- detached: true,
58
- stdio: "ignore",
59
- windowsHide: true,
60
- },
61
- );
62
- expect(child.on).toHaveBeenCalledWith("error", expect.any(Function));
63
- expect(child.unref).toHaveBeenCalledOnce();
64
- });
65
-
66
- it("opens the configured URL with xdg-open on a Linux desktop", () => {
67
- const child = {
68
- on: vi.fn().mockReturnThis(),
69
- unref: vi.fn(),
70
- };
71
- const spawnImpl = vi.fn(() => child as never);
72
-
73
- expect(openWebUiInDefaultBrowser(18080, {
74
- platform: "linux",
75
- env: { DISPLAY: ":0" },
76
- spawnImpl,
77
- })).toBe(true);
78
- expect(spawnImpl).toHaveBeenCalledWith(
79
- "xdg-open",
80
- ["http://localhost:18080/"],
81
- { detached: true, stdio: "ignore" },
82
- );
83
- });
84
-
85
- it("skips xdg-open and prints an SSH tunnel hint on headless Linux", () => {
86
- const spawnImpl = vi.fn();
87
- const onInfo = vi.fn();
88
-
89
- expect(openWebUiInDefaultBrowser(18080, {
90
- platform: "linux",
91
- env: {},
92
- spawnImpl,
93
- onInfo,
94
- })).toBe(false);
95
- expect(spawnImpl).not.toHaveBeenCalled();
96
- expect(onInfo).toHaveBeenCalledWith(expect.stringContaining("ssh -L 18080:127.0.0.1:18080"));
97
- expect(onInfo).toHaveBeenCalledWith(expect.stringContaining("http://localhost:18080/"));
98
- });
99
- });
100
-
101
- describe("ChatCCC service lifecycle guard", () => {
102
- function createHarness() {
103
- let intervalCallback: (() => void) | undefined;
104
- const timer = { ref: vi.fn() };
105
- const setIntervalImpl = vi.fn((callback: () => void) => {
106
- intervalCallback = callback;
107
- return timer;
108
- });
109
- const clearIntervalImpl = vi.fn();
110
- const tracer = vi.fn();
111
- const server = {
112
- listening: true,
113
- address: vi.fn(() => ({ address: "127.0.0.1", port: 18080 })),
114
- ref: vi.fn(),
115
- };
116
- const recoverServer = vi.fn(async () => {
117
- server.listening = true;
118
- });
119
- const guard = createServiceLifecycleGuard({
120
- intervalMs: 10_000,
121
- setIntervalImpl,
122
- clearIntervalImpl,
123
- tracer,
124
- getActiveResourcesInfo: () => ["TCPServerWrap", "Timeout"],
125
- });
126
-
127
- return {
128
- clearIntervalImpl,
129
- getIntervalCallback: () => intervalCallback,
130
- guard,
131
- recoverServer,
132
- server,
133
- setIntervalImpl,
134
- timer,
135
- tracer,
136
- };
137
- }
138
-
139
- it("starts exactly one referenced keep-alive timer", () => {
140
- const h = createHarness();
141
-
142
- h.guard.start();
143
- h.guard.start();
144
-
145
- expect(h.setIntervalImpl).toHaveBeenCalledOnce();
146
- expect(h.setIntervalImpl).toHaveBeenCalledWith(expect.any(Function), 10_000);
147
- expect(h.timer.ref).toHaveBeenCalledOnce();
148
- });
149
-
150
- it("keeps a listening HTTP server referenced", async () => {
151
- const h = createHarness();
152
- h.guard.attachServer(h.server, h.recoverServer);
153
- h.guard.start();
154
-
155
- await h.guard.checkNow();
156
-
157
- expect(h.server.ref).toHaveBeenCalled();
158
- expect(h.recoverServer).not.toHaveBeenCalled();
159
- });
160
-
161
- it("coalesces concurrent recovery when the HTTP server is not listening", async () => {
162
- const h = createHarness();
163
- h.server.listening = false;
164
- let finishRecovery: (() => void) | undefined;
165
- h.recoverServer.mockImplementation(() => new Promise<void>((resolve) => {
166
- finishRecovery = () => {
167
- h.server.listening = true;
168
- resolve();
169
- };
170
- }));
171
- h.guard.attachServer(h.server, h.recoverServer);
172
- h.guard.start();
173
-
174
- const first = h.guard.checkNow();
175
- const second = h.guard.checkNow();
176
- await Promise.resolve();
177
- expect(h.recoverServer).toHaveBeenCalledOnce();
178
-
179
- finishRecovery?.();
180
- await Promise.all([first, second]);
181
- expect(h.tracer).toHaveBeenCalledWith(
182
- "service-lifecycle: HTTP server recovered",
183
- expect.objectContaining({ serverListening: true }),
184
- );
185
- });
186
-
187
- it("re-arms on unexpected beforeExit and records public diagnostics", async () => {
188
- const h = createHarness();
189
- h.guard.attachServer(h.server, h.recoverServer);
190
-
191
- h.guard.handleBeforeExit(0);
192
- await h.guard.checkNow();
193
-
194
- expect(h.setIntervalImpl).toHaveBeenCalledOnce();
195
- expect(h.server.ref).toHaveBeenCalled();
196
- expect(h.tracer).toHaveBeenCalledWith(
197
- "service-lifecycle: unexpected beforeExit",
198
- expect.objectContaining({
199
- code: 0,
200
- activeResources: ["TCPServerWrap", "Timeout"],
201
- serverListening: true,
202
- }),
203
- );
204
- });
205
-
206
- it("stays stopped after an intentional shutdown", () => {
207
- const h = createHarness();
208
- h.guard.start();
209
-
210
- h.guard.beginShutdown("SIGTERM");
211
- h.guard.handleBeforeExit(0);
212
-
213
- expect(h.clearIntervalImpl).toHaveBeenCalledWith(h.timer);
214
- expect(h.setIntervalImpl).toHaveBeenCalledOnce();
215
- expect(h.tracer).toHaveBeenCalledWith(
216
- "service-lifecycle: shutdown requested",
217
- { reason: "SIGTERM" },
218
- );
219
- });
220
-
221
- it("the timer callback runs a health check", async () => {
222
- const h = createHarness();
223
- h.guard.attachServer(h.server, h.recoverServer);
224
- h.guard.start();
225
-
226
- h.getIntervalCallback()?.();
227
- await Promise.resolve();
228
-
229
- expect(h.server.ref).toHaveBeenCalled();
230
- });
231
- });
@@ -1,162 +0,0 @@
1
- // =============================================================================
2
- // stop-session.test.ts — stopSession 单测护栏
3
- // =============================================================================
4
- // 覆盖修复"幽灵 codex 会话"的两条关键行为:
5
- // 1) controller.abort() 必须被触发(让 adapter finally 走 killProcessTree)
6
- // 2) 立刻把 stream-state.status 改成 stopped,不依赖 runAgentSession 的 finally
7
- // (那个 finally 要等 generator 自然结束,子进程没死透就一直停在 running)
8
- // =============================================================================
9
-
10
- import { describe, it, expect, vi, beforeEach } from "vitest";
11
- import type { StreamState } from "../stream-state.ts";
12
-
13
- // mock stream-state,使用模块内可观测的 Map 记录读写
14
- const stateStore = new Map<string, StreamState>();
15
- const writeCalls: StreamState[] = [];
16
- const killProcessTreeMock = vi.hoisted(() => vi.fn(async (_pid?: number) => {}));
17
-
18
- vi.mock("../adapters/proc-tree-kill.ts", () => ({
19
- killProcessTree: killProcessTreeMock,
20
- }));
21
-
22
- vi.mock("../stream-state.ts", () => ({
23
- readStreamState: async (sid: string): Promise<StreamState | null> => {
24
- return stateStore.get(sid) ?? null;
25
- },
26
- writeStreamState: async (state: StreamState): Promise<void> => {
27
- writeCalls.push(structuredClone(state));
28
- stateStore.set(state.sessionId, state);
29
- },
30
- createEmptyStreamState: (sid: string, cwd: string, tool: string, turnCount: number) => ({
31
- sessionId: sid,
32
- status: "running" as const,
33
- accumulatedContent: "",
34
- finalReply: "",
35
- chunkCount: 0,
36
- turnCount,
37
- contextTokens: 0,
38
- updatedAt: Date.now(),
39
- cwd,
40
- tool,
41
- }),
42
- fixStaleStreamStates: async () => {},
43
- STREAMS_DIR: "/tmp/streams-mock",
44
- }));
45
-
46
- import { stopSession } from "../session.ts";
47
- import { activePrompts } from "../session-chat-binding.ts";
48
-
49
- function seedRunningSession(
50
- sid: string,
51
- accumulated = "partial output",
52
- closeSession?: () => void,
53
- ): AbortController {
54
- const controller = new AbortController();
55
- activePrompts.set(sid, { controller, stopped: false, startTime: Date.now(), closeSession });
56
- stateStore.set(sid, {
57
- sessionId: sid,
58
- status: "running",
59
- accumulatedContent: accumulated,
60
- finalReply: "",
61
- chunkCount: 1,
62
- turnCount: 1,
63
- contextTokens: 0,
64
- updatedAt: Date.now(),
65
- cwd: "F:/repo",
66
- tool: "codex",
67
- });
68
- return controller;
69
- }
70
-
71
- async function flush(): Promise<void> {
72
- // 让 stopSession 内 fire-and-forget 的 microtask 跑完
73
- for (let i = 0; i < 5; i++) {
74
- await Promise.resolve();
75
- }
76
- await new Promise((r) => setTimeout(r, 10));
77
- }
78
-
79
- beforeEach(() => {
80
- activePrompts.clear();
81
- stateStore.clear();
82
- writeCalls.length = 0;
83
- killProcessTreeMock.mockClear();
84
- });
85
-
86
- describe("stopSession 行为护栏", () => {
87
- it("没有活跃 session 时返回 false,不做任何事", async () => {
88
- const ok = stopSession("nonexistent");
89
- expect(ok).toBe(false);
90
- await flush();
91
- expect(writeCalls).toHaveLength(0);
92
- });
93
-
94
- it("abort controller + 立刻把 stream-state.status 写成 stopped", async () => {
95
- const controller = seedRunningSession("sid-A", "hello world");
96
- let aborted = false;
97
- controller.signal.addEventListener("abort", () => { aborted = true; });
98
-
99
- const ok = stopSession("sid-A");
100
- expect(ok).toBe(true);
101
- expect(aborted).toBe(true);
102
-
103
- await flush();
104
-
105
- // 关键护栏:必须有一次 writeStreamState 把 status 改成 stopped
106
- expect(writeCalls.length).toBeGreaterThanOrEqual(1);
107
- const lastWrite = writeCalls[writeCalls.length - 1];
108
- expect(lastWrite.sessionId).toBe("sid-A");
109
- expect(lastWrite.status).toBe("stopped");
110
- // 累积内容不丢
111
- expect(lastWrite.accumulatedContent).toBe("hello world");
112
- });
113
-
114
- it("已经是终态(done/stopped/error)的 stream-state 不会被覆盖", async () => {
115
- seedRunningSession("sid-B");
116
- // 模拟 generator finally 已经先写了 done
117
- stateStore.set("sid-B", {
118
- ...stateStore.get("sid-B")!,
119
- status: "done",
120
- });
121
-
122
- const ok = stopSession("sid-B");
123
- expect(ok).toBe(true);
124
- await flush();
125
-
126
- // 不应再有 stopped 覆盖 done 的写入
127
- const stoppedWrites = writeCalls.filter((w) => w.status === "stopped");
128
- expect(stoppedWrites).toHaveLength(0);
129
- });
130
-
131
- it("activePrompts 标记 stopped=true", async () => {
132
- seedRunningSession("sid-C");
133
- stopSession("sid-C");
134
- expect(activePrompts.get("sid-C")?.stopped).toBe(true);
135
- });
136
-
137
- it("调用 adapter 提供的 closeSession 以主动关闭底层 SDK session", () => {
138
- const closeSession = vi.fn();
139
- seedRunningSession("sid-D", "partial output", closeSession);
140
-
141
- const ok = stopSession("sid-D");
142
-
143
- expect(ok).toBe(true);
144
- expect(closeSession).toHaveBeenCalledTimes(1);
145
- });
146
-
147
- it("按完整进程树停止 CLI,不先杀壳进程造成后代进程逃逸", async () => {
148
- seedRunningSession("sid-tree");
149
- activePrompts.get("sid-tree")!.processPid = 4242;
150
- const processKillSpy = vi.spyOn(process, "kill").mockImplementation(() => true);
151
-
152
- try {
153
- expect(stopSession("sid-tree")).toBe(true);
154
- await flush();
155
-
156
- expect(killProcessTreeMock).toHaveBeenCalledWith(4242);
157
- expect(processKillSpy).not.toHaveBeenCalled();
158
- } finally {
159
- processKillSpy.mockRestore();
160
- }
161
- });
162
- });