thincoder 0.12.61 → 0.12.63

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 (230) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/README.md +13 -12
  3. package/bin/thincoder.mjs +63 -28
  4. package/package.json +6 -5
  5. package/src/acp/bridge.mjs +35 -15
  6. package/src/acp/client-caps.mjs +86 -0
  7. package/src/acp/ext.mjs +86 -0
  8. package/src/acp/handlers-session.mjs +240 -0
  9. package/src/acp/handlers-slots.mjs +196 -0
  10. package/src/acp/login.mjs +48 -0
  11. package/src/acp/session.mjs +6 -4
  12. package/src/acp.mjs +67 -371
  13. package/src/cli/distill-command.mjs +3 -3
  14. package/src/cli/make-agent.mjs +59 -17
  15. package/src/cli/memory-command.mjs +3 -3
  16. package/src/cli/permission.mjs +4 -48
  17. package/src/cli/setup-wizard.mjs +1 -1
  18. package/src/completions.mjs +3 -1
  19. package/src/crash-reports.mjs +32 -10
  20. package/src/distill.mjs +4 -4
  21. package/src/heap-watch.mjs +88 -0
  22. package/src/prompt-injections.mjs +20 -0
  23. package/src/tui/agent-turn.mjs +40 -9
  24. package/src/tui/cmd-advisor.mjs +5 -5
  25. package/src/tui/cmd-clear.mjs +2 -0
  26. package/src/tui/cmd-config.mjs +8 -8
  27. package/src/tui/cmd-eng.mjs +25 -9
  28. package/src/tui/cmd-mcp.mjs +9 -8
  29. package/src/tui/cmd-model.mjs +1 -1
  30. package/src/tui/cmd-new.mjs +10 -5
  31. package/src/tui/cmd-reindex.mjs +1 -1
  32. package/src/tui/cmd-restore.mjs +2 -2
  33. package/src/tui/cmd-session.mjs +31 -4
  34. package/src/tui/cmd-skills.mjs +1 -1
  35. package/src/tui/cmd-think.mjs +22 -9
  36. package/src/tui/config-helpers.mjs +1 -1
  37. package/src/tui/display-budget.mjs +206 -0
  38. package/src/tui/index.mjs +40 -12
  39. package/src/tui/interaction.mjs +16 -7
  40. package/src/tui/key-handler-search.mjs +9 -1
  41. package/src/tui/key-modes.mjs +9 -4
  42. package/src/tui/ledger-surface.mjs +85 -0
  43. package/src/tui/model-catalog.mjs +4 -4
  44. package/src/tui/model-picker.mjs +8 -7
  45. package/src/tui/mouse.mjs +11 -6
  46. package/src/tui/pickers.mjs +15 -2
  47. package/src/tui/render-conversation.mjs +1 -1
  48. package/src/tui/render-frame.mjs +17 -6
  49. package/src/tui/render-loop.mjs +1 -1
  50. package/src/tui/render-segments.mjs +3 -1
  51. package/src/tui/slash-commands.mjs +1 -1
  52. package/src/tui/startup.mjs +49 -17
  53. package/src/tui/subagent-blocks.mjs +21 -3
  54. package/src/tui/subagent-children.mjs +86 -14
  55. package/src/tui/subagent-freeze.mjs +80 -3
  56. package/src/tui/suspension-drive.mjs +48 -22
  57. package/src/tui/tool-args.mjs +5 -2
  58. package/src/tui/tool-display.mjs +16 -2
  59. package/src/tui/tool-events.mjs +64 -22
  60. package/src/tui/tui-lifecycle.mjs +9 -2
  61. package/src/tui/wizard.mjs +3 -3
  62. package/src/tui/wrapped-spawn.mjs +21 -5
  63. package/src/abort-provenance.mjs +0 -116
  64. package/src/advisor/citations.mjs +0 -139
  65. package/src/advisor/compaction.mjs +0 -174
  66. package/src/advisor/convergence.mjs +0 -80
  67. package/src/advisor/history.mjs +0 -77
  68. package/src/advisor/loop.mjs +0 -293
  69. package/src/advisor/messages.mjs +0 -299
  70. package/src/advisor/project-context.mjs +0 -194
  71. package/src/advisor/repos.mjs +0 -150
  72. package/src/advisor/run.mjs +0 -293
  73. package/src/advisor/truncate.mjs +0 -57
  74. package/src/advisor.mjs +0 -290
  75. package/src/agent/completion.mjs +0 -146
  76. package/src/agent/dispatch.mjs +0 -489
  77. package/src/agent/helpers.mjs +0 -384
  78. package/src/agent/post-turn.mjs +0 -70
  79. package/src/agent/record-results.mjs +0 -174
  80. package/src/agent/relay-prefix.mjs +0 -39
  81. package/src/agent/run-stages.mjs +0 -242
  82. package/src/agent/setup-reminders.mjs +0 -69
  83. package/src/agent/setup.mjs +0 -354
  84. package/src/agent/spawn-child.mjs +0 -228
  85. package/src/agent-tools/advisor-async.mjs +0 -346
  86. package/src/agent-tools/advisor-settle.mjs +0 -231
  87. package/src/agent-tools/advisor.mjs +0 -260
  88. package/src/agent-tools/async-settle.mjs +0 -191
  89. package/src/agent-tools/batch-segment.mjs +0 -195
  90. package/src/agent-tools/consult.mjs +0 -468
  91. package/src/agent-tools/design-token.mjs +0 -117
  92. package/src/agent-tools/digest-budget.mjs +0 -76
  93. package/src/agent-tools/eng.mjs +0 -67
  94. package/src/agent-tools/escalate-async.mjs +0 -289
  95. package/src/agent-tools/goal.mjs +0 -119
  96. package/src/agent-tools/plan.mjs +0 -81
  97. package/src/agent-tools/read-history.mjs +0 -294
  98. package/src/agent-tools/recent-changes.mjs +0 -24
  99. package/src/agent-tools/review-streak.mjs +0 -93
  100. package/src/agent-tools/settings.mjs +0 -265
  101. package/src/agent-tools/skill.mjs +0 -47
  102. package/src/agent-tools/subagent-actions.mjs +0 -479
  103. package/src/agent-tools/subagent-async.mjs +0 -434
  104. package/src/agent-tools/subagent-panel.mjs +0 -160
  105. package/src/agent-tools/subagent-run.mjs +0 -205
  106. package/src/agent-tools/subagent-scheduler.mjs +0 -392
  107. package/src/agent-tools/subagent-spawn.mjs +0 -453
  108. package/src/agent-tools/subagent.mjs +0 -404
  109. package/src/agent-tools/task.mjs +0 -87
  110. package/src/agent-tools/timer.mjs +0 -46
  111. package/src/agent-tools/verify.mjs +0 -271
  112. package/src/agent-tools.mjs +0 -17
  113. package/src/agent.mjs +0 -413
  114. package/src/auto-think.mjs +0 -115
  115. package/src/config-migrate.mjs +0 -70
  116. package/src/config.mjs +0 -496
  117. package/src/context.mjs +0 -381
  118. package/src/conventions.mjs +0 -223
  119. package/src/embedding.mjs +0 -120
  120. package/src/escape.mjs +0 -152
  121. package/src/expand-home.mjs +0 -16
  122. package/src/explore-distill.mjs +0 -155
  123. package/src/generate-title.mjs +0 -83
  124. package/src/git/checkpoint.mjs +0 -448
  125. package/src/git/gitmem.mjs +0 -100
  126. package/src/hooks.mjs +0 -97
  127. package/src/log.mjs +0 -195
  128. package/src/markdown.mjs +0 -106
  129. package/src/mcp/helpers.mjs +0 -51
  130. package/src/mcp/transport-http.mjs +0 -248
  131. package/src/mcp/transport-stdio.mjs +0 -140
  132. package/src/mcp/transport-ws.mjs +0 -122
  133. package/src/mcp.mjs +0 -295
  134. package/src/memory/code-index.mjs +0 -219
  135. package/src/memory/code-sync.mjs +0 -413
  136. package/src/memory/core.mjs +0 -300
  137. package/src/memory/delete.mjs +0 -236
  138. package/src/memory/docs.mjs +0 -417
  139. package/src/memory/file-walk.mjs +0 -109
  140. package/src/memory/schema.mjs +0 -452
  141. package/src/memory.mjs +0 -21
  142. package/src/model-ref.mjs +0 -66
  143. package/src/model-specs.mjs +0 -179
  144. package/src/peer-domains.mjs +0 -265
  145. package/src/peer-instances.mjs +0 -231
  146. package/src/prompt-overlays.mjs +0 -82
  147. package/src/prompts/advisor-design.md +0 -41
  148. package/src/prompts/advisor-round1.md +0 -41
  149. package/src/prompts/advisor-round2.md +0 -46
  150. package/src/prompts/advisor-round3.md +0 -42
  151. package/src/prompts/common.md +0 -115
  152. package/src/prompts/consult-base.md +0 -19
  153. package/src/prompts/discipline-engineering.md +0 -217
  154. package/src/prompts/discipline-normal.md +0 -179
  155. package/src/prompts/persona-coder.md +0 -21
  156. package/src/prompts/persona-eng-coder.md +0 -37
  157. package/src/prompts/persona-eng-designer.md +0 -55
  158. package/src/prompts/persona-engineering.md +0 -54
  159. package/src/prompts/persona-explore.md +0 -15
  160. package/src/prompts/persona-normal.md +0 -27
  161. package/src/prompts/persona-plan.md +0 -26
  162. package/src/provider/anthropic.mjs +0 -225
  163. package/src/provider/core.mjs +0 -476
  164. package/src/provider/errors.mjs +0 -101
  165. package/src/provider/google.mjs +0 -257
  166. package/src/provider/index.mjs +0 -7
  167. package/src/provider/list-models.mjs +0 -93
  168. package/src/provider/normalize.mjs +0 -81
  169. package/src/provider/rate.mjs +0 -108
  170. package/src/provider/responses.mjs +0 -495
  171. package/src/provider/retry.mjs +0 -88
  172. package/src/provider/sse.mjs +0 -264
  173. package/src/proxy.mjs +0 -261
  174. package/src/rules.mjs +0 -53
  175. package/src/session-gc.mjs +0 -214
  176. package/src/session-guard.mjs +0 -47
  177. package/src/session-migrate.mjs +0 -48
  178. package/src/session-rename.mjs +0 -38
  179. package/src/session-slots.mjs +0 -489
  180. package/src/session.mjs +0 -475
  181. package/src/skills.mjs +0 -153
  182. package/src/token-ttl.mjs +0 -274
  183. package/src/tools/apply_patch.md +0 -15
  184. package/src/tools/bash.md +0 -37
  185. package/src/tools/bash.mjs +0 -268
  186. package/src/tools/checklist-sync.mjs +0 -181
  187. package/src/tools/checklist.md +0 -13
  188. package/src/tools/checklist.mjs +0 -299
  189. package/src/tools/delete.md +0 -13
  190. package/src/tools/edit-batch.mjs +0 -191
  191. package/src/tools/edit-diff.mjs +0 -348
  192. package/src/tools/edit.md +0 -30
  193. package/src/tools/execute.md +0 -21
  194. package/src/tools/execute.mjs +0 -228
  195. package/src/tools/fetch.md +0 -12
  196. package/src/tools/file.mjs +0 -469
  197. package/src/tools/file_ops.md +0 -17
  198. package/src/tools/get_current_time.md +0 -8
  199. package/src/tools/git-checkpoint.mjs +0 -143
  200. package/src/tools/git-ext.mjs +0 -173
  201. package/src/tools/git.md +0 -54
  202. package/src/tools/git.mjs +0 -356
  203. package/src/tools/glob-dialect.mjs +0 -130
  204. package/src/tools/glob.md +0 -11
  205. package/src/tools/grep.md +0 -19
  206. package/src/tools/hashline_edit.md +0 -14
  207. package/src/tools/index.mjs +0 -36
  208. package/src/tools/insert_after.md +0 -15
  209. package/src/tools/lint.md +0 -10
  210. package/src/tools/linter.mjs +0 -128
  211. package/src/tools/ls.md +0 -12
  212. package/src/tools/lsp.md +0 -10
  213. package/src/tools/lsp.mjs +0 -316
  214. package/src/tools/ops.mjs +0 -299
  215. package/src/tools/patch.mjs +0 -282
  216. package/src/tools/process.md +0 -10
  217. package/src/tools/question.md +0 -16
  218. package/src/tools/question.mjs +0 -26
  219. package/src/tools/read.md +0 -20
  220. package/src/tools/read_image.md +0 -8
  221. package/src/tools/repomap.mjs +0 -314
  222. package/src/tools/search.mjs +0 -236
  223. package/src/tools/shared.mjs +0 -446
  224. package/src/tools/tree.md +0 -14
  225. package/src/tools/tree.mjs +0 -66
  226. package/src/tools/wait_for.md +0 -22
  227. package/src/tools/web.mjs +0 -224
  228. package/src/tools/websearch.md +0 -16
  229. package/src/tools/write.md +0 -11
  230. package/src/traces/trace-store.mjs +0 -224
