chatccc 0.2.252 → 0.2.254

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 (296) hide show
  1. package/README.md +10 -1
  2. package/bin/cccagent.mjs +12 -3
  3. package/bin/chatccc.mjs +15 -6
  4. package/config.sample.json +2 -1
  5. package/deepccc-agent/README.md +40 -35
  6. package/deepccc-agent/os-prompts/darwin.md +8 -8
  7. package/deepccc-agent/os-prompts/linux.md +8 -8
  8. package/deepccc-agent/os-prompts/win32.md +11 -11
  9. package/deepccc-agent/package.json +65 -65
  10. package/dist/deepccc-agent/src/cli.js +634 -0
  11. package/dist/deepccc-agent/src/config.js +81 -0
  12. package/dist/deepccc-agent/src/context.js +348 -0
  13. package/dist/deepccc-agent/src/file-log.js +34 -0
  14. package/dist/deepccc-agent/src/file-tools.js +1208 -0
  15. package/dist/deepccc-agent/src/index.js +601 -0
  16. package/dist/deepccc-agent/src/permissions.js +170 -0
  17. package/dist/deepccc-agent/src/privacy.js +124 -0
  18. package/dist/deepccc-agent/src/proc-tree-kill.js +60 -0
  19. package/dist/deepccc-agent/src/progress/cards-helpers.js +70 -0
  20. package/dist/deepccc-agent/src/progress/reducer.js +102 -0
  21. package/dist/deepccc-agent/src/progress/terminal-renderer.js +264 -0
  22. package/dist/deepccc-agent/src/progress/view.js +30 -0
  23. package/dist/deepccc-agent/src/raw-stream-log.js +106 -0
  24. package/dist/deepccc-agent/src/session-search.js +276 -0
  25. package/dist/deepccc-agent/src/session-select.js +23 -0
  26. package/dist/deepccc-agent/src/sigint.js +26 -0
  27. package/dist/deepccc-agent/src/skills.js +178 -0
  28. package/dist/deepccc-agent/src/web-tools.js +246 -0
  29. package/dist/src/adapters/adapter-interface.js +19 -0
  30. package/dist/src/adapters/ccc-adapter.js +112 -0
  31. package/dist/src/adapters/claude-adapter.js +497 -0
  32. package/dist/src/adapters/claude-session-meta-store.js +92 -0
  33. package/dist/src/adapters/codex-adapter.js +279 -0
  34. package/dist/src/adapters/codex-session-meta-store.js +94 -0
  35. package/dist/src/adapters/cursor-adapter.js +491 -0
  36. package/dist/src/adapters/cursor-session-meta-store.js +116 -0
  37. package/dist/src/adapters/jsonl-stream.js +104 -0
  38. package/{src/adapters/proc-tree-kill.ts → dist/src/adapters/proc-tree-kill.js} +94 -97
  39. package/dist/src/adapters/raw-stream-log.js +106 -0
  40. package/dist/src/adapters/resource-monitor.js +113 -0
  41. package/dist/src/agent-activity.js +133 -0
  42. package/dist/src/agent-delegate-task-rpc.js +129 -0
  43. package/dist/src/agent-delegate-task.js +48 -0
  44. package/dist/src/agent-file-rpc.js +152 -0
  45. package/dist/src/agent-image-rpc.js +148 -0
  46. package/dist/src/agent-platform-routing.js +13 -0
  47. package/dist/src/agent-reload-config-rpc.js +23 -0
  48. package/dist/src/agent-rpc-body.js +87 -0
  49. package/dist/src/agent-stop-stuck.js +110 -0
  50. package/dist/src/agent-team/application/board-service.js +142 -0
  51. package/dist/src/agent-team/application/main-agent-service.js +151 -0
  52. package/dist/src/agent-team/domain/board.js +69 -0
  53. package/dist/src/agent-team/http/board-routes.js +213 -0
  54. package/dist/src/agent-team/infrastructure/filesystem-browser.js +112 -0
  55. package/dist/src/agent-team/infrastructure/main-agent-session-runtime.js +30 -0
  56. package/dist/src/agent-team/main-agent-bootstrap.js +15 -0
  57. package/dist/src/agent-team/repositories/board-repository.js +10 -0
  58. package/dist/src/agent-team/repositories/feishu-p2p-contact-store.js +64 -0
  59. package/dist/src/agent-team/repositories/json-board-repository.js +209 -0
  60. package/dist/src/agent-team/repositories/main-agent-binding-repository.js +84 -0
  61. package/dist/src/agent-team/sync/board-sync-adapter.js +1 -0
  62. package/dist/src/agent-team/web/agent-team-page.js +219 -0
  63. package/dist/src/agent-tool.js +10 -0
  64. package/dist/src/card-action-routing.js +7 -0
  65. package/dist/src/card-plain-text.js +101 -0
  66. package/dist/src/cardkit.js +158 -0
  67. package/dist/src/cards.js +573 -0
  68. package/dist/src/chatgpt-subscription-rpc.js +18 -0
  69. package/dist/src/chatgpt-subscription.js +199 -0
  70. package/dist/src/chrome-devtools-guard.js +238 -0
  71. package/dist/src/claude-sdk-installer.js +249 -0
  72. package/dist/src/codex-reset-actions.js +143 -0
  73. package/dist/src/config-utils.js +149 -0
  74. package/dist/src/config.js +808 -0
  75. package/dist/src/cursor-usage.js +77 -0
  76. package/dist/src/exit-banner.js +28 -0
  77. package/dist/src/feishu-api.js +1404 -0
  78. package/dist/src/feishu-message-ingress.js +137 -0
  79. package/dist/src/feishu-platform.js +97 -0
  80. package/dist/src/format-message.js +252 -0
  81. package/dist/src/git-command.js +155 -0
  82. package/dist/src/im-skills.js +121 -0
  83. package/dist/src/index.js +835 -0
  84. package/dist/src/litellm-proxy.js +300 -0
  85. package/dist/src/orchestrator.js +2096 -0
  86. package/dist/src/package-root.js +26 -0
  87. package/dist/src/platform-adapter.js +7 -0
  88. package/dist/src/platform-startup.js +6 -0
  89. package/dist/src/privacy.js +100 -0
  90. package/dist/src/progress/reducer.js +102 -0
  91. package/dist/src/progress/terminal-renderer.js +264 -0
  92. package/dist/src/progress/view.js +30 -0
  93. package/dist/src/response-stall.js +14 -0
  94. package/dist/src/runtime-entry.js +13 -0
  95. package/dist/src/runtime-reload.js +19 -0
  96. package/dist/src/session-chat-binding.js +183 -0
  97. package/dist/src/session-name.js +7 -0
  98. package/dist/src/session.js +2147 -0
  99. package/dist/src/shared-prefix.js +16 -0
  100. package/dist/src/shared.js +493 -0
  101. package/dist/src/sim-agent.js +105 -0
  102. package/dist/src/sim-platform.js +142 -0
  103. package/dist/src/sim-store.js +231 -0
  104. package/dist/src/simplify.js +99 -0
  105. package/dist/src/startup-lifecycle.js +209 -0
  106. package/dist/src/stream-state.js +141 -0
  107. package/dist/src/terminal-error.js +100 -0
  108. package/dist/src/trace.js +50 -0
  109. package/dist/src/turn-cards.js +92 -0
  110. package/dist/src/update-command-guard.js +114 -0
  111. package/{src/web-ui.ts → dist/src/web-ui.js} +753 -839
  112. package/dist/src/wechat-platform.js +545 -0
  113. package/package.json +76 -74
  114. package/deepccc-agent/LICENSE +0 -201
  115. package/deepccc-agent/bin/deepccc.mjs +0 -26
  116. package/deepccc-agent/docs/cache-hit-rate-1.jpg +0 -0
  117. package/deepccc-agent/docs/cache-hit-rate-2.jpg +0 -0
  118. package/deepccc-agent/package-lock.json +0 -2027
  119. package/deepccc-agent/src/__tests__/chat-session.test.ts +0 -877
  120. package/deepccc-agent/src/__tests__/cli-json.test.ts +0 -49
  121. package/deepccc-agent/src/__tests__/config.test.ts +0 -34
  122. package/deepccc-agent/src/__tests__/context.test.ts +0 -341
  123. package/deepccc-agent/src/__tests__/file-tools.test.ts +0 -240
  124. package/deepccc-agent/src/__tests__/permissions.test.ts +0 -199
  125. package/deepccc-agent/src/__tests__/privacy.test.ts +0 -318
  126. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +0 -121
  127. package/deepccc-agent/src/__tests__/session-search.test.ts +0 -262
  128. package/deepccc-agent/src/__tests__/session-select.test.ts +0 -116
  129. package/deepccc-agent/src/__tests__/sigint.test.ts +0 -56
  130. package/deepccc-agent/src/__tests__/skills.test.ts +0 -284
  131. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +0 -247
  132. package/deepccc-agent/src/__tests__/web-tools.test.ts +0 -220
  133. package/deepccc-agent/src/cli.ts +0 -682
  134. package/deepccc-agent/src/config.ts +0 -101
  135. package/deepccc-agent/src/context.ts +0 -465
  136. package/deepccc-agent/src/file-log.ts +0 -38
  137. package/deepccc-agent/src/file-tools.ts +0 -1493
  138. package/deepccc-agent/src/index.ts +0 -710
  139. package/deepccc-agent/src/permissions.ts +0 -226
  140. package/deepccc-agent/src/privacy.ts +0 -141
  141. package/deepccc-agent/src/proc-tree-kill.ts +0 -61
  142. package/deepccc-agent/src/progress/cards-helpers.ts +0 -76
  143. package/deepccc-agent/src/progress/reducer.ts +0 -113
  144. package/deepccc-agent/src/progress/terminal-renderer.ts +0 -294
  145. package/deepccc-agent/src/progress/view.ts +0 -77
  146. package/deepccc-agent/src/raw-stream-log.ts +0 -124
  147. package/deepccc-agent/src/session-search.ts +0 -370
  148. package/deepccc-agent/src/session-select.ts +0 -48
  149. package/deepccc-agent/src/sigint.ts +0 -50
  150. package/deepccc-agent/src/skills.ts +0 -205
  151. package/deepccc-agent/src/web-tools.ts +0 -313
  152. package/deepccc-agent/tsconfig.build.json +0 -13
  153. package/deepccc-agent/tsconfig.json +0 -13
  154. package/deepccc-agent/vitest.config.ts +0 -7
  155. package/src/__tests__/adapter-interface.test.ts +0 -152
  156. package/src/__tests__/agent-activity.test.ts +0 -86
  157. package/src/__tests__/agent-delegate-task-rpc.test.ts +0 -165
  158. package/src/__tests__/agent-image-rpc.test.ts +0 -34
  159. package/src/__tests__/agent-platform-routing.test.ts +0 -26
  160. package/src/__tests__/agent-reload-config-rpc.test.ts +0 -99
  161. package/src/__tests__/agent-rpc-body.test.ts +0 -42
  162. package/src/__tests__/builtin-chat-session.test.ts +0 -532
  163. package/src/__tests__/builtin-cli-json.test.ts +0 -39
  164. package/src/__tests__/builtin-config.test.ts +0 -26
  165. package/src/__tests__/builtin-context.test.ts +0 -319
  166. package/src/__tests__/builtin-file-tools.test.ts +0 -240
  167. package/src/__tests__/builtin-permissions.test.ts +0 -219
  168. package/src/__tests__/builtin-session-search.test.ts +0 -262
  169. package/src/__tests__/builtin-session-select.test.ts +0 -116
  170. package/src/__tests__/builtin-sigint.test.ts +0 -56
  171. package/src/__tests__/builtin-skills.test.ts +0 -284
  172. package/src/__tests__/builtin-web-tools.test.ts +0 -220
  173. package/src/__tests__/card-action-routing.test.ts +0 -18
  174. package/src/__tests__/card-plain-text.test.ts +0 -45
  175. package/src/__tests__/cardkit.test.ts +0 -60
  176. package/src/__tests__/cards.test.ts +0 -607
  177. package/src/__tests__/ccc-adapter.test.ts +0 -194
  178. package/src/__tests__/chatgpt-subscription-rpc.test.ts +0 -89
  179. package/src/__tests__/chatgpt-subscription.test.ts +0 -135
  180. package/src/__tests__/chrome-devtools-guard.test.ts +0 -165
  181. package/src/__tests__/claude-adapter.test.ts +0 -614
  182. package/src/__tests__/claude-raw-stream-log.test.ts +0 -96
  183. package/src/__tests__/claude-sdk-installer.test.ts +0 -285
  184. package/src/__tests__/codex-adapter.test.ts +0 -331
  185. package/src/__tests__/codex-raw-stream-log.test.ts +0 -170
  186. package/src/__tests__/codex-reset-actions.test.ts +0 -146
  187. package/src/__tests__/config-reload.test.ts +0 -284
  188. package/src/__tests__/config-sample.test.ts +0 -97
  189. package/src/__tests__/config-utils.test.ts +0 -40
  190. package/src/__tests__/config.test.ts +0 -395
  191. package/src/__tests__/crash-logging.test.ts +0 -360
  192. package/src/__tests__/cursor-adapter.test.ts +0 -890
  193. package/src/__tests__/cursor-session-meta-store.test.ts +0 -212
  194. package/src/__tests__/feishu-api.test.ts +0 -60
  195. package/src/__tests__/feishu-avatar.test.ts +0 -504
  196. package/src/__tests__/feishu-message-ingress.test.ts +0 -138
  197. package/src/__tests__/feishu-platform.test.ts +0 -75
  198. package/src/__tests__/fixtures/codex_simple_text.jsonl +0 -4
  199. package/src/__tests__/fixtures/codex_with_tool.jsonl +0 -6
  200. package/src/__tests__/fixtures/cursor_partial_only.jsonl +0 -5
  201. package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +0 -13
  202. package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +0 -12
  203. package/src/__tests__/format-message.test.ts +0 -316
  204. package/src/__tests__/git-command.test.ts +0 -288
  205. package/src/__tests__/im-skills.test.ts +0 -125
  206. package/src/__tests__/jsonl-stream.test.ts +0 -79
  207. package/src/__tests__/orchestrator.test.ts +0 -1268
  208. package/src/__tests__/package-files.test.ts +0 -24
  209. package/src/__tests__/platform-startup.test.ts +0 -19
  210. package/src/__tests__/privacy.test.ts +0 -198
  211. package/src/__tests__/proc-tree-kill.test.ts +0 -108
  212. package/src/__tests__/progress-reducer.test.ts +0 -121
  213. package/src/__tests__/raw-stream-log.test.ts +0 -106
  214. package/src/__tests__/response-stall.test.ts +0 -49
  215. package/src/__tests__/restart.test.ts +0 -232
  216. package/src/__tests__/session-ccc-config.test.ts +0 -66
  217. package/src/__tests__/session.test.ts +0 -3004
  218. package/src/__tests__/shared-prefix.test.ts +0 -36
  219. package/src/__tests__/sim-agent.test.ts +0 -174
  220. package/src/__tests__/sim-platform.test.ts +0 -93
  221. package/src/__tests__/sim-store.test.ts +0 -214
  222. package/src/__tests__/simplify.test.ts +0 -283
  223. package/src/__tests__/startup-lifecycle.test.ts +0 -231
  224. package/src/__tests__/stop-session.test.ts +0 -162
  225. package/src/__tests__/stream-state.test.ts +0 -164
  226. package/src/__tests__/terminal-error.test.ts +0 -54
  227. package/src/__tests__/terminal-renderer.test.ts +0 -247
  228. package/src/__tests__/update-command-guard.test.ts +0 -144
  229. package/src/__tests__/web-ui.test.ts +0 -438
  230. package/src/__tests__/wechat-platform.test.ts +0 -111
  231. package/src/adapters/adapter-interface.ts +0 -217
  232. package/src/adapters/ccc-adapter.ts +0 -150
  233. package/src/adapters/claude-adapter.ts +0 -673
  234. package/src/adapters/claude-session-meta-store.ts +0 -120
  235. package/src/adapters/codex-adapter.ts +0 -426
  236. package/src/adapters/codex-session-meta-store.ts +0 -131
  237. package/src/adapters/cursor-adapter.ts +0 -681
  238. package/src/adapters/cursor-session-meta-store.ts +0 -154
  239. package/src/adapters/jsonl-stream.ts +0 -157
  240. package/src/adapters/raw-stream-log.ts +0 -124
  241. package/src/adapters/resource-monitor.ts +0 -141
  242. package/src/agent-activity.ts +0 -175
  243. package/src/agent-delegate-task-rpc.ts +0 -153
  244. package/src/agent-delegate-task.ts +0 -91
  245. package/src/agent-file-rpc.ts +0 -172
  246. package/src/agent-image-rpc.ts +0 -168
  247. package/src/agent-platform-routing.ts +0 -28
  248. package/src/agent-reload-config-rpc.ts +0 -34
  249. package/src/agent-rpc-body.ts +0 -92
  250. package/src/agent-stop-stuck.ts +0 -129
  251. package/src/card-action-routing.ts +0 -14
  252. package/src/card-plain-text.ts +0 -108
  253. package/src/cardkit.ts +0 -179
  254. package/src/cards.ts +0 -684
  255. package/src/chatgpt-subscription-rpc.ts +0 -27
  256. package/src/chatgpt-subscription.ts +0 -299
  257. package/src/chrome-devtools-guard.ts +0 -318
  258. package/src/claude-sdk-installer.ts +0 -324
  259. package/src/codex-reset-actions.ts +0 -184
  260. package/src/config-utils.ts +0 -211
  261. package/src/config.ts +0 -1063
  262. package/src/cursor-usage.ts +0 -128
  263. package/src/exit-banner.ts +0 -33
  264. package/src/feishu-api.ts +0 -1616
  265. package/src/feishu-message-ingress.ts +0 -195
  266. package/src/feishu-platform.ts +0 -159
  267. package/src/format-message.ts +0 -293
  268. package/src/git-command.ts +0 -202
  269. package/src/im-skills.ts +0 -149
  270. package/src/index.ts +0 -1089
  271. package/src/litellm-proxy.ts +0 -374
  272. package/src/orchestrator.ts +0 -2543
  273. package/src/platform-adapter.ts +0 -70
  274. package/src/platform-startup.ts +0 -16
  275. package/src/privacy.ts +0 -118
  276. package/src/progress/reducer.ts +0 -113
  277. package/src/progress/terminal-renderer.ts +0 -294
  278. package/src/progress/view.ts +0 -77
  279. package/src/response-stall.ts +0 -28
  280. package/src/runtime-reload.ts +0 -34
  281. package/src/session-chat-binding.ts +0 -292
  282. package/src/session-name.ts +0 -8
  283. package/src/session.ts +0 -2659
  284. package/src/shared-prefix.ts +0 -29
  285. package/src/shared.ts +0 -552
  286. package/src/sim-agent.ts +0 -167
  287. package/src/sim-platform.ts +0 -177
  288. package/src/sim-store.ts +0 -317
  289. package/src/simplify.ts +0 -120
  290. package/src/startup-lifecycle.ts +0 -250
  291. package/src/stream-state.ts +0 -177
  292. package/src/terminal-error.ts +0 -129
  293. package/src/trace.ts +0 -51
  294. package/src/turn-cards.ts +0 -118
  295. package/src/update-command-guard.ts +0 -165
  296. package/src/wechat-platform.ts +0 -680
