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.
- package/CHANGELOG.md +34 -1
- package/README.md +13 -12
- package/bin/thincoder.mjs +63 -28
- package/package.json +6 -5
- package/src/acp/bridge.mjs +35 -15
- package/src/acp/client-caps.mjs +86 -0
- package/src/acp/ext.mjs +86 -0
- package/src/acp/handlers-session.mjs +240 -0
- package/src/acp/handlers-slots.mjs +196 -0
- package/src/acp/login.mjs +48 -0
- package/src/acp/session.mjs +6 -4
- package/src/acp.mjs +67 -371
- package/src/cli/distill-command.mjs +3 -3
- package/src/cli/make-agent.mjs +59 -17
- package/src/cli/memory-command.mjs +3 -3
- package/src/cli/permission.mjs +4 -48
- package/src/cli/setup-wizard.mjs +1 -1
- package/src/completions.mjs +3 -1
- package/src/crash-reports.mjs +32 -10
- package/src/distill.mjs +4 -4
- package/src/heap-watch.mjs +88 -0
- package/src/prompt-injections.mjs +20 -0
- package/src/tui/agent-turn.mjs +40 -9
- package/src/tui/cmd-advisor.mjs +5 -5
- package/src/tui/cmd-clear.mjs +2 -0
- package/src/tui/cmd-config.mjs +8 -8
- package/src/tui/cmd-eng.mjs +25 -9
- package/src/tui/cmd-mcp.mjs +9 -8
- package/src/tui/cmd-model.mjs +1 -1
- package/src/tui/cmd-new.mjs +10 -5
- package/src/tui/cmd-reindex.mjs +1 -1
- package/src/tui/cmd-restore.mjs +2 -2
- package/src/tui/cmd-session.mjs +31 -4
- package/src/tui/cmd-skills.mjs +1 -1
- package/src/tui/cmd-think.mjs +22 -9
- package/src/tui/config-helpers.mjs +1 -1
- package/src/tui/display-budget.mjs +206 -0
- package/src/tui/index.mjs +40 -12
- package/src/tui/interaction.mjs +16 -7
- package/src/tui/key-handler-search.mjs +9 -1
- package/src/tui/key-modes.mjs +9 -4
- package/src/tui/ledger-surface.mjs +85 -0
- package/src/tui/model-catalog.mjs +4 -4
- package/src/tui/model-picker.mjs +8 -7
- package/src/tui/mouse.mjs +11 -6
- package/src/tui/pickers.mjs +15 -2
- package/src/tui/render-conversation.mjs +1 -1
- package/src/tui/render-frame.mjs +17 -6
- package/src/tui/render-loop.mjs +1 -1
- package/src/tui/render-segments.mjs +3 -1
- package/src/tui/slash-commands.mjs +1 -1
- package/src/tui/startup.mjs +49 -17
- package/src/tui/subagent-blocks.mjs +21 -3
- package/src/tui/subagent-children.mjs +86 -14
- package/src/tui/subagent-freeze.mjs +80 -3
- package/src/tui/suspension-drive.mjs +48 -22
- package/src/tui/tool-args.mjs +5 -2
- package/src/tui/tool-display.mjs +16 -2
- package/src/tui/tool-events.mjs +64 -22
- package/src/tui/tui-lifecycle.mjs +9 -2
- package/src/tui/wizard.mjs +3 -3
- package/src/tui/wrapped-spawn.mjs +21 -5
- package/src/abort-provenance.mjs +0 -116
- package/src/advisor/citations.mjs +0 -139
- package/src/advisor/compaction.mjs +0 -174
- package/src/advisor/convergence.mjs +0 -80
- package/src/advisor/history.mjs +0 -77
- package/src/advisor/loop.mjs +0 -293
- package/src/advisor/messages.mjs +0 -299
- package/src/advisor/project-context.mjs +0 -194
- package/src/advisor/repos.mjs +0 -150
- package/src/advisor/run.mjs +0 -293
- package/src/advisor/truncate.mjs +0 -57
- package/src/advisor.mjs +0 -290
- package/src/agent/completion.mjs +0 -146
- package/src/agent/dispatch.mjs +0 -489
- package/src/agent/helpers.mjs +0 -384
- package/src/agent/post-turn.mjs +0 -70
- package/src/agent/record-results.mjs +0 -174
- package/src/agent/relay-prefix.mjs +0 -39
- package/src/agent/run-stages.mjs +0 -242
- package/src/agent/setup-reminders.mjs +0 -69
- package/src/agent/setup.mjs +0 -354
- package/src/agent/spawn-child.mjs +0 -228
- package/src/agent-tools/advisor-async.mjs +0 -346
- package/src/agent-tools/advisor-settle.mjs +0 -231
- package/src/agent-tools/advisor.mjs +0 -260
- package/src/agent-tools/async-settle.mjs +0 -191
- package/src/agent-tools/batch-segment.mjs +0 -195
- package/src/agent-tools/consult.mjs +0 -468
- package/src/agent-tools/design-token.mjs +0 -117
- package/src/agent-tools/digest-budget.mjs +0 -76
- package/src/agent-tools/eng.mjs +0 -67
- package/src/agent-tools/escalate-async.mjs +0 -289
- package/src/agent-tools/goal.mjs +0 -119
- package/src/agent-tools/plan.mjs +0 -81
- package/src/agent-tools/read-history.mjs +0 -294
- package/src/agent-tools/recent-changes.mjs +0 -24
- package/src/agent-tools/review-streak.mjs +0 -93
- package/src/agent-tools/settings.mjs +0 -265
- package/src/agent-tools/skill.mjs +0 -47
- package/src/agent-tools/subagent-actions.mjs +0 -479
- package/src/agent-tools/subagent-async.mjs +0 -434
- package/src/agent-tools/subagent-panel.mjs +0 -160
- package/src/agent-tools/subagent-run.mjs +0 -205
- package/src/agent-tools/subagent-scheduler.mjs +0 -392
- package/src/agent-tools/subagent-spawn.mjs +0 -453
- package/src/agent-tools/subagent.mjs +0 -404
- package/src/agent-tools/task.mjs +0 -87
- package/src/agent-tools/timer.mjs +0 -46
- package/src/agent-tools/verify.mjs +0 -271
- package/src/agent-tools.mjs +0 -17
- package/src/agent.mjs +0 -413
- package/src/auto-think.mjs +0 -115
- package/src/config-migrate.mjs +0 -70
- package/src/config.mjs +0 -496
- package/src/context.mjs +0 -381
- package/src/conventions.mjs +0 -223
- package/src/embedding.mjs +0 -120
- package/src/escape.mjs +0 -152
- package/src/expand-home.mjs +0 -16
- package/src/explore-distill.mjs +0 -155
- package/src/generate-title.mjs +0 -83
- package/src/git/checkpoint.mjs +0 -448
- package/src/git/gitmem.mjs +0 -100
- package/src/hooks.mjs +0 -97
- package/src/log.mjs +0 -195
- package/src/markdown.mjs +0 -106
- package/src/mcp/helpers.mjs +0 -51
- package/src/mcp/transport-http.mjs +0 -248
- package/src/mcp/transport-stdio.mjs +0 -140
- package/src/mcp/transport-ws.mjs +0 -122
- package/src/mcp.mjs +0 -295
- package/src/memory/code-index.mjs +0 -219
- package/src/memory/code-sync.mjs +0 -413
- package/src/memory/core.mjs +0 -300
- package/src/memory/delete.mjs +0 -236
- package/src/memory/docs.mjs +0 -417
- package/src/memory/file-walk.mjs +0 -109
- package/src/memory/schema.mjs +0 -452
- package/src/memory.mjs +0 -21
- package/src/model-ref.mjs +0 -66
- package/src/model-specs.mjs +0 -179
- package/src/peer-domains.mjs +0 -265
- package/src/peer-instances.mjs +0 -231
- package/src/prompt-overlays.mjs +0 -82
- package/src/prompts/advisor-design.md +0 -41
- package/src/prompts/advisor-round1.md +0 -41
- package/src/prompts/advisor-round2.md +0 -46
- package/src/prompts/advisor-round3.md +0 -42
- package/src/prompts/common.md +0 -115
- package/src/prompts/consult-base.md +0 -19
- package/src/prompts/discipline-engineering.md +0 -217
- package/src/prompts/discipline-normal.md +0 -179
- package/src/prompts/persona-coder.md +0 -21
- package/src/prompts/persona-eng-coder.md +0 -37
- package/src/prompts/persona-eng-designer.md +0 -55
- package/src/prompts/persona-engineering.md +0 -54
- package/src/prompts/persona-explore.md +0 -15
- package/src/prompts/persona-normal.md +0 -27
- package/src/prompts/persona-plan.md +0 -26
- package/src/provider/anthropic.mjs +0 -225
- package/src/provider/core.mjs +0 -476
- package/src/provider/errors.mjs +0 -101
- package/src/provider/google.mjs +0 -257
- package/src/provider/index.mjs +0 -7
- package/src/provider/list-models.mjs +0 -93
- package/src/provider/normalize.mjs +0 -81
- package/src/provider/rate.mjs +0 -108
- package/src/provider/responses.mjs +0 -495
- package/src/provider/retry.mjs +0 -88
- package/src/provider/sse.mjs +0 -264
- package/src/proxy.mjs +0 -261
- package/src/rules.mjs +0 -53
- package/src/session-gc.mjs +0 -214
- package/src/session-guard.mjs +0 -47
- package/src/session-migrate.mjs +0 -48
- package/src/session-rename.mjs +0 -38
- package/src/session-slots.mjs +0 -489
- package/src/session.mjs +0 -475
- package/src/skills.mjs +0 -153
- package/src/token-ttl.mjs +0 -274
- package/src/tools/apply_patch.md +0 -15
- package/src/tools/bash.md +0 -37
- package/src/tools/bash.mjs +0 -268
- package/src/tools/checklist-sync.mjs +0 -181
- package/src/tools/checklist.md +0 -13
- package/src/tools/checklist.mjs +0 -299
- package/src/tools/delete.md +0 -13
- package/src/tools/edit-batch.mjs +0 -191
- package/src/tools/edit-diff.mjs +0 -348
- package/src/tools/edit.md +0 -30
- package/src/tools/execute.md +0 -21
- package/src/tools/execute.mjs +0 -228
- package/src/tools/fetch.md +0 -12
- package/src/tools/file.mjs +0 -469
- package/src/tools/file_ops.md +0 -17
- package/src/tools/get_current_time.md +0 -8
- package/src/tools/git-checkpoint.mjs +0 -143
- package/src/tools/git-ext.mjs +0 -173
- package/src/tools/git.md +0 -54
- package/src/tools/git.mjs +0 -356
- package/src/tools/glob-dialect.mjs +0 -130
- package/src/tools/glob.md +0 -11
- package/src/tools/grep.md +0 -19
- package/src/tools/hashline_edit.md +0 -14
- package/src/tools/index.mjs +0 -36
- package/src/tools/insert_after.md +0 -15
- package/src/tools/lint.md +0 -10
- package/src/tools/linter.mjs +0 -128
- package/src/tools/ls.md +0 -12
- package/src/tools/lsp.md +0 -10
- package/src/tools/lsp.mjs +0 -316
- package/src/tools/ops.mjs +0 -299
- package/src/tools/patch.mjs +0 -282
- package/src/tools/process.md +0 -10
- package/src/tools/question.md +0 -16
- package/src/tools/question.mjs +0 -26
- package/src/tools/read.md +0 -20
- package/src/tools/read_image.md +0 -8
- package/src/tools/repomap.mjs +0 -314
- package/src/tools/search.mjs +0 -236
- package/src/tools/shared.mjs +0 -446
- package/src/tools/tree.md +0 -14
- package/src/tools/tree.mjs +0 -66
- package/src/tools/wait_for.md +0 -22
- package/src/tools/web.mjs +0 -224
- package/src/tools/websearch.md +0 -16
- package/src/tools/write.md +0 -11
- 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**:
|
|
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: [
|
|
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 `/
|
|
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), `/
|
|
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
|
-
```
|
|
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
|
|
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
|
-
"
|
|
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` / `
|
|
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 "
|
|
19
|
-
import { loadConfig, configPath } from "
|
|
20
|
-
import { cleanupTraces } from "
|
|
21
|
-
import { createMemory, syncDir } from "
|
|
22
|
-
|
|
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 /
|
|
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("
|
|
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: (
|
|
176
|
-
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
-
|
|
289
|
-
//
|
|
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
|
-
|
|
301
|
-
//
|
|
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("
|
|
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.
|
|
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
|
|
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"
|
package/src/acp/bridge.mjs
CHANGED
|
@@ -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 "
|
|
23
|
-
import { computeEditEntry, validateEditEntry, assertEditArgsExclusive, hasLineParams } from "
|
|
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 "
|
|
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
|
|
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
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
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⟧
|
|
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: (
|
|
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
|
+
}
|