package/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## [0.12.63] — 2026-09-21
2
+
3
+ > 0.12.62 → 0.12.63(连续号——发布时定号;本版随核首发联动)
4
+
5
+ ### Fixed
6
+
7
+ - **发布依赖收正**:`@thincoder/core` `^0.1.0` → `^0.9.1`(核首发 `@thincoder/core@0.9.1` 同日落地——声明面与 registry 实发对齐;0.12.62 及以前发布形态为合并前自带拷贝、无核依赖)。
8
+ - **smoke 探针 import 修复**:`isBailianHost` 实居 `config.mjs`——原从 `provider/normalize.mjs` 静默容错导入(`catch(() => ({}))`)恒 undefined ⇒ smoke 恒 skip(假绿)✗ 修正后真跑三连测(OFF 无思考 / XHIGH / DEFAULT 有思考——dashscope 百炼 qwen 实测过 ✓)。
9
+
10
+ ## [0.12.62] — 2026-09-13
11
+
12
+ > 0.12.61 → 0.12.62(§4.3 连续号——发布时定号)
13
+
14
+ ### Added
15
+
16
+ - **文档锚一致性机检 V5(DOC-CODE-RECONCILE 批)**:`scripts/doc-anchors.mjs`(三类锚——用例号 / 本仓符号 / 路径——存在性判定 + 假阳类逐条排除 + 注记闭枚举 + 两态 `V5_GATE` / `--v5-gate` / `--v5-report`)· `scripts/doc-impact.mjs`(改动面反查:`--base` 必给、只读不阻断)· 常驻零值锁(`test:full` 内——再出现即红)· **清账 347 处 → 0**。
17
+ - **台账自持机检**:`check-ledger` L4 本仓可解析闸 + 指针 / D3 组计数 / 六态 / 触发 / 老化报告。
18
+ - **工程模式纪律进提示词**:跨仓派单与写域纪律 · 改动面反查(提示词两副本逐字一致)。
19
+
20
+ ### Changed
21
+
22
+ - **文档体系各仓自持**:需求档 / 设计档 / 批次档 / 台账各仓记各仓(禁跨仓写需求与跨仓指针);跨仓引用统一「名称(仓别)§N」规范形态。
23
+ - **台账归档制**:活文件只留未决四态,已决逐条移入 `TODO-archive.md`。
24
+ - **在途批一并签入(8 批)**:TUI 内存取证与上界(session store / segments / 堆遥测)· 台账可见面 · 测试纪律提示词 · 子代理审批面与活动区对位。
25
+
26
+ ### Removed
27
+
28
+ - **散文锚退役(PROSE-ANCHOR-RETIRE 批)**:退役断言「某句在场 / 缺席」类测试 71 例(保留结构机检面:引用可解析 / 组计数 / 六态取值 / 行宽 / slow 归册)。
29
+
30
+ ### Fixed
31
+
32
+ - 文档↔实装漂移清账(逐档处置留痕,禁「只删不记」)· `check-ledger` 扫描域收窄(只扫本仓活档 + 归档档)· 台账 L4 误报修正 · 批次档 / 设计档宽度与计数纪律收正。
33
+
1
34
  ## [0.12.61] — 2026-09-11
