chatccc 0.2.251 → 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 (277) 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/package.json +1 -1
  5. package/dist/deepccc-agent/src/cli.js +634 -0
  6. package/dist/deepccc-agent/src/config.js +76 -0
  7. package/dist/deepccc-agent/src/context.js +348 -0
  8. package/dist/deepccc-agent/src/file-log.js +34 -0
  9. package/dist/deepccc-agent/src/file-tools.js +1208 -0
  10. package/dist/deepccc-agent/src/index.js +571 -0
  11. package/dist/deepccc-agent/src/permissions.js +170 -0
  12. package/dist/deepccc-agent/src/privacy.js +124 -0
  13. package/dist/deepccc-agent/src/proc-tree-kill.js +60 -0
  14. package/dist/deepccc-agent/src/progress/cards-helpers.js +70 -0
  15. package/dist/deepccc-agent/src/progress/reducer.js +102 -0
  16. package/dist/deepccc-agent/src/progress/terminal-renderer.js +264 -0
  17. package/dist/deepccc-agent/src/progress/view.js +30 -0
  18. package/dist/deepccc-agent/src/raw-stream-log.js +106 -0
  19. package/dist/deepccc-agent/src/session-search.js +276 -0
  20. package/dist/deepccc-agent/src/session-select.js +23 -0
  21. package/dist/deepccc-agent/src/sigint.js +26 -0
  22. package/dist/deepccc-agent/src/skills.js +178 -0
  23. package/dist/deepccc-agent/src/web-tools.js +246 -0
  24. package/dist/src/adapters/adapter-interface.js +19 -0
  25. package/dist/src/adapters/ccc-adapter.js +112 -0
  26. package/dist/src/adapters/claude-adapter.js +497 -0
  27. package/dist/src/adapters/claude-session-meta-store.js +92 -0
  28. package/dist/src/adapters/codex-adapter.js +279 -0
  29. package/dist/src/adapters/codex-session-meta-store.js +94 -0
  30. package/dist/src/adapters/cursor-adapter.js +491 -0
  31. package/dist/src/adapters/cursor-session-meta-store.js +116 -0
  32. package/dist/src/adapters/jsonl-stream.js +104 -0
  33. package/{src/adapters/proc-tree-kill.ts → dist/src/adapters/proc-tree-kill.js} +94 -97
  34. package/dist/src/adapters/raw-stream-log.js +106 -0
  35. package/dist/src/adapters/resource-monitor.js +113 -0
  36. package/dist/src/agent-activity.js +133 -0
  37. package/dist/src/agent-delegate-task-rpc.js +129 -0
  38. package/dist/src/agent-delegate-task.js +48 -0
  39. package/dist/src/agent-file-rpc.js +152 -0
  40. package/dist/src/agent-image-rpc.js +148 -0
  41. package/dist/src/agent-platform-routing.js +13 -0
  42. package/dist/src/agent-reload-config-rpc.js +23 -0
  43. package/dist/src/agent-rpc-body.js +87 -0
  44. package/dist/src/agent-stop-stuck.js +110 -0
  45. package/dist/src/card-action-routing.js +7 -0
  46. package/dist/src/card-plain-text.js +101 -0
  47. package/dist/src/cardkit.js +158 -0
  48. package/dist/src/cards.js +573 -0
  49. package/dist/src/chatgpt-subscription-rpc.js +18 -0
  50. package/dist/src/chatgpt-subscription.js +199 -0
  51. package/dist/src/chrome-devtools-guard.js +238 -0
  52. package/dist/src/claude-sdk-installer.js +249 -0
  53. package/dist/src/codex-reset-actions.js +143 -0
  54. package/dist/src/config-utils.js +149 -0
  55. package/dist/src/config.js +804 -0
  56. package/dist/src/cursor-usage.js +77 -0
  57. package/dist/src/exit-banner.js +28 -0
  58. package/dist/src/feishu-api.js +1404 -0
  59. package/dist/src/feishu-message-ingress.js +137 -0
  60. package/dist/src/feishu-platform.js +97 -0
  61. package/dist/src/format-message.js +252 -0
  62. package/dist/src/git-command.js +155 -0
  63. package/dist/src/im-skills.js +121 -0
  64. package/dist/src/index.js +833 -0
  65. package/dist/src/litellm-proxy.js +300 -0
  66. package/dist/src/orchestrator.js +2078 -0
  67. package/dist/src/package-root.js +26 -0
  68. package/dist/src/platform-adapter.js +7 -0
  69. package/dist/src/platform-startup.js +6 -0
  70. package/dist/src/privacy.js +100 -0
  71. package/dist/src/progress/reducer.js +102 -0
  72. package/dist/src/progress/terminal-renderer.js +264 -0
  73. package/dist/src/progress/view.js +30 -0
  74. package/dist/src/response-stall.js +14 -0
  75. package/dist/src/runtime-entry.js +13 -0
  76. package/dist/src/runtime-reload.js +19 -0
  77. package/dist/src/session-chat-binding.js +183 -0
  78. package/dist/src/session-name.js +7 -0
  79. package/dist/src/session.js +2144 -0
  80. package/dist/src/shared-prefix.js +16 -0
  81. package/dist/src/shared.js +493 -0
  82. package/dist/src/sim-agent.js +105 -0
  83. package/dist/src/sim-platform.js +142 -0
  84. package/dist/src/sim-store.js +231 -0
  85. package/dist/src/simplify.js +99 -0
  86. package/dist/src/startup-lifecycle.js +209 -0
  87. package/dist/src/stream-state.js +141 -0
  88. package/dist/src/terminal-error.js +100 -0
  89. package/dist/src/trace.js +50 -0
  90. package/dist/src/turn-cards.js +92 -0
  91. package/dist/src/update-command-guard.js +114 -0
  92. package/{src/web-ui.ts → dist/src/web-ui.js} +749 -823
  93. package/dist/src/wechat-platform.js +545 -0
  94. package/package.json +7 -5
  95. package/deepccc-agent/LICENSE +0 -201
  96. package/deepccc-agent/bin/deepccc.mjs +0 -26
  97. package/deepccc-agent/docs/cache-hit-rate-1.jpg +0 -0
  98. package/deepccc-agent/docs/cache-hit-rate-2.jpg +0 -0
  99. package/deepccc-agent/package-lock.json +0 -2027
  100. package/deepccc-agent/src/__tests__/chat-session.test.ts +0 -852
  101. package/deepccc-agent/src/__tests__/cli-json.test.ts +0 -49
  102. package/deepccc-agent/src/__tests__/config.test.ts +0 -34
  103. package/deepccc-agent/src/__tests__/context.test.ts +0 -341
  104. package/deepccc-agent/src/__tests__/file-tools.test.ts +0 -240
  105. package/deepccc-agent/src/__tests__/permissions.test.ts +0 -199
  106. package/deepccc-agent/src/__tests__/privacy.test.ts +0 -318
  107. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +0 -121
  108. package/deepccc-agent/src/__tests__/session-search.test.ts +0 -262
  109. package/deepccc-agent/src/__tests__/session-select.test.ts +0 -116
  110. package/deepccc-agent/src/__tests__/sigint.test.ts +0 -56
  111. package/deepccc-agent/src/__tests__/skills.test.ts +0 -284
  112. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +0 -247
  113. package/deepccc-agent/src/__tests__/web-tools.test.ts +0 -220
  114. package/deepccc-agent/src/cli.ts +0 -682
  115. package/deepccc-agent/src/config.ts +0 -101
  116. package/deepccc-agent/src/context.ts +0 -465
  117. package/deepccc-agent/src/file-log.ts +0 -38
  118. package/deepccc-agent/src/file-tools.ts +0 -1493
  119. package/deepccc-agent/src/index.ts +0 -676
  120. package/deepccc-agent/src/permissions.ts +0 -226
  121. package/deepccc-agent/src/privacy.ts +0 -141
  122. package/deepccc-agent/src/proc-tree-kill.ts +0 -61
  123. package/deepccc-agent/src/progress/cards-helpers.ts +0 -76
  124. package/deepccc-agent/src/progress/reducer.ts +0 -113
  125. package/deepccc-agent/src/progress/terminal-renderer.ts +0 -294
  126. package/deepccc-agent/src/progress/view.ts +0 -77
  127. package/deepccc-agent/src/raw-stream-log.ts +0 -124
  128. package/deepccc-agent/src/session-search.ts +0 -370
  129. package/deepccc-agent/src/session-select.ts +0 -48
  130. package/deepccc-agent/src/sigint.ts +0 -50
  131. package/deepccc-agent/src/skills.ts +0 -205
  132. package/deepccc-agent/src/web-tools.ts +0 -313
  133. package/deepccc-agent/tsconfig.build.json +0 -13
  134. package/deepccc-agent/tsconfig.json +0 -13
  135. package/deepccc-agent/vitest.config.ts +0 -7
  136. package/src/__tests__/adapter-interface.test.ts +0 -152
  137. package/src/__tests__/agent-activity.test.ts +0 -86
  138. package/src/__tests__/agent-delegate-task-rpc.test.ts +0 -165
  139. package/src/__tests__/agent-image-rpc.test.ts +0 -34
  140. package/src/__tests__/agent-platform-routing.test.ts +0 -26
  141. package/src/__tests__/agent-reload-config-rpc.test.ts +0 -99
  142. package/src/__tests__/agent-rpc-body.test.ts +0 -42
  143. package/src/__tests__/builtin-chat-session.test.ts +0 -532
  144. package/src/__tests__/builtin-cli-json.test.ts +0 -39
  145. package/src/__tests__/builtin-config.test.ts +0 -26
  146. package/src/__tests__/builtin-context.test.ts +0 -319
  147. package/src/__tests__/builtin-file-tools.test.ts +0 -240
  148. package/src/__tests__/builtin-permissions.test.ts +0 -219
  149. package/src/__tests__/builtin-session-search.test.ts +0 -262
  150. package/src/__tests__/builtin-session-select.test.ts +0 -116
  151. package/src/__tests__/builtin-sigint.test.ts +0 -56
  152. package/src/__tests__/builtin-skills.test.ts +0 -284
  153. package/src/__tests__/builtin-web-tools.test.ts +0 -220
  154. package/src/__tests__/card-action-routing.test.ts +0 -18
  155. package/src/__tests__/card-plain-text.test.ts +0 -45
  156. package/src/__tests__/cardkit.test.ts +0 -60
  157. package/src/__tests__/cards.test.ts +0 -607
  158. package/src/__tests__/ccc-adapter.test.ts +0 -194
  159. package/src/__tests__/chatgpt-subscription-rpc.test.ts +0 -89
  160. package/src/__tests__/chatgpt-subscription.test.ts +0 -135
  161. package/src/__tests__/chrome-devtools-guard.test.ts +0 -165
  162. package/src/__tests__/claude-adapter.test.ts +0 -614
  163. package/src/__tests__/claude-raw-stream-log.test.ts +0 -96
  164. package/src/__tests__/claude-sdk-installer.test.ts +0 -285
  165. package/src/__tests__/codex-adapter.test.ts +0 -331
  166. package/src/__tests__/codex-raw-stream-log.test.ts +0 -170
  167. package/src/__tests__/codex-reset-actions.test.ts +0 -146
  168. package/src/__tests__/config-reload.test.ts +0 -284
  169. package/src/__tests__/config-sample.test.ts +0 -97
  170. package/src/__tests__/config-utils.test.ts +0 -40
  171. package/src/__tests__/config.test.ts +0 -395
  172. package/src/__tests__/crash-logging.test.ts +0 -360
  173. package/src/__tests__/cursor-adapter.test.ts +0 -890
  174. package/src/__tests__/cursor-session-meta-store.test.ts +0 -212
  175. package/src/__tests__/feishu-api.test.ts +0 -60
  176. package/src/__tests__/feishu-avatar.test.ts +0 -504
  177. package/src/__tests__/feishu-message-ingress.test.ts +0 -138
  178. package/src/__tests__/feishu-platform.test.ts +0 -75
  179. package/src/__tests__/fixtures/codex_simple_text.jsonl +0 -4
  180. package/src/__tests__/fixtures/codex_with_tool.jsonl +0 -6
  181. package/src/__tests__/fixtures/cursor_partial_only.jsonl +0 -5
  182. package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +0 -13
  183. package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +0 -12
  184. package/src/__tests__/format-message.test.ts +0 -316
  185. package/src/__tests__/git-command.test.ts +0 -288
  186. package/src/__tests__/im-skills.test.ts +0 -125
  187. package/src/__tests__/jsonl-stream.test.ts +0 -79
  188. package/src/__tests__/orchestrator.test.ts +0 -1268
  189. package/src/__tests__/package-files.test.ts +0 -24
  190. package/src/__tests__/platform-startup.test.ts +0 -19
  191. package/src/__tests__/privacy.test.ts +0 -198
  192. package/src/__tests__/proc-tree-kill.test.ts +0 -108
  193. package/src/__tests__/progress-reducer.test.ts +0 -121
  194. package/src/__tests__/raw-stream-log.test.ts +0 -106
  195. package/src/__tests__/response-stall.test.ts +0 -49
  196. package/src/__tests__/restart.test.ts +0 -232
  197. package/src/__tests__/session-ccc-config.test.ts +0 -66
  198. package/src/__tests__/session.test.ts +0 -3004
  199. package/src/__tests__/shared-prefix.test.ts +0 -36
  200. package/src/__tests__/sim-agent.test.ts +0 -174
  201. package/src/__tests__/sim-platform.test.ts +0 -93
  202. package/src/__tests__/sim-store.test.ts +0 -214
  203. package/src/__tests__/simplify.test.ts +0 -283
  204. package/src/__tests__/startup-lifecycle.test.ts +0 -231
  205. package/src/__tests__/stop-session.test.ts +0 -162
  206. package/src/__tests__/stream-state.test.ts +0 -164
  207. package/src/__tests__/terminal-error.test.ts +0 -54
  208. package/src/__tests__/terminal-renderer.test.ts +0 -247
  209. package/src/__tests__/update-command-guard.test.ts +0 -144
  210. package/src/__tests__/web-ui.test.ts +0 -438
  211. package/src/__tests__/wechat-platform.test.ts +0 -111
  212. package/src/adapters/adapter-interface.ts +0 -217
  213. package/src/adapters/ccc-adapter.ts +0 -150
  214. package/src/adapters/claude-adapter.ts +0 -673
  215. package/src/adapters/claude-session-meta-store.ts +0 -120
  216. package/src/adapters/codex-adapter.ts +0 -426
  217. package/src/adapters/codex-session-meta-store.ts +0 -131
  218. package/src/adapters/cursor-adapter.ts +0 -681
  219. package/src/adapters/cursor-session-meta-store.ts +0 -154
  220. package/src/adapters/jsonl-stream.ts +0 -157
  221. package/src/adapters/raw-stream-log.ts +0 -124
  222. package/src/adapters/resource-monitor.ts +0 -141
  223. package/src/agent-activity.ts +0 -175
  224. package/src/agent-delegate-task-rpc.ts +0 -153
  225. package/src/agent-delegate-task.ts +0 -91
  226. package/src/agent-file-rpc.ts +0 -172
  227. package/src/agent-image-rpc.ts +0 -168
  228. package/src/agent-platform-routing.ts +0 -28
  229. package/src/agent-reload-config-rpc.ts +0 -34
  230. package/src/agent-rpc-body.ts +0 -92
  231. package/src/agent-stop-stuck.ts +0 -129
  232. package/src/card-action-routing.ts +0 -14
  233. package/src/card-plain-text.ts +0 -108
  234. package/src/cardkit.ts +0 -179
  235. package/src/cards.ts +0 -684
  236. package/src/chatgpt-subscription-rpc.ts +0 -27
  237. package/src/chatgpt-subscription.ts +0 -299
  238. package/src/chrome-devtools-guard.ts +0 -318
  239. package/src/claude-sdk-installer.ts +0 -324
  240. package/src/codex-reset-actions.ts +0 -184
  241. package/src/config-utils.ts +0 -211
  242. package/src/config.ts +0 -1063
  243. package/src/cursor-usage.ts +0 -128
  244. package/src/exit-banner.ts +0 -33
  245. package/src/feishu-api.ts +0 -1616
  246. package/src/feishu-message-ingress.ts +0 -195
  247. package/src/feishu-platform.ts +0 -159
  248. package/src/format-message.ts +0 -293
  249. package/src/git-command.ts +0 -202
  250. package/src/im-skills.ts +0 -149
  251. package/src/index.ts +0 -1089
  252. package/src/litellm-proxy.ts +0 -374
  253. package/src/orchestrator.ts +0 -2543
  254. package/src/platform-adapter.ts +0 -70
  255. package/src/platform-startup.ts +0 -16
  256. package/src/privacy.ts +0 -118
  257. package/src/progress/reducer.ts +0 -113
  258. package/src/progress/terminal-renderer.ts +0 -294
  259. package/src/progress/view.ts +0 -77
  260. package/src/response-stall.ts +0 -28
  261. package/src/runtime-reload.ts +0 -34
  262. package/src/session-chat-binding.ts +0 -292
  263. package/src/session-name.ts +0 -8
  264. package/src/session.ts +0 -2659
  265. package/src/shared-prefix.ts +0 -29
  266. package/src/shared.ts +0 -552
  267. package/src/sim-agent.ts +0 -167
  268. package/src/sim-platform.ts +0 -177
  269. package/src/sim-store.ts +0 -317
  270. package/src/simplify.ts +0 -120
  271. package/src/startup-lifecycle.ts +0 -250
  272. package/src/stream-state.ts +0 -177
  273. package/src/terminal-error.ts +0 -129
  274. package/src/trace.ts +0 -51
  275. package/src/turn-cards.ts +0 -118
  276. package/src/update-command-guard.ts +0 -165
  277. package/src/wechat-platform.ts +0 -680
