thincoder 0.12.62 → 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 (228) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +13 -12
  3. package/bin/thincoder.mjs +53 -27
  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 -379
  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 +1 -1
  20. package/src/distill.mjs +4 -4
  21. package/src/heap-watch.mjs +1 -1
  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-config.mjs +8 -8
  26. package/src/tui/cmd-eng.mjs +25 -9
  27. package/src/tui/cmd-mcp.mjs +9 -8
  28. package/src/tui/cmd-model.mjs +1 -1
  29. package/src/tui/cmd-new.mjs +6 -5
  30. package/src/tui/cmd-reindex.mjs +1 -1
  31. package/src/tui/cmd-restore.mjs +2 -2
  32. package/src/tui/cmd-session.mjs +24 -1
  33. package/src/tui/cmd-skills.mjs +1 -1
  34. package/src/tui/cmd-think.mjs +22 -9
  35. package/src/tui/config-helpers.mjs +1 -1
  36. package/src/tui/display-budget.mjs +33 -11
  37. package/src/tui/index.mjs +9 -9
  38. package/src/tui/interaction.mjs +16 -7
  39. package/src/tui/key-modes.mjs +9 -4
  40. package/src/tui/ledger-surface.mjs +26 -10
  41. package/src/tui/model-catalog.mjs +4 -4
  42. package/src/tui/model-picker.mjs +8 -7
  43. package/src/tui/mouse.mjs +11 -6
  44. package/src/tui/pickers.mjs +15 -2
  45. package/src/tui/render-conversation.mjs +1 -1
  46. package/src/tui/render-frame.mjs +10 -5
  47. package/src/tui/render-loop.mjs +1 -1
  48. package/src/tui/render-segments.mjs +3 -1
  49. package/src/tui/slash-commands.mjs +1 -1
  50. package/src/tui/startup.mjs +14 -14
  51. package/src/tui/subagent-blocks.mjs +20 -3
  52. package/src/tui/subagent-freeze.mjs +73 -2
  53. package/src/tui/suspension-drive.mjs +46 -22
  54. package/src/tui/tool-events.mjs +11 -8
  55. package/src/tui/wizard.mjs +3 -3
  56. package/src/abort-provenance.mjs +0 -116
  57. package/src/advisor/citations.mjs +0 -139
  58. package/src/advisor/compaction.mjs +0 -174
  59. package/src/advisor/convergence.mjs +0 -80
  60. package/src/advisor/history.mjs +0 -77
  61. package/src/advisor/loop.mjs +0 -293
  62. package/src/advisor/messages.mjs +0 -299
  63. package/src/advisor/project-context.mjs +0 -194
  64. package/src/advisor/repos.mjs +0 -150
  65. package/src/advisor/run.mjs +0 -293
  66. package/src/advisor/truncate.mjs +0 -57
  67. package/src/advisor.mjs +0 -290
  68. package/src/agent/completion.mjs +0 -146
  69. package/src/agent/dispatch.mjs +0 -489
  70. package/src/agent/helpers.mjs +0 -384
  71. package/src/agent/post-turn.mjs +0 -70
  72. package/src/agent/record-results.mjs +0 -174
  73. package/src/agent/relay-prefix.mjs +0 -39
  74. package/src/agent/run-stages.mjs +0 -244
  75. package/src/agent/setup-reminders.mjs +0 -69
  76. package/src/agent/setup.mjs +0 -354
  77. package/src/agent/spawn-child.mjs +0 -243
  78. package/src/agent-tools/advisor-async.mjs +0 -346
  79. package/src/agent-tools/advisor-settle.mjs +0 -231
  80. package/src/agent-tools/advisor.mjs +0 -260
  81. package/src/agent-tools/async-settle.mjs +0 -204
  82. package/src/agent-tools/batch-segment.mjs +0 -195
  83. package/src/agent-tools/consult.mjs +0 -473
  84. package/src/agent-tools/design-token.mjs +0 -117
  85. package/src/agent-tools/digest-budget.mjs +0 -76
  86. package/src/agent-tools/eng.mjs +0 -67
  87. package/src/agent-tools/escalate-async.mjs +0 -295
  88. package/src/agent-tools/goal.mjs +0 -119
  89. package/src/agent-tools/plan.mjs +0 -81
  90. package/src/agent-tools/read-history.mjs +0 -309
  91. package/src/agent-tools/recent-changes.mjs +0 -24
  92. package/src/agent-tools/review-streak.mjs +0 -93
  93. package/src/agent-tools/settings.mjs +0 -265
  94. package/src/agent-tools/skill.mjs +0 -47
  95. package/src/agent-tools/subagent-actions.mjs +0 -482
  96. package/src/agent-tools/subagent-async.mjs +0 -434
  97. package/src/agent-tools/subagent-panel.mjs +0 -160
  98. package/src/agent-tools/subagent-run.mjs +0 -205
  99. package/src/agent-tools/subagent-scheduler.mjs +0 -392
  100. package/src/agent-tools/subagent-spawn.mjs +0 -459
  101. package/src/agent-tools/subagent.mjs +0 -404
  102. package/src/agent-tools/task.mjs +0 -87
  103. package/src/agent-tools/timer.mjs +0 -46
  104. package/src/agent-tools/verify.mjs +0 -271
  105. package/src/agent-tools.mjs +0 -17
  106. package/src/agent.mjs +0 -417
  107. package/src/auto-think.mjs +0 -115
  108. package/src/config-migrate.mjs +0 -70
  109. package/src/config.mjs +0 -496
  110. package/src/context.mjs +0 -392
  111. package/src/conventions.mjs +0 -223
  112. package/src/embedding.mjs +0 -120
  113. package/src/escape.mjs +0 -152
  114. package/src/expand-home.mjs +0 -16
  115. package/src/explore-distill.mjs +0 -155
  116. package/src/generate-title.mjs +0 -88
  117. package/src/git/checkpoint.mjs +0 -448
  118. package/src/git/gitmem.mjs +0 -100
  119. package/src/hooks.mjs +0 -97
  120. package/src/ledger.mjs +0 -227
  121. package/src/log.mjs +0 -195
  122. package/src/markdown.mjs +0 -106
  123. package/src/mcp/helpers.mjs +0 -51
  124. package/src/mcp/transport-http.mjs +0 -248
  125. package/src/mcp/transport-stdio.mjs +0 -140
  126. package/src/mcp/transport-ws.mjs +0 -122
  127. package/src/mcp.mjs +0 -295
  128. package/src/memory/code-index.mjs +0 -219
  129. package/src/memory/code-sync.mjs +0 -415
  130. package/src/memory/core.mjs +0 -299
  131. package/src/memory/delete.mjs +0 -236
  132. package/src/memory/docs.mjs +0 -419
  133. package/src/memory/file-walk.mjs +0 -109
  134. package/src/memory/scan.mjs +0 -95
  135. package/src/memory/schema.mjs +0 -452
  136. package/src/memory.mjs +0 -21
  137. package/src/model-ref.mjs +0 -66
  138. package/src/model-specs.mjs +0 -179
  139. package/src/peer-domains.mjs +0 -265
  140. package/src/peer-instances.mjs +0 -231
  141. package/src/prompt-overlays.mjs +0 -82
  142. package/src/prompts/advisor-design.md +0 -41
  143. package/src/prompts/advisor-round1.md +0 -41
  144. package/src/prompts/advisor-round2.md +0 -46
  145. package/src/prompts/advisor-round3.md +0 -42
  146. package/src/prompts/common.md +0 -115
  147. package/src/prompts/consult-base.md +0 -19
  148. package/src/prompts/discipline-engineering.md +0 -258
  149. package/src/prompts/discipline-normal.md +0 -185
  150. package/src/prompts/persona-coder.md +0 -21
  151. package/src/prompts/persona-eng-coder.md +0 -37
  152. package/src/prompts/persona-eng-designer.md +0 -60
  153. package/src/prompts/persona-engineering.md +0 -55
  154. package/src/prompts/persona-explore.md +0 -15
  155. package/src/prompts/persona-normal.md +0 -27
  156. package/src/prompts/persona-plan.md +0 -26
  157. package/src/provider/anthropic.mjs +0 -225
  158. package/src/provider/core.mjs +0 -476
  159. package/src/provider/errors.mjs +0 -101
  160. package/src/provider/google.mjs +0 -257
  161. package/src/provider/index.mjs +0 -7
  162. package/src/provider/list-models.mjs +0 -93
  163. package/src/provider/normalize.mjs +0 -81
  164. package/src/provider/rate.mjs +0 -108
  165. package/src/provider/responses.mjs +0 -495
  166. package/src/provider/retry.mjs +0 -88
  167. package/src/provider/sse.mjs +0 -264
  168. package/src/proxy.mjs +0 -261
  169. package/src/rules.mjs +0 -53
  170. package/src/session-gc.mjs +0 -221
  171. package/src/session-guard.mjs +0 -59
  172. package/src/session-migrate.mjs +0 -48
  173. package/src/session-rename.mjs +0 -38
  174. package/src/session-segments.mjs +0 -100
  175. package/src/session-slots.mjs +0 -492
  176. package/src/session-store.mjs +0 -441
  177. package/src/session.mjs +0 -492
  178. package/src/skills.mjs +0 -153
  179. package/src/text-budget.mjs +0 -46
  180. package/src/token-ttl.mjs +0 -274
  181. package/src/tools/apply_patch.md +0 -15
  182. package/src/tools/bash.md +0 -37
  183. package/src/tools/bash.mjs +0 -268
  184. package/src/tools/checklist-sync.mjs +0 -181
  185. package/src/tools/checklist.md +0 -13
  186. package/src/tools/checklist.mjs +0 -299
  187. package/src/tools/delete.md +0 -13
  188. package/src/tools/edit-batch.mjs +0 -191
  189. package/src/tools/edit-diff.mjs +0 -348
  190. package/src/tools/edit.md +0 -30
  191. package/src/tools/execute.md +0 -21
  192. package/src/tools/execute.mjs +0 -228
  193. package/src/tools/fetch.md +0 -12
  194. package/src/tools/file.mjs +0 -469
  195. package/src/tools/file_ops.md +0 -17
  196. package/src/tools/get_current_time.md +0 -8
  197. package/src/tools/git-checkpoint.mjs +0 -143
  198. package/src/tools/git-ext.mjs +0 -173
  199. package/src/tools/git.md +0 -54
  200. package/src/tools/git.mjs +0 -356
  201. package/src/tools/glob-dialect.mjs +0 -130
  202. package/src/tools/glob.md +0 -11
  203. package/src/tools/grep.md +0 -19
  204. package/src/tools/hashline_edit.md +0 -14
  205. package/src/tools/index.mjs +0 -36
  206. package/src/tools/insert_after.md +0 -15
  207. package/src/tools/lint.md +0 -10
  208. package/src/tools/linter.mjs +0 -128
  209. package/src/tools/ls.md +0 -12
  210. package/src/tools/lsp.md +0 -10
  211. package/src/tools/lsp.mjs +0 -316
  212. package/src/tools/ops.mjs +0 -299
  213. package/src/tools/patch.mjs +0 -282
  214. package/src/tools/process.md +0 -10
  215. package/src/tools/question.md +0 -16
  216. package/src/tools/question.mjs +0 -26
  217. package/src/tools/read.md +0 -20
  218. package/src/tools/read_image.md +0 -8
  219. package/src/tools/repomap.mjs +0 -314
  220. package/src/tools/search.mjs +0 -236
  221. package/src/tools/shared.mjs +0 -446
  222. package/src/tools/tree.md +0 -14
  223. package/src/tools/tree.mjs +0 -66
  224. package/src/tools/wait_for.md +0 -22
  225. package/src/tools/web.mjs +0 -224
  226. package/src/tools/websearch.md +0 -16
  227. package/src/tools/write.md +0 -11
  228. package/src/traces/trace-store.mjs +0 -355
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
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
+
1
10
  ## [0.12.62] — 2026-09-13