2
35
 
3
36
  > 0.12.60 → 0.12.61(§4.3 连续号——发布时定号)
@@ -291,7 +324,7 @@
291
324
 
292
325
  ### Changed
293
326
 
294
- - **系统提示词加两条约束**:①「先定正确再谈实现」——动手前先回答"正确应该是什么"(每个入口/视图/边界),再谈怎么实现,实现规模是"正确"的结果、不是决策标准;②「确认时暴露方案权衡」——确认理解时列出选的做法、为什么它对(绝不是"改动最小")、考虑过但没选的替代方案。治"最小改动"偷懒病根
327
+ - **系统提示词加两条约束**:①「先定正确再谈实现」——动手前先回答"正确应该是什么"(每个入口/视图/边界),再谈怎么实现,实现规模是"正确"的结果、不是决策标准;②「确认时暴露方案权衡」——确认理解时列出选的做法、为什么它对(判据 = 正确性优先——不是省事程度)、考虑过但没选的替代方案。治"图省事"的偷懒病根
295
328
 
296
329
  ## [0.12.47] — 2026-08-28
297
330
 
package/README.md CHANGED
@@ -15,12 +15,12 @@ Design philosophy (the entire meaning of the name): if the Node standard library
15
15
  - **Fix-verify loop**: file changes without `verify` get pushed back — syntax check + tests must pass before the agent can claim completion (auto-repair up to 3 rounds)
16
16
  - **Checkpoint system**: auto-snapshot before every user task, `list`/`create`/`rewind` tools for the model, single-file restore — rewinding itself is reversible (pre-rewind state auto-saved)
