thincoder 0.12.58 → 0.12.60
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 +78 -2
- package/README.md +3 -3
- package/bin/thincoder.mjs +88 -19
- package/package.json +4 -3
- package/src/acp/bridge.mjs +135 -26
- package/src/advisor/messages.mjs +57 -4
- package/src/advisor/run.mjs +119 -79
- package/src/advisor.mjs +34 -7
- package/src/agent/completion.mjs +17 -11
- package/src/agent/dispatch.mjs +182 -22
- package/src/agent/helpers.mjs +71 -4
- package/src/agent/record-results.mjs +46 -10
- package/src/agent/run-stages.mjs +227 -0
- package/src/agent/setup-reminders.mjs +62 -0
- package/src/agent/setup.mjs +107 -20
- package/src/agent/spawn-child.mjs +54 -4
- package/src/agent-tools/advisor-async.mjs +456 -0
- package/src/agent-tools/advisor.mjs +133 -109
- package/src/agent-tools/async-settle.mjs +191 -0
- package/src/agent-tools/consult.mjs +154 -104
- package/src/agent-tools/design-token.mjs +104 -0
- package/src/agent-tools/eng.mjs +26 -30
- package/src/agent-tools/escalate-async.mjs +286 -0
- package/src/agent-tools/goal.mjs +11 -1
- package/src/agent-tools/read-history.mjs +284 -0
- package/src/agent-tools/recent-changes.mjs +2 -1
- package/src/agent-tools/settings.mjs +152 -0
- package/src/agent-tools/skill.mjs +2 -1
- package/src/agent-tools/subagent-actions.mjs +470 -0
- package/src/agent-tools/subagent-async.mjs +382 -0
- package/src/agent-tools/subagent-panel.mjs +153 -0
- package/src/agent-tools/subagent-run.mjs +202 -0
- package/src/agent-tools/subagent-scheduler.mjs +343 -0
- package/src/agent-tools/subagent-spawn.mjs +406 -0
- package/src/agent-tools/subagent.mjs +203 -377
- package/src/agent-tools/task.mjs +4 -3
- package/src/agent-tools/timer.mjs +9 -4
- package/src/agent-tools/verify.mjs +198 -238
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +145 -242
- package/src/auto-think.mjs +14 -0
- package/src/cli/distill-command.mjs +10 -4
- package/src/cli/make-agent.mjs +4 -1
- package/src/cli/memory-command.mjs +2 -1
- package/src/cli/permission.mjs +8 -1
- package/src/cli/setup-wizard.mjs +17 -12
- package/src/config.mjs +61 -8
- package/src/context.mjs +81 -163
- package/src/crash-reports.mjs +123 -0
- package/src/distill.mjs +30 -12
- package/src/escape.mjs +6 -4
- package/src/explore-distill.mjs +155 -0
- package/src/log.mjs +195 -0
- package/src/memory/code-sync.mjs +2 -1
- package/src/memory/core.mjs +11 -72
- package/src/memory/delete.mjs +234 -0
- package/src/memory/docs.mjs +206 -87
- package/src/memory.mjs +3 -1
- package/src/model-specs.mjs +15 -1
- package/src/peer-domains.mjs +265 -0
- package/src/peer-instances.mjs +231 -0
- package/src/prompt-overlays.mjs +25 -0
- package/src/prompts/advisor-design.md +18 -39
- package/src/prompts/advisor-round1.md +20 -32
- package/src/prompts/advisor-round2.md +16 -16
- package/src/prompts/advisor-round3.md +16 -16
- package/src/prompts/coder.md +7 -28
- package/src/prompts/consult-base.md +4 -11
- package/src/prompts/discipline.md +31 -44
- package/src/prompts/eng-coder.md +9 -34
- package/src/prompts/engineering-sub.md +10 -8
- package/src/prompts/engineering.md +61 -264
- package/src/prompts/explore.md +4 -14
- package/src/prompts/main.md +18 -35
- package/src/prompts/methodology-template.md +32 -38
- package/src/prompts/plan.md +2 -9
- package/src/prompts/system.md +18 -35
- package/src/provider/core.mjs +62 -69
- package/src/provider/errors.mjs +76 -0
- package/src/provider/retry.mjs +8 -45
- package/src/session-gc.mjs +214 -0
- package/src/session-guard.mjs +47 -0
- package/src/session-rename.mjs +38 -0
- package/src/session-slots.mjs +181 -58
- package/src/session.mjs +48 -89
- package/src/token-ttl.mjs +273 -0
- package/src/tools/apply_patch.md +3 -1
- package/src/tools/bash.md +1 -1
- package/src/tools/checklist-sync.mjs +181 -0
- package/src/tools/checklist.mjs +52 -39
- package/src/tools/delete.md +1 -0
- package/src/tools/edit-batch.mjs +131 -44
- package/src/tools/edit-diff.mjs +348 -0
- package/src/tools/edit.md +20 -13
- package/src/tools/execute.md +7 -7
- package/src/tools/execute.mjs +55 -24
- package/src/tools/file.mjs +25 -70
- package/src/tools/file_ops.md +2 -1
- package/src/tools/get_current_time.md +3 -1
- package/src/tools/git.mjs +14 -6
- package/src/tools/glob-dialect.mjs +130 -0
- package/src/tools/glob.md +3 -3
- package/src/tools/grep.md +1 -1
- package/src/tools/hashline_edit.md +2 -0
- package/src/tools/index.mjs +3 -3
- package/src/tools/insert_after.md +2 -1
- package/src/tools/lint.md +2 -0
- package/src/tools/lsp.md +4 -1
- package/src/tools/ops.mjs +175 -3
- package/src/tools/patch.mjs +84 -13
- package/src/tools/question.md +5 -1
- package/src/tools/repomap.mjs +1 -1
- package/src/tools/shared.mjs +18 -25
- package/src/tools/system.mjs +50 -30
- package/src/tools/tree.md +2 -1
- package/src/tools/wait_for.md +22 -0
- package/src/tools/web.mjs +5 -3
- package/src/tools/websearch.md +2 -1
- package/src/tools/write.md +2 -0
- package/src/traces/trace-store.mjs +224 -0
- package/src/tui/agent-turn.mjs +179 -27
- package/src/tui/clipboard.mjs +15 -4
- package/src/tui/cmd-config.mjs +77 -16
- package/src/tui/cmd-eng.mjs +20 -16
- package/src/tui/cmd-extract.mjs +1 -1
- package/src/tui/cmd-mcp.mjs +17 -2
- package/src/tui/cmd-new.mjs +3 -2
- package/src/tui/cmd-session.mjs +19 -4
- package/src/tui/cmd-think.mjs +11 -11
- package/src/tui/cmd-upgrade.mjs +19 -4
- package/src/tui/config-helpers.mjs +28 -16
- package/src/tui/distill-cmd.mjs +1 -1
- package/src/tui/index.mjs +31 -96
- package/src/tui/interaction.mjs +13 -2
- package/src/tui/key-handler.mjs +105 -155
- package/src/tui/key-modes.mjs +215 -0
- package/src/tui/layout.mjs +22 -1
- package/src/tui/mouse.mjs +46 -0
- package/src/tui/pickers.mjs +51 -25
- package/src/tui/render-conversation.mjs +13 -161
- package/src/tui/render-frame.mjs +27 -10
- package/src/tui/render-loop.mjs +4 -1
- package/src/tui/render-segments.mjs +182 -0
- package/src/tui/startup.mjs +40 -0
- package/src/tui/subagent-blocks.mjs +272 -262
- package/src/tui/subagent-children.mjs +176 -0
- package/src/tui/subagent-freeze.mjs +172 -0
- package/src/tui/subagent-panel.mjs +125 -12
- package/src/tui/suspension-drive.mjs +351 -0
- package/src/tui/tool-args.mjs +10 -2
- package/src/tui/tool-display.mjs +142 -0
- package/src/tui/tool-events.mjs +127 -231
- package/src/tui/tui-lifecycle.mjs +29 -0
- package/src/tui/update-notice.mjs +76 -0
- package/src/tui/wizard.mjs +48 -12
- package/src/agent-tools/escalate.mjs +0 -179
- package/src/agent-tools/subagent-check.mjs +0 -107
- package/src/tools/exec-prelude.mjs +0 -84
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,80 @@
|
|
|
1
|
-
## [0.12.
|
|
1
|
+
## [0.12.60] — 2026-09-08
|
|
2
|
+
|
|
3
|
+
> 0.12.59 → 0.12.60(§4.5 连续号——发布时定号)
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **R17 会诊/飞刀完全异步化(AGENT-LOOP.md §25——2026-09-06/07)**:consult_check 退役(digest 自动注入闭环);escalate 入 other 池异步化(async:false 保留);三族 pending 注入/驱动推广;T-R17a..r 全绿(CLI L2 1626/1626 + VS 1301/1301)
|
|
8
|
+
- **记账路径 join→resolve 双前缀修复(R-bug——2026-09-07——AGENT-LOOP.md §24 尾修复注)**:record-results.mjs ×2 + agent.mjs ×1 `join(agent.cwd, p)` → `resolve(agent.cwd, p)`(绝对路径 p 双前缀——touchedFiles/记账错路径——修复前先验失败实拍——guards.test +12 断言)
|
|
9
|
+
- **R18 长测试输出先落盘纪律条款(AGENT-LOOP.md §26——2026-09-06 快车道)**:`src/prompts/system.md` Rules 工具纪律区插逐字条款("长输出命令先落盘"——全量/长测试(≥60s)先重定向日志再查——汇总读尾部/失败 grep——禁过滤管道直跑长命令——镜像锚逐字稿照抄 AGENT-LOOP §26.2——各端独立断言)+ 日志位置执行细则(OS 临时/~/.thincoder 类非工作区——查毕删除——防 git 工作区 untracked 污染——条款逐字不改)。锚测试 T-R18.1/T-R18.2(fail-when-unchanged)。双端各自落端(VS Code 侧并行照抄)。
|
|
10
|
+
- **R19 跨会话历史检索 + 检索族消歧(SESSION.md §13——2026-09-06)**:`read_history` 加 `path` 参数(可选——缺省 = 本会话零行为变化)——path = 会话文件路径 → 跨会话单槽深查(同 filter 面 role/keyword/tool/since/until/limit/direction 应用 + limit clamp);path = "cwd:xxx" → 发现面(列该 cwd 全部槽——每槽摘要行含槽号 + 完整文件路径 + title/消息数/updatedAt——不做死槽过滤——时间序);行扫护栏 READ_HISTORY_SCAN_MAX = 200,000 行——超限返回定稿文案 `{"error":"session too large — refine keyword or since/until"}`(不再读全文);read_history 描述尾段插族表总纲(D-R19b 逐字——checkpoint cat/versions 经核验保留——git 动作面含 versions);recent_changes/memory/doc_search/code_search 描述补互指句。测试 T-R19.1..7(含 3b/3c/3d 错误/边界子行 + 描述锚)。压缩刀锋 fixture 随 schema 增重校准(session-compaction T3b 15500 → 17000——沿革注更新)。
|
|
11
|
+
|
|
12
|
+
- **legacy v1 扩展删除集(SESSION.md §12.7——2026-09-06)**:`session gc` 冷 cwd 整前缀删除集补裸 `{hash}.json`(v1 死数据——§12.6 裁决代码同步);三处同源单集合(dry-run 报告/confirm 警告/删除共用);自动残留 GC 面零触碰。测试 T-V1a..d(含 v1 报告/删除/--all/活跃拒绝)。父侧 L2 1543/1543 全绿。
|
|
13
|
+
|
|
14
|
+
- **测试分层 Phase 3:存量清理 + 红线收窄(TESTING.md §1 D-T4/D-T5——2026-09-06——用户逐项批准删除清单)**:删除一次性迁移零残留锁 11 组 + 防回潮元测试 4 案 + 同锚多断 3 组(1492→1486,差额 -6 全对账);"断言数不减"红线收窄为拆分轮专用(system.md Module Split Policy + 双 METHODOLOGY.md 拆后两验注——清理轮改删除清单制)。测试 T-T5 收窄锚断言。**五杠杆批全链收口——总目标 AC-T4 ✓:链测试墙钟 ~4.5-5.5min → ~1.6min(降 ~70%)**。
|
|
15
|
+
|
|
16
|
+
- **测试分层 Phase 2:eng-coder 首次实现降 L0+(TESTING.md §1 D-T3——2026-09-06——supersede §18.7 D-TS1 首次实现全量 L1 口径)**:`engineering-sub.md` 验证粒度段修订——首次实现 = L0+(语法检查 + §18.12 定向相关测试,秒级),L1 收窄为显式升级档(兜底语义保留),修正轮 L0 与父侧 L2 不变;交付报告义务锚句("not full-suite verified — the parent-side L2 run is the only full-suite point."——双端逐字镜像)。AGENT-LOOP §18.7 原位 supersede 注。测试 T-T4 锚断言(fail-when-unchanged + 旧句零残留)。实测快层 18.5s 全绿 1492 用例。
|
|
17
|
+
|
|
18
|
+
- **测试分层 Phase 1:slow 门防漏拦截 + 快层归册(TESTING.md §1——2026-09-06——用户"测试已成包袱"五杠杆批之零冲突先行段)**:`test/slow-gate.mjs`(reporter 收集叶子用例耗时——未标 slow 超 800ms 拦截阈值即报错红,防快层腐化——env `THINCODER_SLOW_GATE_MS` 可覆盖)+ `test/run-fast.mjs`(npm test 新入口——spec 直通 + gate 判定非零退出 + `--test-concurrency=6`);60 条重 IO 用例归册(只加 slow() 标记,零删除零本体改动);机制自验 `test/slow-gate.test.mjs` 红/绿双证。**实测:快层 38.0s → 19.2-19.7s(≤20s 达标),test:full 1491/1491 全绿**。
|
|
19
|
+
|
|
20
|
+
- **拆分判据修辞防博弈加固(METHODOLOGY.md 2026-09-06 变更条——快车道——用户实测模型引用"才 500 多行、没 2300 行大"拒绝拆分)**:文件档升同级判定表(>500 = 必须拆——硬限、无例外、无相对比较通道——原"兜底红线"定性退役);函数档改定位 = 早期预警线、文件档 = 不可协商底线;新增"相对大小永远不是豁免"封堵句(中英镜像)+ 动机节封口句 + 反例档案新行(相对大小打折硬限实测反例);**AGENTS.md / .thincoder/advisor.md 测试文件+生成代码豁免说辞全删(用户裁定"不允许豁免"——工作区根两文件同删)**——存量 26 个 >500 测试文件登记 TODO 拆分轮。测试 T-S1/S2/S3/S5 锚断言(prompts.test.mjs——含旧修辞零残留负向断言)。
|
|
21
|
+
|
|
22
|
+
- **question 工具使用抑制(AGENT-LOOP.md §22——2026-09-06 快车道——用户实测过度提问/大段文字/一条多问)**:工具描述 Notes 补三条逐字锚(一次一问不塞子问题 / 问题文本要短、背景走正文回复 / 例行确认门走普通文本回复、question 仅留真实决策场景);提示词三文件锚(engineering.md Questioning Style 两条 + system.md 确认门形式句 + discipline.md 工具表反模式扩展);questionTool execute 机械上限(git.mjs 寄生段——question >100 字符 / options >4 条返回错误串、不弹卡不调 onQuestion——用户裁定值——guard 仅 `Array.isArray(options) && length > 4` 触发、非数组透传)。测试 T-Q1/T-Q3/T-Q4/T-Q5/T-Q6/T-Q7(git.test + prompts.test——锚 fail-when-unchanged + 超限拒绝/边界透传)。
|
|
23
|
+
|
|
24
|
+
- **async spawn 收尾引导(AGENT-LOOP.md §19.7——2026-09-06)**:subagent 工具描述 Async spawn 段尾部补收尾引导锚句("After an async spawn the turn winds down normally — nothing expects you to wait for it…"——逐字定稿于设计文档 §19.7 D-A2)——模型 async spawn 后回合自然收尾、unchecked 结果自动到达(下轮注入/挂起态 digest)、无需自发 status/check 轮询等待;锚句内容断言 fail-when-unchanged(设计锚 + 两端各自断言——防描述再漂移——D-A4)。测试 T-D-A2。
|
|
25
|
+
- **subagent check 动作删除 + 锚句收紧(AGENT-LOOP.md §19.8——2026-09-06 用户裁定"check 是冗余 API")**:工具面六动作 → 五动作(spawn/status/escalate/cancel/panel)——删 `action:"check"` + `n` 参数 + `executeCheckAction`/`MAX_ASYNC_CHECKS`/`_asyncCheckLastN` 全链路;async 子代理结果仅自动通道送达(回合尾 collect/挂起 digest——§17.5 机制零改动——consumed 终态墓碑保留——dependsOn 语义不变);Async spawn 段描述替换为 D-CH2 锚句逐字("…do not poll or wait for the result…use a synchronous spawn instead—pass `async:false`…"——fail-when-unchanged 断言);main.md/discipline.md/工具描述 check 引导句改自动送达口径;停滞检测(P-SL2)check 守卫路径删除——F-SL2 语义面迁至 status 视图(可显示停滞标记不变)。测试:check 域用例删 8 条(逐条见交付报告)+ 2 条在位改自动通道形态(T-SD14/T-H3)+ T-M16 改 status 形态 + D-A2 断言在位改 D-CH2。
|
|
26
|
+
- **工程模式 token 防伪层删除(ENGINEERING-MODE.md 2026-09-06 段——用户裁定"过度工程给我删了")**:advisor.mjs 删 DEFAULT_TOKEN_SECRET / USING_DEFAULT_SECRET / TOKEN_SECRET / warnIfDefaultSecret + 启动警告与 HMAC 签名/验签——token 回归**无签名流程凭证**(格式 uuid:expiresAt——TTL 检查 fail-closed + designId 槽位精确匹配不变);存量 3 段 token 格式判错 fail-closed 拒绝(不迁移——重评一次);NFR3/§2.3/§2.7/§2.6 文档同步。
|
|
27
|
+
- **会话"当前槽位"按端分离(R4——2026-09-05——SESSION.md §10)**:CLI 与 VS Code 各自 end marker(`{manifest}.cli|.vscode`,本端单写者)记录"本端最后使用的槽位"——恢复决策 resumeSlot(本端记录 → 无记录一次性继承 active 死主槽 → 全新分配)替代共享 manifest.active 作为恢复依据——双端同开时退出 CLI 重进不再进错会话/空白新槽;启动即钉 _slot 闭合"load→首保存"迁移窗口;/session 列表高亮按本端记录(含被删守卫)。双端同批镜像(L2:CLI 1485/1437+48skip/0 + VS Code 1196/1196 全绿)。
|
|
28
|
+
- **git 工具 add/rm/commit 多路径(2026-09-05 发版后——git add 单路径被迫 N 次调用)**:`path` 空格分隔多路径(`git add a b c`——ref 多值先例同法——split(/\s+/));path 描述尾追加注(保既有原子串断言);双端同改(CLI git.test + VS Code git.test 镜像用例——add/rm/commit 三动作多路径覆盖)。
|
|
29
|
+
- **release:check 一键发版门禁(scripts/release-check.mjs——2026-09-05 0.12.59 发版教训)**:lint + test:full 合并一键;全量输出捕获不刷屏(只打摘要);失败自动提取 failing tests 详情段(此前为看错误详情跑 3 次全量);RELEASE.md §1 发布前检查合并 + 修复迭代局部重跑纪律 + prepublishOnly 双保险注。
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- fix: 子代理块 done 后定格修复批(2026-09-07 id:14)——CLI trimSubTree done 豁免 + route* done 守卫(迟到 chunk 丢弃)
|
|
34
|
+
- fix: prompts.test 折行断言折叠化收口(2026-09-07 id:3)——双侧 \s+ 归一匹配——负守卫保持负向——锚整块/落位序断言加固
|
|
35
|
+
|
|
36
|
+
## [0.12.59] — 2026-09-05
|
|
37
|
+
|
|
38
|
+
> 0.12.58 → 0.12.59(§4.5 连续号——发布时定号)
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
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)。
|
|
43
|
+
- **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
|
+
- **P-SL2 停滞机械检测(AGENT-LOOP §21.1 扩展注 P-SL2——2026-09-05)**:混合边环形等待停滞 → check/status 守卫明确报错列阻塞链(cancel 破环引导)——防御性(自然流不可达——人工注入可构造)。测试 T-SL2 ①-⑤(scheduler 22 → 27 用例)。
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- **轨迹存档隐私默认翻转 + 启动清理(AGENT-LOOP.md §18.6 D-TR6/D-TR10——2026-09-05 用户"不希望用户那边也采集")**:`traces.enabled` 默认 **on → off**(发布后新用户零采集——本地分析可显式开);新增 `traces.retentionHours`(默认 24h——保留期可配置);CLI 启动时删除超过保留期的轨迹文件(`cleanupTraces`——D-TR10——fire-and-forget 不阻塞启动——空日期目录一并清除);`/config` 菜单新增 traces.enabled(开关)与 traces.retentionHours(保留期)两项。测试 T-TR15。
|
|
49
|
+
- **edit 空白差异自动落点(TOOLS.md §14.2——P15.11——2026-09-05 用户裁定)**:edit not-found 且文件存在**唯一内容相同仅空白不同**窗口 → 自动落点应用 + 结果附 note(`whitespace-only match`——双端同句);歧义(多窗口)/实质差异仍 not-found(不猜)。测试 P15.11a-d 双端。
|
|
50
|
+
- **编辑纪律三条固化进 discipline.md(AGENT-LOOP.md §21 扩展注 2——2026-09-05 记忆清空实验)**:① 新鲜读来源(never reconstruct from memory);② hash 来源(never invent one);③ 重试上限(never retry the identical input a third time)。测试 T-N1.8 双端。
|
|
51
|
+
- **普通模式两段式(AGENT-LOOP.md §21 F-N1.5——2026-09-05 用户"把 coder 用起来……解决自查问题")**:规模实现批次默认委托 coder 子代理(async——设计书为 task book)——执行/检查心智分离(隔离上下文破自查盲区);小改动/探索留内联;复核走 F-N1.4(偏差退回 coder ≤2 轮)。测试 T-N1.9 双端。
|
|
52
|
+
- **委托操作标准(AGENT-LOOP.md §21 扩展注 4——F-N1.6——2026-09-05 用户"spawn coder 干活现在并没有明确的标准是吗")**:规模判据可操作化(≥2 文件/单文件 >30 行逻辑/模块边界/双端镜像 → 委托;≤30 行/文档同步/探索 → 内联);任务书七字段标准(目标/已知事实/设计+禁止/约束/硬验收/报告格式/调度元数据——缺字段=委托缺陷);委托模式判据(async 默认/sync 仅依赖链/并行仅文件域互斥);通用验收基线。测试 T-N1.10 双端。
|
|
53
|
+
- **/config 交互修复(2026-09-05)**:改配置项保存后回主菜单(不再退到输入框)——每轮刷新 ac/tc 配置引用(reloadConfig 换对象后显示新值);view 浏览态同回菜单。
|
|
54
|
+
- **版本号连续性规则(RELEASE.md §4.2/§4.3——2026-09-05 用户裁定"不要跳号")**:号在发布时定、开发期不预占(CHANGELOG 挂 [Unreleased]);待发号 = registry 最高 + 1;缺口不补。
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **§18.14 域拆分 import 残留两处(2026-09-05 发版 test:full 门禁抓出——slow 门控测试快层永不执行致漏网)**:session.test.mjs 缺 existsSync + advisor-review.test.mjs 缺 prepareAdvisorMessages。
|
|
59
|
+
- **压缩 fixture 阈值重校准 14000 → 15500(T3b——2026-09-05)**:提示词批(F-N1.4/1.5/1.6 main.md 委托句 + discipline 三条纪律 ≈550 token)抬升 systemPrompt 估算越刀锋——沿革惯例 +1500 档。
|
|
60
|
+
|
|
61
|
+
### Changed (追加——2026-09-04 后半批)
|
|
62
|
+
|
|
63
|
+
- **advisor 角色定位锚(AGENT-LOOP.md §12.1)**:四模板(advisor-design/round1/round2/round3)开头插入"Your role"段(独立评审者/证据纪律/边界/中立——Known behavior 记忆断言禁止)——修复"评审对象模糊/定级拉锯/行为验证捷径/出界犹豫"人格缺口。
|
|
64
|
+
- **子代理人格锚(AGENT-LOOP.md §7.3)**:coder.md(IMPLEMENTER——证据纪律/设计冲突停报/边界)+ consult-base.md(证据纪律/I-don't-know 合法/无权威)——审计发现"职责越重人格越薄"倒挂。
|
|
65
|
+
- **审计范围引导(AGENT-LOOP.md §18.13)**:eng-coder 内部审计 explore 改 quick 档 + 任务书加机械预算句(只读 touched 文件+点名节——10 轮上限——超时报 PROBLEM)——治理"审计 explore 跑非常久"。
|
|
66
|
+
- **工具输出预览保头保尾(TOOL-OUTPUT-LIMITS-TUNING.md §5)**:超 64K 落盘预览 = 头 16K + 中间省略注 + 尾(tail 优先——测试统计/错误在尾不再被截)——与压缩/蒸馏同口径。
|
|
67
|
+
- **bash 重定向护栏删除(TOOLS.md §13)**:`hasFileRedirection` 整条删除(用户裁定"拦截只误伤正常操作"——零文本拦截与 §5 安全剧场决策彻底对齐)——`2>&1` 误报、测试收窄被拦整链消除。
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- **完整轨迹存档(AGENT-LOOP.md §18.6)**:`chat()` 出口统一收集——每次模型调用落 JSONL 到 `~/.thincoder/traces/YYYY-MM-DD/`(本地时区分日/seq=当日 max+1/脱敏复用 log.mjs 黑名单+SECRET_FORM/错误路径也落盘/`traces.enabled` 开关默认 on)——覆盖主/子代理/advisor/compress/distill/consult/auto-think 全部调用;续写标记 `isContinuation`。
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- **子代理零 git(§18.5)**:explore/plan spawn 不再注入 `<untrusted_git_context>`(全角色零 git——审计证据=设计文档+磁盘状态+_touchedFiles 并集;审计任务书附零 git 范围权威声明;explore.md 删除 git 注入声明/命令承诺,描述改为“No git context injected”);顶层主 agent git 上下文不变(§3)。
|
|
76
|
+
- **审计任务书零 git 声明**(D-AG3):`_touchedFiles` 为审计范围——工作区未列改动不作超清单依据。
|
|
77
|
+
|
|
2
78
|
|
|
3
79
|
### Changed
|
|
4
80
|
|
|
@@ -39,7 +115,7 @@
|
|
|
39
115
|
|
|
40
116
|
### Added
|
|
41
117
|
|
|
42
|
-
- **`/mcp` edit/add 统一字段 picker 表单(v2)+ agent 代配闭环**(docs/design/MCP.md §5,CLI-only):① **edit = 字段选择表单**——picker 列可编辑字段行(`HTTP URL https://…` / `Token
|
|
118
|
+
- **`/mcp` edit/add 统一字段 picker 表单(v2)+ agent 代配闭环**(docs/design/MCP.md §5,CLI-only):① **edit = 字段选择表单**——picker 列可编辑字段行(`HTTP URL https://…` / `Token ••••…` 打码 / `Headers 2 items`;stdio `Command/Args/Env`;name 不可改无行)+ 末行 `✓ Save & test`;选中字段只输入该字段新值(提示 `(current: …)`——空=不变、`-`=删可选字段、`k=`=删 header/env 项、required 字段拒绝 `-`)→ 回 picker 循环连改多字段(中间 Esc 回 picker 不丢已改值);**废除逐字段预填重问**(改 token 不再被迫路过 URL/headers);② **add 复用同一表单**——空 entry 起、必填字段 `(required)` 标注、Save 校验必填非空(未满足提示并停留表单不落盘)、headers/env 不选即跳过、add 的 name 可编辑(重复名检查);③ **保存前预览 + 探活 + 字段级重试合一**——`showPreview`(token 遮蔽)→ `probeMcpServer` 零副作用探活(`✓ N tools, Xms` / `✗ 错误`)→ `Save? (Y/n)`;探活失败 → `Save anyway? (y/N)` 显式 y,否则**回同一字段表单**只重输失败字段复 probe(**独立 retry 路径废除**,不重启流程;save-anyway 显式 y 保留);④ **表单文件拆分**——新增 `src/tui/cmd-mcp-form.mjs`(fieldPicker 机制独立文件;`cmd-mcp.mjs` 499→382 行,脱离 500 硬限压线);⑤ **列表即菜单**——主菜单 = server 行(●/○ 连接态 + tool 数)+ `+ Add server` + `↻ Refresh` + 顶部 agent 代配提示行;选中行 → per-server 子菜单(Edit/Test/Reconnect/Remove),"先选操作再选 server"双弹层与 View list 废除;⑥ **磁盘重读**——`config.mjs` 新增 `reloadMcpFromDisk()`(菜单打开边界 + Refresh):磁盘→内存仅替换 mcp 段;畸形 config.json 回退内存态 + `⚠ disk config unreadable` 提示行;disk 删除/变更的已连接 server 连接不断 + 行尾 `⚠ disk changed` 对账标记(persistRaw 落盘后重读幂等防环);⑦ AI 生成降 transport picker 末位(生成的 entry 同走预览+探活确认环,失败回表单补齐/修正);`/mcp edit|test|remove|connect <name>` 直达参数路径零改动
|
|
43
119
|
- **MCP Streamable POST 误判修复 + `/mcp edit`/`/mcp test` + token 一等字段**(docs/design/MCP.md §4,两端落地):① `httpTransport` 增 postOnly 标记——GET SSE 405 降级后的纯 POST 模式 `isAlive()` 不再因 `eventSource == null` 误判死(glm-websearch "reconnect failed after 4 attempts" 根因:降级后 isAlive 恒 false → ensureAlive 触发无意义重连循环;legacy SSE 流断仍正常 fireDead 重连不回归);② `/mcp edit [name]`:逐字段预填重问(空输入保留 / `-` 删除可选字段 / `k=` 删除单个 header 项),persistRaw 原位替换保数组序,保存后自动重连(config 指纹含 token,变更自动关旧连接);③ `/mcp test [name]`:probeMcpServer 一次性探活(initialize + tools/list 计时 → `OK — N tools, Xms` / 错误透传),零副作用(不进 session 表、不动 agent.tools、探完即关);④ token 一等字段:config 增 `token` 字段,connect 链自动合成 `Authorization: Bearer <token>`(显式 headers 优先,不写回 config);⑤ parseHeaders 改逗号分隔(`Authorization=Bearer abc, X-Foo=bar`——修复空格截断把 Bearer token 截成 "Bearer" 的缺陷);⑥ VS Code 同构:http transport 同款修复 + probeMcpServer 镜像 + 面板 [Edit]/[Test] 按钮(同一表单编辑预填,token 字段 + 逗号分隔 headers 提示)+ **面板 [Reconnect] 死按钮修复**(webview 发的 `reconnectMcp` 消息在路由拆分时丢失 case,按钮此前无效)
|
|
44
120
|
- **memory_delete 工具**(跨端):三层记忆条目删除——personal 行级删(embedding/FTS 随行)、project/team 文件级删;scope 与 id 前缀匹配校验,非法 scope 明确报错
|
|
45
121
|
|
package/README.md
CHANGED
|
@@ -15,13 +15,13 @@ 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
|
|
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.
|
|
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
23
|
- **Concurrent subagents**: three roles — `explore`/`plan`/`coder` — dispatched in parallel, streaming output visible, reports land in the conversation; per-subagent model override (`subagent` tool `model` arg or `agent.subagentModel` config — e.g. discuss with `glm-5.2`, let `deepseek-v4-flash` implement)
|
|
24
|
-
- **Multi-model consultation 会诊 + 飞刀 (escalate)** ⭐0.12.30: `consult_start`/`
|
|
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
|
|
27
27
|
- **Task tracking**: `task` tool breaks down multi-step work, status bar ✓n/m live progress, auto-filters completed items
|
|
@@ -41,7 +41,7 @@ Three layers, all "query if present, skip if absent", unified hybrid retrieval:
|
|
|
41
41
|
- **Hybrid retrieval**: FTS5 (BM25, per-character CJK indexing, bigrams matchable) + embedding vectors (brute-force cosine) + RRF(k=60) fusion ranking
|
|
42
42
|
- **Embeddings**: OpenAI-compatible `/v1/embeddings`, defaults to SiliconFlow `BAAI/bge-m3` (free tier, good CJK support); Ollama works as an offline option. Vectors generated lazily — not computed on write, backfilled and persisted on first search
|
|
43
43
|
- **Entry format**: Markdown + frontmatter (type/title/tags/author/created), readable and reviewable directly on GitHub; one file per entry, naturally avoiding merge conflicts; real conflicts produce honest errors, never auto-merged
|
|
44
|
-
- **Dual-track accumulation**: conventions written manually (`
|
|
44
|
+
- **Dual-track accumulation**: conventions written manually (the `memory` tool, action put), experience extracted from sessions via `/extract` — **the LLM proposes candidates, a human confirms each y/n** before anything is stored; never fully automatic
|
|
45
45
|
- **Retrieval isolation**: the Project layer is isolated by project path — project A's memories never leak into project B
|
|
46
46
|
|
|
47
47
|
- **Agent Client Protocol** ⭐: `thincoder acp` exposes the agent over [ACP](https://agentclientprotocol.com/) v1 on stdio — one terminal login drives sessions from **Zed**, **JetBrains** AI chat, or **Paseo**:
|
package/bin/thincoder.mjs
CHANGED
|
@@ -7,6 +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
11
|
* thincoder acp Agent Client Protocol server (stdio, for Zed/JetBrains/Paseo)
|
|
11
12
|
* thincoder -v Print version
|
|
12
13
|
* thincoder --help Print help
|
|
@@ -16,25 +17,63 @@ import { readFileSync } from "node:fs"
|
|
|
16
17
|
import { join } from "node:path"
|
|
17
18
|
import { runAgent } from "../src/agent.mjs"
|
|
18
19
|
import { loadConfig, configPath } from "../src/config.mjs"
|
|
20
|
+
import { cleanupTraces } from "../src/traces/trace-store.mjs"
|
|
19
21
|
import { createMemory, syncDir } from "../src/memory.mjs"
|
|
20
22
|
import { assembleAgent, teamConfig, gitAuthor, validateProvider } from "../src/cli/make-agent.mjs"
|
|
21
23
|
import { memoryCommand } from "../src/cli/memory-command.mjs"
|
|
22
24
|
import { setupWizard } from "../src/cli/setup-wizard.mjs"
|
|
23
25
|
import { summarize, askPermission } from "../src/cli/permission.mjs"
|
|
24
26
|
import { distillCommand } from "../src/cli/distill-command.mjs"
|
|
27
|
+
import { prepareCrashReporting, recentCrashHint, writeCrashRecord } from "../src/crash-reports.mjs"
|
|
28
|
+
import { setTuiActive, restoreTerminalAfterCrash } from "../src/tui/tui-lifecycle.mjs"
|
|
25
29
|
|
|
26
30
|
const [command, ...args] = process.argv.slice(2)
|
|
27
31
|
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version
|
|
28
32
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
// R25(F-R25b):crash-reports 预建 + process.report 启用——入口最前(一切重活前——缩编程
|
|
34
|
+
// 期窗口)——V8 OOM/原生 fatal 自动写 report.*.json(实现批实测:目录缺失时 Node 静默不写
|
|
35
|
+
// ——预建为必要动作)。失败不阻断启动(尽力面)。
|
|
36
|
+
prepareCrashReporting()
|
|
37
|
+
|
|
38
|
+
// R25(F-R25a)异常钩子升级:原"只 console.error 一行(TUI 全屏下不可见)"→ ① 落盘
|
|
39
|
+
// crash-{ts}-{pid}.json ② TUI 活动态终端恢复 ③ console.error(恢复后打印才可见)
|
|
40
|
+
// ④ exit 非 0。执行序列定死(评审 #5 + 复审 #2):一次性 guard(防再入递归)→ 同步写 →
|
|
41
|
+
// 终端恢复 → console.error → exit 非 0——各步独立 try/catch(写失败/恢复失败不阻断后续步)。
|
|
42
|
+
let crashHandled = false
|
|
43
|
+
function handleFatal(type, error) {
|
|
44
|
+
if (crashHandled) {
|
|
45
|
+
// 再入(本序列内又抛异常)→ 直接退出,不再递归
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
crashHandled = true
|
|
49
|
+
try {
|
|
50
|
+
// ① 同步写诊断记录(设计字段集:时间/类型/消息+堆栈/uptime/argv/cwd/内存/node+版本)
|
|
51
|
+
writeCrashRecord({ type, error })
|
|
52
|
+
} catch { /* 写失败不阻断后续步 */ }
|
|
53
|
+
try {
|
|
54
|
+
// ② TUI 终端恢复:仅 TUI 活动态执行(writeCleanupSequence 同源——chat 模式 stdout
|
|
55
|
+
// 管道不得收 ANSI——误判即污染输出)
|
|
56
|
+
restoreTerminalAfterCrash()
|
|
57
|
+
} catch { /* 恢复失败不阻断后续步 */ }
|
|
58
|
+
try {
|
|
59
|
+
// ③ 原 console.error 行保留(非 TUI 模式可见——恢复后打印才可见)
|
|
60
|
+
console.error(`[error] ${error?.message ?? error}`)
|
|
61
|
+
} catch { /* 打印失败不阻断 exit */ }
|
|
62
|
+
// ④ exit 非 0 恒达(exitSoon——Windows/Node 24 上 fetch 后立即 exit 触发 libuv 断言先例)
|
|
36
63
|
exitSoon(1)
|
|
37
|
-
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Top-level safety net: print one-line error and exit cleanly, no stack traces to the user
|
|
67
|
+
process.on("uncaughtException", (error) => handleFatal("uncaughtException", error))
|
|
68
|
+
process.on("unhandledRejection", (error) => handleFatal("unhandledRejection", error))
|
|
69
|
+
|
|
70
|
+
// R25 测试门(T-R25a.1/a.2——子进程 env 注入——生产零路径):THINCODER_TEST_CRASH=1 抛
|
|
71
|
+
// 未捕获异常走完整崩溃序列;THINCODER_TEST_TUI_ACTIVE=1 模拟 TUI 活动态(同一 setter——
|
|
72
|
+
// 测试缝同源);THINCODER_TEST_CLEANUP_OUT 指向文件时恢复序列写入该文件(tui-lifecycle 读)。
|
|
73
|
+
if (process.env.THINCODER_TEST_CRASH === "1") {
|
|
74
|
+
if (process.env.THINCODER_TEST_TUI_ACTIVE === "1") setTuiActive(true)
|
|
75
|
+
throw new Error("R25 test crash (THINCODER_TEST_CRASH)")
|
|
76
|
+
}
|
|
38
77
|
|
|
39
78
|
const USAGE = `thincoder - thin coding agent
|
|
40
79
|
|
|
@@ -48,9 +87,11 @@ Usage:
|
|
|
48
87
|
thincoder memory remove <id> Remove an entry
|
|
49
88
|
thincoder sync Sync team memory repo (pull --rebase + reindex)
|
|
50
89
|
thincoder reindex Rebuild the local index from markdown sources
|
|
51
|
-
thincoder distill <file> [--yes] [--
|
|
90
|
+
thincoder distill <file> [--yes] [--layer=<s>]
|
|
52
91
|
Extract knowledge candidates from a session
|
|
53
92
|
transcript file; confirm each before saving
|
|
93
|
+
thincoder session gc --dry-run | --confirm <hash|--all>
|
|
94
|
+
Session dir GC: report/delete cold project data (cold = manifest idle >90d, no live slots)
|
|
54
95
|
thincoder upgrade Update to the latest version from npm
|
|
55
96
|
thincoder completion <sh> Generate shell completion script (bash / zsh / fish)
|
|
56
97
|
thincoder -v, --version Print version
|
|
@@ -68,6 +109,13 @@ function exitSoon(code) {
|
|
|
68
109
|
setTimeout(() => process.exit(code), 100)
|
|
69
110
|
}
|
|
70
111
|
|
|
112
|
+
// D-TR9(2026-09-05):启动轨迹清理——删除超过 traces.retentionHours(默认 24h)的
|
|
113
|
+
// 轨迹文件(fire-and-forget——不阻塞启动——失败静默——与轨迹写盘同纪律)。
|
|
114
|
+
try {
|
|
115
|
+
const startupCfg = loadConfig()
|
|
116
|
+
cleanupTraces({ retentionHours: startupCfg.traces?.retentionHours ?? 24 }).catch(() => {})
|
|
117
|
+
} catch { /* 配置缺失/损坏 → 跳过清理(零风险) */ }
|
|
118
|
+
|
|
71
119
|
switch (command) {
|
|
72
120
|
case "chat": {
|
|
73
121
|
const auto = args.includes("--auto")
|
|
@@ -78,6 +126,10 @@ switch (command) {
|
|
|
78
126
|
break
|
|
79
127
|
}
|
|
80
128
|
|
|
129
|
+
// R25(F-R25c):非交互/chat 模式——上次异常终止提示写 stderr 一行(无匹配不提示)
|
|
130
|
+
const crashNotice = recentCrashHint()
|
|
131
|
+
if (crashNotice) console.error(crashNotice)
|
|
132
|
+
|
|
81
133
|
const agent = await assembleAgent()
|
|
82
134
|
// SESSION.md §8 D-S4(F4):headless 无 TUI —— 可读错误 + 退出码 1,不弹 UI、不崩溃
|
|
83
135
|
if (agent._providerInvalid) {
|
|
@@ -231,10 +283,12 @@ switch (command) {
|
|
|
231
283
|
if (agent._providerInvalid) agent.provider = null
|
|
232
284
|
const config = loadConfig()
|
|
233
285
|
// 恢复上次的会话(同一项目目录);provider 按保存的名字切回(用户上次可能换过模型)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
286
|
+
// 2026-09-05 §10(R4):恢复决策按本端记录 resumeSlot(D-2 ①②③)——manifest active
|
|
287
|
+
// 只作"无记录端"的一次性继承源,不再作本端恢复第一依据(D-6)。
|
|
288
|
+
const { resumeSlot, applySession } = await import("../src/session.mjs")
|
|
289
|
+
const { slot, data } = resumeSlot(process.cwd())
|
|
290
|
+
if (data) {
|
|
291
|
+
const switched = applySession(agent, data)
|
|
238
292
|
if (switched && agent.config?.agent?.compactThresholdAuto) {
|
|
239
293
|
// 压缩阈值跟模型走(与 TUI 切换 provider 时的处理一致);传 provider 对象——
|
|
240
294
|
// providers[].context 覆盖生效(PROVIDER.md §15 T-C2)
|
|
@@ -242,6 +296,10 @@ switch (command) {
|
|
|
242
296
|
agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
|
|
243
297
|
}
|
|
244
298
|
}
|
|
299
|
+
// D-3 钉槽:applySession 清 _slot 后立即钉回恢复槽——首保存必落恢复槽(消除
|
|
300
|
+
// "load → 首回合保存"窗口内并发方翻 active 导致的静默迁移);/new 经 resetSessionState
|
|
301
|
+
// 清 _slot(newSession 已认领 + 写记录,语义保留)
|
|
302
|
+
agent._slot = slot
|
|
245
303
|
// D-S3 优先级补全:applySession 可能已用会话中的有效 provider 修复(config 无效 + 会话有效)——
|
|
246
304
|
// 修复后复验清除标记,仅当两者都无效才弹重选(validateProvider 幂等)
|
|
247
305
|
if (agent._providerInvalid) validateProvider(agent)
|
|
@@ -260,7 +318,9 @@ switch (command) {
|
|
|
260
318
|
projectDir: config.memory.projectDir ? join(process.cwd(), config.memory.projectDir) : null,
|
|
261
319
|
team: teamConfig(config),
|
|
262
320
|
author: gitAuthor(),
|
|
263
|
-
restored,
|
|
321
|
+
restored: data,
|
|
322
|
+
// R25(F-R25c):TUI 启动显示一行"上次运行异常终止"提示(showStartup 渲染——无匹配不传)
|
|
323
|
+
crashNotice: recentCrashHint() ?? undefined,
|
|
264
324
|
})
|
|
265
325
|
} catch (error) {
|
|
266
326
|
console.error(`[error] ${error.message}`)
|
|
@@ -290,10 +350,10 @@ switch (command) {
|
|
|
290
350
|
list) COMPREPLY=( \\$(compgen -W "--type=rule --type=knowledge --type=decision --type=pattern" -- "\\$cur") ) ;;
|
|
291
351
|
put) COMPREPLY=( \\$(compgen -W "--type= --title= --content= --tags=" -- "\\$cur") ) ;;
|
|
292
352
|
esac ;;
|
|
293
|
-
distill) COMPREPLY=( \\$(compgen -W "--yes --
|
|
353
|
+
distill) COMPREPLY=( \\$(compgen -W "--yes --layer=" -- "\\$cur") ) ;;
|
|
294
354
|
completion) COMPREPLY=( \\$(compgen -W "bash zsh fish" -- "\\$cur") ) ;;
|
|
295
355
|
*)
|
|
296
|
-
COMPREPLY=( \\$(compgen -W "chat acp memory sync reindex distill upgrade completion -v --version -h --help" -- "\\$cur") ) ;;
|
|
356
|
+
COMPREPLY=( \\$(compgen -W "chat acp memory sync reindex distill upgrade completion session -v --version -h --help" -- "\\$cur") ) ;;
|
|
297
357
|
esac
|
|
298
358
|
}
|
|
299
359
|
complete -F _thincoder thincoder
|
|
@@ -318,7 +378,8 @@ _thincoder() {
|
|
|
318
378
|
'reindex[Rebuild local index from markdown]' \\
|
|
319
379
|
'distill[Extract knowledge from session transcript]' \\
|
|
320
380
|
'upgrade[Update to latest version from npm]' \\
|
|
321
|
-
'completion[Generate shell completion script]'
|
|
381
|
+
'completion[Generate shell completion script]' \\
|
|
382
|
+
'session[Session dir GC: session gc --dry-run|--confirm]'
|
|
322
383
|
;;
|
|
323
384
|
args)
|
|
324
385
|
case "\\$words[1]" in
|
|
@@ -328,7 +389,7 @@ _thincoder() {
|
|
|
328
389
|
list) _arguments '--type=[Filter by type]' ;;
|
|
329
390
|
put) _arguments '--type=[Entry type]' '--title=[Title]' '--content=[Content]' '--tags=[Space-separated tags]' ;;
|
|
330
391
|
esac ;;
|
|
331
|
-
distill) _arguments '--yes[Skip confirmation]' '--
|
|
392
|
+
distill) _arguments '--yes[Skip confirmation]' '--layer=[Layer filter]' ;;
|
|
332
393
|
completion) _values 'shell' 'bash' 'zsh' 'fish' ;;
|
|
333
394
|
esac ;;
|
|
334
395
|
esac
|
|
@@ -347,6 +408,7 @@ complete -c thincoder -a reindex -d 'Rebuild local index from markdown'
|
|
|
347
408
|
complete -c thincoder -a distill -d 'Extract knowledge from session'
|
|
348
409
|
complete -c thincoder -a upgrade -d 'Update to latest version'
|
|
349
410
|
complete -c thincoder -a completion -d 'Shell completion'
|
|
411
|
+
complete -c thincoder -a session -d 'Session dir GC (session gc --dry-run|--confirm)'
|
|
350
412
|
complete -c thincoder -a acp -d 'Agent Client Protocol server for IDEs'
|
|
351
413
|
|
|
352
414
|
# Flags
|
|
@@ -373,7 +435,7 @@ complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_su
|
|
|
373
435
|
|
|
374
436
|
# distill flags
|
|
375
437
|
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l yes -d 'Skip confirmation'
|
|
376
|
-
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l
|
|
438
|
+
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l layer -d 'Layer filter'
|
|
377
439
|
|
|
378
440
|
# completion shells
|
|
379
441
|
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a bash -d 'Bash completions'
|
|
@@ -411,6 +473,13 @@ complete -c thincoder -n '__fish_seen_subcommand_from completion' -a fish -d 'Fi
|
|
|
411
473
|
break
|
|
412
474
|
}
|
|
413
475
|
|
|
476
|
+
case "session": {
|
|
477
|
+
// SESSION.md §12:会话目录 GC 手动面(F2 冷 cwd 报告/删除——VS Code 端无 shell 通道,仅 CLI)
|
|
478
|
+
const { runSessionGc } = await import("../src/session-gc.mjs")
|
|
479
|
+
process.exitCode = runSessionGc(args)
|
|
480
|
+
break
|
|
481
|
+
}
|
|
482
|
+
|
|
414
483
|
case "--help":
|
|
415
484
|
case "-h": {
|
|
416
485
|
process.stdout.write(USAGE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thincoder",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.60",
|
|
4
4
|
"description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
"url": "git+https://github.com/xinbo-tech/thincoder.git"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"test": "node
|
|
35
|
-
"prepublishOnly": "npm run
|
|
34
|
+
"test": "node test/run-fast.mjs",
|
|
35
|
+
"prepublishOnly": "npm run release:check",
|
|
36
36
|
"lint": "node scripts/check-syntax.mjs",
|
|
37
|
+
"release:check": "node scripts/release-check.mjs",
|
|
37
38
|
"test:full": "node test/run-full.mjs"
|
|
38
39
|
},
|
|
39
40
|
"author": "liwei <liwei@51marine.com> (上海新舶)",
|
package/src/acp/bridge.mjs
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
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 {
|
|
23
|
-
import {
|
|
22
|
+
import { detectDanger, normalizeEOL, joinWithEol } from "../tools/shared.mjs"
|
|
23
|
+
import { computeEditEntry, validateEditEntry, assertEditArgsExclusive, hasLineParams } from "../tools/edit-diff.mjs"
|
|
24
24
|
|
|
25
25
|
/** ACP ToolKind inference (schema v1 enum) — best-effort, clients render by kind. */
|
|
26
26
|
function inferToolKind(name) {
|
|
@@ -56,25 +56,128 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
56
56
|
const update = (sessionUpdate, extra = {}) =>
|
|
57
57
|
notify("session/update", { sessionId, update: { sessionUpdate, ...extra } })
|
|
58
58
|
let toolSeq = 0
|
|
59
|
-
|
|
59
|
+
// D15.8(TOOLS.md §15.1):tool id FIFO 队列——并行同名工具按 call 序配对(dispatch B1
|
|
60
|
+
// 已测:并行结果回调顺序 = call 顺序——T-TS8/T-TS9)。取代旧 Map 按名覆盖(后写覆盖先写
|
|
61
|
+
// → tool_call_update 与 tool_call id 错配)。条目 { name, id, toolId }——toolId = 模型级
|
|
62
|
+
// toolCall.id(dispatch 在 onToolCall/onToolResult 均传第 3 参——同一 item 恒相同)。
|
|
63
|
+
// 拒绝/中断路径:dispatch 在 onToolCall 之前拒绝(被拒工具从未入队——无孤儿可滞);
|
|
64
|
+
// 中断/异常路径(onToolResult 永不回调——dispatch.mjs catch 分支——T-F5 契约)留下的
|
|
65
|
+
// 孤儿靠 onToolCall 的「同名同 toolId 先弹出」隔离(见 onToolCall)——模型级 id 跨轮
|
|
66
|
+
// 可重复(sse.mjs 每轮从 call_0 重置)——弹出保证精确配对恒命中最新条目。
|
|
67
|
+
const toolQueue = [] // FIFO of pending { name, id, toolId }
|
|
60
68
|
|
|
61
69
|
const toolCallId = () => `t${++toolSeq}`
|
|
70
|
+
/** D15.8:peek 同名最早项 id——权限面板展示用——不消费(result 仍要与自己的条目配对)。 */
|
|
71
|
+
const peekToolId = (name) => {
|
|
72
|
+
for (const e of toolQueue) if (e.name === name) return e.id
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
/** D15.8:消费——①模型级 toolId 精确配对(中断孤儿隔离)②无 id/未命中 → 名称 FIFO 回退
|
|
76
|
+
* (B1 保序)③均未命中 → null(调用方回退新 id——防御)。 */
|
|
77
|
+
const takeToolId = (name, toolId) => {
|
|
78
|
+
if (toolId != null) {
|
|
79
|
+
const i = toolQueue.findIndex((e) => e.name === name && e.toolId === toolId)
|
|
80
|
+
if (i >= 0) return toolQueue.splice(i, 1)[0].id
|
|
81
|
+
}
|
|
82
|
+
for (let i = 0; i < toolQueue.length; i++) {
|
|
83
|
+
if (toolQueue[i].name === name) return toolQueue.splice(i, 1)[0].id
|
|
84
|
+
}
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
const contentBlock = (text) => ({ type: "content", content: { type: "text", text } })
|
|
63
89
|
const pathOf = (args) => {
|
|
64
90
|
const p = args?.path ?? args?.filePath
|
|
65
91
|
return typeof p === "string" && p ? p : null
|
|
66
92
|
}
|
|
67
93
|
|
|
94
|
+
// §15.1(TOOLS.md)D15.7 委派:edit 判定/应用单一权威 = 本地 computeEditEntry
|
|
95
|
+
// (edit-diff.mjs——校验→判定序→应用:行级 LCS、零重叠→替换即删、replace_all 字面替换全部)。
|
|
96
|
+
// 桥只留「读 IDE 缓冲 → computeEditEntry → 写回 IDE 缓冲」——错误文本经抛错原样透传
|
|
97
|
+
// ——与本地通道逐字一致(NF15.6b / AC15.10:not found / occurrences / 空 old / 空 new)。
|
|
98
|
+
const EDIT_ABORT_PREFIX = "edit aborted (atomic — no files written): "
|
|
99
|
+
|
|
100
|
+
const readBuffer = async (p) => {
|
|
101
|
+
try {
|
|
102
|
+
const read = await request("fs/read_text_file", { sessionId, path: p }, { timeoutMs: 30000 })
|
|
103
|
+
return read?.text ?? read?.content ?? ""
|
|
104
|
+
} catch (e) {
|
|
105
|
+
throw new Error(`fs/read_text_file failed: ${e.message}`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const writeBuffer = async (p, content) => {
|
|
109
|
+
try {
|
|
110
|
+
await request("fs/write_text_file", { sessionId, path: p, content }, { timeoutMs: 30000 })
|
|
111
|
+
} catch (e) {
|
|
112
|
+
throw new Error(`fs/write_text_file failed: ${e.message}`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** 单形态:读 IDE 缓冲 → computeEditEntry(rich——无 abortPrefix——同本地 runSingleEdit)
|
|
116
|
+
* → 写回。EOL 权威(F1):判定/应用在 normalizeEOL 后的 LF 域;写回 joinWithEol 按原文
|
|
117
|
+
* 首换行恢复(LF 域判定——CRLF 域写回——与本地 edit 工具同判同恢复)。 */
|
|
118
|
+
const editSingle = async (p, args) => {
|
|
119
|
+
const raw = await readBuffer(p)
|
|
120
|
+
const content = normalizeEOL(raw)
|
|
121
|
+
const out = computeEditEntry(content, args, { path: p })
|
|
122
|
+
await writeBuffer(p, joinWithEol(normalizeEOL(out.updated).split("\n"), raw))
|
|
123
|
+
return `OK: edited ${p} via IDE (${out.occurrences} occurrence(s))${out.note ? ` — ${out.note}` : ""}`
|
|
124
|
+
}
|
|
125
|
+
/** 数组形态(D15.7):条目校验(path——顶层默认自 args.path ?? args.filePath(pathOf)——
|
|
126
|
+
* 2026-09-05 用户裁定 CLI parity——/validateEditEntry/互斥(只对顶层 old/new)——同本地 edit-batch 措辞)→
|
|
127
|
+
* 读全部涉及文件缓冲(同文件去重——一次读)→ 逐条 computeEditEntry(abortPrefix——批量
|
|
128
|
+
* 原子前缀;同文件条目按数组序串行累积——第二条基于第一条结果)→ 全部通过 → 逐文件写回
|
|
129
|
+
* 一次(判失败 → 零写;写失败 → 同本地 edit-batch 既有原子语义)。 */
|
|
130
|
+
const editBatch = async (args) => {
|
|
131
|
+
const edits = args.edits
|
|
132
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
133
|
+
throw new Error("edits must be a non-empty array of {path, old_string | line/startLine+endLine, new_string}")
|
|
134
|
+
}
|
|
135
|
+
assertEditArgsExclusive(args)
|
|
136
|
+
const groups = new Map() // path → { path, raw, content, edits }
|
|
137
|
+
for (const e of edits) {
|
|
138
|
+
// 2026-09-05 用户裁定(CLI parity——本地 edit-batch 同句):条目自带 path 优先;
|
|
139
|
+
// 缺省回退顶层 path(pathOf——path/filePath 别名同单形态)
|
|
140
|
+
const p = e.path ?? pathOf(args)
|
|
141
|
+
if (!p) throw new Error("each edit must have a path — give each entry its own path or pass a top-level path")
|
|
142
|
+
validateEditEntry(e, { label: `edit for ${p}: `, rich: false })
|
|
143
|
+
let g = groups.get(p)
|
|
144
|
+
if (!g) {
|
|
145
|
+
g = { path: p, raw: "", content: "", edits: [] }
|
|
146
|
+
groups.set(p, g)
|
|
147
|
+
}
|
|
148
|
+
g.edits.push(e)
|
|
149
|
+
}
|
|
150
|
+
for (const g of groups.values()) {
|
|
151
|
+
g.raw = await readBuffer(g.path)
|
|
152
|
+
g.content = normalizeEOL(g.raw)
|
|
153
|
+
}
|
|
154
|
+
const outcomes = []
|
|
155
|
+
for (const g of groups.values()) {
|
|
156
|
+
for (const e of g.edits) {
|
|
157
|
+
const out = computeEditEntry(g.content, e, { path: g.path, abortPrefix: EDIT_ABORT_PREFIX })
|
|
158
|
+
outcomes.push({ g, out })
|
|
159
|
+
g.content = out.updated // 同文件串行累积
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const g of groups.values()) {
|
|
163
|
+
await writeBuffer(g.path, joinWithEol(normalizeEOL(g.content).split("\n"), g.raw))
|
|
164
|
+
}
|
|
165
|
+
return outcomes.map((o) => `OK: edited ${o.g.path} via IDE (${o.out.occurrences} occurrence(s))${o.out.note ? ` — ${o.out.note}` : ""}`).join("\n")
|
|
166
|
+
}
|
|
167
|
+
|
|
68
168
|
const callbacks = {
|
|
69
169
|
onToken: (text) => {
|
|
70
170
|
// Strip the subagent `[model]` metadata token (role#id/[model]<name>) — it's a
|
|
71
171
|
// TUI/webview display signal, not conversation content, and must not reach ACP clients.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
172
|
+
// §19.5 D-M8 (round2 #6): nested prefixes recurse — eng-coder#2/explore#1/[model]…
|
|
173
|
+
if (/^(?:[\w-]+#\d+\/)*\[model\]/.test(text)) return
|
|
174
|
+
// D7 (AGENT-LOOP.md §7.2 + §19.5 round2 #6 + D-M7b): strip ⟦ev⟧ event tokens (bare or
|
|
175
|
+
// any-depth prefixed variants — turn/approval/done/settled/stopped/async — async
|
|
176
|
+
// = §19.5 D-M7b zero-field spawn marker) — they
|
|
177
|
+
// carry RS control characters and are a TUI display signal; structured ACP
|
|
75
178
|
// mapping (tool_call_update) is tracked separately in docs/TODO.md.
|
|
76
|
-
|
|
77
|
-
if (/^(?:[\w-]+#\d+\/)
|
|
179
|
+
// 有意为之:控制字符协议/转义序列剥离正则(ANSI/⟦ev⟧/SGR/history 双线分隔)
|
|
180
|
+
if (/^(?:[\w-]+#\d+\/)*⟦ev⟧(?:turn|approval|done|settled|stopped|async)\x1e/.test(text)) return
|
|
78
181
|
update("agent_message_chunk", { content: { type: "text", text } })
|
|
79
182
|
},
|
|
80
183
|
onReasoning: (text) => update("agent_thought_chunk", { content: { type: "text", text } }),
|
|
@@ -82,9 +185,19 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
82
185
|
onWait: ({ phase, seconds }) => log(`[rate-limit] ${phase} waiting ~${seconds}s`),
|
|
83
186
|
onCompress: () => log("[context] auto-compacted"),
|
|
84
187
|
|
|
85
|
-
onToolCall: (name, args) => {
|
|
188
|
+
onToolCall: (name, args, toolId) => {
|
|
86
189
|
const id = toolCallId()
|
|
87
|
-
|
|
190
|
+
// D15.8(advisor 🔴#1 修复):模型级 id 每轮重置(sse.mjs finalizeToolCalls 内
|
|
191
|
+
// seq=0——call_0 call_1… 跨轮/跨消息可重复——设计自注「跨 turn 不保证唯一」)。
|
|
192
|
+
// 因此 push 前若队列已有同名同 toolId 条目,它必是结果永不回调的陈旧孤儿
|
|
193
|
+
// (dispatch 失败/中断路径不调 onToolResult——T-F5 契约)——先弹出再入队——
|
|
194
|
+
// 精确配对恒命中最新——"下个同名结果永不配到旧项"(设计目标,无需动 dispatch)。
|
|
195
|
+
if (toolId != null) {
|
|
196
|
+
for (let i = toolQueue.length - 1; i >= 0; i--) {
|
|
197
|
+
if (toolQueue[i].name === name && toolQueue[i].toolId === toolId) toolQueue.splice(i, 1)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
toolQueue.push({ name, id, toolId: toolId ?? null })
|
|
88
201
|
update("tool_call", {
|
|
89
202
|
toolCallId: id,
|
|
90
203
|
title: name,
|
|
@@ -95,9 +208,8 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
95
208
|
})
|
|
96
209
|
},
|
|
97
210
|
|
|
98
|
-
onToolResult: (name, result) => {
|
|
99
|
-
const id =
|
|
100
|
-
toolIds.delete(name)
|
|
211
|
+
onToolResult: (name, result, toolId) => {
|
|
212
|
+
const id = takeToolId(name, toolId) ?? toolCallId()
|
|
101
213
|
update("tool_call_update", {
|
|
102
214
|
toolCallId: id,
|
|
103
215
|
status: "completed",
|
|
@@ -117,7 +229,7 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
117
229
|
if (danger) content.push(contentBlock(`⚠️ Dangerous: ${danger}`))
|
|
118
230
|
content.push(contentBlock(JSON.stringify(args ?? {})))
|
|
119
231
|
const toolCall = {
|
|
120
|
-
toolCallId:
|
|
232
|
+
toolCallId: peekToolId(name) ?? toolCallId(), // D15.8:peek 不消费——result 仍要与自己的条目配对
|
|
121
233
|
title: name,
|
|
122
234
|
content,
|
|
123
235
|
}
|
|
@@ -137,7 +249,8 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
137
249
|
/**
|
|
138
250
|
* fs reverse-RPC router (dispatch.mjs toolRouter, M2):
|
|
139
251
|
* - write → fs/write_text_file (full content, no read-back)
|
|
140
|
-
* - edit → fs/read_text_file →
|
|
252
|
+
* - edit → fs/read_text_file → computeEditEntry(本地权威——单/数组形态)→ fs/write_text_file
|
|
253
|
+
* (§15.1 D15.7 委派——双通道同语义;数组=原子批量——逐条目串行累积)
|
|
141
254
|
* - apply_patch → local (unified-diff application is not routed in M2)
|
|
142
255
|
* - delete, reads → local
|
|
143
256
|
*/
|
|
@@ -156,19 +269,15 @@ export function buildAcpCallbacks({ sessionId, notify, request, log = () => {} }
|
|
|
156
269
|
return { handled: true, result: `Error: fs/write_text_file failed: ${e.message}` }
|
|
157
270
|
}
|
|
158
271
|
}
|
|
159
|
-
|
|
272
|
+
// 2026-09-08 D1:单形态按行号改(path + line/startLine/endLine + new_string——无
|
|
273
|
+
// old_string)同样走 IDE 缓冲通道(hasLineParams 判定)——否则回落本地写盘会与
|
|
274
|
+
// IDE 缓冲脱敏;editSingle → computeEditEntry 行号语义自动继承。
|
|
275
|
+
if (base === "edit" && (Array.isArray(args?.edits) || (path && typeof args?.new_string === "string" && (typeof args?.old_string === "string" || hasLineParams(args))))) {
|
|
160
276
|
try {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
const idx = current.indexOf(args.old_string)
|
|
164
|
-
if (idx === -1) {
|
|
165
|
-
return { handled: true, result: `Error: old_string not found in ${path} (read via IDE buffer)` }
|
|
166
|
-
}
|
|
167
|
-
const next = current.slice(0, idx) + args.new_string + current.slice(idx + args.old_string.length)
|
|
168
|
-
await request("fs/write_text_file", { sessionId, path, content: next }, { timeoutMs: 30000 })
|
|
169
|
-
return { handled: true, result: `OK: edited ${path} via IDE (1 replacement)` }
|
|
277
|
+
const text = Array.isArray(args?.edits) ? await editBatch(args) : await editSingle(path, args)
|
|
278
|
+
return { handled: true, result: text }
|
|
170
279
|
} catch (e) {
|
|
171
|
-
return { handled: true, result: `Error:
|
|
280
|
+
return { handled: true, result: `Error: ${e.message}` }
|
|
172
281
|
}
|
|
173
282
|
}
|
|
174
283
|
return { handled: false } // read-only tools, delete, apply_patch stay local
|