@@ -0,0 +1,104 @@
1
+ import { createInterface } from "node:readline";
2
+ export const DEFAULT_BAD_JSON_IDLE_TIMEOUT_MS = 120_000;
3
+ export const BAD_JSON_IDLE_TIMEOUT_CODE = "BAD_JSON_IDLE_TIMEOUT";
4
+ export class BadJsonIdleTimeoutError extends Error {
5
+ code = BAD_JSON_IDLE_TIMEOUT_CODE;
6
+ tool;
7
+ tag;
8
+ timeoutMs;
9
+ lineNumber;
10
+ lineExcerpt;
11
+ parseError;
12
+ constructor(args) {
13
+ super(`${args.tool} stream stopped after invalid JSON for ${args.timeoutMs}ms ` +
14
+ `(tag=${args.tag}, line=${args.lineNumber}): ${args.lineExcerpt}`);
15
+ this.name = "BadJsonIdleTimeoutError";
16
+ this.tool = args.tool;
17
+ this.tag = args.tag;
18
+ this.timeoutMs = args.timeoutMs;
19
+ this.lineNumber = args.lineNumber;
20
+ this.lineExcerpt = args.lineExcerpt;
21
+ this.parseError = args.parseError;
22
+ }
23
+ }
24
+ function isJsonLike(line) {
25
+ return line.startsWith("{") || line.startsWith("[");
26
+ }
27
+ function lineExcerpt(line) {
28
+ const max = 500;
29
+ return line.length <= max ? line : `${line.slice(0, max)}...`;
30
+ }
31
+ function parseErrorMessage(error) {
32
+ return error instanceof Error ? error.message : String(error);
33
+ }
34
+ export async function* readJsonLinesWithBadJsonIdleWatchdog({ input, tool, tag = tool, signal, rawLog, idleTimeoutMs = DEFAULT_BAD_JSON_IDLE_TIMEOUT_MS, parse = (line) => JSON.parse(line), onRawLine, onParsedLine, onDone, }) {
35
+ const rl = createInterface({ input, crlfDelay: Infinity });
36
+ let lineCount = 0;
37
+ let protocolError = null;
38
+ let badJsonIdleTimer = null;
39
+ const clearBadJsonIdleTimer = () => {
40
+ if (!badJsonIdleTimer)
41
+ return;
42
+ clearTimeout(badJsonIdleTimer);
43
+ badJsonIdleTimer = null;
44
+ };
45
+ const armBadJsonIdleTimer = (line, lineNumber, error) => {
46
+ if (idleTimeoutMs <= 0)
47
+ return;
48
+ clearBadJsonIdleTimer();
49
+ badJsonIdleTimer = setTimeout(() => {
50
+ protocolError = new BadJsonIdleTimeoutError({
51
+ tool,
52
+ tag,
53
+ timeoutMs: idleTimeoutMs,
54
+ lineNumber,
55
+ lineExcerpt: lineExcerpt(line),
56
+ parseError: parseErrorMessage(error),
57
+ });
58
+ rl.close();
59
+ }, idleTimeoutMs);
60
+ const timerWithUnref = badJsonIdleTimer;
61
+ timerWithUnref.unref?.();
62
+ };
63
+ const onAbort = () => {
64
+ clearBadJsonIdleTimer();
65
+ rl.close();
66
+ };
67
+ signal?.addEventListener("abort", onAbort, { once: true });
68
+ try {
69
+ for await (const line of rl) {
70
+ if (signal?.aborted)
71
+ break;
72
+ lineCount++;
73
+ onRawLine?.(line, lineCount);
74
+ const trimmed = line.trim();
75
+ if (!trimmed)
76
+ continue;
77
+ clearBadJsonIdleTimer();
78
+ rawLog?.writeLine(trimmed);
79
+ try {
80
+ const parsed = parse(trimmed);
81
+ onParsedLine?.(parsed, trimmed, lineCount);
82
+ yield parsed;
83
+ }
84
+ catch (error) {
85
+ if (isJsonLike(trimmed)) {
86
+ armBadJsonIdleTimer(trimmed, lineCount, error);
87
+ }
88
+ }
89
+ }
90
+ if (protocolError && !signal?.aborted) {
91
+ throw protocolError;
92
+ }
93
+ }
94
+ finally {
95
+ signal?.removeEventListener("abort", onAbort);
96
+ clearBadJsonIdleTimer();
97
+ rl.close();
98
+ onDone?.({
99
+ lineCount,
100
+ signalAborted: signal?.aborted ?? false,
101
+ protocolError,
102
+ });
103
+ }
104
+ }
@@ -1,97 +1,94 @@
1
- // =============================================================================
2
- // proc-tree-kill.ts — 跨平台进程树强杀工具
3
- // =============================================================================
4
- // 背景:codex / cursor adapter 通过 `spawn(cmd, args, { shell: true })` 启动 CLI
5
- // 时,Node 拿到的 proc.pid 是最外层 cmd.exe(Windows)或 /bin/sh(其它)的
6
- // PID。真正干活的是它再 spawn 出来的:
7
- //
8
- // cmd.exe ← proc.kill() 只能杀到这一层
9
- // └─ node codex.js ← Codex CLI 入口
10
- // └─ codex.exe ← 实际 Rust 二进制(继续烧 token)
11
- //
12
- // 单纯 proc.kill() 在 Windows 上等价于 TerminateProcess 顶层壳,孙子进程不会
13
- // 收到任何信号、继续运行,导致用户 /stop 看似生效(adapter 标记 stopped)但
14
- // 实际 codex 仍在后台跑、stream-state 一直停在 "running"。
15
- //
16
- // 解决方案:abort 时不要走 proc.kill(),而是用本工具按 pid 杀掉整棵进程树。
17
- // - Windows: `taskkill /pid <pid> /T /F`(/T = 递归子进程, /F = 强制)
18
- // - 其它:`process.kill(-pgid, "SIGTERM")` + 兜底 SIGKILL(adapter spawn 时
19
- // 需配合 detached:true 让子进程拥有独立 process group)
20
- // =============================================================================
21
-
22
- import { spawn } from "node:child_process";
23
-
24
- /** 异步杀掉以 pid 为根的整棵进程树。
25
- *
26
- * 设计目标:永不抛错、永不阻塞调用者。
27
- * - pid 不存在、参数缺失 静默返回
28
- * - 子进程 spawn 失败 → console.warn 但不 reject
29
- * - Windows taskkill 异步执行,不阻塞 event loop
30
- *
31
- * 调用方约定:返回的 Promise 在 kill 命令发出后立即 resolve。
32
- * 真正的进程退出由 OS 异步完成,调用方如果需要确认"已死透",应自己再轮询
33
- * `process.kill(pid, 0)`。
34
- */
35
- export async function killProcessTree(pid: number | undefined): Promise<void> {
36
- if (pid == null || !Number.isFinite(pid) || pid <= 0) return;
37
- if (process.platform === "win32") {
38
- await killWindowsTree(pid);
39
- return;
40
- }
41
- await killPosixTree(pid);
42
- }
43
-
44
- // ---------------------------------------------------------------------------
45
- // Windows: taskkill /T /F
46
- // ---------------------------------------------------------------------------
47
-
48
- function killWindowsTree(pid: number): Promise<void> {
49
- return new Promise<void>((resolve) => {
50
- let resolved = false;
51
- const done = () => {
52
- if (resolved) return;
53
- resolved = true;
54
- resolve();
55
- };
56
- try {
57
- const proc = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
58
- stdio: "ignore",
59
- windowsHide: true,
60
- // taskkill 本身很快(<200ms),不需要 detached
61
- });
62
- proc.once("error", (err) => {
63
- console.warn(`[killProcessTree] taskkill spawn error for pid=${pid}: ${(err as Error).message}`);
64
- done();
65
- });
66
- proc.once("close", () => { done(); });
67
- // 兜底超时:3 秒后强制 resolve,避免极端情况下 hang 住调用方
68
- setTimeout(done, 3000).unref();
69
- } catch (err) {
70
- console.warn(`[killProcessTree] taskkill failed for pid=${pid}: ${(err as Error).message}`);
71
- done();
72
- }
73
- });
74
- }
75
-
76
- // ---------------------------------------------------------------------------
77
- // POSIX: 优先按 process group 杀,回退到按 pid
78
- // ---------------------------------------------------------------------------
79
-
80
- async function killPosixTree(pid: number): Promise<void> {
81
- // 第一次尝试:按 process group 发 SIGTERM。要求 spawn 时 detached:true。
82
- trySignal(-pid, "SIGTERM");
83
- trySignal(pid, "SIGTERM");
84
- // 给进程 1 秒优雅退出机会
85
- await new Promise((r) => setTimeout(r, 1000));
86
- // 兜底:SIGKILL
87
- trySignal(-pid, "SIGKILL");
88
- trySignal(pid, "SIGKILL");
89
- }
90
-
91
- function trySignal(target: number, signal: NodeJS.Signals): void {
92
- try {
93
- process.kill(target, signal);
94
- } catch {
95
- // 进程已不存在或权限不足,忽略
96
- }
97
- }
1
+ // =============================================================================
2
+ // proc-tree-kill.ts — 跨平台进程树强杀工具
3
+ // =============================================================================
4
+ // 背景:codex / cursor adapter 通过 `spawn(cmd, args, { shell: true })` 启动 CLI
5
+ // 时,Node 拿到的 proc.pid 是最外层 cmd.exe(Windows)或 /bin/sh(其它)的
6
+ // PID。真正干活的是它再 spawn 出来的:
7
+ //
8
+ // cmd.exe ← proc.kill() 只能杀到这一层
9
+ // └─ node codex.js ← Codex CLI 入口
10
+ // └─ codex.exe ← 实际 Rust 二进制(继续烧 token)
11
+ //
12
+ // 单纯 proc.kill() 在 Windows 上等价于 TerminateProcess 顶层壳,孙子进程不会
13
+ // 收到任何信号、继续运行,导致用户 /stop 看似生效(adapter 标记 stopped)但
14
+ // 实际 codex 仍在后台跑、stream-state 一直停在 "running"。
15
+ //
16
+ // 解决方案:abort 时不要走 proc.kill(),而是用本工具按 pid 杀掉整棵进程树。
17
+ // - Windows: `taskkill /pid <pid> /T /F`(/T = 递归子进程, /F = 强制)
18
+ // - 其它:`process.kill(-pgid, "SIGTERM")` + 兜底 SIGKILL(adapter spawn 时
19
+ // 需配合 detached:true 让子进程拥有独立 process group)
20
+ // =============================================================================
21
+ import { spawn } from "node:child_process";
22
+ /** 异步杀掉以 pid 为根的整棵进程树。
23
+ *
24
+ * 设计目标:永不抛错、永不阻塞调用者。
25
+ * - pid 不存在、参数缺失 → 静默返回
26
+ * - 子进程 spawn 失败 → console.warn 但不 reject
27
+ * - Windows taskkill 异步执行,不阻塞 event loop
28
+ *
29
+ * 调用方约定:返回的 Promise kill 命令发出后立即 resolve。
30
+ * 真正的进程退出由 OS 异步完成,调用方如果需要确认"已死透",应自己再轮询
31
+ * `process.kill(pid, 0)`。
32
+ */
33
+ export async function killProcessTree(pid) {
34
+ if (pid == null || !Number.isFinite(pid) || pid <= 0)
35
+ return;
36
+ if (process.platform === "win32") {
37
+ await killWindowsTree(pid);
38
+ return;
39
+ }
40
+ await killPosixTree(pid);
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Windows: taskkill /T /F
44
+ // ---------------------------------------------------------------------------
45
+ function killWindowsTree(pid) {
46
+ return new Promise((resolve) => {
47
+ let resolved = false;
48
+ const done = () => {
49
+ if (resolved)
50
+ return;
51
+ resolved = true;
52
+ resolve();
53
+ };
54
+ try {
55
+ const proc = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
56
+ stdio: "ignore",
57
+ windowsHide: true,
58
+ // taskkill 本身很快(<200ms),不需要 detached
59
+ });
60
+ proc.once("error", (err) => {
61
+ console.warn(`[killProcessTree] taskkill spawn error for pid=${pid}: ${err.message}`);
62
+ done();
63
+ });
64
+ proc.once("close", () => { done(); });
65
+ // 兜底超时:3 秒后强制 resolve,避免极端情况下 hang 住调用方
66
+ setTimeout(done, 3000).unref();
67
+ }
68
+ catch (err) {
69
+ console.warn(`[killProcessTree] taskkill failed for pid=${pid}: ${err.message}`);
70
+ done();
71
+ }
72
+ });
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // POSIX: 优先按 process group 杀,回退到按 pid 杀
76
+ // ---------------------------------------------------------------------------
77
+ async function killPosixTree(pid) {
78
+ // 第一次尝试:按 process group 发 SIGTERM。要求 spawn 时 detached:true。
79
+ trySignal(-pid, "SIGTERM");
80
+ trySignal(pid, "SIGTERM");
81
+ // 给进程 1 秒优雅退出机会
82
+ await new Promise((r) => setTimeout(r, 1000));
83
+ // 兜底:SIGKILL
84
+ trySignal(-pid, "SIGKILL");
85
+ trySignal(pid, "SIGKILL");
86
+ }
87
+ function trySignal(target, signal) {
88
+ try {
89
+ process.kill(target, signal);
90
+ }
91
+ catch {
92
+ // 进程已不存在或权限不足,忽略
93
+ }
94
+ }
@@ -0,0 +1,106 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
3
+ import { once } from "node:events";
4
+ import { join } from "node:path";
5
+ import { createGzip } from "node:zlib";
6
+ export function sanitizeLogPathSegment(value) {
7
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^[._]+|_+$/g, "");
8
+ return safe || "unknown";
9
+ }
10
+ async function cleanupOldRawStreamLogs(rootDir, retentionDays) {
11
+ if (!Number.isFinite(retentionDays) || retentionDays <= 0)
12
+ return;
13
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
14
+ const visit = async (dir) => {
15
+ let entries;
16
+ try {
17
+ entries = await readdir(dir, { withFileTypes: true });
18
+ }
19
+ catch {
20
+ return;
21
+ }
22
+ await Promise.all(entries.map(async (entry) => {
23
+ const path = join(dir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ await visit(path);
26
+ try {
27
+ await rm(path, { recursive: false });
28
+ }
29
+ catch {
30
+ // Directory is not empty or already gone.
31
+ }
32
+ return;
33
+ }
34
+ if (!entry.isFile())
35
+ return;
36
+ try {
37
+ const info = await stat(path);
38
+ if (info.mtimeMs < cutoff)
39
+ await rm(path, { force: true });
40
+ }
41
+ catch {
42
+ // Best-effort cleanup only.
43
+ }
44
+ }));
45
+ };
46
+ await visit(rootDir);
47
+ }
48
+ export async function createRawStreamLog(options) {
49
+ if (!options.enabled)
50
+ return null;
51
+ const maxBytes = Math.max(0, Math.floor(options.maxBytesPerTurn));
52
+ const tool = sanitizeLogPathSegment(options.tool);
53
+ const session = sanitizeLogPathSegment(options.sessionId);
54
+ const label = sanitizeLogPathSegment(options.label);
55
+ const dir = join(options.rootDir, tool, session);
56
+ await mkdir(dir, { recursive: true });
57
+ void cleanupOldRawStreamLogs(join(options.rootDir, tool), options.retentionDays);
58
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
59
+ const filePath = join(dir, `${timestamp}-${label}.jsonl.gz`);
60
+ const output = createWriteStream(filePath);
61
+ const gzip = createGzip();
62
+ gzip.pipe(output);
63
+ let bytes = 0;
64
+ let truncated = false;
65
+ let ended = false;
66
+ const writeLine = (line) => {
67
+ if (ended || truncated)
68
+ return;
69
+ const payload = `${line}\n`;
70
+ const payloadBytes = Buffer.byteLength(payload, "utf-8");
71
+ if (maxBytes > 0 && bytes + payloadBytes > maxBytes) {
72
+ const marker = JSON.stringify({
73
+ type: "chatccc_raw_stream_log_truncated",
74
+ reason: "max_bytes_per_turn_exceeded",
75
+ maxBytesPerTurn: maxBytes,
76
+ writtenBytes: bytes,
77
+ });
78
+ gzip.write(`${marker}\n`);
79
+ truncated = true;
80
+ return;
81
+ }
82
+ bytes += payloadBytes;
83
+ gzip.write(payload);
84
+ };
85
+ const close = async ({ keep }) => {
86
+ if (ended)
87
+ return;
88
+ ended = true;
89
+ gzip.end();
90
+ try {
91
+ await once(output, "finish");
92
+ }
93
+ catch {
94
+ // Ignore close errors; caller cannot recover from debug log failures.
95
+ }
96
+ if (!keep) {
97
+ try {
98
+ await unlink(filePath);
99
+ }
100
+ catch {
101
+ // Best-effort cleanup only.
102
+ }
103
+ }
104
+ };
105
+ return { filePath, writeLine, close };
106
+ }
@@ -0,0 +1,113 @@
1
+ // =============================================================================
2
+ // resource-monitor.ts — CLI 进程资源监控(CPU + 内存)
3
+ // =============================================================================
4
+ // 对所有 chatccc 启动的 CLI 进程持续监控 CPU 和内存占用。
5
+ // 若连续 3 分钟两项指标均无变化,判定为僵死,发出 "stuck" 事件。
6
+ // =============================================================================
7
+ import { exec } from "node:child_process";
8
+ import { EventEmitter } from "node:events";
9
+ const CHECK_INTERVAL_MS = 30_000; // 30 秒检查一次
10
+ const STUCK_THRESHOLD = 6; // 连续 6 次无变化 = 3 分钟
11
+ export const resourceMonitor = new EventEmitter();
12
+ const tracked = new Map();
13
+ let timer = null;
14
+ function startIfNeeded() {
15
+ if (timer)
16
+ return;
17
+ timer = setInterval(checkAll, CHECK_INTERVAL_MS);
18
+ timer.unref?.();
19
+ }
20
+ function stopIfIdle() {
21
+ if (tracked.size > 0)
22
+ return;
23
+ if (timer) {
24
+ clearInterval(timer);
25
+ timer = null;
26
+ }
27
+ }
28
+ export function registerProcess(pid, sessionId) {
29
+ tracked.set(pid, { pid, sessionId, snapshot: { cpu: -1, memory: -1, unchangedCount: 0 } });
30
+ startIfNeeded();
31
+ }
32
+ export function unregisterProcess(pid) {
33
+ tracked.delete(pid);
34
+ stopIfIdle();
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // 批量查询进程指标(Windows PowerShell)
38
+ // ---------------------------------------------------------------------------
39
+ function execPowerShell(script, timeoutMs) {
40
+ return new Promise((resolve, reject) => {
41
+ exec(`powershell -NoProfile -Command "${script}"`, { timeout: timeoutMs, windowsHide: true }, (err, stdout) => {
42
+ if (err)
43
+ reject(err);
44
+ else
45
+ resolve(stdout);
46
+ });
47
+ });
48
+ }
49
+ async function getProcessMetrics(pids) {
50
+ const result = new Map();
51
+ if (pids.length === 0)
52
+ return result;
53
+ const psScript = `Get-Process -Id ${pids.join(",")} -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Id)|$($_.CPU)|$($_.WorkingSet64)" }`;
54
+ try {
55
+ const stdout = await execPowerShell(psScript, 10_000);
56
+ for (const line of stdout.trim().split(/\r?\n/)) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed)
59
+ continue;
60
+ const [idStr, cpuStr, memStr] = trimmed.split("|");
61
+ const id = parseInt(idStr, 10);
62
+ const cpu = parseFloat(cpuStr);
63
+ const memory = parseInt(memStr, 10);
64
+ if (!isNaN(id) && !isNaN(cpu) && !isNaN(memory)) {
65
+ result.set(id, { cpu, memory });
66
+ }
67
+ }
68
+ }
69
+ catch {
70
+ // PowerShell 查询失败时跳过本轮,下次重试
71
+ }
72
+ return result;
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // 定时检查
76
+ // ---------------------------------------------------------------------------
77
+ async function checkAll() {
78
+ if (tracked.size === 0)
79
+ return;
80
+ const pids = [...tracked.keys()];
81
+ const metrics = await getProcessMetrics(pids);
82
+ for (const [pid, tp] of tracked) {
83
+ const m = metrics.get(pid);
84
+ if (!m) {
85
+ // 进程已不存在,停止追踪
86
+ tracked.delete(pid);
87
+ continue;
88
+ }
89
+ const prev = tp.snapshot;
90
+ const cpuChanged = m.cpu !== prev.cpu;
91
+ // 内存允许 ±1% 波动,避免正常抖动触发误判
92
+ const memTolerance = prev.memory > 0 ? Math.max(prev.memory * 0.01, 1024 * 1024) : 1024 * 1024;
93
+ const memChanged = Math.abs(m.memory - prev.memory) > memTolerance;
94
+ if (!cpuChanged && !memChanged) {
95
+ tp.snapshot.unchangedCount++;
96
+ if (tp.snapshot.unchangedCount >= STUCK_THRESHOLD) {
97
+ const idleMinutes = Math.round((tp.snapshot.unchangedCount * CHECK_INTERVAL_MS) / 60_000);
98
+ resourceMonitor.emit("stuck", {
99
+ pid: tp.pid,
100
+ sessionId: tp.sessionId,
101
+ idleMinutes,
102
+ });
103
+ tracked.delete(pid);
104
+ }
105
+ }
106
+ else {
107
+ tp.snapshot.unchangedCount = 0;
108
+ }
109
+ tp.snapshot.cpu = m.cpu;
110
+ tp.snapshot.memory = m.memory;
111
+ }
112
+ stopIfIdle();
113
+ }
@@ -0,0 +1,133 @@
1
+ export function createAgentActivityTracker(now = Date.now()) {
2
+ return {
3
+ activity: { kind: "starting", startedAt: now },
4
+ activeTools: new Map(),
5
+ nextAnonymousToolId: 1,
6
+ };
7
+ }
8
+ function sameVisibleActivity(left, right) {
9
+ return left.kind === right.kind
10
+ && left.toolName === right.toolName
11
+ && left.toolCount === right.toolCount;
12
+ }
13
+ function setActivity(tracker, next) {
14
+ if (sameVisibleActivity(tracker.activity, next))
15
+ return false;
16
+ tracker.activity = next;
17
+ return true;
18
+ }
19
+ function refreshToolActivity(tracker) {
20
+ const tools = [...tracker.activeTools.values()];
21
+ const first = tools[0];
22
+ if (!first)
23
+ return false;
24
+ return setActivity(tracker, {
25
+ kind: "tool",
26
+ startedAt: first.startedAt,
27
+ toolName: first.name,
28
+ toolCount: tools.length,
29
+ });
30
+ }
31
+ function removeCompletedTool(tracker, toolUseId) {
32
+ if (toolUseId && tracker.activeTools.delete(toolUseId))
33
+ return;
34
+ // Older adapters did not always include a tool ID. Prefer an anonymous entry;
35
+ // if there is only one active call, it is still safe to match that result.
36
+ const anonymousId = [...tracker.activeTools.keys()].find((id) => id.startsWith("anonymous:"));
37
+ if (anonymousId) {
38
+ tracker.activeTools.delete(anonymousId);
39
+ }
40
+ else if (tracker.activeTools.size === 1) {
41
+ const onlyId = tracker.activeTools.keys().next().value;
42
+ if (onlyId)
43
+ tracker.activeTools.delete(onlyId);
44
+ }
45
+ }
46
+ /**
47
+ * Applies one normalized Agent event and returns whether the persisted activity
48
+ * changed. Tool activity takes precedence while a tool call is still active.
49
+ */
50
+ export function updateAgentActivity(tracker, block, now = Date.now()) {
51
+ if (block.type === "tool_use") {
52
+ const id = block.id || `anonymous:${tracker.nextAnonymousToolId++}`;
53
+ const existing = tracker.activeTools.get(id);
54
+ tracker.activeTools.set(id, {
55
+ id,
56
+ name: block.name || "未知工具",
57
+ startedAt: existing?.startedAt ?? now,
58
+ });
59
+ return refreshToolActivity(tracker);
60
+ }
61
+ if (block.type === "tool_result") {
62
+ removeCompletedTool(tracker, block.tool_use_id);
63
+ if (tracker.activeTools.size > 0)
64
+ return refreshToolActivity(tracker);
65
+ return setActivity(tracker, { kind: "processing", startedAt: now });
66
+ }
67
+ if (tracker.activeTools.size > 0)
68
+ return false;
69
+ switch (block.type) {
70
+ case "agent_status":
71
+ return setActivity(tracker, {
72
+ kind: block.status === "compacting" ? "compacting" : "responding",
73
+ startedAt: now,
74
+ });
75
+ case "thinking":
76
+ case "redacted_thinking":
77
+ return setActivity(tracker, { kind: "thinking", startedAt: now });
78
+ case "text":
79
+ case "text_final":
80
+ return setActivity(tracker, { kind: "responding", startedAt: now });
81
+ case "search_result":
82
+ return setActivity(tracker, { kind: "searching", startedAt: now });
83
+ case "compact_boundary":
84
+ return setActivity(tracker, { kind: "compacting", startedAt: now });
85
+ }
86
+ }
87
+ function formatElapsed(startedAt, now) {
88
+ const totalSeconds = Math.max(0, Math.floor((now - startedAt) / 1000));
89
+ if (totalSeconds < 60)
90
+ return `${totalSeconds}秒`;
91
+ const totalMinutes = Math.floor(totalSeconds / 60);
92
+ const seconds = totalSeconds % 60;
93
+ if (totalMinutes < 60)
94
+ return `${totalMinutes}分${seconds}秒`;
95
+ const hours = Math.floor(totalMinutes / 60);
96
+ const minutes = totalMinutes % 60;
97
+ return `${hours}小时${minutes}分`;
98
+ }
99
+ function displayToolName(name) {
100
+ const normalized = (name || "未知工具").replace(/\s+/g, " ").trim();
101
+ return normalized.length > 24 ? `${normalized.slice(0, 23)}…` : normalized;
102
+ }
103
+ export function formatAgentActivityTitle(activity, now = Date.now()) {
104
+ if (!activity)
105
+ return "正在处理";
106
+ let label;
107
+ switch (activity.kind) {
108
+ case "starting":
109
+ label = "正在启动 Agent";
110
+ break;
111
+ case "thinking":
112
+ label = "思考中";
113
+ break;
114
+ case "tool": {
115
+ const count = Math.max(1, activity.toolCount ?? 1);
116
+ label = `正在执行 ${displayToolName(activity.toolName)}${count > 1 ? ` 等 ${count} 项` : ""}`;
117
+ break;
118
+ }
119
+ case "processing":
120
+ label = "正在处理工具结果";
121
+ break;
122
+ case "responding":
123
+ label = "正在生成回复";
124
+ break;
125
+ case "searching":
126
+ label = "正在处理搜索结果";
127
+ break;
128
+ case "compacting":
129
+ label = "正在整理上下文";
130
+ break;
131
+ }
132
+ return `${label} · ${formatElapsed(activity.startedAt, now)}`;
133
+ }