17
17
  - **Codebase understanding** ⭐0.5.0: `repo_outline` (dependency outline, auto-injected at startup), `code_search` (source FTS5 + vectors + JSDoc extraction), `doc_search` (docs chunked by ## headings) — background indexing, auto-incremental updates on file writes, three tools guided by "structure → intent → details"
18
- - **Model adaptation** ⭐: top-tier only, latest only. Built-in flagship models from twenty providers — DeepSeek / Kimi / Kimi For Coding / GLM / GLM Coding Plan (智谱 Coding) / Qwen / Qwen Token Plan / MiMo (小米) / MiMo Token Plan (小米套餐) / MiniMax / OpenAI / Claude / Gemini / Grok / Mistral / Volcengine Ark (豆包) / Hunyuan (腾讯混元) / SiliconFlow (硅基流动) / OpenRouter / Groq. No legacy model compatibility. Auto-matched context windows, truncation-resume protocols (prefix/partial), thinking-mode APIs (thinking.type / reasoning_effort), reasoning_content echo strategies (reasoningEcho), output limits, temperature range clamping — all deeply adapted.
18
+ - **Model adaptation** ⭐: top-tier only, latest only. Built-in flagship models from twenty-one providers — DeepSeek / Kimi / Kimi For Coding / GLM / GLM Coding Plan (智谱 Coding) / Qwen / Qwen Token Plan / MiMo (小米) / MiMo Token Plan (小米套餐) / MiniMax / OpenAI / Claude / Gemini / Grok / Mistral / Volcengine Ark (豆包) / Hunyuan (腾讯混元) / Tencent TokenHub (腾讯混元网关) / SiliconFlow (硅基流动) / OpenRouter / Groq. No legacy model compatibility. Auto-matched context windows, truncation-resume protocols (prefix/partial), thinking-mode APIs (thinking.type / reasoning_effort), reasoning_content echo strategies (reasoningEcho), output limits, temperature range clamping — all deeply adapted.
19
19
  - **Toolset**: `read` / `write` / `edit` / `bash` / `glob` (supports `**`) / `grep` / `websearch` / `ls` / `fetch` + `read_image` (image/video paste) + three retrieval tools + MCP — all zero-dependency, file tools confined to the working directory
20
20
  - **Memory system**: three layers (personal/project/team), FTS5 + vector RRF hybrid retrieval, git-friendly markdown format
21
21
  - **Two-phase tool scheduling**: permission prompts serialized, read-only tools parallelized, side-effect tools serialized
22
22
  - **Session persistence** ⭐0.5.0: unlimited archive slots, `/session` to switch anytime, tool results visible after restore. Process-level isolation — multiple instances in the same directory each get their own session slot
23
- - **Concurrent subagents**: three roles — `explore`/`plan`/`coder` — dispatched in parallel, streaming output visible, reports land in the conversation; per-subagent model override (`subagent` tool `model` arg or `agent.subagentModel` config — e.g. discuss with `glm-5.2`, let `deepseek-v4-flash` implement)
23
+ - **Concurrent subagents**: five roles — `explore`/`plan`/`coder`/`eng-coder`/`eng-designer` — dispatched in parallel, streaming output visible, reports land in the conversation; per-subagent model override (`subagent` tool `model` arg or `agent.subagentModel` config — e.g. discuss with `glm-5.2`, let `deepseek-v4-flash` implement)
24
24
  - **Multi-model consultation 会诊 + 飞刀 (escalate)** ⭐0.12.30: `consult_start`/`consult_stop` run several configured models in parallel as independent read-only consultants (each with its own TUI activity card, `main_history` access to the failure trail); when every model settles, the full verdict digest is delivered automatically (R17 — consult_check retired). `escalate` flies in a stronger model for a single expert implementation run with full write access (default-async since R17 — the post-op report arrives automatically with its mutations merged). Candidate pool = `agent.consultModels` ([{ provider, model, effort? }], up to 5); budgets via `agent.consultTurns` / `agent.consultTimeoutMs`
25
25
  - **Plan Mode**: read-only exploration + design, implement after user approval
26
26
  - **AUTO mode**: `/auto` full authorization, no confirmations on long tasks
@@ -49,7 +49,8 @@ Three layers, all "query if present, skip if absent", unified hybrid retrieval:
49
49
  - IDE-native diffs — `write`/`edit` route through the editor buffer
50
50
  - Persisted sessions: list / load (history replay) / resume / delete
51
51
  - Per-session config: model / thinking / mode
52
- - Setup: [docs/guides/ides.md](docs/guides/ides.md)
52
+ - Setup: [ACP 接入设计](../docs/cli/design/ACP-CLIENT.md)
53
+ - Login: `thincoder acp --login` — one interactive terminal login; ACP clients and orchestrators then drive sessions headlessly
53
54
 
54
55
 
55
56
  ## Requirements
@@ -68,7 +69,7 @@ npm install -g thincoder
68
69
  thincoder
69
70
  ```
70
71
 
71
- First launch walks you through a setup wizard: arrow keys to pick a provider (built-in presets or a custom endpoint) → enter API key → optionally enter an embedding key (SiliconFlow, enables vector memory search, skippable) → arrow keys to pick a model — no hand-editing config files. Adjust anytime with `/provider`, `/model`, `/config embedkey`. `chat`/`distill` also offer in-place interactive setup when no key is configured in a terminal (in pipes/CI they exit with an error and instructions).
72
+ First launch walks you through a setup wizard: arrow keys to pick a provider (built-in presets or a custom endpoint) → enter API key → optionally enter an embedding key (SiliconFlow, enables vector memory search, skippable) → arrow keys to pick a model — no hand-editing config files. Adjust anytime with `/model` (add/remove providers, set keys, custom endpoints) or `/config embedkey`. `chat`/`distill` also offer in-place interactive setup when no key is configured in a terminal (in pipes/CI they exit with an error and instructions).
72
73
 
73
74
  You can also hand-write `~/.thincoder/config.json` (see "Configuration" below), then:
74
75
 
@@ -94,7 +95,7 @@ thincoder upgrade
94
95
 
95
96
  Running from source: replace `thincoder` above with `node bin/thincoder.mjs`.
96
97
 
97
- Slash commands in the TUI: `/help`, `/model` (two-level picker: first select provider, then model; `/model <provider>:<name>` switches directly), `/submodel` (subagent models per type — picker over global + explore/plan/coder/eng-coder slots, or `/submodel <type> <provider:model>` directly), `/shell` (platform-aware picker of available shells — e.g. `/shell` → pick Git Bash/pwsh, or `/shell "C:\Program Files\Git\bin\bash.exe"`, `/shell reset`; fixes win11 cmd encoding/command issues), `/provider` (add/remove providers, set keys, custom endpoints), `/think` (thinking mode toggle and reasoning effort), `/config` (view config, `/config embedkey` for the embedding key, `/config set` for parameters), `/session` (list/switch archived sessions), `/reindex` (rebuild the index), `/extract` (extract knowledge from the current session), `/restore` (restore checkpoint), `/clear`, `/exit`. High-frequency commands support abbreviations: `/h` `/x` `/m` `/p` `/t` `/c` `/n`. Typing `/` shows live matching hints in the status bar. Model picker supports search/filter — type to narrow down results.
98
+ Slash commands in the TUI: `/help`, `/model` (two-level picker: first select provider, then model; also add/remove providers, set keys, custom endpoints; `/model <provider>:<name>` switches directly), `/submodel` (subagent models per type — picker over global + explore/plan/coder/eng-coder/eng-designer slots, or `/submodel <type> <provider:model>` directly), `/shell` (platform-aware picker of available shells — e.g. `/shell` → pick Git Bash/pwsh, or `/shell "C:\Program Files\Git\bin\bash.exe"`, `/shell reset`; fixes win11 cmd encoding/command issues), `/think` (thinking mode toggle and reasoning effort), `/config` (view config; no-argument form opens the interactive menu — e.g. default model, embedding key, proxy; `/config embedkey` sets the embedding key directly), `/session` (list/switch archived sessions), `/reindex` (rebuild the index), `/extract` (extract knowledge from the current session), `/restore` (restore checkpoint), `/clear`, `/exit`. High-frequency commands support abbreviations: `/h` `/x` `/m` `/p` `/t` `/c` `/n`. Typing `/` shows live matching hints in the status bar. Model picker supports search/filter — type to narrow down results.
98
99
 
99
100
  Configuration comes exclusively from `~/.thincoder/config.json` — no environment-variable configuration is supported.
100
101
 
@@ -102,16 +103,16 @@ Configuration comes exclusively from `~/.thincoder/config.json` — no environme
102
103
 
103
104
  ## Configuration
104
105
 
105
- `~/.thincoder/config.json`:
106
+ `~/.thincoder/config.json` — strictly parsed (`JSON.parse`); the `//` annotations below are illustrative only and are not valid in the actual file:
106
107
 
107
- ```jsonc
108
+ ```json
108
109
  {
109
110
  "providers": [
110
111
  // multiple allowed; switch with /model <name>
111
112
  {
112
113
  "name": "deepseek",
113
114
  "baseURL": "https://api.deepseek.com/v1", // any OpenAI-compatible endpoint
114
- "apiKey": "sk-...", // or leave empty to use env vars
115
+ "apiKey": "sk-...", // or leave empty — the TUI prompts for a key when this provider is selected
115
116
  "model": "deepseek-chat",
116
117
  // optional: proactive throttling budget (match your account's rate-limit tier;
117
118
  // without it the gate is off, 429 backoff still applies).
@@ -120,7 +121,7 @@ Configuration comes exclusively from `~/.thincoder/config.json` — no environme
120
121
  // "rpm": 50, // requests/minute
121
122
  },
122
123
  ],
123
- "activeProvider": "deepseek", // currently active provider name
124
+ "defaultModel": "deepseek:deepseek-chat", // new-session starting model — "provider:model" (set via /config → 默认模型; /model switches the current session)
124
125
  "shell": null, // bash tool shell (win11: e.g. "C:\\Program Files\\Git\\bin\\bash.exe" or "pwsh"); null = system default — cmd on Windows (UTF-8 forced per command), /bin/sh elsewhere. TUI: /shell; a leading ~ in the path expands to the home directory
125
126
  "embedding": {
126
127
  // optional: without it, retrieval is pure FTS
@@ -131,7 +132,7 @@ Configuration comes exclusively from `~/.thincoder/config.json` — no environme
131
132
  "agent": {
132
133
  "maxTurns": 100, // tool-loop cap
133
134
  "subagentModel": null, // default subagent provider/model override: "provider:model" | provider name | model name; null = inherit parent provider. Per-call: subagent tool `model` arg
134
- "subagentModels": {}, // per-type override: { "explore": "...", "plan": "...", "coder": "...", "eng-coder": "..." }; priority: tool model arg > this > subagentModel > parent provider
135
+ "subagentModels": {}, // per-type override: { "explore": "...", "plan": "...", "coder": "...", "eng-coder": "...", "eng-designer": "..." }; priority: tool model arg > this > subagentModel > parent provider
135
136
  "compactThreshold": 100000, // context compaction threshold (approx. tokens)
136
137
  },
137
138
  "memory": {
@@ -197,7 +198,7 @@ src/
197
198
  tui.mjs re-export shim → src/tui/index.mjs
198
199
  tui-render.mjs re-export shim → src/tui/render.mjs
199
200
  prompts/ prompt texts — slot-based (PROMPT-SYSTEM): persona-engineering / persona-normal /
200
- persona-{eng-coder,explore,coder,plan} + common + discipline-engineering / discipline-normal
201
+ persona-{eng-coder,explore,coder,plan,eng-designer} + common + discipline-engineering / discipline-normal
201
202
  + special modules (consult-base / advisor-design / advisor-round{1,2,3});
202
203
  assembly = assemblePrompt (prompt-overlays.mjs): persona → common → discipline → [4] AGENTS+skills
203
204
  test/ node:test offline unit tests (npm test)
@@ -303,7 +304,7 @@ Code conventions: pure `.mjs`, no semicolons, no npm dependencies allowed (inclu
303
304
 
304
305
  ### 0.9.0 (2026-07)
305
306
  - **Checkpoint v2** — snapshot-before-destructive with full workspace copies, per-file restore (`cat` preview), auto-recover on failed apply, and escape-hatch hints when git checks fail
306
- - **Lifecycle Hooks** — `PreToolUse` / `PostToolUse` / `PostToolUseFailure` / `Notification` events. User-defined shell commands in config, with per-tool regex matching, timeout control, and `block`/`allow`/`notify` actions. Implemented in `src/hooks.mjs`, integrated into tool dispatch.
307
+ - **Lifecycle Hooks** — `PreToolUse` / `PostToolUse` / `PostToolUseFailure` / `Stop` events. User-defined shell commands in config, with per-tool regex matching, timeout control, and `block`/`allow`/`notify` actions. Implemented in `thincoder-core/hooks.mjs`, integrated into tool dispatch.
307
308
  - **Built-in Skills (5)** — `pdf-create`, `xlsx-create`, `frontend-design`, `code-review`, `api-design` ship with the installation. Each is a standalone markdown instruction file using zero-dependency approaches (Chrome headless for PDF, PowerShell for Excel, etc.).
308
309
  - **Conversation message folding** — Long tool result blocks (>8 consecutive dim lines) auto-collapse to first 2 lines + "… N more lines — Enter to expand". `/fold on|off` toggles globally.
309
310
  - **Tree-shaped tasks** — `checklist` tool now supports hierarchical task IDs (`T1`, `T1.1`, `T1.2.1`) with auto-assigned numbering. `add` accepts `parent` parameter for subtree positioning. Indentation-based persistence in `checklist.md`.
package/bin/thincoder.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  * thincoder memory <sub> Memory management: list / search / put / remove
8
8
  * thincoder upgrade Update to the latest version from npm
9
9
  * thincoder completion <sh> Shell completion: bash / zsh / fish
10
- * thincoder session gc Session dir GC: --dry-run report / --confirm delete cold projects (SESSION.md §12)
10
+ * thincoder session gc Session dir GC: --dry-run report / --confirm delete cold projects (SESSION.md §6.12)
11
11
  * thincoder acp Agent Client Protocol server (stdio, for Zed/JetBrains/Paseo)
12
12
  * thincoder -v Print version
13
13
  * thincoder --help Print help
@@ -15,11 +15,13 @@
15
15
 
16
16
  import { readFileSync } from "node:fs"
17
17
  import { isAbsolute, join } from "node:path"
18
- import { runAgent } from "../src/agent.mjs"
19
- import { loadConfig, configPath } from "../src/config.mjs"
20
- import { cleanupTraces } from "../src/traces/trace-store.mjs"
21
- import { createMemory, syncDir } from "../src/memory.mjs"
22
- import { assembleAgent, teamConfig, gitAuthor, validateProvider } from "../src/cli/make-agent.mjs"
18
+ import { runAgent } from "@thincoder/core/agent.mjs"
19
+ import { loadConfig, configPath } from "@thincoder/core/config.mjs"
20
+ import { cleanupTraces } from "@thincoder/core/traces/trace-store.mjs"
21
+ import { createMemory, syncDir } from "@thincoder/core/memory.mjs"
22
+ // 批 1 CORE-DEFECT-FIXES B3:onWait 相位值域 + 文案单源(消费面禁各自枚举——PROVIDER.md §6.20)
23
+ import { waitStatusText } from "@thincoder/core/provider/wait-status.mjs"
24
+ import { assembleAgent, attachManifest, teamConfig, gitAuthor, validateProvider } from "../src/cli/make-agent.mjs"
23
25
  import { memoryCommand } from "../src/cli/memory-command.mjs"
24
26
  import { setupWizard } from "../src/cli/setup-wizard.mjs"
25
27
  import { summarize, askPermission } from "../src/cli/permission.mjs"
@@ -27,6 +29,13 @@ import { distillCommand } from "../src/cli/distill-command.mjs"
27
29
  import { prepareCrashReporting, recentCrashHint, writeCrashRecord } from "../src/crash-reports.mjs"
28
30
  import { setTuiActive, restoreTerminalAfterCrash } from "../src/tui/tui-lifecycle.mjs"
29
31
  import { spawnTuiWrapped } from "../src/tui/wrapped-spawn.mjs"
32
+ import { configurePromptInjections } from "@thincoder/core/prompt-files.mjs"
33
+ import { CLI_PROMPT_INJECTIONS } from "../src/prompt-injections.mjs"
34
+
35
+ // U2(CORE-UNIFICATION §2.6.3 专项补⑦ / §2.13.8(六)):CLI 端锚取值表——**进程入口、
36
+ // 任何装配之前**注册一次(调用期应用 ⇒ 无导入序要求)。漏配 = 锚字面静默进模型
37
+ // ⇒ 入口面用例(test/integration/cli-prompt-entry.test.mjs)显式覆盖本调用路径。
38
+ configurePromptInjections(CLI_PROMPT_INJECTIONS)
30
39
 
31
40
  const [command, ...args] = process.argv.slice(2)
32
41
  // TUI-STDERR-CAPTURE(F-1):TUI 启动(tui/无命令)默认包装——父 spawn 子 tee stderr 落盘(外部
@@ -41,6 +50,13 @@ const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.u
41
50
  // 期窗口)——V8 OOM/原生 fatal 自动写 report.*.json(实现批实测:目录缺失时 Node 静默不写
42
51
  // ——预建为必要动作)。失败不阻断启动(尽力面)。
43
52
  prepareCrashReporting()
53
+ // TUI-OOM-ROOTCAUSE(CRASH-REPORTS.md §8.3 武装点——全命令同源单点):堆遥测/看门狗——
54
+ // 60s 采样 + 双档(70/85%)比例边缘预警(stderr + TUI 行 + 事件日志);定时器 unref
55
+ // (一次性命令自然退出零阻塞);默认开 / THINCODER_HEAP_WATCH 关值集可不启。失败面全吞。
56
+ try {
57
+ const { startHeapWatch } = await import("../src/heap-watch.mjs")
58
+ startHeapWatch()
59
+ } catch { /* 看门狗启动失败不阻断启动(尽力面) */ }
44
60
 
45
61
  // R25(F-R25a)异常钩子升级:原"只 console.error 一行(TUI 全屏下不可见)"→ ① 落盘
46
62
  // crash-{ts}-{pid}.json ② TUI 活动态终端恢复 ③ console.error(恢复后打印才可见)
@@ -86,8 +102,10 @@ const USAGE = `thincoder - thin coding agent
86
102
 
87
103
  Usage:
88
104
  thincoder Launch the interactive TUI
105
+ thincoder tui Launch the interactive TUI (explicit form of the default)
89
106
  thincoder chat [--auto] <prompt> One-shot agent run (tools enabled), streams reply to stdout; --auto approves all tool calls
90
107
  thincoder acp Agent Client Protocol server (stdio — Zed/JetBrains/Paseo drive sessions)
108
+ thincoder acp --login Authenticate this machine for ACP clients (terminal auth flow), then exit
91
109
  thincoder memory list [--type=<t>] List memory entries
92
110
  thincoder memory search <query> Search memory
93
111
  thincoder memory put --type=<t> --title=<t> --content=<c> [--tags=<t>]
@@ -108,7 +126,7 @@ Config: ~/.thincoder/config.json — providers[] (one default model per channel)
108
126
 
109
127
  /** Unified message when no API key is configured */
110
128
  function noKeyMessage() {
111
- return `No API key configured yet. Run "thincoder" to enter TUI, use /provider add and /provider key; or edit ${configPath} directly`
129
+ return `No API key configured yet. Run "thincoder" to enter TUI, use /model to add a provider and set its key; or edit ${configPath} directly`
112
130
  }
113
131
 
114
132
  /** Delay exit: process.exit right after fetch triggers libuv assertion on Windows/Node 24; let handles drain first */
@@ -138,7 +156,7 @@ switch (command) {
138
156
  if (crashNotice) console.error(crashNotice)
139
157
 
140
158
  const agent = await assembleAgent()
141
- // SESSION.md §8 D-S4(F4)+ MODEL-MERGE-SESSION:headless 无 TUI —— 可读错误 + 退出码 1,
159
+ // SESSION.md §6.8 D-S4(F4)+ MODEL-MERGE-SESSION:headless 无 TUI —— 可读错误 + 退出码 1,
142
160
  // 不弹 UI、不崩溃;文案改引 defaultModel(F-6 引导 A headless 面——/config → 默认模型)
143
161
  if (agent._providerInvalid) {
144
162
  const dm = agent.config?.defaultModel
@@ -162,7 +180,7 @@ switch (command) {
162
180
  // Wizard may have configured an embedding key: attach vector search
163
181
  const fresh = loadConfig()
164
182
  if (fresh.embedding?.apiKey && agent.memory && !agent.memory.embedder) {
165
- const { createEmbedder } = await import("../src/embedding.mjs")
183
+ const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
166
184
  agent.memory.embedder = createEmbedder(fresh.embedding)
167
185
  }
168
186
  }
@@ -172,8 +190,9 @@ switch (command) {
172
190
  try {
173
191
  await runAgent(agent, prompt, {
174
192
  onToken: (text) => process.stdout.write(text),
175
- onWait: ({ phase, seconds }) => {
176
- console.error(phase === "gate" ? `[rate-limit] TPM throttle waiting ~${seconds}s` : `[rate-limit] 429 response, retrying in ${seconds}s`)
193
+ onWait: (ev) => {
194
+ const s = waitStatusText(ev)
195
+ if (s) console.error(`[rate-limit] ${s}`)
177
196
  },
178
197
  onToolCall: (name, toolArgs) => {
179
198
  console.error(`\n[tool] ${name} ${summarize(toolArgs)}`)
@@ -220,7 +239,7 @@ switch (command) {
220
239
  const config = loadConfig()
221
240
  const memory = createMemory({ dbPath: config.memory.dbPath })
222
241
  if (config.embedding?.apiKey) {
223
- const { createEmbedder } = await import("../src/embedding.mjs")
242
+ const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
224
243
  memory.embedder = createEmbedder(config.embedding)
225
244
  }
226
245
  if (config.memory.projectDir) {
@@ -241,7 +260,7 @@ switch (command) {
241
260
  break
242
261
  }
243
262
  const memory = createMemory({ dbPath: config.memory.dbPath })
244
- const { ensureClone, pullTeam } = await import("../src/git/gitmem.mjs")
263
+ const { ensureClone, pullTeam } = await import("@thincoder/core/git/gitmem.mjs")
245
264
  try {
246
265
  const cloned = await ensureClone(team)
247
266
  if (cloned) console.log(`Cloned team repo to ${team.dir}`)
@@ -274,7 +293,7 @@ switch (command) {
274
293
  }
275
294
  const team = teamConfig(config)
276
295
  if (team) {
277
- const { ensureClone } = await import("../src/git/gitmem.mjs")
296
+ const { ensureClone } = await import("@thincoder/core/git/gitmem.mjs")
278
297
  await ensureClone(team)
279
298
  const s = await syncDir(memory, { layer: "team", dir: team.dir })
280
299
  total.added += s.added
@@ -285,20 +304,26 @@ switch (command) {
285
304
 
286
305
  case "tui":
287
306
  case undefined: {
288
- const agent = await assembleAgent()
289
- // SESSION.md §8 D-S1:TUI 路径在 startTUI 前清空无效 provider——空 provider 不流入 runAgent
307
+ // 恢复上次的会话(同一项目目录);provider 按保存的名字切回(用户上次可能换过模型)
308
+ // 2026-09-05 §6.10(R4):恢复决策按本端记录 resumeSlot(D-2 ①②③)——manifest active
309
+ // 只作"无记录端"的一次性继承源,不再作本端恢复第一依据(D-6)。
310
+ // 2026-09-18(装配门禁批 #30 · KD-M1-13 / KD-M1-15):resumeSlot **前移至装配之前**——
311
+ // 装配期模式门判据 = 会话权威值(槽优先 + config 回退);恢复记录经 slotData 形参进
312
+ // 装配钩子(同进程同一调用前移 ⇒ 零新增写动作;钩子内不调 resumeSlot——非纯读)。
313
+ const { resumeSlot, applySession, sessionDescriptor } = await import("@thincoder/core/session.mjs")
314
+ // F-MI7:resumeSlot = async(探测束不阻塞事件循环)——此处必须 await
315
+ const { slot, data } = await resumeSlot(process.cwd())
316
+ const agent = await assembleAgent({ slotData: data })
317
+ // SESSION.md §6.8 D-S1:TUI 路径在 startTUI 前清空无效 provider——空 provider 不流入 runAgent
290
318
  // (崩溃源:chat() 缺 model → 网关 400 或 fetch("undefined/...") TypeError)
291
319
  if (agent._providerInvalid) agent.provider = null
292
320
  const config = loadConfig()
293
- // 恢复上次的会话(同一项目目录);provider 按保存的名字切回(用户上次可能换过模型)
294
- // 2026-09-05 §10(R4):恢复决策按本端记录 resumeSlot(D-2 ①②③)——manifest active
295
- // 只作"无记录端"的一次性继承源,不再作本端恢复第一依据(D-6)。
296
- const { resumeSlot, applySession } = await import("../src/session.mjs")
297
- const { slot, data } = resumeSlot(process.cwd())
298
321
  if (data) {
299
322
  // applySession 内部已按槽复合重算 compactThreshold(auto 时)——不再需要 switched 分支
300
- applySession(agent, data)
301
- // SESSION.md §11.2(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
323
+ // TUI-OOM-ROOTCAUSE(SESSION.md §6.14):传 slot → 绑定记录存储(身份核验 + 对账)
324
+ // ——人读线 = 尾窗(磁盘为准);未传 = 模式 F(全量数组)
325
+ applySession(agent, data, { slot })
326
+ // SESSION.md §6.11(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
302
327
  // 信号——仅本启动 resume 路径设一次(真进程重启恢复盘上会话);/session 切换与 ACP
303
328
  // 加载走同一 applySession 收敛但不设(不误报进程重启)。prepareRun 发句即清——进程
304
329
  // 内一次。resumed:yes 独立由 applySession 的恢复事件武装(data.history 非空)。
@@ -308,6 +333,10 @@ switch (command) {
308
333
  // "load → 首回合保存"窗口内并发方翻 active 导致的静默迁移);/new 经 resetSessionState
309
334
  // 清 _slot(newSession 已认领 + 写记录,语义保留)
310
335
  agent._slot = slot
336
+ // M1 重估点(会话起点②——applySession 之后 · KD-M1-13 取值点②):CLI 的工程模式权威值 =
337
+ // 会话槽(/eng 只写槽不写 config.json);此时 agent.config.agent.engineering 已由
338
+ // applySession 按槽订正 ⇒ 与装配期判据同值 ⇒ 幂等重估(值同 ⇒ 无副作用)。
339
+ attachManifest(agent)
311
340
  // D-S3 优先级补全:applySession 可能已用会话中的有效 provider 修复(config 无效 + 会话有效)——
312
341
  // 修复后复验清除标记,仅当两者都无效才弹重选(validateProvider 幂等)
313
342
  if (agent._providerInvalid) validateProvider(agent)
@@ -326,7 +355,7 @@ switch (command) {
326
355
  projectDir: config.memory.projectDir ? (isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)) : null,
327
356
  team: teamConfig(config),
328
357
  author: gitAuthor(),
329
- restored: data,
358
+ restored: data ? sessionDescriptor(agent, data) : data,
330
359
  // R25(F-R25c):TUI 启动显示一行"上次运行异常终止"提示(showStartup 渲染——无匹配不传)
331
360
  crashNotice: recentCrashHint() ?? undefined,
332
361
  })
@@ -373,15 +402,21 @@ switch (command) {
373
402
  }
374
403
 
375
404
  case "acp": {
376
- const { runAcpServer } = await import("../src/acp.mjs")
405
+ const { runAcpServer, runAcpLogin } = await import("../src/acp.mjs")
406
+ // §11.2 改法 4:`args` 语义 = 追加到已配置的 agent 调用——`--login` = 终端认证流程,
407
+ // 不进 stdio 服务模式(不带该旗标时行为零变);退出码语义 = 0 成功 / 非 0 失败。
408
+ if (args.includes("--login")) {
409
+ exitSoon(await runAcpLogin())
410
+ break
411
+ }
377
412
  await runAcpServer()
378
413
  break
379
414
  }
380
415
 
381
416
  case "session": {
382
- // SESSION.md §12:会话目录 GC 手动面(F2 冷 cwd 报告/删除——VS Code 端无 shell 通道,仅 CLI)
383
- const { runSessionGc } = await import("../src/session-gc.mjs")
384
- process.exitCode = runSessionGc(args)
417
+ // SESSION.md §6.12:会话目录 GC 手动面(F2 冷 cwd 报告/删除——VS Code 端无 shell 通道,仅 CLI)
418
+ const { runSessionGc } = await import("@thincoder/core/session-gc.mjs")
419
+ process.exitCode = await runSessionGc(args)
385
420
  break
386
421
  }
387
422
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.61",
3
+ "version": "0.12.63",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -19,6 +19,9 @@
19
19
  "engines": {
20
20
  "node": ">=24"
21
21
  },
22
+ "dependencies": {
23
+ "@thincoder/core": "^0.9.1"
24
+ },
22
25
  "files": [
23
26
  "bin/",
24
27
  "src/",
@@ -31,12 +34,10 @@
31
34
  "url": "git+https://github.com/xinbo-tech/thincoder.git"
32
35
  },
33
36
  "scripts": {
34
- "test": "node test/run-fast.mjs",
37
+ "test": "node test/run.mjs",
35
38
  "prepublishOnly": "npm run release:check",
36
39
  "lint": "node scripts/check-syntax.mjs",
37
- "release:check": "node scripts/release-check.mjs",
38
- "test:full": "node test/run-full.mjs",
39
- "test:integration": "node test/run-integration.mjs"
40
+ "release:check": "node scripts/release-check.mjs"
40
41
  },
41
42
  "author": "liwei <liwei@51marine.com> (上海新舶)",
42
43
  "license": "MIT"
@@ -19,10 +19,12 @@
19
19
  * End-of-turn is NOT a notification: `session/prompt` resolves with
20
20
  * `{ stopReason: "end_turn" }` (kimi session.ts parity).
21
21
  */
22
- import { detectDanger, normalizeEOL, joinWithEol } from "../tools/shared.mjs"
23
- import { computeEditEntry, validateEditEntry, assertEditArgsExclusive, hasLineParams } from "../tools/edit-diff.mjs"
22
+ import { detectDanger, normalizeEOL, joinWithEol } from "@thincoder/core/tools/shared.mjs"
23
+ import { computeEditEntry, validateEditEntry, assertEditArgsExclusive, hasLineParams } from "@thincoder/core/tools/edit-diff.mjs"
24
24
  // 第 27 批 §12.3①/③:relay 前缀文法单一权威(模块直连——不自持正则副本)。
25
- import { parseRelayPath } from "../agent/relay-prefix.mjs"
25
+ import { parseRelayPath } from "@thincoder/core/agent/relay-prefix.mjs"
26
+ // 批 1 CORE-DEFECT-FIXES B3:onWait 相位值域 + 文案单源(本面仅日志——PROVIDER.md §6.20)
27
+ import { waitStatusText } from "@thincoder/core/provider/wait-status.mjs"
26
28
 
27
29
  /** ACP ToolKind inference (schema v1 enum) — best-effort, clients render by kind. */
28
30
  function inferToolKind(name) {
@@ -52,11 +54,18 @@ function permissionToBoolean(response) {
52
54
 
53
55
  /**
54
56
  * Build the runAgent callbacks for an ACP session.
55
- * @param {{ sessionId: string, notify: (m, p) => void, request: (m, p, o?) => Promise<any>, log?: (s) => void }} deps
57
+ * @param {{ sessionId: string, notify: (m, p) => void, request: (m, p, o?) => Promise<any>, log?: (s) => void,
58
+ * clientCaps?: { fs?: { readTextFile?: boolean, writeTextFile?: boolean } } }} deps
59
+ * `clientCaps` = the §3.4 snapshot (taken at `session/new`); capabilities omitted by the
60
+ * client are UNSUPPORTED ⇒ the fs reverse-RPC face stays local (§11.4 — never 30s 干等).
56
61
  */
57
- export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }) {
62
+ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {}, clientCaps = {} }) {
58
63
  const update = (sessionUpdate, extra = {}) =>
59
64
  notify("session/update", { sessionId, update: { sessionUpdate, ...extra } })
65
+ // §11.4 fs 能力位(默认 false——文档「MUST treat all capabilities omitted … as UNSUPPORTED」);
66
+ // 判据同构 = kimi server.ts:626-636。edit 桥需读回 + 写回 ⇒ 两位皆真才路由。
67
+ const canReadFile = clientCaps?.fs?.readTextFile === true
68
+ const canWriteFile = clientCaps?.fs?.writeTextFile === true
60
69
  let toolSeq = 0
61
70
  // D15.8(TOOLS.md §15.1):tool id FIFO 队列——并行同名工具按 call 序配对(dispatch B1
62
71
  // 已测:并行结果回调顺序 = call 顺序——T-TS8/T-TS9)。取代旧 Map 按名覆盖(后写覆盖先写
@@ -99,7 +108,10 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
99
108
  // ——与本地通道逐字一致(NF15.6b / AC15.10:not found / occurrences / 空 old / 空 new)。
100
109
  const EDIT_ABORT_PREFIX = "edit aborted (atomic — no files written): "
101
110
 
111
+ // 不变量(§11.7-2):**任何** fs/* 反向 RPC 发出前必过客户端能力位——守卫住在本函数内
112
+ // (防御面:路由分支先判,守卫兜底,漏判不发静默超时)。
102
113
  const readBuffer = async (p) => {
114
+ if (!canReadFile) throw new Error("client does not advertise fs.readTextFile")
103
115
  try {
104
116
  const read = await request("fs/read_text_file", { sessionId, path: p }, { timeoutMs: 30000 })
105
117
  return read?.text ?? read?.content ?? ""
@@ -108,6 +120,7 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
108
120
  }
109
121
  }
110
122
  const writeBuffer = async (p, content) => {
123
+ if (!canWriteFile) throw new Error("client does not advertise fs.writeTextFile")
111
124
  try {
112
125
  await request("fs/write_text_file", { sessionId, path: p, content }, { timeoutMs: 30000 })
113
126
  } catch (e) {
@@ -176,12 +189,14 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
176
189
  // Strip the subagent `[model]` metadata token ([model]<name>) — it's a
177
190
  // TUI/webview display signal, not conversation content, and must not reach ACP clients.
178
191
  if (/^\[model\]/.test(payload)) return
179
- // D7 (AGENT-LOOP.md §7.2 + §19.5 round2 #6 + D-M7b): strip ⟦ev⟧ event tokens (turn/
180
- // approval/done/settled/stopped/async — async = §19.5 D-M7b zero-field spawn marker)
181
- // — they carry RS control characters and are a TUI display signal; structured ACP
182
- // mapping (tool_call_update) is tracked separately in docs/TODO.md.
192
+ // D7 + 批 1 B2/D13(ACP-CLIENT.md §7.2):剥离判据 = **形态**(`⟦ev⟧<小写名>` + RS
193
+ // 终止符同现),不再枚举事件名——枚举是漏项发生器(`queued` / `cancelled` 发射点在位
194
+ // 而白名单未跟 ⇒ 随 agent_message_chunk 泄漏给客户端)。终止符约束 load-bearing:
195
+ // 放宽为无终止符形态会吞真实正文(先例教训见 src/tui/render.mjs:250-252)。
196
+ // 事件 token 是 TUI 显示信号;结构化 ACP 映射(tool_call_update)另行跟踪
197
+ // (docs/TODO.md)。
183
198
  // 有意为之:控制字符协议/转义序列剥离正则(ANSI/⟦ev⟧/SGR/history 双线分隔)
184
- if (/^⟦ev⟧(?:turn|approval|done|settled|stopped|async)\x1e/.test(payload)) return
199
+ if (/^⟦ev⟧[a-z]+\x1e/.test(payload)) return
185
200
  if (!payload) return // D6:剥后空载荷不发通知(防噪声空 chunk)
186
201
  update("agent_message_chunk", { content: { type: "text", text: payload } })
187
202
  },
@@ -193,7 +208,10 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
193
208
  update("agent_thought_chunk", { content: { type: "text", text: payload } })
194
209
  },
195
210
  onUsage: (usage) => update("usage_update", { usage }),
196
- onWait: ({ phase, seconds }) => log(`[rate-limit] ${phase} waiting ~${seconds}s`),
211
+ onWait: (ev) => {
212
+ const s = waitStatusText(ev)
213
+ if (s) log(`[rate-limit] ${s}`)
214
+ },
197
215
  onCompress: () => log("[context] auto-compacted"),
198
216
 
199
217
  onToolCall: (name, args, toolId) => {
@@ -265,17 +283,19 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
265
283
  },
266
284
 
267
285
  /**
268
- * fs reverse-RPC router (dispatch.mjs toolRouter, M2):
269
- * - write → fs/write_text_file (full content, no read-back)
286
+ * fs reverse-RPC router (dispatch.mjs toolRouter, M2) — 能力位 + 工具形态双判(§11.4):
287
+ * - write → fs/write_text_file (full content, no read-back) — 需 clientCaps.fs.writeTextFile
270
288
  * - edit → fs/read_text_file → computeEditEntry(本地权威——单/数组形态)→ fs/write_text_file
271
289
  * (§15.1 D15.7 委派——双通道同语义;数组=原子批量——逐条目串行累积)
290
+ * — 需 readTextFile ∧ writeTextFile(读回是 edit 桥的必要前提)
272
291
  * - apply_patch → local (unified-diff application is not routed in M2)
273
292
  * - delete, reads → local
293
+ * 未宣告 ⇒ `{ handled: false }` 回落本地(零反向 RPC、零超时等待)。
274
294
  */
275
295
  toolRouter: async (name, args) => {
276
296
  const base = name.includes("/") ? name.split("/").pop() : name
277
297
  const path = pathOf(args)
278
- if (base === "write" && path) {
298
+ if (base === "write" && path && canWriteFile) {
279
299
  if (typeof args?.content !== "string") {
280
300
  return { handled: true, result: `Error: write content must be a string (got ${typeof args?.content})` }
281
301
  }
@@ -290,7 +310,7 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
290
310
  // 2026-09-08 D1:单形态按行号改(path + line/startLine/endLine + new_string——无
291
311
  // old_string)同样走 IDE 缓冲通道(hasLineParams 判定)——否则回落本地写盘会与
292
312
  // IDE 缓冲脱敏;editSingle → computeEditEntry 行号语义自动继承。
293
- if (base === "edit" && (Array.isArray(args?.edits) || (path && typeof args?.new_string === "string" && (typeof args?.old_string === "string" || hasLineParams(args))))) {
313
+ if (base === "edit" && canReadFile && canWriteFile && (Array.isArray(args?.edits) || (path && typeof args?.new_string === "string" && (typeof args?.old_string === "string" || hasLineParams(args))))) {
294
314
  try {
295
315
  const text = Array.isArray(args?.edits) ? await editBatch(args) : await editSingle(path, args)
296
316
  return { handled: true, result: text }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * client-caps.mjs — `initialize` / `authenticate` handlers + the server-level
3
+ * client-capability snapshot (ACP-CLIENT.md §3.4 · §11.2 · §11.3).
4
+ *
5
+ * `initialize` is the protocol's ONLY capability-exchange point (schema:
6
+ * `InitializeRequest.clientCapabilities`). The snapshot written here is read once
7
+ * — by the auth-methods gate (this module) and the fs reverse-RPC gate
8
+ * (`bridge.mjs`) — and never mutated again (protocol semantics). Omitted
9
+ * capabilities are UNSUPPORTED (文档逐字「MUST treat all capabilities omitted in
10
+ * the initialize request as UNSUPPORTED」) — never optimistic defaults.
11
+ */
12
+ import { ACP_ERRORS } from "./transport.mjs"
13
+
14
+ /** Protocol versions this agent speaks (schema `ProtocolVersion`; v1 — §11.3 改法①). */
15
+ export const SUPPORTED_PROTOCOL_VERSIONS = [1]
16
+
17
+ /** Version negotiation (文档逐字): same version when supported, else the latest we support. */
18
+ export function negotiateProtocolVersion(requested, supported = SUPPORTED_PROTOCOL_VERSIONS) {
19
+ const latest = Math.max(...supported)
20
+ return Number.isInteger(requested) && supported.includes(requested) ? requested : latest
21
+ }
22
+
23
+ /** `initialize.authMethods` entries (schema `AuthMethod` objects — `AuthMethodTerminal`
24
+ * requires `id` + `name`). The `terminal` entry MUST be advertised **only when the client
25
+ * enabled its terminal auth capability** (`clientCaps.auth.terminal === true`); otherwise
26
+ * `[]`. `args` are APPENDED to the configured agent invocation (the descriptor cannot
27
+ * provide a command) ⇒ the client actually launches `thincoder acp --login`. */
28
+ export function authMethodsFor(clientCaps) {
29
+ if (clientCaps?.auth?.terminal !== true) return []
30
+ return [{
31
+ id: "terminal",
32
+ type: "terminal",
33
+ name: "Terminal login",
34
+ description: "Run `thincoder acp --login` in an interactive terminal to configure the provider API key.",
35
+ args: ["--login"],
36
+ }]
37
+ }
38
+
39
+ /**
40
+ * Build the initialize / authenticate handlers.
41
+ * @param {{ version: string, clientCaps: { current: object },
42
+ * requireConfigured: () => { error?: object }, log?: (s: string) => void }} deps
43
+ * `requireConfigured` = the shared credential gate (§11.5 — lives in handlers-session.mjs).
44
+ * @returns {Record<string, (params: object) => object>}
45
+ */
46
+ export function createCapsHandlers({ version, clientCaps, requireConfigured, log = () => {} }) {
47
+ return {
48
+ initialize: (params) => {
49
+ const caps = params?.clientCapabilities && typeof params.clientCapabilities === "object" ? params.clientCapabilities : {}
50
+ clientCaps.current = caps // §3.4 — written once (initialize is the only exchange point)
51
+ log(`[acp] initialize — protocolVersion=${JSON.stringify(params?.protocolVersion)} auth.terminal=${caps.auth?.terminal === true}`)
52
+ return {
53
+ protocolVersion: negotiateProtocolVersion(params?.protocolVersion),
54
+ // 能力面如实声明(§11.3 改法③):已实现的 loadSession + 四键 sessionCapabilities;
55
+ // promptCapabilities 全 false(本面不接 image/audio/embeddedContext——不虚报);
56
+ // mcpCapabilities / additionalDirectories 省略 = 不支持(G7 / G4 本批不做)。
57
+ agentCapabilities: {
58
+ loadSession: true,
59
+ promptCapabilities: { image: false, audio: false, embeddedContext: false },
60
+ sessionCapabilities: { list: {}, resume: {}, delete: {}, close: {} },
61
+ },
62
+ authMethods: authMethodsFor(caps),
63
+ agentInfo: { name: "thincoder", version },
64
+ }
65
+ },
66
+
67
+ authenticate: (params) => {
68
+ // 契约形状(§11.2 改法③):methodId 不属于**当前已宣告**的方法 → -32602;
69
+ // 凭据不可解析 → -32000;通过 → 空结果 `{}`(AuthenticateResponse 只有 `_meta`)。
70
+ // 注:契约禁止客户端以 `terminal` 调本方法——若客户端仍调且该法已宣告,本实现宽容放行。
71
+ const advertised = authMethodsFor(clientCaps.current)
72
+ const methodId = params?.methodId
73
+ if (!advertised.some((m) => m.id === methodId)) {
74
+ return {
75
+ error: {
76
+ ...ACP_ERRORS.INVALID_PARAMS,
77
+ message: `unknown auth method: ${String(methodId)} (advertised: ${advertised.map((m) => m.id).join(", ") || "none"})`,
78
+ },
79
+ }
80
+ }
81
+ const gate = requireConfigured()
82
+ if (gate.error) return gate
83
+ return {}
84
+ },
85
+ }
86
+ }