thincoder 0.12.60 → 0.12.62
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 +49 -2
- package/README.md +8 -6
- package/bin/thincoder.mjs +38 -124
- package/package.json +3 -2
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +38 -17
- package/src/acp.mjs +19 -6
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +36 -134
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +124 -329
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +3 -2
- package/src/agent/completion.mjs +1 -1
- package/src/agent/dispatch.mjs +47 -12
- package/src/agent/helpers.mjs +71 -13
- package/src/agent/record-results.mjs +13 -5
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +24 -7
- package/src/agent/setup-reminders.mjs +16 -9
- package/src/agent/setup.mjs +92 -128
- package/src/agent/spawn-child.mjs +43 -11
- package/src/agent-tools/advisor-async.mjs +70 -180
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +69 -20
- package/src/agent-tools/async-settle.mjs +13 -0
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +28 -10
- package/src/agent-tools/design-token.mjs +14 -1
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +3 -3
- package/src/agent-tools/escalate-async.mjs +22 -13
- package/src/agent-tools/read-history.mjs +31 -6
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +130 -17
- package/src/agent-tools/subagent-actions.mjs +18 -6
- package/src/agent-tools/subagent-async.mjs +66 -14
- package/src/agent-tools/subagent-panel.mjs +22 -15
- package/src/agent-tools/subagent-run.mjs +10 -7
- package/src/agent-tools/subagent-scheduler.mjs +57 -8
- package/src/agent-tools/subagent-spawn.mjs +69 -16
- package/src/agent-tools/subagent.mjs +175 -49
- package/src/agent-tools/verify.mjs +13 -34
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +42 -21
- package/src/cli/distill-command.mjs +2 -2
- package/src/cli/make-agent.mjs +23 -7
- package/src/cli/memory-command.mjs +2 -2
- package/src/cli/setup-wizard.mjs +29 -9
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +132 -63
- package/src/context.mjs +12 -1
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +35 -8
- package/src/expand-home.mjs +16 -0
- package/src/generate-title.mjs +9 -4
- package/src/heap-watch.mjs +88 -0
- package/src/hooks.mjs +7 -3
- package/src/ledger.mjs +227 -0
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +77 -36
- package/src/memory/core.mjs +8 -9
- package/src/memory/delete.mjs +2 -0
- package/src/memory/docs.mjs +17 -11
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/scan.mjs +95 -0
- package/src/memory/schema.mjs +15 -3
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/prompt-overlays.mjs +73 -16
- package/src/prompts/advisor-design.md +19 -9
- package/src/prompts/advisor-round1.md +8 -2
- package/src/prompts/advisor-round2.md +14 -3
- package/src/prompts/advisor-round3.md +14 -3
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +2 -0
- package/src/prompts/discipline-engineering.md +258 -0
- package/src/prompts/discipline-normal.md +185 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +60 -0
- package/src/prompts/persona-engineering.md +55 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +13 -32
- package/src/provider/errors.mjs +26 -1
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- package/src/session-gc.mjs +9 -2
- package/src/session-guard.mjs +12 -0
- package/src/session-segments.mjs +100 -0
- package/src/session-slots.mjs +10 -2
- package/src/session-store.mjs +441 -0
- package/src/session.mjs +137 -99
- package/src/text-budget.mjs +46 -0
- package/src/token-ttl.mjs +2 -1
- package/src/tools/{system.mjs → bash.mjs} +6 -243
- package/src/tools/file.mjs +30 -10
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +15 -34
- package/src/tools/index.mjs +4 -2
- package/src/tools/ops.mjs +20 -7
- package/src/tools/question.md +1 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -1
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/traces/trace-store.mjs +195 -64
- package/src/tui/agent-turn.mjs +32 -13
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-clear.mjs +2 -0
- package/src/tui/cmd-config.mjs +108 -37
- package/src/tui/cmd-eng.mjs +11 -27
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-new.mjs +5 -1
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-session.mjs +8 -4
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/display-budget.mjs +184 -0
- package/src/tui/index.mjs +69 -40
- package/src/tui/key-handler-search.mjs +9 -1
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/ledger-surface.mjs +69 -0
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +47 -10
- package/src/tui/pickers.mjs +28 -410
- package/src/tui/render-frame.mjs +38 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +5 -19
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/startup.mjs +45 -13
- package/src/tui/subagent-blocks.mjs +70 -90
- package/src/tui/subagent-children.mjs +125 -67
- package/src/tui/subagent-freeze.mjs +48 -45
- package/src/tui/subagent-panel.mjs +21 -66
- package/src/tui/suspension-drive.mjs +31 -83
- package/src/tui/tool-args.mjs +9 -4
- package/src/tui/tool-display.mjs +20 -5
- package/src/tui/tool-events.mjs +76 -30
- package/src/tui/tui-lifecycle.mjs +27 -7
- package/src/tui/wizard.mjs +52 -18
- package/src/tui/wrapped-spawn.mjs +54 -0
- package/src/prompts/coder.md +0 -13
- package/src/prompts/discipline.md +0 -84
- package/src/prompts/eng-coder.md +0 -19
- package/src/prompts/engineering-sub.md +0 -14
- package/src/prompts/engineering.md +0 -87
- package/src/prompts/explore.md +0 -12
- package/src/prompts/main.md +0 -34
- package/src/prompts/methodology-template.md +0 -38
- package/src/prompts/plan.md +0 -9
- package/src/prompts/system.md +0 -44
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
## [0.12.62] — 2026-09-13
|
|
2
|
+
|
|
3
|
+
> 0.12.61 → 0.12.62(§4.3 连续号——发布时定号)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **文档锚一致性机检 V5(DOC-CODE-RECONCILE 批)**:`scripts/doc-anchors.mjs`(三类锚——用例号 / 本仓符号 / 路径——存在性判定 + 假阳类逐条排除 + 注记闭枚举 + 两态 `V5_GATE` / `--v5-gate` / `--v5-report`)· `scripts/doc-impact.mjs`(改动面反查:`--base` 必给、只读不阻断)· 常驻零值锁(`test:full` 内——再出现即红)· **清账 347 处 → 0**。
|
|
8
|
+
- **台账自持机检**:`check-ledger` L4 本仓可解析闸 + 指针 / D3 组计数 / 六态 / 触发 / 老化报告。
|
|
9
|
+
- **工程模式纪律进提示词**:跨仓派单与写域纪律 · 改动面反查(提示词两副本逐字一致)。
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **文档体系各仓自持**:需求档 / 设计档 / 批次档 / 台账各仓记各仓(禁跨仓写需求与跨仓指针);跨仓引用统一「名称(仓别)§N」规范形态。
|
|
14
|
+
- **台账归档制**:活文件只留未决四态,已决逐条移入 `TODO-archive.md`。
|
|
15
|
+
- **在途批一并签入(8 批)**:TUI 内存取证与上界(session store / segments / 堆遥测)· 台账可见面 · 测试纪律提示词 · 子代理审批面与活动区对位。
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- **散文锚退役(PROSE-ANCHOR-RETIRE 批)**:退役断言「某句在场 / 缺席」类测试 71 例(保留结构机检面:引用可解析 / 组计数 / 六态取值 / 行宽 / slow 归册)。
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- 文档↔实装漂移清账(逐档处置留痕,禁「只删不记」)· `check-ledger` 扫描域收窄(只扫本仓活档 + 归档档)· 台账 L4 误报修正 · 批次档 / 设计档宽度与计数纪律收正。
|
|
24
|
+
|
|
25
|
+
## [0.12.61] — 2026-09-11
|
|
26
|
+
|
|
27
|
+
> 0.12.60 → 0.12.61(§4.3 连续号——发布时定号)
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **集成集 + 发布门三环(TEST-LIFECYCLE 批)**:`test/integration/`(7 场景 22 用例:普通模式工具流 / 工程模式全链 / 子代理生命周期 / 会话恢复 / TUI 基本盘 / commit 验证关口 / 配置与 provider 选路)+ `test/run-integration.mjs`;`npm run release:check` = lint → test:full → test:integration(prepublishOnly 接线)——发布唯一门禁。
|
|
32
|
+
- **需求池机检 `scripts/check-ledger.mjs`**(FR18 收拢):指针解析 / D3 组计数 / 六态 / 触发字段 / 老化报告 / 基线降报告;两仓台账收拢(活 30+9 · 归档档 40+11)。
|
|
33
|
+
- **provider.headers 全通路铺开**(Gitee #IKDWH7)· **`~` 家目录展开**(HOME-EXPANSION)· **websearch provider key**(工具面)。
|
|
34
|
+
- **DeepSeek V4.1-Flash 接入** + 模型清单 provider 化(去候选否决权)。
|
|
35
|
+
- **工程模式链路**:角色重定义(FR9 三分 + 内容权)/ 公共层扩容(common.md 10 节·双端 18 档)/ 批次档段写入工具 / 可移植性 FR10–FR15(项目约定去硬编码:`conventions.mjs` + `project-context.mjs`)。
|
|
36
|
+
- **逃生与可观测面**:子代理 abort 来源标注 · 评审池丢弃(报告不达提醒 + 不签发 token)· consult/escalate 完全异步化 · `read_history` 跨会话检索(R19)。
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **测试生命周期 v3**:三层来源(单元=开发期脚手架 / 集成-业务 / 集成-生产)+ 退役常态 + 削段合档(11 档退役 ~880 行 · 3 并 · 防回潮静态锚 T75/T76 收归)。
|
|
41
|
+
- **台账体系**:TODO 归档机制(活文件只留未决 + `TODO-archive.md`)+ 归属修订「记录 + 状态推进 + 物理落笔 = 主 agent」+ 六态机检。
|
|
42
|
+
- **TUI**:选择面统一(picker/wizard/question)· 输入修复集 · 方向键(连续上溯/多行竖移)· Stop 钩子 · 跨段 turn 累计 · attention 状态。
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- settings 工具 null 默认键类型校验 · ACP 通道修整(headless 裁剪 + relay 前缀剥离)· 评审注入路径硬编码(可移植性 A 家族)· advisor 上下文预算(CJK 加权)· 文档一致性检查扩面 + 宽度基线。
|
|
47
|
+
|
|
1
48
|
## [0.12.60] — 2026-09-08
|
|
2
49
|
|
|
3
50
|
> 0.12.59 → 0.12.60(§4.5 连续号——发布时定号)
|
|
@@ -39,7 +86,7 @@
|
|
|
39
86
|
|
|
40
87
|
### Added
|
|
41
88
|
|
|
42
|
-
- **settings 工具(SETTINGS-TOOL.md——2026-09-05 用户三项裁定)**:agent 配置调整通道——list/get/set(全量 config.json 任意键点分路径);set = 写盘 + 热应用(运行中即生效);敏感键(apiKey/token/secret/password 段)回显永遮罩;已知键类型校验(DEFAULTS 自动派生);list/get 只读动作(planMode 放行);set 审批门。测试 T-S1.1-11(CLI 11 + VS Code 6
|
|
89
|
+
- **settings 工具(SETTINGS-TOOL.md——2026-09-05 用户三项裁定)**:agent 配置调整通道——list/get/set(全量 config.json 任意键点分路径);set = 写盘 + 热应用(运行中即生效);敏感键(apiKey/token/secret/password 段)回显永遮罩;已知键类型校验(DEFAULTS 自动派生);list/get 只读动作(planMode 放行);set 审批门。测试 T-S1.1-11(CLI 11 + VS Code 6)——**记账更正(2026-09-11):原声称的测试未随批落地(欠账);已于 2026-09-11 补齐:CLI 25 例 / VS Code 6 例(第 8 批)。**
|
|
43
90
|
- **subagent-async 模块拆分(§20.9 Module Split Policy——2026-09-05 F-N1.5 两段式首批)**:CLI subagent-async.mjs 1020 → 405 行(subagent-scheduler.mjs 231——§20 调度组 + 文件域组 / subagent-actions.mjs 416——status/panel/escalate 动作执行器组)——纯迁移零行为变化(测试零改动 + 断言计数前后对拍一致)。
|
|
44
91
|
- **P-SL2 停滞机械检测(AGENT-LOOP §21.1 扩展注 P-SL2——2026-09-05)**:混合边环形等待停滞 → check/status 守卫明确报错列阻塞链(cancel 破环引导)——防御性(自然流不可达——人工注入可构造)。测试 T-SL2 ①-⑤(scheduler 22 → 27 用例)。
|
|
45
92
|
|
|
@@ -268,7 +315,7 @@
|
|
|
268
315
|
|
|
269
316
|
### Changed
|
|
270
317
|
|
|
271
|
-
- **系统提示词加两条约束**:①「先定正确再谈实现」——动手前先回答"正确应该是什么"(每个入口/视图/边界),再谈怎么实现,实现规模是"正确"
|
|
318
|
+
- **系统提示词加两条约束**:①「先定正确再谈实现」——动手前先回答"正确应该是什么"(每个入口/视图/边界),再谈怎么实现,实现规模是"正确"的结果、不是决策标准;②「确认时暴露方案权衡」——确认理解时列出选的做法、为什么它对(判据 = 正确性优先——不是省事程度)、考虑过但没选的替代方案。治"图省事"的偷懒病根
|
|
272
319
|
|
|
273
320
|
## [0.12.47] — 2026-08-28
|
|
274
321
|
|
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ Configuration comes exclusively from `~/.thincoder/config.json` — no environme
|
|
|
121
121
|
},
|
|
122
122
|
],
|
|
123
123
|
"activeProvider": "deepseek", // currently active provider name
|
|
124
|
-
"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
|
|
124
|
+
"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
125
|
"embedding": {
|
|
126
126
|
// optional: without it, retrieval is pure FTS
|
|
127
127
|
"baseURL": "https://api.siliconflow.cn/v1",
|
|
@@ -135,8 +135,9 @@ Configuration comes exclusively from `~/.thincoder/config.json` — no environme
|
|
|
135
135
|
"compactThreshold": 100000, // context compaction threshold (approx. tokens)
|
|
136
136
|
},
|
|
137
137
|
"memory": {
|
|
138
|
-
|
|
139
|
-
"
|
|
138
|
+
// Path fields (dbPath / projectDir / team.dir / shell) expand a leading ~ (~, ~/, ~\) to the home directory at load time
|
|
139
|
+
"dbPath": "~/.thincoder/memory.db", // sqlite index path (~ expands to the home directory)
|
|
140
|
+
"projectDir": ".thincoder/memory", // Project layer directory (relative to project root; ~ expands to an absolute path used as-is)
|
|
140
141
|
"team": {
|
|
141
142
|
// optional: Team layer disabled without it
|
|
142
143
|
"name": "myteam",
|
|
@@ -156,7 +157,6 @@ Configuration comes exclusively from `~/.thincoder/config.json` — no environme
|
|
|
156
157
|
"websearch": {
|
|
157
158
|
// optional: Tavily structured search (stable JSON API, no HTML scraping).
|
|
158
159
|
// Empty apiKey → silently falls back to Bing HTML extraction (zero-config).
|
|
159
|
-
"provider": "tavily",
|
|
160
160
|
"apiKey": "tvly-...", // https://tavily.com — has a free monthly tier
|
|
161
161
|
},
|
|
162
162
|
}
|
|
@@ -196,8 +196,10 @@ src/
|
|
|
196
196
|
clipboard.mjs (paste/copy), ansi.mjs
|
|
197
197
|
tui.mjs re-export shim → src/tui/index.mjs
|
|
198
198
|
tui-render.mjs re-export shim → src/tui/render.mjs
|
|
199
|
-
prompts/ prompt texts —
|
|
200
|
-
|
|
199
|
+
prompts/ prompt texts — slot-based (PROMPT-SYSTEM): persona-engineering / persona-normal /
|
|
200
|
+
persona-{eng-coder,explore,coder,plan} + common + discipline-engineering / discipline-normal
|
|
201
|
+
+ special modules (consult-base / advisor-design / advisor-round{1,2,3});
|
|
202
|
+
assembly = assemblePrompt (prompt-overlays.mjs): persona → common → discipline → [4] AGENTS+skills
|
|
201
203
|
test/ node:test offline unit tests (npm test)
|
|
202
204
|
scripts/ real-environment verification scripts (compaction, team sync)
|
|
203
205
|
```
|
package/bin/thincoder.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { readFileSync } from "node:fs"
|
|
17
|
-
import { join } from "node:path"
|
|
17
|
+
import { isAbsolute, join } from "node:path"
|
|
18
18
|
import { runAgent } from "../src/agent.mjs"
|
|
19
19
|
import { loadConfig, configPath } from "../src/config.mjs"
|
|
20
20
|
import { cleanupTraces } from "../src/traces/trace-store.mjs"
|
|
@@ -26,14 +26,28 @@ import { summarize, askPermission } from "../src/cli/permission.mjs"
|
|
|
26
26
|
import { distillCommand } from "../src/cli/distill-command.mjs"
|
|
27
27
|
import { prepareCrashReporting, recentCrashHint, writeCrashRecord } from "../src/crash-reports.mjs"
|
|
28
28
|
import { setTuiActive, restoreTerminalAfterCrash } from "../src/tui/tui-lifecycle.mjs"
|
|
29
|
+
import { spawnTuiWrapped } from "../src/tui/wrapped-spawn.mjs"
|
|
29
30
|
|
|
30
31
|
const [command, ...args] = process.argv.slice(2)
|
|
32
|
+
// TUI-STDERR-CAPTURE(F-1):TUI 启动(tui/无命令)默认包装——父 spawn 子 tee stderr 落盘(外部
|
|
33
|
+
// 终止/native abort——第 4 类崩溃面——诊断默认捕获)。须在 prepareCrashReporting 前(父不预建/不设
|
|
34
|
+
// report——子进程做——R25 保留)。env 门已设(包装内子进程)或包装失败 → 直行现逻辑(尽力面)。
|
|
35
|
+
if ((command === undefined || command === "tui") && !process.env.THINCODER_TUI_WRAPPED) {
|
|
36
|
+
if (spawnTuiWrapped()) await new Promise(() => {}) // 包装成功 → 挂起(tee/收尾退出全在 wrapped-spawn——不达下方 switch)
|
|
37
|
+
}
|
|
31
38
|
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version
|
|
32
39
|
|
|
33
40
|
// R25(F-R25b):crash-reports 预建 + process.report 启用——入口最前(一切重活前——缩编程
|
|
34
41
|
// 期窗口)——V8 OOM/原生 fatal 自动写 report.*.json(实现批实测:目录缺失时 Node 静默不写
|
|
35
42
|
// ——预建为必要动作)。失败不阻断启动(尽力面)。
|
|
36
43
|
prepareCrashReporting()
|
|
44
|
+
// TUI-OOM-ROOTCAUSE(CRASH-REPORTS.md §8.3 武装点——全命令同源单点):堆遥测/看门狗——
|
|
45
|
+
// 60s 采样 + 双档(70/85%)比例边缘预警(stderr + TUI 行 + 事件日志);定时器 unref
|
|
46
|
+
// (一次性命令自然退出零阻塞);默认开 / THINCODER_HEAP_WATCH 关值集可不启。失败面全吞。
|
|
47
|
+
try {
|
|
48
|
+
const { startHeapWatch } = await import("../src/heap-watch.mjs")
|
|
49
|
+
startHeapWatch()
|
|
50
|
+
} catch { /* 看门狗启动失败不阻断启动(尽力面) */ }
|
|
37
51
|
|
|
38
52
|
// R25(F-R25a)异常钩子升级:原"只 console.error 一行(TUI 全屏下不可见)"→ ① 落盘
|
|
39
53
|
// crash-{ts}-{pid}.json ② TUI 活动态终端恢复 ③ console.error(恢复后打印才可见)
|
|
@@ -96,7 +110,7 @@ Usage:
|
|
|
96
110
|
thincoder completion <sh> Generate shell completion script (bash / zsh / fish)
|
|
97
111
|
thincoder -v, --version Print version
|
|
98
112
|
|
|
99
|
-
Config: ~/.thincoder/config.json
|
|
113
|
+
Config: ~/.thincoder/config.json — providers[] (one default model per channel) + defaultModel (new-session starting point); the available-model list is fetched from the provider at runtime (GET /models); manage via /config → 默认模型 and /model (session-level) in TUI
|
|
100
114
|
`
|
|
101
115
|
|
|
102
116
|
/** Unified message when no API key is configured */
|
|
@@ -131,10 +145,11 @@ switch (command) {
|
|
|
131
145
|
if (crashNotice) console.error(crashNotice)
|
|
132
146
|
|
|
133
147
|
const agent = await assembleAgent()
|
|
134
|
-
// SESSION.md §8 D-S4(F4
|
|
148
|
+
// SESSION.md §8 D-S4(F4)+ MODEL-MERGE-SESSION:headless 无 TUI —— 可读错误 + 退出码 1,
|
|
149
|
+
// 不弹 UI、不崩溃;文案改引 defaultModel(F-6 引导 A headless 面——/config → 默认模型)
|
|
135
150
|
if (agent._providerInvalid) {
|
|
136
|
-
const
|
|
137
|
-
console.error(`[error]
|
|
151
|
+
const dm = agent.config?.defaultModel
|
|
152
|
+
console.error(`[error] 未配置有效模型(defaultModel "${dm ?? "(not set)"}":${agent._providerInvalidReason})。请运行 thincoder 进入 TUI 设置(/config → 默认模型;或 /model 选择后以会话槽生效),或编辑 ${configPath}`)
|
|
138
153
|
exitSoon(1)
|
|
139
154
|
break
|
|
140
155
|
}
|
|
@@ -216,7 +231,7 @@ switch (command) {
|
|
|
216
231
|
memory.embedder = createEmbedder(config.embedding)
|
|
217
232
|
}
|
|
218
233
|
if (config.memory.projectDir) {
|
|
219
|
-
memory.projectOrigin = join(process.cwd(), config.memory.projectDir)
|
|
234
|
+
memory.projectOrigin = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)
|
|
220
235
|
}
|
|
221
236
|
const code = await memoryCommand(memory, args)
|
|
222
237
|
if (code) exitSoon(code)
|
|
@@ -261,7 +276,7 @@ switch (command) {
|
|
|
261
276
|
const cwd = process.cwd()
|
|
262
277
|
let total = { added: 0, removed: 0 }
|
|
263
278
|
if (config.memory.projectDir) {
|
|
264
|
-
const s = await syncDir(memory, { layer: "project", dir: join(cwd, config.memory.projectDir) })
|
|
279
|
+
const s = await syncDir(memory, { layer: "project", dir: isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(cwd, config.memory.projectDir) })
|
|
265
280
|
total.added += s.added
|
|
266
281
|
}
|
|
267
282
|
const team = teamConfig(config)
|
|
@@ -285,16 +300,18 @@ switch (command) {
|
|
|
285
300
|
// 恢复上次的会话(同一项目目录);provider 按保存的名字切回(用户上次可能换过模型)
|
|
286
301
|
// 2026-09-05 §10(R4):恢复决策按本端记录 resumeSlot(D-2 ①②③)——manifest active
|
|
287
302
|
// 只作"无记录端"的一次性继承源,不再作本端恢复第一依据(D-6)。
|
|
288
|
-
const { resumeSlot, applySession } = await import("../src/session.mjs")
|
|
303
|
+
const { resumeSlot, applySession, sessionDescriptor } = await import("../src/session.mjs")
|
|
289
304
|
const { slot, data } = resumeSlot(process.cwd())
|
|
290
305
|
if (data) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
306
|
+
// applySession 内部已按槽复合重算 compactThreshold(auto 时)——不再需要 switched 分支
|
|
307
|
+
// TUI-OOM-ROOTCAUSE(SESSION.md §14.3.4):传 slot → 绑定记录存储(身份核验 + 对账)
|
|
308
|
+
// ——人读线 = 尾窗(磁盘为准);未传 = 模式 F(全量数组)
|
|
309
|
+
applySession(agent, data, { slot })
|
|
310
|
+
// SESSION.md §11.2(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
|
|
311
|
+
// 信号——仅本启动 resume 路径设一次(真进程重启恢复盘上会话);/session 切换与 ACP
|
|
312
|
+
// 加载走同一 applySession 收敛但不设(不误报进程重启)。prepareRun 发句即清——进程
|
|
313
|
+
// 内一次。resumed:yes 独立由 applySession 的恢复事件武装(data.history 非空)。
|
|
314
|
+
agent._processRestartPending = true
|
|
298
315
|
}
|
|
299
316
|
// D-3 钉槽:applySession 清 _slot 后立即钉回恢复槽——首保存必落恢复槽(消除
|
|
300
317
|
// "load → 首回合保存"窗口内并发方翻 active 导致的静默迁移);/new 经 resetSessionState
|
|
@@ -315,10 +332,10 @@ switch (command) {
|
|
|
315
332
|
const { startTUI } = await import("../src/tui.mjs")
|
|
316
333
|
try {
|
|
317
334
|
await startTUI(agent, {
|
|
318
|
-
projectDir: config.memory.projectDir ? join(process.cwd(), config.memory.projectDir) : null,
|
|
335
|
+
projectDir: config.memory.projectDir ? (isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)) : null,
|
|
319
336
|
team: teamConfig(config),
|
|
320
337
|
author: gitAuthor(),
|
|
321
|
-
restored: data,
|
|
338
|
+
restored: data ? sessionDescriptor(agent, data) : data,
|
|
322
339
|
// R25(F-R25c):TUI 启动显示一行"上次运行异常终止"提示(showStartup 渲染——无匹配不传)
|
|
323
340
|
crashNotice: recentCrashHint() ?? undefined,
|
|
324
341
|
})
|
|
@@ -336,113 +353,10 @@ switch (command) {
|
|
|
336
353
|
exitSoon(1)
|
|
337
354
|
break
|
|
338
355
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
344
|
-
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
345
|
-
case "\${COMP_WORDS[1]}" in
|
|
346
|
-
chat) COMPREPLY=( \\$(compgen -W "--auto" -- "\\$cur") ) ;;
|
|
347
|
-
memory)
|
|
348
|
-
case "\\$prev" in
|
|
349
|
-
memory) COMPREPLY=( \\$(compgen -W "list search put remove" -- "\\$cur") ) ;;
|
|
350
|
-
list) COMPREPLY=( \\$(compgen -W "--type=rule --type=knowledge --type=decision --type=pattern" -- "\\$cur") ) ;;
|
|
351
|
-
put) COMPREPLY=( \\$(compgen -W "--type= --title= --content= --tags=" -- "\\$cur") ) ;;
|
|
352
|
-
esac ;;
|
|
353
|
-
distill) COMPREPLY=( \\$(compgen -W "--yes --layer=" -- "\\$cur") ) ;;
|
|
354
|
-
completion) COMPREPLY=( \\$(compgen -W "bash zsh fish" -- "\\$cur") ) ;;
|
|
355
|
-
*)
|
|
356
|
-
COMPREPLY=( \\$(compgen -W "chat acp memory sync reindex distill upgrade completion session -v --version -h --help" -- "\\$cur") ) ;;
|
|
357
|
-
esac
|
|
358
|
-
}
|
|
359
|
-
complete -F _thincoder thincoder
|
|
360
|
-
`)
|
|
361
|
-
} else if (shell === "zsh") {
|
|
362
|
-
process.stdout.write(`#compdef thincoder
|
|
363
|
-
|
|
364
|
-
_thincoder() {
|
|
365
|
-
local context state state_descr line
|
|
366
|
-
typeset -A opt_args
|
|
367
|
-
_arguments -C \\
|
|
368
|
-
'1: :->cmd' \\
|
|
369
|
-
'*:: :->args'
|
|
370
|
-
|
|
371
|
-
case "\\$state" in
|
|
372
|
-
cmd)
|
|
373
|
-
_values 'command' \\
|
|
374
|
-
'chat[One-shot agent run with tools]' \\
|
|
375
|
-
'acp[Agent Client Protocol server for IDEs]' \\
|
|
376
|
-
'memory[Manage long-term memory]' \\
|
|
377
|
-
'sync[Sync team memory repo]' \\
|
|
378
|
-
'reindex[Rebuild local index from markdown]' \\
|
|
379
|
-
'distill[Extract knowledge from session transcript]' \\
|
|
380
|
-
'upgrade[Update to latest version from npm]' \\
|
|
381
|
-
'completion[Generate shell completion script]' \\
|
|
382
|
-
'session[Session dir GC: session gc --dry-run|--confirm]'
|
|
383
|
-
;;
|
|
384
|
-
args)
|
|
385
|
-
case "\\$words[1]" in
|
|
386
|
-
chat) _arguments '--auto[Auto-approve all tool calls]' ;;
|
|
387
|
-
memory)
|
|
388
|
-
case "\\$words[2]" in
|
|
389
|
-
list) _arguments '--type=[Filter by type]' ;;
|
|
390
|
-
put) _arguments '--type=[Entry type]' '--title=[Title]' '--content=[Content]' '--tags=[Space-separated tags]' ;;
|
|
391
|
-
esac ;;
|
|
392
|
-
distill) _arguments '--yes[Skip confirmation]' '--layer=[Layer filter]' ;;
|
|
393
|
-
completion) _values 'shell' 'bash' 'zsh' 'fish' ;;
|
|
394
|
-
esac ;;
|
|
395
|
-
esac
|
|
396
|
-
}
|
|
397
|
-
_thincoder
|
|
398
|
-
`)
|
|
399
|
-
} else if (shell === "fish") {
|
|
400
|
-
process.stdout.write(`# thincoder completions for fish shell
|
|
401
|
-
complete -c thincoder -f
|
|
402
|
-
|
|
403
|
-
# Subcommands
|
|
404
|
-
complete -c thincoder -a chat -d 'One-shot agent run with tools'
|
|
405
|
-
complete -c thincoder -a memory -d 'Manage long-term memory'
|
|
406
|
-
complete -c thincoder -a sync -d 'Sync team memory repo'
|
|
407
|
-
complete -c thincoder -a reindex -d 'Rebuild local index from markdown'
|
|
408
|
-
complete -c thincoder -a distill -d 'Extract knowledge from session'
|
|
409
|
-
complete -c thincoder -a upgrade -d 'Update to latest version'
|
|
410
|
-
complete -c thincoder -a completion -d 'Shell completion'
|
|
411
|
-
complete -c thincoder -a session -d 'Session dir GC (session gc --dry-run|--confirm)'
|
|
412
|
-
complete -c thincoder -a acp -d 'Agent Client Protocol server for IDEs'
|
|
413
|
-
|
|
414
|
-
# Flags
|
|
415
|
-
complete -c thincoder -s v -l version -d 'Print version'
|
|
416
|
-
complete -c thincoder -s h -l help -d 'Print help'
|
|
417
|
-
|
|
418
|
-
# chat flags
|
|
419
|
-
complete -c thincoder -n '__fish_seen_subcommand_from chat' -l auto -d 'Auto-approve tool calls'
|
|
420
|
-
|
|
421
|
-
# memory subcommands
|
|
422
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a list -d 'List entries'
|
|
423
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a search -d 'Search memory'
|
|
424
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a put -d 'Add entry'
|
|
425
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a remove -d 'Remove entry'
|
|
426
|
-
|
|
427
|
-
# memory list flags
|
|
428
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from list' -l type -d 'Filter by type' -xa 'rule knowledge decision pattern'
|
|
429
|
-
|
|
430
|
-
# memory put flags
|
|
431
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l type -d 'Entry type'
|
|
432
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l title -d 'Title'
|
|
433
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l content -d 'Content'
|
|
434
|
-
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l tags -d 'Space-separated tags'
|
|
435
|
-
|
|
436
|
-
# distill flags
|
|
437
|
-
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l yes -d 'Skip confirmation'
|
|
438
|
-
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l layer -d 'Layer filter'
|
|
439
|
-
|
|
440
|
-
# completion shells
|
|
441
|
-
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a bash -d 'Bash completions'
|
|
442
|
-
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a zsh -d 'Zsh completions'
|
|
443
|
-
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a fish -d 'Fish completions'
|
|
444
|
-
`)
|
|
445
|
-
}
|
|
356
|
+
// §11.2 交付行数债(2026-09-08——500 行硬限触碰执行):直写段逐字迁出
|
|
357
|
+
// src/completions.mjs(脚本字节不变——printCompletion 发射)
|
|
358
|
+
const { printCompletion } = await import("../src/completions.mjs")
|
|
359
|
+
printCompletion(shell)
|
|
446
360
|
break
|
|
447
361
|
}
|
|
448
362
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thincoder",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.62",
|
|
4
4
|
"description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"prepublishOnly": "npm run release:check",
|
|
36
36
|
"lint": "node scripts/check-syntax.mjs",
|
|
37
37
|
"release:check": "node scripts/release-check.mjs",
|
|
38
|
-
"test:full": "node test/run-full.mjs"
|
|
38
|
+
"test:full": "node test/run-full.mjs",
|
|
39
|
+
"test:integration": "node test/run-integration.mjs"
|
|
39
40
|
},
|
|
40
41
|
"author": "liwei <liwei@51marine.com> (上海新舶)",
|
|
41
42
|
"license": "MIT"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abort-provenance.mjs — abort 来源标注词汇表(单一权威源——AGENT-LOOP.md §20.3 第 1/2 条)
|
|
3
|
+
*
|
|
4
|
+
* 子代理 / 评审 / 会诊死亡的可诊断性:产生点标注结构化来源(`err.abortInfo`)→
|
|
5
|
+
* controller 链 hop 逐跳保 reason(§20.3 第 4 条 #10)→ 结算 / 报告面用 `deathLine`
|
|
6
|
+
* 合成死亡行(原 message 前缀逐字保留 + 来源后缀)。
|
|
7
|
+
*
|
|
8
|
+
* 纯函数、零 import——任意层可引、无环(provider / proxy / agent / settle 族共用)。
|
|
9
|
+
* 词汇表:
|
|
10
|
+
* - trigger(5 值):`user`(reason.interrupt)· `timeout`(TimeoutError / 定时器面)·
|
|
11
|
+
* `cancel`(定向中止)· `stop`(整批 / 会话 / 回合级停)· `unknown`(残留 / 未标注——告警态)。
|
|
12
|
+
* - layer(3 值):`provider` / `agent` / `settle`;未标注错误回落 `unrecorded`(合成器 token)。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** trigger 枚举权威(测试锁计数 5——AGENT-LOOP.md §20.3 第 1 条表)。 */
|
|
16
|
+
export const TRIGGERS = ["user", "timeout", "cancel", "stop", "unknown"]
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* trigger 判定(判据序 = §20.3 第 1 条表;signal.reason 四形态:
|
|
20
|
+
* ① `{interrupt:true(,message)}`(既有 user 面)② `TimeoutError`(原生——timeout 面)
|
|
21
|
+
* ③ `{abortTrigger:"cancel"|"stop"|"timeout"(,abortDetail)}`(新增——程序性中止)
|
|
22
|
+
* ④ 缺失 / 无已知键(→ unknown——诊断告警态,不得静默)。
|
|
23
|
+
*/
|
|
24
|
+
export function triggerOf(signal) {
|
|
25
|
+
const reason = signal?.reason
|
|
26
|
+
if (reason == null) return "unknown"
|
|
27
|
+
if (reason.interrupt === true) return "user"
|
|
28
|
+
if (reason.name === "TimeoutError" || reason.abortTrigger === "timeout") return "timeout"
|
|
29
|
+
if (reason.abortTrigger === "cancel") return "cancel"
|
|
30
|
+
if (reason.abortTrigger === "stop") return "stop"
|
|
31
|
+
return "unknown"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 产生点错误(abort 面):AbortError + `reason` 透传 + `abortInfo` 标注。
|
|
36
|
+
* message 取既有通用文案(既有前缀断言零回归——死亡行前缀逐字保留)。
|
|
37
|
+
*/
|
|
38
|
+
export function abortError(signal, layer, detail) {
|
|
39
|
+
const e = new DOMException("The operation was aborted", "AbortError")
|
|
40
|
+
e.reason = signal?.reason
|
|
41
|
+
e.abortInfo = { trigger: triggerOf(signal), layer, detail }
|
|
42
|
+
return e
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 产生点错误(定时器面):自有 message + `abortInfo{trigger:"timeout"}`。
|
|
47
|
+
* name 保持 "Error"(既有分类谓词——`AbortError` 分支——零变化)。
|
|
48
|
+
*/
|
|
49
|
+
export function timeoutError(message, layer, detail) {
|
|
50
|
+
const e = new Error(message)
|
|
51
|
+
e.abortInfo = { trigger: "timeout", layer, detail }
|
|
52
|
+
return e
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 外部错误补标(undici / fetch 拒否等):缺 `abortInfo` 才补(不覆盖产生点标注);
|
|
57
|
+
* 不改 `name` / `message`(消费面零回归)。
|
|
58
|
+
* 第 4 参 detail 为站点名(站点表逐站点传——自由短串)。
|
|
59
|
+
*/
|
|
60
|
+
export function annotateAbort(err, signal, layer, detail) {
|
|
61
|
+
if (err?.abortInfo) return err
|
|
62
|
+
try {
|
|
63
|
+
err.abortInfo = { trigger: triggerOf(signal), layer, detail: detail ?? "unrecorded" }
|
|
64
|
+
} catch { /* frozen error——保持未标注(合成器 unknown 告警兜底) */ }
|
|
65
|
+
return err
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 死亡行长度上限(AGENT-LOOP.md §20.3 第 3 条——超长优先截 detail)。 */
|
|
69
|
+
const MAX_LEN = 300
|
|
70
|
+
/** unknown 形态 detail token(§20.3 第 3 条逐字)。 */
|
|
71
|
+
const NO_REASON = "no reason on signal"
|
|
72
|
+
/** 未标注回落 token(layer / detail 两面兜底)。 */
|
|
73
|
+
const UNRECORDED = "unrecorded"
|
|
74
|
+
|
|
75
|
+
/** 信号域 detail 回落(§20.3 第 1 条形态③——`abortDetail` 载站点名;错误未标注时仍可辨发起动作)。 */
|
|
76
|
+
const detailOf = (signal) => (typeof signal?.reason?.abortDetail === "string" ? signal.reason.abortDetail : null)
|
|
77
|
+
|
|
78
|
+
/** 求值链(§20.3 第 1 条:err.abortInfo → triggerOf(signal) → err.name 兜底)。 */
|
|
79
|
+
function resolveAbort(err, signal) {
|
|
80
|
+
const info = err?.abortInfo
|
|
81
|
+
if (info?.trigger) return { trigger: info.trigger, layer: info.layer ?? UNRECORDED, detail: info.detail || null }
|
|
82
|
+
const fromSignal = triggerOf(signal)
|
|
83
|
+
if (fromSignal !== "unknown") return { trigger: fromSignal, layer: UNRECORDED, detail: detailOf(signal) }
|
|
84
|
+
if (err?.name === "TimeoutError") return { trigger: "timeout", layer: UNRECORDED, detail: detailOf(signal) }
|
|
85
|
+
// AbortError / 无名——告警态(unknown 显式,空后缀即违规)
|
|
86
|
+
return { trigger: "unknown", layer: UNRECORDED, detail: NO_REASON }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** detail 裁剪:优先截 detail(budget = 上限内剩余字符)。 */
|
|
90
|
+
function clipDetail(detail, budget) {
|
|
91
|
+
if (budget <= 0) return ""
|
|
92
|
+
return detail.length <= budget ? detail : detail.slice(0, Math.max(1, budget - 1)) + "…"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 死亡行合成器(§20.3 第 3 条——报告面单点):
|
|
97
|
+
* `<原 message>[ ← cause: <cause.message>][ · abort(<trigger>@<layer>:<detail>)]`
|
|
98
|
+
* - 原 message 前缀逐字保留(既有前缀 / 包含断言零回归);
|
|
99
|
+
* - 后缀出现条件 = `err.abortInfo` 存在 ∨ `signal?.aborted` ∨ `err.name ∈ {AbortError, TimeoutError}`;
|
|
100
|
+
* - cause 段(P1)独立于后缀("fetch failed" 类网络死亡真因同判);
|
|
101
|
+
* - 总长 ≤300 字符(超长先截 detail,仍超再截尾)。
|
|
102
|
+
*/
|
|
103
|
+
export function deathLine(err, signal) {
|
|
104
|
+
let line = err?.message ?? String(err ?? "")
|
|
105
|
+
if (err?.cause?.message) line += ` ← cause: ${err.cause.message}`
|
|
106
|
+
const abortish = Boolean(err?.abortInfo) || Boolean(signal?.aborted) ||
|
|
107
|
+
err?.name === "AbortError" || err?.name === "TimeoutError"
|
|
108
|
+
if (abortish) {
|
|
109
|
+
const { trigger, layer, detail } = resolveAbort(err, signal)
|
|
110
|
+
const head = `${line} · abort(${trigger}@${layer}:`
|
|
111
|
+
// unknown 形态逐字用告警 token(§20.3 第 3 条——站点 detail 不进告警行);非串 detail 归一化
|
|
112
|
+
const text = trigger === "unknown" ? NO_REASON : (detail == null ? UNRECORDED : String(detail))
|
|
113
|
+
line = `${head}${clipDetail(text, MAX_LEN - head.length - 1)})`
|
|
114
|
+
}
|
|
115
|
+
return line.length > MAX_LEN ? line.slice(0, MAX_LEN - 1) + "…" : line
|
|
116
|
+
}
|
package/src/acp/bridge.mjs
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { detectDanger, normalizeEOL, joinWithEol } from "../tools/shared.mjs"
|
|
23
23
|
import { computeEditEntry, validateEditEntry, assertEditArgsExclusive, hasLineParams } from "../tools/edit-diff.mjs"
|
|
24
|
+
// 第 27 批 §12.3①/③:relay 前缀文法单一权威(模块直连——不自持正则副本)。
|
|
25
|
+
import { parseRelayPath } from "../agent/relay-prefix.mjs"
|
|
24
26
|
|
|
25
27
|
/** ACP ToolKind inference (schema v1 enum) — best-effort, clients render by kind. */
|
|
26
28
|
function inferToolKind(name) {
|
|
@@ -167,26 +169,38 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
167
169
|
|
|
168
170
|
const callbacks = {
|
|
169
171
|
onToken: (text) => {
|
|
170
|
-
//
|
|
172
|
+
// 第 27 批 §12.3③ 行 1(R-A2.1):relay 前缀(role#id/——任意嵌套深度)零进显示面——
|
|
173
|
+
// 先剥前缀取 payload,信号判定与正文发送都作用在 payload 上。
|
|
174
|
+
const path = parseRelayPath(text)
|
|
175
|
+
const payload = path ? path.rest : text
|
|
176
|
+
// Strip the subagent `[model]` metadata token ([model]<name>) — it's a
|
|
171
177
|
// TUI/webview display signal, not conversation content, and must not reach ACP clients.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
// = §19.5 D-M7b zero-field spawn marker) — they
|
|
177
|
-
// carry RS control characters and are a TUI display signal; structured ACP
|
|
178
|
+
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
|
|
178
182
|
// mapping (tool_call_update) is tracked separately in docs/TODO.md.
|
|
179
183
|
// 有意为之:控制字符协议/转义序列剥离正则(ANSI/⟦ev⟧/SGR/history 双线分隔)
|
|
180
|
-
if (
|
|
181
|
-
|
|
184
|
+
if (/^⟦ev⟧(?:turn|approval|done|settled|stopped|async)\x1e/.test(payload)) return
|
|
185
|
+
if (!payload) return // D6:剥后空载荷不发通知(防噪声空 chunk)
|
|
186
|
+
update("agent_message_chunk", { content: { type: "text", text: payload } })
|
|
187
|
+
},
|
|
188
|
+
onReasoning: (text) => {
|
|
189
|
+
// 第 27 批 §12.3③ 行 2:同 onToken 取 payload;空载荷不发(D6)。
|
|
190
|
+
const path = parseRelayPath(text)
|
|
191
|
+
const payload = path ? path.rest : text
|
|
192
|
+
if (!payload) return
|
|
193
|
+
update("agent_thought_chunk", { content: { type: "text", text: payload } })
|
|
182
194
|
},
|
|
183
|
-
onReasoning: (text) => update("agent_thought_chunk", { content: { type: "text", text } }),
|
|
184
195
|
onUsage: (usage) => update("usage_update", { usage }),
|
|
185
196
|
onWait: ({ phase, seconds }) => log(`[rate-limit] ${phase} waiting ~${seconds}s`),
|
|
186
197
|
onCompress: () => log("[context] auto-compacted"),
|
|
187
198
|
|
|
188
199
|
onToolCall: (name, args, toolId) => {
|
|
189
200
|
const id = toolCallId()
|
|
201
|
+
// 第 27 批 §12.3③ 行 3:显示面剥前缀(title / kind);配对键 toolQueue 存原样 name(D3)。
|
|
202
|
+
const path = parseRelayPath(name)
|
|
203
|
+
const shown = path ? path.rest : name
|
|
190
204
|
// D15.8(advisor 🔴#1 修复):模型级 id 每轮重置(sse.mjs finalizeToolCalls 内
|
|
191
205
|
// seq=0——call_0 call_1… 跨轮/跨消息可重复——设计自注「跨 turn 不保证唯一」)。
|
|
192
206
|
// 因此 push 前若队列已有同名同 toolId 条目,它必是结果永不回调的陈旧孤儿
|
|
@@ -200,8 +214,8 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
200
214
|
toolQueue.push({ name, id, toolId: toolId ?? null })
|
|
201
215
|
update("tool_call", {
|
|
202
216
|
toolCallId: id,
|
|
203
|
-
title:
|
|
204
|
-
kind: inferToolKind(
|
|
217
|
+
title: shown,
|
|
218
|
+
kind: inferToolKind(shown),
|
|
205
219
|
status: "in_progress",
|
|
206
220
|
rawInput: args ?? {},
|
|
207
221
|
content: [contentBlock(JSON.stringify(args ?? {}))],
|
|
@@ -222,15 +236,19 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
222
236
|
* client. Any transport failure → reject (safety-first, kimi parity).
|
|
223
237
|
*/
|
|
224
238
|
onPermissionRequest: async (name, args) => {
|
|
239
|
+
// 第 27 批 §12.3③ 行 4:显示面(请求文本 + toolCall.title)剥前缀;
|
|
240
|
+
// 危险基名判定与 peekToolId 照旧(配对键 = 原样名——D3)。
|
|
241
|
+
const path = parseRelayPath(name)
|
|
242
|
+
const shown = path ? path.rest : name
|
|
225
243
|
// 危险命令标注(只提示不拦截):kimi 同款模式,帮助编辑器端用户审批
|
|
226
|
-
const base =
|
|
244
|
+
const base = shown.includes("/") ? shown.split("/").pop() : shown
|
|
227
245
|
const danger = base === "bash" ? detectDanger(args?.command ?? "") : undefined
|
|
228
|
-
const content = [contentBlock(`Requesting approval to run ${
|
|
246
|
+
const content = [contentBlock(`Requesting approval to run ${shown}`)]
|
|
229
247
|
if (danger) content.push(contentBlock(`⚠️ Dangerous: ${danger}`))
|
|
230
248
|
content.push(contentBlock(JSON.stringify(args ?? {})))
|
|
231
249
|
const toolCall = {
|
|
232
|
-
toolCallId: peekToolId(name) ?? toolCallId(), // D15.8:peek
|
|
233
|
-
title:
|
|
250
|
+
toolCallId: peekToolId(name) ?? toolCallId(), // D15.8:peek 不消费(原样名键)——result 仍要与自己的条目配对
|
|
251
|
+
title: shown,
|
|
234
252
|
content,
|
|
235
253
|
}
|
|
236
254
|
try {
|
|
@@ -328,7 +346,10 @@ export function replayHistory({ sessionId, notify, history, log = () => {} }) {
|
|
|
328
346
|
// later tool_call_updates by toolCallId; an orphan update would be ignored.
|
|
329
347
|
pendingToolCalls = calls.map((tc, i) => {
|
|
330
348
|
const id = `t${++toolSeq}`
|
|
331
|
-
|
|
349
|
+
// 第 27 批 §12.3③ 行 5(防御面——T18):历史工具名带前缀 → 剥;无前缀 → 零变化。
|
|
350
|
+
const raw = tc?.name ?? "tool"
|
|
351
|
+
const path = parseRelayPath(raw)
|
|
352
|
+
const title = path ? path.rest : raw
|
|
332
353
|
update("tool_call", {
|
|
333
354
|
toolCallId: id,
|
|
334
355
|
title,
|