thincoder 0.12.60 → 0.12.61
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 +24 -1
- package/README.md +8 -6
- package/bin/thincoder.mjs +27 -122
- package/package.json +3 -2
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +38 -17
- package/src/acp.mjs +6 -1
- 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 +21 -6
- package/src/agent/setup-reminders.mjs +16 -9
- package/src/agent/setup.mjs +92 -128
- package/src/agent/spawn-child.mjs +26 -9
- 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/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +23 -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 +16 -13
- package/src/agent-tools/read-history.mjs +13 -3
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +130 -17
- package/src/agent-tools/subagent-actions.mjs +15 -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 +9 -6
- package/src/agent-tools/subagent-scheduler.mjs +57 -8
- package/src/agent-tools/subagent-spawn.mjs +63 -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 +38 -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/conventions.mjs +223 -0
- package/src/crash-reports.mjs +7 -2
- package/src/expand-home.mjs +16 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +70 -31
- package/src/memory/delete.mjs +2 -0
- package/src/memory/docs.mjs +10 -6
- package/src/memory/file-walk.mjs +109 -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 +18 -8
- package/src/prompts/advisor-round1.md +7 -1
- package/src/prompts/advisor-round2.md +13 -2
- package/src/prompts/advisor-round3.md +13 -2
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +2 -0
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -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 +55 -0
- package/src/prompts/persona-engineering.md +54 -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-slots.mjs +7 -2
- package/src/session.mjs +59 -38
- 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/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-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-reindex.mjs +7 -0
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/index.mjs +38 -37
- 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/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 +32 -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/subagent-blocks.mjs +69 -90
- package/src/tui/subagent-children.mjs +50 -64
- package/src/tui/subagent-freeze.mjs +40 -43
- package/src/tui/subagent-panel.mjs +21 -66
- package/src/tui/suspension-drive.mjs +28 -82
- package/src/tui/tool-args.mjs +4 -2
- package/src/tui/tool-display.mjs +4 -3
- package/src/tui/tool-events.mjs +23 -16
- package/src/tui/tui-lifecycle.mjs +18 -5
- package/src/tui/wizard.mjs +52 -18
- package/src/tui/wrapped-spawn.mjs +38 -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,26 @@
|
|
|
1
|
+
## [0.12.61] — 2026-09-11
|
|
2
|
+
|
|
3
|
+
> 0.12.60 → 0.12.61(§4.3 连续号——发布时定号)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **集成集 + 发布门三环(TEST-LIFECYCLE 批)**:`test/integration/`(7 场景 22 用例:普通模式工具流 / 工程模式全链 / 子代理生命周期 / 会话恢复 / TUI 基本盘 / commit 验证关口 / 配置与 provider 选路)+ `test/run-integration.mjs`;`npm run release:check` = lint → test:full → test:integration(prepublishOnly 接线)——发布唯一门禁。
|
|
8
|
+
- **需求池机检 `scripts/check-ledger.mjs`**(FR18 收拢):指针解析 / D3 组计数 / 六态 / 触发字段 / 老化报告 / 基线降报告;两仓台账收拢(活 30+9 · 归档档 40+11)。
|
|
9
|
+
- **provider.headers 全通路铺开**(Gitee #IKDWH7)· **`~` 家目录展开**(HOME-EXPANSION)· **websearch provider key**(工具面)。
|
|
10
|
+
- **DeepSeek V4.1-Flash 接入** + 模型清单 provider 化(去候选否决权)。
|
|
11
|
+
- **工程模式链路**:角色重定义(FR9 三分 + 内容权)/ 公共层扩容(common.md 10 节·双端 18 档)/ 批次档段写入工具 / 可移植性 FR10–FR15(项目约定去硬编码:`conventions.mjs` + `project-context.mjs`)。
|
|
12
|
+
- **逃生与可观测面**:子代理 abort 来源标注 · 评审池丢弃(报告不达提醒 + 不签发 token)· consult/escalate 完全异步化 · `read_history` 跨会话检索(R19)。
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **测试生命周期 v3**:三层来源(单元=开发期脚手架 / 集成-业务 / 集成-生产)+ 退役常态 + 削段合档(11 档退役 ~880 行 · 3 并 · 防回潮静态锚 T75/T76 收归)。
|
|
17
|
+
- **台账体系**:TODO 归档机制(活文件只留未决 + `TODO-archive.md`)+ 归属修订「记录 + 状态推进 + 物理落笔 = 主 agent」+ 六态机检。
|
|
18
|
+
- **TUI**:选择面统一(picker/wizard/question)· 输入修复集 · 方向键(连续上溯/多行竖移)· Stop 钩子 · 跨段 turn 累计 · attention 状态。
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- settings 工具 null 默认键类型校验 · ACP 通道修整(headless 裁剪 + relay 前缀剥离)· 评审注入路径硬编码(可移植性 A 家族)· advisor 上下文预算(CJK 加权)· 文档一致性检查扩面 + 宽度基线。
|
|
23
|
+
|
|
1
24
|
## [0.12.60] — 2026-09-08
|
|
2
25
|
|
|
3
26
|
> 0.12.59 → 0.12.60(§4.5 连续号——发布时定号)
|
|
@@ -39,7 +62,7 @@
|
|
|
39
62
|
|
|
40
63
|
### Added
|
|
41
64
|
|
|
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
|
|
65
|
+
- **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
66
|
- **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
67
|
- **P-SL2 停滞机械检测(AGENT-LOOP §21.1 扩展注 P-SL2——2026-09-05)**:混合边环形等待停滞 → check/status 守卫明确报错列阻塞链(cancel 破环引导)——防御性(自然流不可达——人工注入可构造)。测试 T-SL2 ①-⑤(scheduler 22 → 27 用例)。
|
|
45
68
|
|
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,8 +26,15 @@ 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 启用——入口最前(一切重活前——缩编程
|
|
@@ -96,7 +103,7 @@ Usage:
|
|
|
96
103
|
thincoder completion <sh> Generate shell completion script (bash / zsh / fish)
|
|
97
104
|
thincoder -v, --version Print version
|
|
98
105
|
|
|
99
|
-
Config: ~/.thincoder/config.json
|
|
106
|
+
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
107
|
`
|
|
101
108
|
|
|
102
109
|
/** Unified message when no API key is configured */
|
|
@@ -131,10 +138,11 @@ switch (command) {
|
|
|
131
138
|
if (crashNotice) console.error(crashNotice)
|
|
132
139
|
|
|
133
140
|
const agent = await assembleAgent()
|
|
134
|
-
// SESSION.md §8 D-S4(F4
|
|
141
|
+
// SESSION.md §8 D-S4(F4)+ MODEL-MERGE-SESSION:headless 无 TUI —— 可读错误 + 退出码 1,
|
|
142
|
+
// 不弹 UI、不崩溃;文案改引 defaultModel(F-6 引导 A headless 面——/config → 默认模型)
|
|
135
143
|
if (agent._providerInvalid) {
|
|
136
|
-
const
|
|
137
|
-
console.error(`[error]
|
|
144
|
+
const dm = agent.config?.defaultModel
|
|
145
|
+
console.error(`[error] 未配置有效模型(defaultModel "${dm ?? "(not set)"}":${agent._providerInvalidReason})。请运行 thincoder 进入 TUI 设置(/config → 默认模型;或 /model 选择后以会话槽生效),或编辑 ${configPath}`)
|
|
138
146
|
exitSoon(1)
|
|
139
147
|
break
|
|
140
148
|
}
|
|
@@ -216,7 +224,7 @@ switch (command) {
|
|
|
216
224
|
memory.embedder = createEmbedder(config.embedding)
|
|
217
225
|
}
|
|
218
226
|
if (config.memory.projectDir) {
|
|
219
|
-
memory.projectOrigin = join(process.cwd(), config.memory.projectDir)
|
|
227
|
+
memory.projectOrigin = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)
|
|
220
228
|
}
|
|
221
229
|
const code = await memoryCommand(memory, args)
|
|
222
230
|
if (code) exitSoon(code)
|
|
@@ -261,7 +269,7 @@ switch (command) {
|
|
|
261
269
|
const cwd = process.cwd()
|
|
262
270
|
let total = { added: 0, removed: 0 }
|
|
263
271
|
if (config.memory.projectDir) {
|
|
264
|
-
const s = await syncDir(memory, { layer: "project", dir: join(cwd, config.memory.projectDir) })
|
|
272
|
+
const s = await syncDir(memory, { layer: "project", dir: isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(cwd, config.memory.projectDir) })
|
|
265
273
|
total.added += s.added
|
|
266
274
|
}
|
|
267
275
|
const team = teamConfig(config)
|
|
@@ -288,13 +296,13 @@ switch (command) {
|
|
|
288
296
|
const { resumeSlot, applySession } = await import("../src/session.mjs")
|
|
289
297
|
const { slot, data } = resumeSlot(process.cwd())
|
|
290
298
|
if (data) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
299
|
+
// applySession 内部已按槽复合重算 compactThreshold(auto 时)——不再需要 switched 分支
|
|
300
|
+
applySession(agent, data)
|
|
301
|
+
// SESSION.md §11.2(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
|
|
302
|
+
// 信号——仅本启动 resume 路径设一次(真进程重启恢复盘上会话);/session 切换与 ACP
|
|
303
|
+
// 加载走同一 applySession 收敛但不设(不误报进程重启)。prepareRun 发句即清——进程
|
|
304
|
+
// 内一次。resumed:yes 独立由 applySession 的恢复事件武装(data.history 非空)。
|
|
305
|
+
agent._processRestartPending = true
|
|
298
306
|
}
|
|
299
307
|
// D-3 钉槽:applySession 清 _slot 后立即钉回恢复槽——首保存必落恢复槽(消除
|
|
300
308
|
// "load → 首回合保存"窗口内并发方翻 active 导致的静默迁移);/new 经 resetSessionState
|
|
@@ -315,7 +323,7 @@ switch (command) {
|
|
|
315
323
|
const { startTUI } = await import("../src/tui.mjs")
|
|
316
324
|
try {
|
|
317
325
|
await startTUI(agent, {
|
|
318
|
-
projectDir: config.memory.projectDir ? join(process.cwd(), config.memory.projectDir) : null,
|
|
326
|
+
projectDir: config.memory.projectDir ? (isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)) : null,
|
|
319
327
|
team: teamConfig(config),
|
|
320
328
|
author: gitAuthor(),
|
|
321
329
|
restored: data,
|
|
@@ -336,113 +344,10 @@ switch (command) {
|
|
|
336
344
|
exitSoon(1)
|
|
337
345
|
break
|
|
338
346
|
}
|
|
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
|
-
}
|
|
347
|
+
// §11.2 交付行数债(2026-09-08——500 行硬限触碰执行):直写段逐字迁出
|
|
348
|
+
// src/completions.mjs(脚本字节不变——printCompletion 发射)
|
|
349
|
+
const { printCompletion } = await import("../src/completions.mjs")
|
|
350
|
+
printCompletion(shell)
|
|
446
351
|
break
|
|
447
352
|
}
|
|
448
353
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thincoder",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.61",
|
|
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,
|
package/src/acp.mjs
CHANGED
|
@@ -71,12 +71,17 @@ export function defaultIsConfigured() {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/** 第 27 批 §12.3⑤(R-A1.1):ACP 会话装配期剔除的工具。ACP 是 headless 通道——
|
|
75
|
+
* question 工具需要交互 UI(`src/tools/question.mjs` 无 `ctx.onQuestion` 即 throw),
|
|
76
|
+
* 模型可见即会调用、调用必错 → 装配期剔除(schema 直接少一条)。 */
|
|
77
|
+
export const ACP_EXCLUDED_TOOLS = ["question"]
|
|
78
|
+
|
|
74
79
|
/** M1/M2 session factory: one agent per session, built from the process cwd (single-cwd model).
|
|
75
80
|
* `id` is the ACP session id — it is baked into the callbacks at construction time
|
|
76
81
|
* (buildAcpCallbacks closure), so it must be known BEFORE createAcpSession runs.
|
|
77
82
|
* `request` is the transport's reverse-RPC channel (permissions + fs routing). */
|
|
78
83
|
export async function defaultCreateSession({ id, notify, request, log }) {
|
|
79
|
-
const agent = await assembleAgent()
|
|
84
|
+
const agent = await assembleAgent({ excludeTools: ACP_EXCLUDED_TOOLS })
|
|
80
85
|
return createAcpSession({ id, agent, notify, request, log })
|
|
81
86
|
}
|
|
82
87
|
|