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
package/src/simplify.ts DELETED
@@ -1,120 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { resolve as resolvePath } from "node:path";
3
- import { PROJECT_ROOT } from "./config.ts";
4
-
5
- // ---------------------------------------------------------------------------
6
- // 消息简化规则 —— 数据驱动,在 simplify.json 中配置
7
- // ---------------------------------------------------------------------------
8
-
9
- interface ToolRule {
10
- template: string;
11
- maxLength: number;
12
- }
13
-
14
- interface SimplifyConfig {
15
- tool_use?: Record<string, ToolRule>;
16
- tool_result?: Record<string, ToolRule>;
17
- }
18
-
19
- let config: SimplifyConfig | null = null;
20
- let loaded = false;
21
-
22
- function loadConfig(): SimplifyConfig {
23
- const filePath = resolvePath(PROJECT_ROOT, "simplify.json");
24
- if (!existsSync(filePath)) return {};
25
- try {
26
- const raw = readFileSync(filePath, "utf-8");
27
- const parsed = JSON.parse(raw);
28
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
29
- console.error("[SIMPLIFY] simplify.json 格式错误:应为对象");
30
- return {};
31
- }
32
- return parsed as SimplifyConfig;
33
- } catch (err) {
34
- console.error(`[SIMPLIFY] 读取 simplify.json 失败: ${(err as Error).message}`);
35
- return {};
36
- }
37
- }
38
-
39
- function getConfig(): SimplifyConfig {
40
- if (!loaded) {
41
- config = loadConfig();
42
- loaded = true;
43
- }
44
- return config!;
45
- }
46
-
47
- /** 热重载 */
48
- export function reloadSimplifyConfig(): void {
49
- loaded = false;
50
- config = null;
51
- }
52
-
53
- /**
54
- * 对模板字符串中的 {field} 占位符做替换。
55
- * fields 为可用字段映射,extra 是额外的上下文(如 tool_use_id 的 id)。
56
- */
57
- function resolveTemplate(template: string, fields: Record<string, unknown>, extra?: Record<string, string>): string {
58
- let result = template;
59
- if (extra) {
60
- for (const [k, v] of Object.entries(extra)) {
61
- result = result.split(`{${k}}`).join(v);
62
- }
63
- }
64
- for (const [k, v] of Object.entries(fields)) {
65
- const strVal = typeof v === "string" ? v : JSON.stringify(v);
66
- result = result.split(`{${k}}`).join(strVal);
67
- }
68
- return result;
69
- }
70
-
71
- /**
72
- * 简化 tool_use 展示。
73
- * 返回 null 表示无规则,调用方应回退到默认格式化。
74
- */
75
- export function simplifyToolUse(name: string, input: unknown): string | null {
76
- const cfg = getConfig();
77
- const rules = cfg.tool_use;
78
- if (!rules) return null;
79
- const rule = rules[name];
80
- if (!rule) return null;
81
-
82
- const fields = typeof input === "object" && input !== null
83
- ? input as Record<string, unknown>
84
- : {};
85
- let result = resolveTemplate(rule.template, fields);
86
- if (result.length > rule.maxLength) {
87
- result = result.slice(0, rule.maxLength) + "...";
88
- }
89
- return result;
90
- }
91
-
92
- /**
93
- * 简化 tool_result 展示。
94
- * 返回 null 表示无规则,调用方应回退到默认格式化。
95
- * toolCallInput 为对应的 tool_use 输入(可选),用于在 result 模板中引用输入字段。
96
- */
97
- export function simplifyToolResult(
98
- name: string,
99
- toolUseId: string,
100
- isError: boolean,
101
- toolCallInput?: unknown,
102
- ): string | null {
103
- const cfg = getConfig();
104
- const rules = cfg.tool_result;
105
- if (!rules) return null;
106
- const rule = rules[name];
107
- if (!rule) return null;
108
-
109
- const id = toolUseId.slice(-6);
110
- const extra = { id };
111
- const fields = toolCallInput && typeof toolCallInput === "object"
112
- ? toolCallInput as Record<string, unknown>
113
- : {};
114
- let result = resolveTemplate(rule.template, fields, extra);
115
- if (isError) result = "❌ " + result;
116
- if (result.length > rule.maxLength) {
117
- result = result.slice(0, rule.maxLength) + "...";
118
- }
119
- return result;
120
- }
@@ -1,250 +0,0 @@
1
- import { spawn, type ChildProcess } from "node:child_process";
2
-
3
- const DEFAULT_SERVICE_HEALTH_INTERVAL_MS = 10_000;
4
-
5
- interface RefTimer {
6
- ref?: () => unknown;
7
- }
8
-
9
- export interface ServiceLifecycleServer {
10
- listening: boolean;
11
- address: () => unknown;
12
- ref: () => unknown;
13
- }
14
-
15
- interface ServiceLifecycleGuardOptions {
16
- intervalMs?: number;
17
- setIntervalImpl?: (callback: () => void, delayMs: number) => RefTimer;
18
- clearIntervalImpl?: (timer: RefTimer) => void;
19
- tracer?: (message: string, extra?: Record<string, unknown>) => void;
20
- getActiveResourcesInfo?: () => string[];
21
- }
22
-
23
- export interface ServiceLifecycleGuard {
24
- start: () => void;
25
- attachServer: (
26
- server: ServiceLifecycleServer,
27
- recoverServer?: () => void | Promise<void>,
28
- ) => void;
29
- checkNow: () => Promise<void>;
30
- handleBeforeExit: (code: number) => void;
31
- beginShutdown: (reason: string) => void;
32
- }
33
-
34
- /**
35
- * 为 ChatCCC 这种常驻服务建立一个明确的进程生命周期锚点。
36
- *
37
- * 正常情况下,正在 listen 的 HTTP Server 自己就足以维持事件循环;额外的
38
- * referenced timer 是最后一道保险,避免某个依赖升级或异常 close/unref 让进程在
39
- * 没有信号、异常或退出码的情况下静默消失。定时检查同时会重新 ref Server,并在
40
- * Server 确实停止监听时串行触发恢复,避免只把一个失去服务能力的僵尸进程留下来。
41
- */
42
- export function createServiceLifecycleGuard(
43
- options: ServiceLifecycleGuardOptions = {},
44
- ): ServiceLifecycleGuard {
45
- const intervalMs = options.intervalMs ?? DEFAULT_SERVICE_HEALTH_INTERVAL_MS;
46
- const setIntervalImpl = options.setIntervalImpl
47
- ?? ((callback, delayMs) => setInterval(callback, delayMs));
48
- const clearIntervalImpl = options.clearIntervalImpl
49
- ?? ((timer) => clearInterval(timer as NodeJS.Timeout));
50
- const tracer = options.tracer ?? (() => {});
51
- const getActiveResourcesInfo = options.getActiveResourcesInfo
52
- ?? (() => process.getActiveResourcesInfo());
53
-
54
- let timer: RefTimer | null = null;
55
- let server: ServiceLifecycleServer | null = null;
56
- let recoverServer: (() => void | Promise<void>) | undefined;
57
- let recoveryPromise: Promise<void> | null = null;
58
- let shuttingDown = false;
59
-
60
- const trace = (message: string, extra?: Record<string, unknown>): void => {
61
- try { tracer(message, extra); } catch { /* 诊断路径不能反过来打断服务 */ }
62
- };
63
-
64
- const serverAddress = (): unknown => {
65
- try { return server?.address() ?? null; } catch { return null; }
66
- };
67
-
68
- const activeResources = (): string[] => {
69
- try { return getActiveResourcesInfo(); } catch { return []; }
70
- };
71
-
72
- const diagnostics = (extra: Record<string, unknown> = {}): Record<string, unknown> => ({
73
- ...extra,
74
- uptimeSeconds: Math.floor(process.uptime()),
75
- activeResources: activeResources(),
76
- serverAttached: server !== null,
77
- serverListening: server?.listening ?? false,
78
- serverAddress: serverAddress(),
79
- });
80
-
81
- const start = (): void => {
82
- if (shuttingDown || timer) return;
83
- timer = setIntervalImpl(() => { void checkNow(); }, intervalMs);
84
- // Node 的 Timeout 默认就是 ref 状态;显式 ref 让常驻服务契约不会依赖默认值。
85
- try { timer.ref?.(); } catch { /* ignore */ }
86
- trace("service-lifecycle: guard started", { intervalMs });
87
- };
88
-
89
- const checkNow = async (): Promise<void> => {
90
- if (shuttingDown || !server) return;
91
- if (server.listening) {
92
- try { server.ref(); } catch (err) {
93
- trace("service-lifecycle: HTTP server ref failed", diagnostics({
94
- error: (err as Error).message,
95
- }));
96
- }
97
- return;
98
- }
99
-
100
- if (recoveryPromise) return recoveryPromise;
101
- trace("service-lifecycle: HTTP server inactive", diagnostics());
102
- if (!recoverServer) return;
103
-
104
- recoveryPromise = Promise.resolve()
105
- .then(() => recoverServer?.())
106
- .then(() => {
107
- if (server?.listening) {
108
- try { server.ref(); } catch { /* 下一轮健康检查会再次尝试 */ }
109
- trace("service-lifecycle: HTTP server recovered", diagnostics());
110
- } else {
111
- trace("service-lifecycle: HTTP recovery completed without listening", diagnostics());
112
- }
113
- })
114
- .catch((err: unknown) => {
115
- trace("service-lifecycle: HTTP server recovery failed", diagnostics({
116
- error: err instanceof Error ? err.message : String(err),
117
- }));
118
- })
119
- .finally(() => {
120
- recoveryPromise = null;
121
- });
122
- return recoveryPromise;
123
- };
124
-
125
- const attachServer = (
126
- nextServer: ServiceLifecycleServer,
127
- nextRecoverServer?: () => void | Promise<void>,
128
- ): void => {
129
- server = nextServer;
130
- recoverServer = nextRecoverServer;
131
- if (server.listening) {
132
- try { server.ref(); } catch { /* 下一轮健康检查会记录 */ }
133
- }
134
- trace("service-lifecycle: HTTP server attached", diagnostics());
135
- };
136
-
137
- const handleBeforeExit = (code: number): void => {
138
- if (shuttingDown) return;
139
- trace("service-lifecycle: unexpected beforeExit", diagnostics({ code }));
140
- start();
141
- void checkNow();
142
- };
143
-
144
- const beginShutdown = (reason: string): void => {
145
- if (shuttingDown) return;
146
- shuttingDown = true;
147
- if (timer) {
148
- try { clearIntervalImpl(timer); } catch { /* process 即将退出 */ }
149
- timer = null;
150
- }
151
- trace("service-lifecycle: shutdown requested", { reason });
152
- };
153
-
154
- return { start, attachServer, checkNow, handleBeforeExit, beginShutdown };
155
- }
156
-
157
- /**
158
- * ChatCCC 自己拉起替代进程时使用的内部标记。
159
- *
160
- * 不能用“是否已有配置”判断是否打开控制台:首次配置和日常直接启动都应该
161
- * 打开,而 `/restart`、`/update` 和 Web UI 重启都不应该打扰用户。环境变量
162
- * 会自然穿过 cmd/bash/npx 这几层启动器,因此也适用于 Windows 与 Linux。
163
- */
164
- export const INTERNAL_RESTART_ENV_VAR = "CHATCCC_INTERNAL_RESTART";
165
-
166
- type Environment = Record<string, string | undefined>;
167
-
168
- export function createInternalRestartEnv(
169
- inherited: Environment = process.env,
170
- ): NodeJS.ProcessEnv {
171
- return {
172
- ...inherited,
173
- [INTERNAL_RESTART_ENV_VAR]: "1",
174
- };
175
- }
176
-
177
- /** 用户直接启动时打开;ChatCCC 内部重启产生的替代进程不打开。 */
178
- interface AutoOpenWebUiOptions {
179
- env?: Environment;
180
- openOnStart?: boolean;
181
- }
182
-
183
- export function shouldAutoOpenWebUi(options: AutoOpenWebUiOptions = {}): boolean {
184
- const env = options.env ?? process.env;
185
- return options.openOnStart !== false && env[INTERNAL_RESTART_ENV_VAR] !== "1";
186
- }
187
-
188
- /** Web UI 始终使用 localhost,并跟随实际配置端口。 */
189
- export function buildWebUiUrl(port: number): string {
190
- return `http://localhost:${port}/`;
191
- }
192
-
193
- interface OpenBrowserDeps {
194
- platform?: NodeJS.Platform;
195
- env?: Environment;
196
- spawnImpl?: typeof spawn;
197
- onError?: (message: string) => void;
198
- onInfo?: (message: string) => void;
199
- }
200
-
201
- /**
202
- * 调用操作系统默认浏览器打开 Web UI,与 Chrome CDP 守护功能完全独立。
203
- * 返回值仅表示打开请求是否成功发起;浏览器是否复用标签页由系统浏览器决定。
204
- */
205
- export function openWebUiInDefaultBrowser(
206
- port: number,
207
- deps: OpenBrowserDeps = {},
208
- ): boolean {
209
- const url = buildWebUiUrl(port);
210
- const platform = deps.platform ?? process.platform;
211
- const env = deps.env ?? process.env;
212
- const spawnImpl = deps.spawnImpl ?? spawn;
213
- const onError = deps.onError ?? ((message: string) => console.error(message));
214
- const onInfo = deps.onInfo ?? ((message: string) => console.log(message));
215
-
216
- // Linux 服务器通常没有图形会话。此时调用 xdg-open 只会制造噪音;
217
- // 直接给出可复制的 SSH 隧道命令,让用户从自己的电脑访问本地 Web UI。
218
- if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
219
- onInfo(
220
- `[WEB-UI] 未检测到 Linux 图形桌面,跳过自动打开浏览器。` +
221
- `可在本机执行 ssh -L ${port}:127.0.0.1:${port} <user>@<server>,` +
222
- `然后访问 ${url}`,
223
- );
224
- return false;
225
- }
226
-
227
- try {
228
- let child: ChildProcess;
229
- if (platform === "win32") {
230
- // `start` 会把第一个带引号的参数当窗口标题,空字符串是必要占位符。
231
- child = spawnImpl("cmd.exe", ["/c", "start", "", url], {
232
- detached: true,
233
- stdio: "ignore",
234
- windowsHide: true,
235
- });
236
- } else if (platform === "darwin") {
237
- child = spawnImpl("open", [url], { detached: true, stdio: "ignore" });
238
- } else {
239
- child = spawnImpl("xdg-open", [url], { detached: true, stdio: "ignore" });
240
- }
241
- child.on("error", (err) => {
242
- onError(`[WEB-UI] 自动打开浏览器失败: ${err.message}`);
243
- });
244
- child.unref();
245
- return true;
246
- } catch (err) {
247
- onError(`[WEB-UI] 自动打开浏览器失败: ${(err as Error).message}`);
248
- return false;
249
- }
250
- }
@@ -1,177 +0,0 @@
1
- import { readFile, writeFile, mkdir, rename, unlink } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
3
-
4
- import { USER_DATA_DIR, ts } from "./config.ts";
5
- import { createAgentActivityTracker } from "./agent-activity.ts";
6
- import type { AgentActivity } from "./agent-activity.ts";
7
- import type { TerminalErrorInfo } from "./terminal-error.ts";
8
-
9
- // ---------------------------------------------------------------------------
10
- // stream-state.json — 每个 session 的流式输出持久化文件
11
- // ---------------------------------------------------------------------------
12
-
13
- export const STREAMS_DIR = join(USER_DATA_DIR, "state", "streams");
14
-
15
- export interface StreamState {
16
- sessionId: string;
17
- status: "running" | "done" | "stopped" | "error" | "auto_ended";
18
- accumulatedContent: string;
19
- /** 本轮会话中 LLM 输出的全部文本内容(所有 text block 的累加)。
20
- * 命名含 "final" 但实为"全部累积文本",并非仅"最终一段回复"。
21
- * 参见 session.ts 的 AccumulatorState 注释。 */
22
- finalReply: string;
23
- /** Current user-visible work phase for running progress cards. */
24
- activity?: AgentActivity;
25
- /** The turn whose terminal text reply has already been delivered to IM. */
26
- finalReplySentTurn?: number;
27
- finalReplySentAt?: number;
28
- chunkCount: number;
29
- turnCount: number;
30
- contextTokens: number;
31
- updatedAt: number;
32
- cwd: string;
33
- tool: string;
34
- /** Set by stop-stuck-loop to prevent the session from being resumed.
35
- * The orchestrator checks this before resuming and creates a new session instead. */
36
- stuckAt?: number;
37
- /** Set when the shared response watchdog ends a turn after three minutes without new characters. */
38
- autoEndedAt?: number;
39
- /** 脱敏后的用户可见根因;完整原始异常只保留在运行日志中。 */
40
- terminalError?: TerminalErrorInfo;
41
- }
42
-
43
- function getStreamStatePath(sessionId: string): string {
44
- return join(STREAMS_DIR, `${sessionId}.json`);
45
- }
46
-
47
- export async function readStreamState(sessionId: string): Promise<StreamState | null> {
48
- try {
49
- const raw = await readFile(getStreamStatePath(sessionId), "utf-8");
50
- const parsed = JSON.parse(raw) as StreamState;
51
- if (parsed && typeof parsed.sessionId === "string") return parsed;
52
- return null;
53
- } catch {
54
- return null;
55
- }
56
- }
57
-
58
- // rename 的可注入实现——仅供测试模拟"Windows EPERM 降级"路径。
59
- // 生产代码使用 node:fs/promises.rename。
60
- let renameImpl: typeof rename = rename;
61
-
62
- /**
63
- * 仅供单测:注入自定义 rename 实现以验证降级分支。
64
- * 调用方负责测试结束后用 _resetRenameForTest 还原。
65
- */
66
- export function _setRenameForTest(impl: typeof rename): void {
67
- renameImpl = impl;
68
- }
69
-
70
- export function _resetRenameForTest(): void {
71
- renameImpl = rename;
72
- }
73
-
74
- /**
75
- * 把 StreamState 持久化到磁盘——尽量原子地写。
76
- *
77
- * 为什么需要原子写:display loop 每 3 秒读一次 stream-state.json,而
78
- * runAgentSession 每 2 秒写一次。如果用 `writeFile(filePath, ...)` 直接
79
- * 覆盖,reader 在写入过程中读会拿到半截 JSON,JSON.parse 报错——虽然
80
- * readStreamState 包了 try/catch 返回 null 不会崩,但相当于"丢一帧"。
81
- *
82
- * 实现:
83
- * 1) 先把内容写到 `<filePath>.tmp`(reader 不会读 .tmp)
84
- * 2) 再用 `rename` 把 .tmp 替换成正式文件——`rename` 在 POSIX 上是真原子
85
- * 操作,reader 任何时刻读到的都是某个完整的旧/新版本
86
- *
87
- * Windows 兼容性:Windows 上 `rename` 偶尔会因为目标文件正被 reader 打开
88
- * 抛 EPERM/EBUSY(实测罕见,但非零概率)。降级方案:直接 writeFile 覆盖
89
- * 真文件,这一帧可能被 reader 读到半截,但 readStreamState 的 try/catch 兜底
90
- * 会让 loop 跳过这一帧,2 秒后下次 write 大概率成功——比写盘失败丢数据好。
91
- */
92
- export async function writeStreamState(state: StreamState): Promise<void> {
93
- const filePath = getStreamStatePath(state.sessionId);
94
- const payload = JSON.stringify(state, null, 2);
95
- try {
96
- await mkdir(dirname(filePath), { recursive: true });
97
- const tmpPath = filePath + ".tmp";
98
- await writeFile(tmpPath, payload, "utf-8");
99
- try {
100
- await renameImpl(tmpPath, filePath);
101
- } catch (renameErr) {
102
- // Windows 偶发 EPERM/EBUSY:rename 失败时降级为直接覆盖写。
103
- // 此次写入失去原子性(reader 可能读到半截 JSON,但 readStreamState
104
- // 的 try/catch 会兜底返回 null,只是丢一帧 display 更新)。
105
- console.warn(
106
- `[${ts()}] [STREAM-STATE] rename failed for ${state.sessionId}, ` +
107
- `fallback to overwrite: ${(renameErr as Error).message}`,
108
- );
109
- await writeFile(filePath, payload, "utf-8");
110
- // 尽力清理 .tmp,失败也无所谓——下次 write 会覆盖它
111
- await unlink(tmpPath).catch(() => {});
112
- }
113
- } catch (err) {
114
- console.error(`[${ts()}] Failed to write stream-state for ${state.sessionId}: ${(err as Error).message}`);
115
- }
116
- }
117
-
118
- export function isFinalReplySentForTurn(state: Pick<StreamState, "turnCount" | "finalReplySentTurn">): boolean {
119
- return state.finalReplySentTurn === state.turnCount;
120
- }
121
-
122
- export async function markFinalReplySent(sessionId: string, turnCount: number, sentAt = Date.now()): Promise<void> {
123
- const state = await readStreamState(sessionId);
124
- if (!state) return;
125
- if (state.turnCount !== turnCount) return;
126
- if (state.status === "running") return;
127
-
128
- state.finalReplySentTurn = turnCount;
129
- state.finalReplySentAt = sentAt;
130
- state.updatedAt = Date.now();
131
- await writeStreamState(state);
132
- }
133
-
134
- export function createEmptyStreamState(sessionId: string, cwd: string, tool: string, turnCount: number): StreamState {
135
- const now = Date.now();
136
- return {
137
- sessionId,
138
- status: "running",
139
- accumulatedContent: "",
140
- finalReply: "",
141
- activity: createAgentActivityTracker(now).activity,
142
- chunkCount: 0,
143
- turnCount,
144
- contextTokens: 0,
145
- updatedAt: now,
146
- cwd,
147
- tool,
148
- };
149
- }
150
-
151
- /** 进程重启时修正虚假的 "running" 状态 */
152
- export async function fixStaleStreamStates(): Promise<void> {
153
- // 启动时将残留的 running 标记为 error
154
- // 实际 agent 进程已不存在,下次 prompt 时会自然覆盖
155
- try {
156
- const { readdir } = await import("node:fs/promises");
157
- let entries: string[];
158
- try {
159
- entries = await readdir(STREAMS_DIR);
160
- } catch {
161
- return;
162
- }
163
- for (const entry of entries) {
164
- if (!entry.endsWith(".json")) continue;
165
- const state = await readStreamState(entry.replace(".json", ""));
166
- if (state && state.status === "running") {
167
- state.status = "error";
168
- state.accumulatedContent += "\n\n⚠️ 进程重启,流式输出中断。";
169
- state.updatedAt = Date.now();
170
- await writeStreamState(state);
171
- console.log(`[${ts()}] [STREAM-STATE] marked stale running as error: ${state.sessionId}`);
172
- }
173
- }
174
- } catch (err) {
175
- console.error(`[${ts()}] [STREAM-STATE] fixStaleStreamStates failed: ${(err as Error).message}`);
176
- }
177
- }
@@ -1,129 +0,0 @@
1
- /** 用户可见的终态错误。这里只保存脱敏摘要;完整原始异常继续只写运行日志。 */
2
- export type TerminalErrorKind =
3
- | "network_timeout"
4
- | "network"
5
- | "authentication"
6
- | "rate_limit"
7
- | "provider"
8
- | "process"
9
- | "resource"
10
- | "unknown";
11
-
12
- export interface TerminalErrorInfo {
13
- kind: TerminalErrorKind;
14
- title: string;
15
- message: string;
16
- occurredAt: number;
17
- }
18
-
19
- const MAX_UNKNOWN_ERROR_LENGTH = 300;
20
-
21
- function errorMessage(error: unknown): string {
22
- if (error instanceof Error) return error.message;
23
- if (typeof error === "string") return error;
24
- try {
25
- return JSON.stringify(error);
26
- } catch {
27
- return String(error);
28
- }
29
- }
30
-
31
- /**
32
- * 未分类错误仍应给出可诊断线索,但不能把常见凭据带进群聊或持久化状态。
33
- * 已分类错误使用固定文案,不复述地址、请求体或 provider 原始响应。
34
- */
35
- export function sanitizeTerminalErrorDetail(raw: string): string {
36
- return raw
37
- .replace(/\bBearer\s+[^\s,;]+/gi, "Bearer [已脱敏]")
38
- .replace(/\b(api[_-]?key|access[_-]?token|token|secret)\s*[:=]\s*[^\s,;]+/gi, "$1=[已脱敏]")
39
- .replace(/\bsk-[A-Za-z0-9._-]{6,}\b/g, "sk-[已脱敏]")
40
- .replace(/([?&](?:api[_-]?key|access[_-]?token|token|secret)=)[^&\s]+/gi, "$1[已脱敏]")
41
- .slice(0, MAX_UNKNOWN_ERROR_LENGTH);
42
- }
43
-
44
- function parsePositiveInt(message: string, pattern: RegExp): number | undefined {
45
- const value = Number.parseInt(message.match(pattern)?.[1] ?? "", 10);
46
- return Number.isFinite(value) && value > 0 ? value : undefined;
47
- }
48
-
49
- function formatSeconds(milliseconds: number): string {
50
- if (milliseconds % 1000 === 0) return `${milliseconds / 1000} 秒`;
51
- return `${milliseconds} 毫秒`;
52
- }
53
-
54
- export function classifyTerminalError(error: unknown, occurredAt = Date.now()): TerminalErrorInfo {
55
- const raw = errorMessage(error);
56
- const lower = raw.toLowerCase();
57
- const attempts = parsePositiveInt(raw, /\bafter\s+(\d+)\s+attempts?\b/i);
58
- const timeoutMs = parsePositiveInt(raw, /\btimeout\s*:\s*(\d+)\s*ms\b/i);
59
-
60
- if (/\b429\b|rate[ _-]?limit|too many requests/.test(lower)) {
61
- return {
62
- kind: "rate_limit",
63
- title: "请求受到限流",
64
- message: "模型服务请求受到限流,请稍后重试。",
65
- occurredAt,
66
- };
67
- }
68
-
69
- if (/\b401\b|\b403\b|unauthori[sz]ed|forbidden|invalid api key|authentication/.test(lower)) {
70
- return {
71
- kind: "authentication",
72
- title: "模型服务鉴权失败",
73
- message: "模型服务拒绝了当前凭据,请检查 API Key、账号权限或凭据是否过期。",
74
- occurredAt,
75
- };
76
- }
77
-
78
- if (
79
- /connect timeout|connection timed out|etimedout|und_err_connect_timeout/.test(lower)
80
- || (lower.includes("cannot connect") && lower.includes("timeout"))
81
- ) {
82
- const retryText = attempts ? `,已重试 ${attempts} 次` : "";
83
- const timeoutText = timeoutMs ? `,单次连接等待 ${formatSeconds(timeoutMs)}` : "";
84
- return {
85
- kind: "network_timeout",
86
- title: "网络连接超时",
87
- message: `连接模型服务失败${retryText}${timeoutText}。请检查网络、VPN或模型服务状态后重试。`,
88
- occurredAt,
89
- };
90
- }
91
-
92
- if (/econnrefused|econnreset|enotfound|eai_again|socket hang up|network error|cannot connect/.test(lower)) {
93
- return {
94
- kind: "network",
95
- title: "无法连接模型服务",
96
- message: "与模型服务的网络连接失败,请检查网络、VPN、DNS或服务状态后重试。",
97
- occurredAt,
98
- };
99
- }
100
-
101
- const httpStatus = raw.match(/\b(?:HTTP\s*)?(5\d\d)\b/i)?.[1];
102
- if (httpStatus || /service unavailable|bad gateway|gateway timeout|provider error/.test(lower)) {
103
- return {
104
- kind: "provider",
105
- title: "模型服务暂时不可用",
106
- message: `模型服务返回异常${httpStatus ? `(HTTP ${httpStatus})` : ""},请稍后重试。`,
107
- occurredAt,
108
- };
109
- }
110
-
111
- const safeDetail = sanitizeTerminalErrorDetail(raw).trim() || "未提供错误详情";
112
- return {
113
- kind: "unknown",
114
- title: "Agent 执行错误",
115
- message: `发生未分类错误:${safeDetail}`,
116
- occurredAt,
117
- };
118
- }
119
-
120
- export function formatTerminalErrorReason(error: TerminalErrorInfo): string {
121
- return `⚠️ 异常结束:${error.title}\n${error.message}`;
122
- }
123
-
124
- export function formatTerminalErrorNotice(error: TerminalErrorInfo, finalReply = ""): string {
125
- const reason = formatTerminalErrorReason(error);
126
- return finalReply.trim()
127
- ? `${reason}\n\n以下回复可能不完整:\n\n${finalReply.trim()}`
128
- : reason;
129
- }