2
11
 
3
12
  > 0.12.61 → 0.12.62(§4.3 连续号——发布时定号)
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 落盘(外部
@@ -93,8 +102,10 @@ const USAGE = `thincoder - thin coding agent
93
102
 
94
103
  Usage:
95
104
  thincoder Launch the interactive TUI
105
+ thincoder tui Launch the interactive TUI (explicit form of the default)
96
106
  thincoder chat [--auto] <prompt> One-shot agent run (tools enabled), streams reply to stdout; --auto approves all tool calls
97
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
98
109
  thincoder memory list [--type=<t>] List memory entries
99
110
  thincoder memory search <query> Search memory
100
111
  thincoder memory put --type=<t> --title=<t> --content=<c> [--tags=<t>]
@@ -115,7 +126,7 @@ Config: ~/.thincoder/config.json — providers[] (one default model per channel)
115
126
 
116
127
  /** Unified message when no API key is configured */
117
128
  function noKeyMessage() {
118
- 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`
119
130
  }
120
131
 
121
132
  /** Delay exit: process.exit right after fetch triggers libuv assertion on Windows/Node 24; let handles drain first */
@@ -145,7 +156,7 @@ switch (command) {
145
156
  if (crashNotice) console.error(crashNotice)
146
157
 
147
158
  const agent = await assembleAgent()
148
- // 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,
149
160
  // 不弹 UI、不崩溃;文案改引 defaultModel(F-6 引导 A headless 面——/config → 默认模型)
150
161
  if (agent._providerInvalid) {
151
162
  const dm = agent.config?.defaultModel
@@ -169,7 +180,7 @@ switch (command) {
169
180
  // Wizard may have configured an embedding key: attach vector search
170
181
  const fresh = loadConfig()
171
182
  if (fresh.embedding?.apiKey && agent.memory && !agent.memory.embedder) {
172
- const { createEmbedder } = await import("../src/embedding.mjs")
183
+ const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
173
184
  agent.memory.embedder = createEmbedder(fresh.embedding)
174
185
  }
175
186
  }
@@ -179,8 +190,9 @@ switch (command) {
179
190
  try {
180
191
  await runAgent(agent, prompt, {
181
192
  onToken: (text) => process.stdout.write(text),
182
- onWait: ({ phase, seconds }) => {
183
- 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}`)
184
196
  },
185
197
  onToolCall: (name, toolArgs) => {
186
198
  console.error(`\n[tool] ${name} ${summarize(toolArgs)}`)
@@ -227,7 +239,7 @@ switch (command) {
227
239
  const config = loadConfig()
228
240
  const memory = createMemory({ dbPath: config.memory.dbPath })
229
241
  if (config.embedding?.apiKey) {
230
- const { createEmbedder } = await import("../src/embedding.mjs")
242
+ const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
231
243
  memory.embedder = createEmbedder(config.embedding)
232
244
  }
233
245
  if (config.memory.projectDir) {
@@ -248,7 +260,7 @@ switch (command) {
248
260
  break
249
261
  }
250
262
  const memory = createMemory({ dbPath: config.memory.dbPath })
251
- const { ensureClone, pullTeam } = await import("../src/git/gitmem.mjs")
263
+ const { ensureClone, pullTeam } = await import("@thincoder/core/git/gitmem.mjs")
252
264
  try {
253
265
  const cloned = await ensureClone(team)
254
266
  if (cloned) console.log(`Cloned team repo to ${team.dir}`)
@@ -281,7 +293,7 @@ switch (command) {
281
293
  }
282
294
  const team = teamConfig(config)
283
295
  if (team) {
284
- const { ensureClone } = await import("../src/git/gitmem.mjs")
296
+ const { ensureClone } = await import("@thincoder/core/git/gitmem.mjs")
285
297
  await ensureClone(team)
286
298
  const s = await syncDir(memory, { layer: "team", dir: team.dir })
287
299
  total.added += s.added
@@ -292,22 +304,26 @@ switch (command) {
292
304
 
293
305
  case "tui":
294
306
  case undefined: {
295
- const agent = await assembleAgent()
296
- // 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
297
318
  // (崩溃源:chat() 缺 model → 网关 400 或 fetch("undefined/...") TypeError)
298
319
  if (agent._providerInvalid) agent.provider = null
299
320
  const config = loadConfig()
300
- // 恢复上次的会话(同一项目目录);provider 按保存的名字切回(用户上次可能换过模型)
301
- // 2026-09-05 §10(R4):恢复决策按本端记录 resumeSlot(D-2 ①②③)——manifest active
302
- // 只作"无记录端"的一次性继承源,不再作本端恢复第一依据(D-6)。
303
- const { resumeSlot, applySession, sessionDescriptor } = await import("../src/session.mjs")
304
- const { slot, data } = resumeSlot(process.cwd())
305
321
  if (data) {
306
322
  // applySession 内部已按槽复合重算 compactThreshold(auto 时)——不再需要 switched 分支
307
- // TUI-OOM-ROOTCAUSE(SESSION.md §14.3.4):传 slot → 绑定记录存储(身份核验 + 对账)
323
+ // TUI-OOM-ROOTCAUSE(SESSION.md §6.14):传 slot → 绑定记录存储(身份核验 + 对账)
308
324
  // ——人读线 = 尾窗(磁盘为准);未传 = 模式 F(全量数组)
309
325
  applySession(agent, data, { slot })
310
- // SESSION.md §11.2(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
326
+ // SESSION.md §6.11(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
311
327
  // 信号——仅本启动 resume 路径设一次(真进程重启恢复盘上会话);/session 切换与 ACP
312
328
  // 加载走同一 applySession 收敛但不设(不误报进程重启)。prepareRun 发句即清——进程
313
329
  // 内一次。resumed:yes 独立由 applySession 的恢复事件武装(data.history 非空)。
@@ -317,6 +333,10 @@ switch (command) {
317
333
  // "load → 首回合保存"窗口内并发方翻 active 导致的静默迁移);/new 经 resetSessionState
318
334
  // 清 _slot(newSession 已认领 + 写记录,语义保留)
319
335
  agent._slot = slot
336
+ // M1 重估点(会话起点②——applySession 之后 · KD-M1-13 取值点②):CLI 的工程模式权威值 =
337
+ // 会话槽(/eng 只写槽不写 config.json);此时 agent.config.agent.engineering 已由
338
+ // applySession 按槽订正 ⇒ 与装配期判据同值 ⇒ 幂等重估(值同 ⇒ 无副作用)。
339
+ attachManifest(agent)
320
340
  // D-S3 优先级补全:applySession 可能已用会话中的有效 provider 修复(config 无效 + 会话有效)——
321
341
  // 修复后复验清除标记,仅当两者都无效才弹重选(validateProvider 幂等)
322
342
  if (agent._providerInvalid) validateProvider(agent)
@@ -382,15 +402,21 @@ switch (command) {
382
402
  }
383
403
 
384
404
  case "acp": {
385
- 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
+ }
386
412
  await runAcpServer()
387
413
  break
388
414
  }
389
415
 
390
416
  case "session": {
391
- // SESSION.md §12:会话目录 GC 手动面(F2 冷 cwd 报告/删除——VS Code 端无 shell 通道,仅 CLI)
392
- const { runSessionGc } = await import("../src/session-gc.mjs")
393
- 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)
394
420
  break
395
421
  }
396
422
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.62",
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
+ }