@@ -0,0 +1,571 @@
1
+ /**
2
+ * DeepCCC builtin Agent core API — 同步自 ChatCCC(保留 DeepCCC 英文品牌)
3
+ *
4
+ * ChatSession 是程序化入口,既可以被 CLI 调用,也可以被其他模块调用。
5
+ */
6
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
7
+ import { createAnthropic } from "@ai-sdk/anthropic";
8
+ import { generateText, isLoopFinished, stepCountIs, streamText } from "ai";
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { config as appConfig, normalizeDeepCccProvider, RAW_STREAM_LOGS_DIR, } from "./config.js";
14
+ import { createRawStreamLog, } from "./raw-stream-log.js";
15
+ import { buildPersistedAssistantMessage, buildSummaryPrompt, BuiltinContextManager, defaultBuiltinSessionId, } from "./context.js";
16
+ import { createBuiltinFileTools } from "./file-tools.js";
17
+ import { PermissionGate } from "./permissions.js";
18
+ import { buildDefaultSkillDirs, buildSkillsIndexPrompt, scanSkillsDirs, } from "./skills.js";
19
+ import { applyPrivacy, applyPrivacyToJson } from "./privacy.js";
20
+ // ---------------------------------------------------------------------------
21
+ // 系统提示词 — 编译期冻结常量(DeepCCC 英文品牌)
22
+ // ---------------------------------------------------------------------------
23
+ const SYSTEM_PROMPT = [
24
+ "你是 DeepCCC,一个运行在终端工作区的轻量级 AI 编程智能体。",
25
+ "",
26
+ "## 固定规则",
27
+ "- 除非用户另有要求,否则用用户的语言回复。",
28
+ "- 优先给出直接、可用的答案和具体行动,而非长篇解释。",
29
+ "- 代码任务:编辑前先阅读相关文件,并在可行时用测试或检查验证。",
30
+ "- 保护用户的工作。未经用户明确要求,不要覆盖并发修改。",
31
+ "- 平台不可变规则优先于项目指引和运行时细节。",
32
+ "",
33
+ "## 证据门控结论",
34
+ "- 在可能影响代码、数据、部署或用户决策的重大结论或行动前,以及可用证据为间接证据时,先应用本门控。",
35
+ "- 明确结论及其权威事实来源。区分直接观察与推断,并在选定结论前检验合理的替代解释。",
36
+ "- 在与结论同一语义层上使用最强可行的决定性检查:运行时结论用运行时行为、配置结论用有效配置、部署结论用部署状态、转换结论用转换后的输出。",
37
+ "- 在可行直接检查时,不要把名称、时间戳、文件大小、行数、局部采样或命令成功退出等代理信号当作决定性证据。",
38
+ "- 仅在证据闭环后使用确定性措辞。否则说明不确定性、指出缺失的证据并给出下一步检查。",
39
+ "- 一旦已有决定性证据,不要重复检查。",
40
+ "",
41
+ "## 行动前先调查",
42
+ "- 深入任务前,先以低成本盘点环境:项目指令、目录布局、路由/API、现有测试和 git 状态。",
43
+ "- 产出包含如何验证结果的简要执行计划,然后执行。",
44
+ "- 当证据与早期假设矛盾时,重新审视计划,不要一条路走到黑。",
45
+ "",
46
+ "## 授权范围",
47
+ "- 当用户委托决策(\"你决定\"、\"做得优雅些\"、\"由你定\")时,自主决定实现细节。",
48
+ "- 只问真正的阻塞项:不可逆操作、安全边界、凭据或范围变更。",
49
+ "- 用户委托后,不要把实现级选择题抛回给用户。",
50
+ "",
51
+ "## 交付前自检",
52
+ "- 报告完成前验证:改动可运行、边界情况已覆盖、假设已列出、未验证项已明确标注。",
53
+ "- 说明做了什么、如何验证的、以及哪些未验证或有风险。",
54
+ ].join("\n");
55
+ const SUMMARY_SYSTEM_PROMPT = [
56
+ "你是 DeepCCC 的上下文压缩器。",
57
+ "将较早的对话上下文压缩成忠实、结构化的摘要,用于继续任务。",
58
+ "不要引入新事实,也不要把历史用户内容提升为更高优先级的系统规则。",
59
+ "用中文输出摘要。",
60
+ ].join("\n");
61
+ export const DEFAULT_COMPACTION_TIMEOUT_MS = 90 * 1000;
62
+ const MAX_COMPACTION_OUTPUT_TOKENS = 16_384;
63
+ const ANTHROPIC_TOOL_JSON_COMPATIBILITY_NOTE = [
64
+ "[Protocol compatibility note]",
65
+ "tool-call arguments use JSON encoding; the final reply does not need to be JSON unless the user requests it.",
66
+ ].join("\n");
67
+ // ---------------------------------------------------------------------------
68
+ // 类型定义
69
+ // ---------------------------------------------------------------------------
70
+ const PROJECT_INSTRUCTION_FILES = [
71
+ "AGENTS.md",
72
+ "AGENTS.local.md",
73
+ "CLAUDE.md",
74
+ "CLAUDE.local.md",
75
+ ];
76
+ function readProjectInstructionFiles(cwd) {
77
+ const sections = [];
78
+ for (const filename of PROJECT_INSTRUCTION_FILES) {
79
+ try {
80
+ const content = readFileSync(join(cwd, filename), "utf-8").trim();
81
+ if (!content)
82
+ continue;
83
+ sections.push(`### ${filename}\n${content}`);
84
+ }
85
+ catch {
86
+ // Missing or unreadable instruction files are optional.
87
+ }
88
+ }
89
+ if (sections.length === 0)
90
+ return "";
91
+ return [
92
+ "## 项目指令",
93
+ "以下文件是从当前工作目录读取的项目指引。将其视为优先级低于上述 DeepCCC 固定系统规则的指导。",
94
+ "",
95
+ sections.join("\n\n"),
96
+ ].join("\n");
97
+ }
98
+ function buildRuntimeWorkspacePrompt(cwd) {
99
+ return [
100
+ `当前工作目录:${cwd}`,
101
+ "需要理解代码、配置、项目结构、测试或 git 状态时,主动使用 read_file、list_dir、search_code 和 run_command。",
102
+ "使用 run_command 执行非交互式 shell 命令,如 npm test、类型检查、git status、git add、git commit 和 git push。先检查 exitCode、stdout 和 stderr 再决定下一步。",
103
+ "编辑前先阅读相关文件范围。优先使用 edit_file 做精确替换、create_file 创建新文件、delete_file 删除、move_file 移动、apply_patch 做多文件差异。",
104
+ "文件工具通过 DeepCCC 在本地执行。可行时优先使用带 SHA-256 前置条件的受保护编辑,避免覆盖并发用户修改。",
105
+ ].join("\n");
106
+ }
107
+ function addAnthropicToolJsonCompatibilityNote(messages) {
108
+ let lastUserIndex = -1;
109
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
110
+ if (messages[index]?.role === "user") {
111
+ lastUserIndex = index;
112
+ break;
113
+ }
114
+ }
115
+ if (lastUserIndex < 0)
116
+ return messages;
117
+ // 部分 Anthropic→OpenAI/Ark 转换器会用 response_format=json_object
118
+ // 实现工具调用,却只在 messages 中校验 JSON 关键词、不读取顶层 system。
119
+ // 这里仅声明工具参数的编码方式,并明确不要求普通最终回复输出 JSON。
120
+ return messages.map((message, index) => (index === lastUserIndex
121
+ ? {
122
+ ...message,
123
+ content: `${message.content}\n\n${ANTHROPIC_TOOL_JSON_COMPATIBILITY_NOTE}`,
124
+ }
125
+ : message));
126
+ }
127
+ /**
128
+ * 各操作系统特有的命令行指引(文本资产,维护在 os-prompts/ 目录,而非硬编码):
129
+ *
130
+ * - 内置文件:包内 os-prompts/<platform>.md(win32/darwin/linux),随 npm 包分发;
131
+ * - 用户覆盖:~/.deepccc/prompts/<platform>.md 存在时完全替代内置内容(可自定义)。
132
+ *
133
+ * 文件内容自带标题(如 "## Windows Command-Line Notes"),读取后 trim 直接作为
134
+ * 一个段落注入固定规则区(项目指令之前)。未知平台或文件缺失时返回空字符串。
135
+ */
136
+ export function loadPlatformCommandPrompt(platform = process.platform, dirs = {}) {
137
+ const filename = platform === "win32" ? "win32.md" :
138
+ platform === "darwin" ? "darwin.md" :
139
+ platform === "linux" ? "linux.md" :
140
+ null;
141
+ if (!filename)
142
+ return "";
143
+ // import.meta.url 定位包根:chatccc 源码运行时指向 deepccc-agent/os-prompts/,
144
+ // deepccc dist 运行时指向包根 os-prompts/(dist/index.js 的 ../os-prompts/)。
145
+ const adjacentBuiltinDir = fileURLToPath(new URL("../os-prompts/", import.meta.url));
146
+ const embeddedBuiltinDir = fileURLToPath(new URL("../../../deepccc-agent/os-prompts/", import.meta.url));
147
+ const builtinDir = dirs.builtinDir ?? (existsSync(adjacentBuiltinDir) ? adjacentBuiltinDir : embeddedBuiltinDir);
148
+ const userDir = dirs.userDir ?? join(homedir(), ".deepccc", "prompts");
149
+ // 用户覆盖优先;读取失败时静默回退内置,内置也失败则返回空。
150
+ const userFile = join(userDir, filename);
151
+ if (existsSync(userFile)) {
152
+ try {
153
+ return readFileSync(userFile, "utf-8").trim();
154
+ }
155
+ catch {
156
+ // fall through to builtin
157
+ }
158
+ }
159
+ const builtinFile = join(builtinDir, filename);
160
+ if (existsSync(builtinFile)) {
161
+ try {
162
+ return readFileSync(builtinFile, "utf-8").trim();
163
+ }
164
+ catch {
165
+ return "";
166
+ }
167
+ }
168
+ return "";
169
+ }
170
+ function normalizeMaxSteps(value) {
171
+ if (value === undefined)
172
+ return undefined;
173
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
174
+ throw new Error("maxSteps must be a positive integer when provided");
175
+ }
176
+ return value;
177
+ }
178
+ function normalizeAnthropicBaseURL(baseURL) {
179
+ const normalized = baseURL.trim().replace(/\/+$/, "");
180
+ return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
181
+ }
182
+ export class ChatSession {
183
+ model;
184
+ provider;
185
+ cwd;
186
+ context;
187
+ compactionTimeoutMs;
188
+ maxSteps;
189
+ effort;
190
+ permissionGate;
191
+ skillDirs;
192
+ customSystemPrompt;
193
+ /** 最近一次 chat() 使用的 system prompt(供 history 等读取) */
194
+ systemPrompt = "";
195
+ constructor(overrides = {}, options = {}) {
196
+ const apiKey = overrides.apiKey ?? appConfig.apiKey;
197
+ if (!apiKey) {
198
+ throw new Error("DEEPCCC_API_KEY is not set. Configure ~/.deepccc/config.json, set an environment variable, or pass --api-key.");
199
+ }
200
+ const baseURL = overrides.baseURL ?? appConfig.baseURL;
201
+ const modelId = overrides.model ?? appConfig.model;
202
+ this.provider = normalizeDeepCccProvider(overrides.provider ?? appConfig.provider);
203
+ this.effort = (overrides.effort ?? appConfig.effort ?? "").trim();
204
+ if (this.provider === "anthropic") {
205
+ const provider = createAnthropic({
206
+ baseURL: normalizeAnthropicBaseURL(baseURL),
207
+ apiKey,
208
+ });
209
+ this.model = provider(modelId);
210
+ }
211
+ else {
212
+ const provider = createOpenAICompatible({
213
+ name: "deepccc",
214
+ baseURL,
215
+ apiKey,
216
+ includeUsage: true,
217
+ });
218
+ this.model = provider(modelId);
219
+ }
220
+ this.cwd = options.cwd ?? process.cwd();
221
+ this.maxSteps = normalizeMaxSteps(options.maxSteps);
222
+ this.compactionTimeoutMs = Math.max(1, options.compactionTimeoutMs ?? DEFAULT_COMPACTION_TIMEOUT_MS);
223
+ this.customSystemPrompt = options.systemPrompt ?? "";
224
+ // 技能目录在构造时确定;技能内容在每次 chat() 前重新扫描(mtime 热加载),
225
+ // 因此创建/修改技能后下一次对话自动生效,无需重启。
226
+ this.skillDirs =
227
+ options.skillsDirs?.map((d) => ({ dir: d, source: "deepccc", scope: "project" })) ??
228
+ buildDefaultSkillDirs(this.cwd);
229
+ this.context = new BuiltinContextManager({
230
+ persist: options.persist ?? false,
231
+ contextDir: options.contextDir,
232
+ sessionId: options.sessionId ?? defaultBuiltinSessionId(this.cwd),
233
+ cwd: this.cwd,
234
+ compactAtTokens: options.compactAtTokens,
235
+ keepRecentMessages: options.keepRecentMessages,
236
+ });
237
+ this.permissionGate = new PermissionGate(options.permissionMode ?? "ask", options.permissionResolver);
238
+ }
239
+ /**
240
+ * 组装系统提示词。顺序遵循“稳定性优先”原则(缓存命中友好):
241
+ * 固定规则 → 项目指令 → runtime 上下文 → 用户补充 → 技能索引(最后)。
242
+ * 技能索引是最易变的部分(热加载,任何 SKILL.md 变化都会改前缀),
243
+ * 放最后可以让前面的稳定内容尽量命中缓存,只丢尾段。
244
+ */
245
+ buildSystemPrompt(skills) {
246
+ const systemContent = [SYSTEM_PROMPT];
247
+ const platformPrompt = loadPlatformCommandPrompt();
248
+ if (platformPrompt) {
249
+ systemContent.push("", platformPrompt);
250
+ }
251
+ const projectInstructions = readProjectInstructionFiles(this.cwd);
252
+ if (projectInstructions) {
253
+ systemContent.push("", projectInstructions);
254
+ }
255
+ systemContent.push("", buildRuntimeWorkspacePrompt(this.cwd));
256
+ if (this.customSystemPrompt) {
257
+ systemContent.push("", this.customSystemPrompt);
258
+ }
259
+ const skillsPrompt = buildSkillsIndexPrompt(skills);
260
+ if (skillsPrompt) {
261
+ systemContent.push("", skillsPrompt);
262
+ }
263
+ return systemContent.join("\n");
264
+ }
265
+ async *chat(userMessage, signal) {
266
+ this.context.appendMessage({ role: "user", content: userMessage });
267
+ let fullText = "";
268
+ let safeAccumulated = "";
269
+ let rawLog = null;
270
+ let completed = false;
271
+ // 结构化工具调用存档:按 toolCallId 关联入参/出参/错误,落盘到 context.json 的
272
+ // assistant 消息 toolCalls 字段;[Tool transcript] 文本视图仍按原格式生成。
273
+ const toolCallsById = new Map();
274
+ const toolCallOrder = [];
275
+ try {
276
+ if (this.context.planCompaction()) {
277
+ yield { type: "status", phase: "compacting" };
278
+ const compactedMessages = await this.compactIfNeeded(signal);
279
+ if (compactedMessages > 0) {
280
+ yield { type: "compact", compactedMessages };
281
+ }
282
+ }
283
+ yield { type: "status", phase: "generating" };
284
+ const rawLogConfig = appConfig.rawStreamLogs;
285
+ try {
286
+ rawLog = await createRawStreamLog({
287
+ enabled: rawLogConfig.enabled,
288
+ rootDir: RAW_STREAM_LOGS_DIR,
289
+ tool: "deepccc",
290
+ sessionId: this.context.sessionId,
291
+ label: "prompt",
292
+ maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
293
+ retentionDays: rawLogConfig.retentionDays,
294
+ });
295
+ }
296
+ catch (err) {
297
+ console.error(`[DeepCCC raw stream log] create failed: ${errorMessage(err)}`);
298
+ }
299
+ const toolContext = [];
300
+ const maxSteps = this.maxSteps;
301
+ // 每次对话前重新扫描技能索引(并行 + mtime 缓存,开销极小):
302
+ // 新技能/修改的技能在下一次对话自动生效(热加载)。
303
+ const skills = await scanSkillsDirs(this.skillDirs);
304
+ const system = this.buildSystemPrompt(skills);
305
+ this.systemPrompt = system;
306
+ const contextMessages = this.context.buildModelMessages();
307
+ const modelMessages = this.provider === "anthropic"
308
+ ? addAnthropicToolJsonCompatibilityNote(contextMessages)
309
+ : contextMessages;
310
+ // effort 按协议映射:
311
+ // - OpenAI 兼容:providerOptions.deepseek.reasoningEffort 由 @ai-sdk/openai-compatible
312
+ // 自动映射为请求体 reasoning_effort 字段(DeepSeek 原生支持);
313
+ // - Anthropic:providerOptions.anthropic.effort 由 @ai-sdk/anthropic 组装为请求体
314
+ // output_config.effort(官方 Effort API,见 platform.claude.com/docs/en/build-with-claude/effort)
315
+ let effortProviderOptions;
316
+ if (this.effort) {
317
+ effortProviderOptions = this.provider === "openai"
318
+ ? { deepseek: { reasoningEffort: this.effort } }
319
+ : { anthropic: { effort: this.effort } };
320
+ }
321
+ const generationOptions = {
322
+ model: this.model,
323
+ system,
324
+ messages: modelMessages,
325
+ tools: createBuiltinFileTools(this.cwd, { permissionGate: this.permissionGate }),
326
+ stopWhen: maxSteps !== undefined ? stepCountIs(maxSteps) : isLoopFinished(),
327
+ abortSignal: signal,
328
+ ...(effortProviderOptions ? { providerOptions: effortProviderOptions } : {}),
329
+ };
330
+ let stream;
331
+ if (appConfig.streaming) {
332
+ const result = streamText(generationOptions);
333
+ stream = result.fullStream ?? textStreamToFullStream(result.textStream);
334
+ }
335
+ else {
336
+ const result = await generateText(generationOptions);
337
+ stream = generateResultToFullStream(result);
338
+ }
339
+ for await (const part of stream) {
340
+ rawLog?.writeLine(safeRawStreamJson(part));
341
+ if (part.type === "text-delta") {
342
+ fullText += part.text;
343
+ // 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
344
+ // fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
345
+ const safeText = applyPrivacy(part.text);
346
+ safeAccumulated += safeText;
347
+ yield { type: "text", text: safeText, accumulated: safeAccumulated };
348
+ }
349
+ else if (part.type === "tool-call") {
350
+ toolContext.push(`tool_call ${part.toolName}: ${safeJson(part.input)}`);
351
+ toolCallsById.set(part.toolCallId, { name: part.toolName, input: safeJson(part.input) });
352
+ toolCallOrder.push(part.toolCallId);
353
+ yield {
354
+ type: "tool_use",
355
+ id: part.toolCallId,
356
+ name: part.toolName,
357
+ input: applyPrivacyToJson(part.input),
358
+ };
359
+ }
360
+ else if (part.type === "tool-result") {
361
+ toolContext.push(`tool_result ${part.toolName}: ${truncateToolContext(safeJson(part.output))}`);
362
+ const call = toolCallsById.get(part.toolCallId);
363
+ if (call) {
364
+ call.output = truncateToolContext(safeJson(part.output));
365
+ }
366
+ yield {
367
+ type: "tool_result",
368
+ tool_use_id: part.toolCallId,
369
+ name: part.toolName,
370
+ content: applyPrivacyToJson(part.output),
371
+ is_error: false,
372
+ };
373
+ }
374
+ else if (part.type === "tool-error") {
375
+ const message = errorMessage(part.error);
376
+ toolContext.push(`tool_error ${part.toolName}: ${message}`);
377
+ const call = toolCallsById.get(part.toolCallId);
378
+ if (call) {
379
+ call.output = message;
380
+ call.is_error = true;
381
+ }
382
+ yield {
383
+ type: "tool_result",
384
+ tool_use_id: part.toolCallId,
385
+ name: part.toolName,
386
+ content: applyPrivacy(message),
387
+ is_error: true,
388
+ };
389
+ }
390
+ else if (part.type === "error") {
391
+ const message = errorMessage(part.error);
392
+ yield { type: "error", message: applyPrivacy(message) };
393
+ throw new Error(message);
394
+ }
395
+ }
396
+ completed = true;
397
+ const collectedToolCalls = toolCallOrder
398
+ .map((id) => toolCallsById.get(id))
399
+ .filter((call) => call !== undefined);
400
+ this.context.appendMessage(buildPersistedAssistantMessage({
401
+ fullText,
402
+ transcriptLines: toolContext,
403
+ toolCalls: collectedToolCalls,
404
+ }));
405
+ yield { type: "done", text: safeAccumulated };
406
+ }
407
+ catch (err) {
408
+ const message = err instanceof Error ? err.message : String(err);
409
+ if (err.name === "AbortError" || signal?.aborted) {
410
+ // 被中断时,不保存不完整的助手消息
411
+ if (fullText) {
412
+ this.context.appendMessage({ role: "assistant", content: `${fullText}\n[interrupted]` });
413
+ }
414
+ yield { type: "done", text: safeAccumulated };
415
+ return;
416
+ }
417
+ yield { type: "error", message: applyPrivacy(message) };
418
+ throw err;
419
+ }
420
+ finally {
421
+ const rawLogConfig = appConfig.rawStreamLogs;
422
+ await rawLog?.close({
423
+ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !completed,
424
+ });
425
+ }
426
+ }
427
+ /** 返回当前的会话历史(只读) */
428
+ get history() {
429
+ const history = [{ role: "system", content: this.systemPrompt }];
430
+ if (this.context.summary) {
431
+ history.push({
432
+ role: "system",
433
+ content: [
434
+ "更早的对话摘要:",
435
+ "",
436
+ this.context.summary,
437
+ ].join("\n"),
438
+ });
439
+ }
440
+ history.push(...this.context.messages);
441
+ return history;
442
+ }
443
+ /** 返回当前轮数(不含 system 消息) */
444
+ get turnCount() {
445
+ return this.context.totalMessages;
446
+ }
447
+ /** 清空会话历史,保留 system 消息 */
448
+ reset() {
449
+ this.context.reset();
450
+ }
451
+ async compactIfNeeded(signal) {
452
+ if (!this.context.planCompaction())
453
+ return 0;
454
+ const timeoutController = new AbortController();
455
+ const timeout = setTimeout(() => timeoutController.abort(), this.compactionTimeoutMs);
456
+ timeout.unref?.();
457
+ const compactionSignal = signal
458
+ ? AbortSignal.any([signal, timeoutController.signal])
459
+ : timeoutController.signal;
460
+ try {
461
+ const plan = this.context.planCompaction();
462
+ if (!plan)
463
+ return 0;
464
+ const result = await generateText({
465
+ model: this.model,
466
+ system: SUMMARY_SYSTEM_PROMPT,
467
+ messages: [{ role: "user", content: buildSummaryPrompt(plan) }],
468
+ abortSignal: compactionSignal,
469
+ temperature: 0,
470
+ // 摘要单轮生成:显式放宽 maxOutputTokens,避免 AI SDK 对未知模型的
471
+ // 兼容模式默认 4096 上限导致摘要生成不完(那是旧版多轮压缩的根因);
472
+ // 同时锁低 effort(OpenAI reasoning_effort=none / Anthropic
473
+ // output_config.effort=low),避免继承主对话的高 effort 拖慢"压缩上下文中"阶段。
474
+ maxOutputTokens: MAX_COMPACTION_OUTPUT_TOKENS,
475
+ providerOptions: this.provider === "openai"
476
+ ? { deepseek: { reasoningEffort: "none" } }
477
+ : { anthropic: { effort: "low" } },
478
+ });
479
+ if (!result.text.trim()) {
480
+ throw new Error("Context compaction returned an empty summary");
481
+ }
482
+ this.context.applyCompaction(result.text, plan);
483
+ // 单轮压缩:不再反复迭代重试。若上下文仍超预算(如 recent 消息本身超大),
484
+ // 留给下一次对话前再次压缩,避免阻塞当前回复生成(业界同步压缩的标准取舍)。
485
+ return plan.oldMessages.length;
486
+ }
487
+ catch (error) {
488
+ if (timeoutController.signal.aborted && !signal?.aborted) {
489
+ throw new Error(`Context compaction timed out after ${formatDuration(this.compactionTimeoutMs)}`);
490
+ }
491
+ throw error;
492
+ }
493
+ finally {
494
+ clearTimeout(timeout);
495
+ }
496
+ }
497
+ }
498
+ async function* textStreamToFullStream(stream) {
499
+ for await (const text of stream) {
500
+ yield { type: "text-delta", text };
501
+ }
502
+ }
503
+ async function* generateResultToFullStream(result) {
504
+ let emittedText = false;
505
+ for (const step of result.steps ?? []) {
506
+ for (const call of step.toolCalls ?? []) {
507
+ yield {
508
+ type: "tool-call",
509
+ toolCallId: call.toolCallId,
510
+ toolName: call.toolName,
511
+ input: call.input,
512
+ };
513
+ }
514
+ for (const toolResult of step.toolResults ?? []) {
515
+ yield {
516
+ type: "tool-result",
517
+ toolCallId: toolResult.toolCallId,
518
+ toolName: toolResult.toolName,
519
+ output: toolResult.output,
520
+ };
521
+ }
522
+ if (step.text) {
523
+ emittedText = true;
524
+ yield { type: "text-delta", text: step.text };
525
+ }
526
+ }
527
+ if (!emittedText && result.text) {
528
+ yield { type: "text-delta", text: result.text };
529
+ }
530
+ }
531
+ function safeJson(value) {
532
+ try {
533
+ return JSON.stringify(value);
534
+ }
535
+ catch {
536
+ return String(value);
537
+ }
538
+ }
539
+ function safeRawStreamJson(value) {
540
+ try {
541
+ const serialized = JSON.stringify(value, (_key, nested) => {
542
+ if (nested instanceof Error) {
543
+ return {
544
+ name: nested.name,
545
+ message: nested.message,
546
+ };
547
+ }
548
+ return nested;
549
+ });
550
+ return serialized ?? "null";
551
+ }
552
+ catch (err) {
553
+ return JSON.stringify({
554
+ type: "deepccc_raw_stream_log_serialize_error",
555
+ message: errorMessage(err),
556
+ });
557
+ }
558
+ }
559
+ function truncateToolContext(value) {
560
+ return value.length > 8000 ? `${value.slice(0, 8000)}...[truncated]` : value;
561
+ }
562
+ function formatDuration(ms) {
563
+ if (ms % 60_000 === 0)
564
+ return `${ms / 60_000} minutes`;
565
+ if (ms % 1_000 === 0)
566
+ return `${ms / 1_000} seconds`;
567
+ return `${ms} ms`;
568
+ }
569
+ function errorMessage(value) {
570
+ return value instanceof Error ? value.message : String(value);
571
+ }