thincoder 0.12.38 → 0.12.40

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 ADDED
@@ -0,0 +1,311 @@
1
+ # Changelog
2
+
3
+ 本文件记录 ThinCoder CLI 的发布历史。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),版本遵循[语义化版本](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## [0.12.40] — 2026-08-23
6
+
7
+ ### Fixed
8
+
9
+ - **`shrinkOversized` 数据丢失**:原地改共享消息对象会污染持久化的人读线(巨型粘贴内容被永久截断)——改为复制-on-write(机器线替换、人读线不动)
10
+ - 轮末探索蒸馏边界只在压缩**真重建**时重置(shrink 路径不再误重置)
11
+ - `createAgent` 补 `_emptyRetries` 初始化;删 advisor 分支无用 JSON.parse
12
+
13
+ ## [0.12.39] — 2026-08-23
14
+
15
+ ### Added
16
+
17
+ - **主 agent 委托策略**:`main.md` 把「广度探索 → explore 子代理(隔离上下文,逐步读/搜不进主历史)」「仅当即将立刻编辑时才自己 read」「coder 验证 = 读改动文件 + 跑测试」从建议升级为明确规则
18
+ - **历史卫生**:轮末 `summarizeRunExplorations` 把一轮内连续探索结果(read/grep/glob/ls/code_search/doc_search/repo_outline)LLM 蒸馏为 `[Exploration summary]`(机器线收缩、人读线全量不变);压缩 `SUMMARIZE_PROMPT` 加「已改动文件清单 + 未决点/待办」两清单
19
+ - **编码纪律**:`discipline.md` 工作流程 + 调试策略要求用 `task` 跟踪;「改码前读文档」「中/小改后更新文档」嵌入 Workflow 各 tier 箭头序列
20
+
21
+ ### Fixed
22
+
23
+ - 轮末探索摘要边界 `_runStartHistoryLen` 在压缩重建机器线后变 stale → 重置到 tail 起点
24
+
25
+ ## [0.12.38] — 2026-08-23
26
+
27
+ ### Added
28
+
29
+ - **对齐 thinworker 编程工具集**:新增 `file_ops`(move/copy/rename)、`process`(列进程)、`get_current_time`、`sleep` 四个内置工具,及 `tree`(递归目录树 + `depth`,对齐 thinworker `repomap`);各工具描述带「Route to X instead of bash」反向路由
30
+ - **内部能力对齐 thinworker**:`grep` 加 `literal`/`ignoreCase`;`ls` 加 `filter`(通配符);`bash` 加 `filter`(正则行过滤);`git` 加 `show`/`rm`/`commit`/`push` 子命令 + `filter`;`verify` 加 `filter`/`workdir`
31
+ - **`execute` 工具重做**:由同步 vm 沙箱改为 `node --input-type=module --eval` 子进程——支持顶层 `await`、动态 `import()`(直接加载项目 `.mjs`)、原生 `console`/`fetch`,新增 `workdir`/`filter` 参数,保留 killable 超时(无限循环可被终止);`exec-prelude.mjs` 提供 readFile/writeFile/glob/grep/log/require(路径隔离 workspace)
32
+ - **consult 指定子集模型**:`consult_start` 新增 `models` 选择器(`provider:model`/裸 provider/裸 model,大小写不敏感、去重保序)
33
+ - **模型规格更新**:新增 glm-5.3、deepseek-v4-flash-vision-exp;gpt-5.6/claude-5 收敛 `thinking=false`
34
+
35
+ ### Fixed
36
+
37
+ - **apply_patch 多 hunk 错位**:同一文件多个 hunks 因前一段改变行数而错位应用、静默损坏——改为重扫上下文 + splice
38
+ - **git 写操作吞错**:`commit`/`push`/`rm` 改用 `runGitStrict` 返回 stderr + exit code,失败不再伪装成功;`show` 补 ref 校验(防 `-` 开头注入选项)
39
+ - **tool 补齐项加固**:`get_current_time` 补 weekday;`sleep` 防 NaN;`tree` 深度/计数校验、省略号单次、根目录报错;`log` 非法 count 回退默认
40
+
41
+ ### Docs
42
+
43
+ - `TOOLS.md` 注册表计数同步(含 ops/tree)
44
+ - Changelog backfill 0.12.36 / 0.12.33
45
+
46
+ ## [0.12.37] — 2026-08-22
47
+
48
+ ### Fixed
49
+
50
+ - **advisor 标签还原**:状态栏徽标与 /advisor 菜单项由 "GUARD" 显示名还原为 "ADVISOR"(内部配置字段 `advisor.guard` 不变,仅显示名)
51
+ - **SSE 流式 tool_calls 防御性合并**(PROVIDER.md §10):跳过 null/畸形元素并计数,按 index/id/name/tail 合并、补齐缺失 id、计数丢弃
52
+ - **Windows 剪贴板 BOM 防御**:UTF-8 输出后剥离前导 `\uFEFF`(IK9UWM 后续)
53
+ - **上下文压缩空安全**:`t.function?.name` 防 tool name 缺失
54
+
55
+ ### Refactor
56
+
57
+ - **session.mjs 拆分**:`migrateHashLength` 抽到 `session-migrate.mjs`,文件回到 ≤500 行硬限内
58
+ - **/config embedding 保存重构**:`embeddingPatch()` 抽取 + `DEFAULTS` 导出(供单测)
59
+
60
+ ### Prompt
61
+
62
+ - **确认纪律 carve-out**:system.md 补 doc/code 一致性例外——"改动前确认"门禁不适用于已确认任务的既有义务(文档跟码一致、记录刚做的决策、关闭 advisor 标记的文档缺口)
63
+
64
+ ## [0.12.36] — 2026-08-22
65
+
66
+ ### Docs & advisor
67
+
68
+ - **文档归属纪律 + advisor 设计评审增强**(规格 AGENT-LOOP.md §12):新建 `docs/design/README.md` 文档地图(板块→文档映射表 + 存量碎片"待合并(TODO)"标注 + 归属规则);system.md 补文档归属纪律条款(写文档前先查地图定位所属板块——找到就改、不得为既有板块新建文件;确无归属才新建并登记;同一机制只在一处详述权威源、其余引用不复制);advisor-design.md 加第 7 维 **Document ownership**(与现有文档矛盾 🔴、该并入却新建/重复描述 🟡)与引用纪律(引用原文用精确 file:line、未核实标注 unverified);design 提示词 fallback 删除转硬加载(`loadPrompt` 同 round1/2/3 待遇,缺失即抛错——静默降级会丢 Approval Signal 规则致评审无法批准);messages.mjs design 分支 Instructions 补 Methodology compliance 维度、存在文档地图时注入 Document Map 段供归属检查对照。两端 prompts 保持 byte-identical、测试同步覆盖
69
+
70
+ ## [0.12.35] — 2026-08-21
71
+
72
+ ### Changed
73
+
74
+ - **advisor 开关语义重构**:评审能力恒启用——`advisor` 工具任何模式都可调用(删除 `advisor.enabled` gate,不再返回 "not enabled");开关语义收敛为 guard——收尾推回仅当 `advisor.guard === true`(默认 OFF,评审自愿调用,打开才强制)。工程模式行为不变(评审恒可用、guard 豁免)
75
+ - **`advisor.enabled` 废弃**:字段不再读写,存量配置不迁移——旧 `{ enabled: true }` 用户升级后不再强制评审(pre-release 约定,CHANGELOG 说明即可);/advisor 菜单删除 "Advisor ON/OFF" toggle,Guard 成为唯一开关
76
+
77
+ ### Prompt system
78
+
79
+ - **提示词借鉴增量(kimi-code 对照)**:explore.md 新增 Thoroughness levels 三档(quick 单点定向 / medium 默认适度并行 / thorough 全面分析且报告须列出搜索过什么与没找到什么);main.md Delegate well 补委派 explore 时在 task 描述中指定彻底度(未指定走默认);system.md 确认理解句补 "including the most important acceptance criteria";subagent 工具 description 同步补彻底度说明。两端 15 个 prompt 文件保持 byte-identical(新增 CLI 侧比对测试防漂移)
80
+ - **开工前计划确认纪律**:system.md 追加无豁免纪律——任何写文件动作(write/edit/apply_patch/insert_after/delete/hashline_edit 及一切写文件的 bash)前必须纯文字复述理解+计划要点并等待用户明确确认(未确认/沉默/用户回复新问题或新要求 → 一律不动手;"这太明显了不用问"不是跳过理由;用户的新问题不是确认;需求变化后重新复述重新确认);engineering.md 澄清完成后、写需求/设计文档前同样须把理解+计划文字化并等待确认。两端 15 个 prompt 文件保持 byte-identical(两端测试断言关键句)
81
+
82
+ ## [0.12.34] — 2026-08-18
83
+
84
+ ### Added
85
+
86
+ - **/rename 命令** — 改会话标题(renameSlot 双写,与 VS Code 共享)
87
+
88
+ ### Fixed
89
+
90
+ - **/config 候选池 effort picker 显示真实枚举** — 从固定 min/low/medium/high/max 改为动态读 specForModel(model).reasoningEffortEnum;无枚举的模型跳过 effort 步
91
+ - **question 工具 options 防御** — LLM 误传对象时取 label 字段,避免渲染 [object Object]
92
+
93
+ ## [0.12.33] — 2026-08-17
94
+
95
+ ### Changed
96
+
97
+ - **撞轮数墙可无限继续**:subagent/飞刀/会诊统一经 continue 面板无限续(resume 保留 history,会诊继续重置墙钟;去掉 MAX_RESUMES)
98
+ - **MiMo 预置 provider**:按量付费(api.xiaomimimo.com/v1) + Token Plan(token-plan-cn.xiaomimimo.com/v1),模型规格 mimo-v2.5-pro/mimo-v2.5(1M 上下文 / 128K 输出 / 深度思考)
99
+ - **环境变量配置源彻底移除**:THINCODER_* 回退全部删除,config.json 唯一配置源;空配置不再合成 deepseek 默认 provider
100
+ - **effort 枚举钳制 + qwen3.8-max spec 修正**(consult/escalate)
101
+
102
+ ## [0.12.32] — 2026-08-16
103
+
104
+ ### Fixed
105
+
106
+ - **会诊触发条款重构**(两轮会诊驱动的修复):触发规则从飞刀段移入会诊段且自包含——功能请求语义("会诊一下"触发、"consult the docs"不触发)+ 用户请求覆盖自主判断;consult_start 描述补对称触发句;飞刀段补 fly-in/口语变体
107
+ - **飞刀三个真实断链**(会诊发现,此前"代码在但真实跑必翻车"):
108
+ - 删墙钟看门狗——固定墙钟误杀正常但慢的手术(实测两个 max-effort 顾问读 5 个文件即撞 10min 墙);完全依赖 turns + FETCH_TIMEOUT + 用户 Stop 直传
109
+ - effort 枚举钳制——池 effort 越界不再让候选"起飞即死",回退预设并标注
110
+ - AUTO 传导对齐 subagent——headless 嵌入下父 autoApprove 正确放行子 agent 写操作
111
+ - **config 加载校验**:consultModels 池 provider 名必须存在于 providers[],条目形状校验——静默运行时失败改为启动即报错
112
+ - **撞墙可继续(kimi-k3 飞刀)**:escalate 子 agent 撞 turn 上限后弹"继续?"(复用 onPermissionRequest,TUI 同款 y/n 面板),resume:true 续跑不重复注入任务、预算重置,上限 2 次;顺带修复 ContinueError e.turns → e.turn(原来打印 "undefined turns")
113
+ - consult 死代码补 precheck(无 key 时明确失败回复而非原始 401)
114
+
115
+ ### Docs
116
+
117
+ - CLI CONSULTATION.md / ESCALATE.md 文档地图收录、FEATURES.md 功能全览补齐(7→11 个)、README 会诊别名、checklist T2-T6 验收
118
+
119
+ ## [0.12.31] — 2026-08-16
120
+
121
+ ### Fixed
122
+
123
+ - **/config consult model picker**: adding a consult model now uses pickModelForSlot (provider AND model are both OPTION pickers, reusing /model's async-fetched model list) — was free-text for the model name
124
+ - **Prompt adaptation**: CLI main.md was missing the consult (会诊) + escalate (飞刀) sections — the CLI main agent did not know "会诊" meant consult_start. Ported both + added the 会诊 alias to consult_start's tool description
125
+
126
+ ### Prompt system
127
+
128
+ - Attention optimization + cross-end consistency: split over-long sentences, fixed an escalate-timing contradiction (up-front ability judgment), unified Review discipline + advisor rounds — all 15 prompt files byte-identical with the plugin
129
+
130
+ ### Docs
131
+
132
+ - CONSULTATION.md + ESCALATE.md design docs (CLI implementation differences vs the plugin)
133
+
134
+ ## [0.12.30] — 2026-08-16
135
+
136
+ ### New: 会诊 (consult) + 飞刀 (escalate) — full VS Code plugin parity
137
+
138
+ - `consult_start` / `consult_check` / `consult_stop`: several configured models run as parallel independent read-only consultants — each with its own TUI activity card, `main_history` access to the failure trail, arrival-order reply queue, stopped/terminated settle states, wall-clock watchdog
139
+ - `escalate`: fly in a stronger model for one expert implementation run — coder role, full write path, permission gate, mutations merge into the parent's verify/advisor guards, turn-cap reads as partial work, timeout reads as timeout
140
+ - Config: `agent.consultModels` ([{ provider, model, effort? }], up to 5, validated), `agent.consultTurns` (40), `agent.consultTimeoutMs` (600000)
141
+ - **/config now manages the consult pool**: list / add / remove models; per-model reasoning-effort is an OPTION picker (none/min/low/medium/high/max); consultTurns + consultTimeoutMs (entered in minutes)
142
+
143
+ ### Discipline
144
+
145
+ - UI rule added: fixed-choice values must be OPTIONS (picker/menu), never free-text — free-text only for genuinely open-ended input
146
+
147
+ ## [0.12.29] — 2026-08-16
148
+
149
+ ### Fixed
150
+
151
+ - **Coder sub-agents (subagent role=coder) get verify + advisor** — CLI parity with the plugin's escalate diagnosis: the system prompt names both tools but the tool table only gave them to eng-coder; a coder sub-agent hit unknown-tool and self-verified via bash
152
+ - **Cache-audit follow-ups**: OS/cwd reminder injected once per process (was every run); interrupt-resume now re-grounds the time (was stuck on the pre-interrupt time); skills scan sorted deterministically (filesystem-dependent readdir order could byte-change the system prompt with zero content change)
153
+
154
+ ## [0.12.28] — 2026-08-16
155
+
156
+ ### Cache-hit-rate fix (user-reported low hit rate on session start)
157
+
158
+ - **Machine line (contextHistory) now keeps transient messages on persist** — every CLI invocation is a new process; the previous reals-only reload plus fresh re-injections (git/OS/outline/doc/memory/time) diverged at index ~1 → whole-prefix cache miss on the first request of every session. Resume now rebuilds a byte-identical machine line; new injections append at the tail
159
+ - Time reminder moved to the END of the message sequence (after the user input) — aligned with the plugin fix, robust against any future machine-line disk reload
160
+
161
+ ### Fixed
162
+
163
+ - normalizeToolPairing early-return hole: toolById empty must not skip placeholder filling when assistant tool_calls are declared (dangling tool_calls 400 otherwise)
164
+
165
+ ## [0.12.27] — 2026-08-15
166
+
167
+ - Time injection moved OUT of the system prompt into a transient per-run user reminder — system prompts fully static again (prefix caches hit across hours, not minutes); local time + IANA timezone at second precision; now covers ALL agent depths (subagents previously had no time grounding at all)
168
+
169
+ ## [0.12.26] — 2026-08-15
170
+
171
+ ### ACP extensions for thincoder-desktop (proposals ①②③④, all implemented)
172
+
173
+ - **① Session persistence**: every ACP turn end (success/cancel/failure — finally semantics) writes the session archive via saveSession; session/list / load / resume now have a real data source. Save is injectable and failures never break the queue
174
+ - **② Checkpoints**: checkpoint/create / checkpoint/list / checkpoint/restore ACP handlers; NON-git cwds now snapshot by full-directory copy (v2 layout, nongit meta) instead of silently returning null
175
+ - **③ Memory**: memory/list / memory/remove ACP handlers over the shared ~/.thincoder store
176
+ - **④ Custom provider headers**: provider.headers object in config.json merges into every LLM request (chat + /models); Authorization cannot be overridden; non-string values sanitized out
177
+
178
+ ### Fixed
179
+
180
+ - Time injection vs prefix-cache conflict: system-prompt "Current time" is now MINUTE precision — byte-identical within the same minute so DeepSeek prefix caches still hit (was: seconds precision broke the cache every run)
181
+
182
+ ## [0.12.25] — 2026-08-15
183
+
184
+ - Local time + timezone injected into every system prompt (main agent, subagents, advisor) — prepareRun appends `Current time: <local> (<IANA zone>)`; sessionStart was ISO/UTC and session-scoped, subagents had nothing
185
+ - bash.md Windows guidance corrected: the shell is cmd.exe (NOT Git Bash) — &&/|| work, cmd built-ins, NUL not /dev/null, prefer node -e for complex logic
186
+
187
+ ## [0.12.24] — 2026-08-14
188
+
189
+ ### Added
190
+
191
+ - **glm-code provider preset** — the Zhipu GLM Coding Plan endpoint (`https://open.bigmodel.cn/api/coding/paas/v4`, glm-5.2, same key as GLM; server-side forced thinking).
192
+
193
+ ### Fixed
194
+
195
+ - **Model specs synced with official vendor docs (verified 2026-08)** — DeepSeek v4 duals effort enum +low and cacheMode→auto; qwen3.x/max/plus maxOutput→131072 (qwen-plus was 32K).
196
+ - **Retired models dropped** — deepseek-chat/reasoner, kimi-k2, moonshot v1 (vendor shutdowns; unknown IDs fall back to the 128K default spec).
197
+
198
+ ### Changed
199
+
200
+ - Repository URL → github.com/xinbo-tech/thincoder.
201
+
202
+ ## [0.12.23] - 2026-08-13
203
+
204
+ - **修复** svg 图片毒化会话——read_image 读 svg 后以 image_url 进历史,Kimi 等视觉 API(全部仅支持位图)此后每轮请求 400 "unsupported image format",会话永久卡死;现在发送时按格式净化:非 png/jpeg/gif/webp 的 image part 替换为占位文本,净化上移至 format dispatch 之前覆盖 openai/anthropic/google 全部通路,历史本身不改写(切回支持的模型/格式可恢复)
205
+ - **改进** read_image 对 svg 返回文本源码(svg 本是文本标记,任何模型可读,绕过 vision gate);bmp 拒绝并提示转 PNG(无主流视觉 API 支持)
206
+
207
+ ## [0.12.22] - 2026-08-13
208
+
209
+ - **修复** 缓存命中率对 Kimi 显示——usage 缓存字段归一化:Kimi/OpenAI 风格 `prompt_tokens_details.cached_tokens` 映射为 DeepSeek 风格 `prompt_cache_hit_tokens`,miss 由 prompt_tokens − hit 推导(此前 Kimi 的命中率永不显示)
210
+ - **安全** fetch 重定向目标做 SSRF 检查——3xx 可把公网 URL 跳进内网(重定向绕过);相对 URL 正确解析、仅 http/https、私网/元数据拦截
211
+ - **新增** Tavily 结构化搜索(可选)——config 配 `websearch.apiKey` 后 `websearch` 走 Tavily API(稳定 JSON,不再依赖 Bing 页面结构);无 key 回退 Bing 抓取,零门槛不破坏
212
+
213
+ ## [0.12.21] - 2026-08-13
214
+
215
+ - **修复** 恢复会话大量重复 "❯ ThinCoder:" 标签——history 按每次 LLM 调用存一条 assistant 消息(一个 turn 多段),恢复时每段都渲染了标签;现在只在 turn 开始渲染一次,跨页懒加载边界状态正确保留
216
+ - **改进** 恢复保真度:完整工具结果(不再一行摘要)+ reasoning 思考流以 dim 行恢复(超长自动折叠)——恢复后的会话与退出前基本一致;首帧渲染实测约 50ms
217
+
218
+ ## [0.12.20] - 2026-08-13
219
+
220
+ - **修复** TUI 恢复旧 display 快照导致"看不到最新消息"——display 字段彻底废弃(saveSession 不再写、loadSession 不再读),恢复永远从 history 重建;配合 VS Code 端 0.1.5 的清空,跨端会话漂移根治
221
+ - **新增** TUI 懒加载历史恢复:启动只物化最近 200 条消息(8000+ 条会话不再冻结启动),PgUp 到顶按 50 条/页加载更早历史,scroll 补偿保持视觉位置
222
+ - **新增** question 选项列表末尾追加"✍ Custom answer…"——选中切自由输入,用户可补充/修正 AI 的预设选项
223
+ - **重构** execute 工具移除假沙箱:require()/process 全可用(bash 本就能触达任意 Node API,拦 require 只会误导模型);移除动态 import 拦截与 SSRF 私网拒绝;保留 timeout / cwd 约束 / 输出上限等工程保护
224
+ - **改进** 工程模式 prompt:新增提问风格指引(默认开放式自由文本,选项仅用于有限枚举);审查修复 5 处(需求优先步骤、designToken 仅走参数、用户审批呈现 advisor 发现、澄清完成判据、advisor 重试 3 轮上限)
225
+
226
+ ## [0.12.19] - 2026-08-11
227
+
228
+ - **重构** bash 工具安全模型:移除全部破坏性命令文本拦截(rm -rf / DROP TABLE 等)——文本匹配是安全剧场(恶意模型可绕过、误伤正常操作),真实防线 = 审批层(autoApprove)+ 快照(gitGuardSnapshot / checkpoint),与 env 透传、git"快照后放行永不拦截"统一
229
+ - **新增** 危险命令标注(detectDanger,参考 kimi-code):recursive-delete / sudo / pipe-to-shell / dd / mkfs / 裸设备 / chmod 777 / fork bomb 在 TUI 与 ACP 审批面板红色标注——只提示不拦截,帮人做审批决策;引号感知(commit message 等纯文本不误标,反引号内容保留)
230
+ - **文档** TOOLS.md 安全边界同步(零文本拦截 + 危险标注)
231
+
232
+ ## [0.12.18] - 2026-08-10
233
+
234
+ - **修复** Qwen 路由等代理的模型 ID(如 `kimi/kimi-k3`)跳过 `reasoning_effort` 参数——路由可能误处理该参数导致空回复
235
+
236
+ ## [0.12.17] - 2026-08-10
237
+
238
+ - **修复** `kimi/kimi-k3`(Qwen 路由前缀 ID)模型规格匹配 — 正确继承 kimi-k3 的 1M 上下文 / 131K 输出 / 多模态
239
+
240
+ ## [0.12.16] - 2026-08-10
241
+
242
+ - **修复** 非 SSE JSON 响应被误判为错误(HTTP 200 + tool_calls 的合法 JSON 响应现在正确解析,而非报 "Response is not SSE")
243
+ - **改进** API 错误信息可读性:非 SSE 错误响应包含 HTTP 状态码 + 具体错误原因
244
+
245
+ ## [0.12.15] - 2026-08-10
246
+
247
+ - **改进** API 错误信息可读性:非 SSE 响应预拦截,提取 HTTP 状态码 + 具体错误原因(替代原来模糊的 "Response is not SSE")
248
+
249
+ ## [0.12.14] - 2026-08-10
250
+
251
+ - **修复** 小终端 permission 面板挤出输入框(layout 溢出补偿新增 permission 压缩)
252
+ - **修复** iTerm2 Ctrl+C 键盘协议序列泄漏(stdin 剥离未处理的 CSI u 序列)
253
+ - **修复** eng 模式 advisor token 正则错配(改用完整 token 构建正则,与 prompt 格式一致)
254
+ - **修复** 输入框 ↑ 键历史导航草稿丢失(进入/编辑历史模式时扩容草稿保护)
255
+ - **重构** key-handler 拆分搜索模块(key-handler-search.mjs)
256
+ - **文档** 架构文档计数/模块/状态同步更新
257
+
258
+ ## [0.12.13] - 2026-08-08
259
+
260
+ 评审机制全面重构(用户驱动的三轮决策):
261
+
262
+ - **prior 硬解析移除**:收敛轮注入上一轮评审的完整原文(模型直接理解),删除表头匹配与 all-clear 短语两类"字符串解析 LLM 输出"的脆弱机制
263
+ - **评审触发范围收缩**:评审只跟代码修改绑定——bash/git 等副作用工具不再触发多余评审轮(评审后读日志/清理临时文件不再要求重复评审)
264
+ - **AGENTS.md 文档地图**:需求基线声明(REQUIREMENTS.md + 设计文档 + 对话背景)+ docs/design/ 27 份文档分组清单,评审者按地图定位需求文档
265
+ - **项目根发现**:多项目工作区从评审范围定位子项目 AGENTS.md(工作区元地图不遮蔽);修复混合路径分隔符误判
266
+ - **收敛体共享模块**:round 2+ 消息构建单一来源;空回复/纯工具输出不再冒充评审记录
267
+
268
+ ## [0.12.12] - 2026-08-07
269
+
270
+ - advisor 记录按真实时序落盘(timeline)、markdown 表格 render-before-measure 对齐修复(含 heading 多行/双重粗体)、requirements 兜底、评审结论可用性提示
271
+ - 双线消息历史(人读线 + 机读线)、压缩只作用于机读线、机读消息不进人读线
272
+ - 临时文件(tmp-*)不触发 advisor guard;config.mjs 加固(spec 预排序、providers 守卫、saveConfig 写副本)
273
+ - VS Code 扩展发布准备(marketplace 元数据、.vscodeignore、vscode-mock 依赖修复)
274
+
275
+ ## [0.12.11] - 2026-08-05
276
+
277
+ - subagent 按类型配置模型(`/submodel` + `subagentModels`)
278
+ - 可配置 bash shell(`/shell platform` 切换)
279
+ - 其他稳定性与体验改进
280
+
281
+ ## [0.12.10] - 2026-08-05
282
+
283
+ - 代码质量梳理:清理未使用的导出、advisor 计时器与静态导入修复、复评不再因旧会话数据误报
284
+
285
+ ## [0.12.9] - 2026-08-04
286
+
287
+ - 提示词体系质量梳理:移除工程模式与 advisor 的冲突、交付评审语义修正
288
+
289
+ ## [0.12.8] - 2026-08-04
290
+
291
+ - pending-task 推回最多触发一次(消除无界完成循环)
292
+
293
+ ## [0.12.7] - 2026-08-03
294
+
295
+ - 折叠可读性修复(主输出/思考永不折叠)、窄终端宽表格裁剪
296
+
297
+ ## [0.12.6] - 2026-08-02
298
+
299
+ - checkpoint v2、git 破坏性命令保护、鼠标支持、长消息折叠、bash 行为约束
300
+
301
+ ## [0.12.5] - 2026-08-01
302
+
303
+ - 行内代码下划线样式
304
+
305
+ ## [0.12.4] - 2026-08-01
306
+
307
+ - 压缩统一规范、Kimi For Coding、Ctrl+C 双重确认、空响应重试、markdown 渲染修复
308
+
309
+ ## [0.12.3] 及更早
310
+
311
+ v0.12.x 早期版本、v0.11.x、v0.8.x、v0.7.x 与 v0.2–v0.6 系列——完整历史见 [git 提交记录](https://gitee.com/shanghai-xinbo/thincoder/commits/main)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.38",
3
+ "version": "0.12.40",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -23,11 +23,12 @@
23
23
  "bin/",
24
24
  "src/",
25
25
  "README.md",
26
+ "CHANGELOG.md",
26
27
  "LICENSE"
27
28
  ],
28
29
  "repository": {
29
30
  "type": "git",
30
- "url": "https://github.com/xinbo-tech/thincoder.git"
31
+ "url": "git+https://github.com/xinbo-tech/thincoder.git"
31
32
  },
32
33
  "scripts": {
33
34
  "test": "node --test \"test/*.mjs\"",
package/src/agent.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { chat } from "./provider/index.mjs"
6
6
  import { estimateText } from "./provider/rate.mjs"
7
- import { compressIfNeeded, compressFallback, COMPRESS_FAILURE_LIMIT, pushReal } from "./context.mjs"
7
+ import { compressIfNeeded, compressFallback, COMPRESS_FAILURE_LIMIT, pushReal, summarizeRunExplorations } from "./context.mjs"
8
8
  import { specForModel } from "./config.mjs"
9
9
  import { readFileSync } from "node:fs"
10
10
  import { join, dirname } from "node:path"
@@ -95,6 +95,8 @@ export function createAgent({
95
95
  _sessionStart: sessionStart,
96
96
  _lastPromptTokens: null, _usageAtLen: null,
97
97
  _compressFailures: 0,
98
+ _emptyRetries: 0, // empty-response retry budget (per-run; reset on a fresh user turn)
99
+ _runStartHistoryLen: 0, // machine-line length at the start of the current run — end-of-run exploration distillation slices from here
98
100
  _currentTurn: 0, _maxTurns: 100, // turn counter for status bar display
99
101
  }
100
102
  }
@@ -106,6 +108,10 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
106
108
  { depth, signal, overrideTurns, resume, systemPrompt: SYSTEM_PROMPT, disciplineRules: DISCIPLINE_RULES, mainOverlay: MAIN_OVERLAY },
107
109
  )
108
110
 
111
+ // End-of-run exploration distillation boundary (CONTEXT-COMPACTION §5): prepareRun has already
112
+ // pushed the user input + injections, so everything appended from here is "this run's" work.
113
+ agent._runStartHistoryLen = agent.history.length
114
+
109
115
  // Per-run bookkeeping reset. On `resume` (ContinueError continuation) these are
110
116
  // PRESERVED: the resumed run must keep mutation tracking so the advisor/verify
111
117
  // guards stay active (a guard pushback on the last turn must not silently vanish),
@@ -301,6 +307,12 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
301
307
  honestReminderInjected = cr.honestReminderInjected
302
308
  advisorPushbacks = cr.advisorPushbacks
303
309
  if (cr.action === "continue") continue
310
+ if (depth === 0) {
311
+ // End-of-run exploration distillation (CONTEXT-COMPACTION §5): this run's inline
312
+ // exploration results become one semantic note before the final return. Silent (N3):
313
+ // distillation failure must never block the return or lose history.
314
+ try { await summarizeRunExplorations(agent, callbacks, signal) } catch { /* silent (N3) */ }
315
+ }
304
316
  return cr.content
305
317
  }
306
318
 
@@ -407,11 +419,6 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
407
419
  // how many reviews have run (round 1→2→3→4→5), not how many succeeded.
408
420
  // A failed/interrupted review is still a review attempt and should use
409
421
  // the next round's prompt on retry.
410
- try {
411
- JSON.parse(toolCall.arguments || "{}")
412
- } catch {
413
- /* arguments unparseable — still counts as a review attempt */
414
- }
415
422
  agent._advisorRound++
416
423
  }
417
424
  if (FILE_MUTATORS.has(toolCall.name)) {
package/src/context.mjs CHANGED
@@ -48,13 +48,14 @@ function keepTailSize(provider, historyLen) {
48
48
  return Math.min(Math.max(10, Math.floor((ctxWindow / 100_000) * 30)), Math.floor(historyLen * 0.4))
49
49
  }
50
50
 
51
- const SUMMARIZE_PROMPT = `You are a conversation compressor. Summarize the following agent work log into a compact summary for use as context in the ongoing conversation.
51
+ export const SUMMARIZE_PROMPT = `You are a conversation compressor. Summarize the following agent work log into a compact summary for use as context in the ongoing conversation.
52
52
  Requirements:
53
53
  - Write in first person, present tense — these are "my" handover notes, continuing my own train of thought
54
54
  - Most important: preserve design decisions and their reasons — architecture choices, API contracts, naming conventions, trade-off rationale. These are the anchors the subsequent code must not deviate from
55
55
  - Distinguish COMPLETED vs IN-PROGRESS work: completed tasks get a ONE-LINE recap each (what was done, key outcome); spend the detail budget on unresolved issues, next steps, and the CURRENT task
56
56
  - The user's most recent request defines the current task — anchor on it. Earlier requests are likely already completed and only need the one-line recap; do NOT preserve them at full fidelity
57
- - Keep: files modified and why, unresolved issues, next steps
57
+ - Explicitly list FILES CHANGED: every modified file path plus a one-line "why" so post-compaction work can re-locate what was edited and where
58
+ - Explicitly list UNRESOLVED ISSUES / TODOs: anything still open plus the next steps — so post-compaction recovery knows where to resume
58
59
  - Drop: pleasantries, repetition, fine-grained tool output details
59
60
  - Honestly mark uncertain items: anything not actually verified must say "unverified"; do not present guesses as facts
60
61
  - Use bullet-point output; aim for information completeness, not a hard word limit (old 500-char cap is deprecated; in a 1M-context era, err on the long side)
@@ -152,6 +153,15 @@ function applyCompression(agent, headEnd, tailStart, note) {
152
153
  { role: "assistant", content: "Understood. I'll continue from these notes, re-verifying anything transient." },
153
154
  ...tail,
154
155
  ]
156
+ // Compaction REBUILDS the machine line (head + note + "Understood" + tail), so the pre-compaction
157
+ // _runStartHistoryLen index is stale — a longer array shrank beneath it, and end-of-run exploration
158
+ // distillation would then silently skip or slice from the wrong offset. Reset the boundary to the
159
+ // verbatim tail start (head.length + 2: the note and the "Understood" placeholder sit between head
160
+ // and tail). Exploration before the tail was already covered by the compaction summary, so only the
161
+ // still-raw tail needs distilling. `head` is empty today (KEEP_HEAD = 0) — the formula stays
162
+ // correct if KEEP_HEAD ever grows. (shrinkOversized only truncates message bodies in place and
163
+ // leaves the array length unchanged, so this boundary stays valid there — no reset needed.)
164
+ agent._runStartHistoryLen = head.length + 2
155
165
  // Measured token baseline is invalidated along with old history (prompt_tokens were for pre-compaction context), fall back to estimation until next response
156
166
  agent._lastPromptTokens = null
157
167
  agent._usageAtLen = null
@@ -262,22 +272,167 @@ const OVERSIZE_CONTENT_LIMIT = 8_000
262
272
  */
263
273
  function shrinkOversized(agent, limit = OVERSIZE_CONTENT_LIMIT) {
264
274
  let shrunk = false
265
- for (const m of agent.history) {
266
- if ((m.role !== "user" && m.role !== "tool") || typeof m.content !== "string") continue
267
- if (m.content.length <= limit) continue
275
+ // Copy-on-write: build a NEW array and replace only truncated entries. pushReal stores the SAME
276
+ // message object in both `agent.history` (machine line) and `agent._fullHistory` (human/persistence
277
+ // line), so in-place `m.content = ...` would ALSO truncate the never-compacted human line and lose
278
+ // the original pasted content on session persist (session.mjs persists _fullHistory). VS Code port
279
+ // already copies (`history.map(m => ({ ...m }))`); this brings CLI to parity.
280
+ const next = agent.history.map((m) => {
281
+ if ((m.role !== "user" && m.role !== "tool") || typeof m.content !== "string") return m
282
+ if (m.content.length <= limit) return m
268
283
  // Truncate keeping head + tail, insert stub in between; keepHead/keepTail proportional but not exceeding 50%/25% of limit
269
284
  const keepHead = Math.min(Math.floor(limit * 0.5), 4000)
270
285
  const keepTail = Math.min(Math.floor(limit * 0.25), 2000)
271
- m.content =
272
- m.content.slice(0, keepHead) +
273
- `\n[... ${m.content.length - keepHead - keepTail} chars truncated — single message too large for context window ...]\n` +
274
- m.content.slice(-keepTail)
275
286
  shrunk = true
276
- }
287
+ return {
288
+ ...m,
289
+ content:
290
+ m.content.slice(0, keepHead) +
291
+ `\n[... ${m.content.length - keepHead - keepTail} chars truncated — single message too large for context window ...]\n` +
292
+ m.content.slice(-keepTail),
293
+ }
294
+ })
277
295
  if (shrunk) {
296
+ agent.history = next
278
297
  // Same as compaction: measured token baseline is invalidated by the changed history, fall back to estimation until next response
279
298
  agent._lastPromptTokens = null
280
299
  agent._usageAtLen = null
281
300
  }
282
301
  return shrunk
283
302
  }
303
+
304
+ // ─── End-of-run exploration distillation (AGENT-LOOP §13 + CONTEXT-COMPACTION §5, 2026-08-23) ───
305
+ // The main agent's machine line is flooded by inline step-by-step exploration (read/grep/...).
306
+ // At run end we distill THIS run's exploration tool-results into one semantic summary note that
307
+ // replaces them in the machine line, while agent._fullHistory (the human line) stays untouched.
308
+
309
+ /** Read-only knowledge tools counted as "exploration" (execute writes files → never exploration). */
310
+ export const EXPLORE_TOOLS = new Set([
311
+ "read", "grep", "glob", "ls", "code_search", "doc_search", "repo_outline",
312
+ ])
313
+
314
+ /** Summary prompt for turning a burst of exploration results into a semantic summary. */
315
+ export const EXPLORE_SUMMARY_PROMPT = `You are distilling exploration tool results. Summarize the following read-only codebase exploration into a compact semantic summary for the main agent's own context.
316
+
317
+ Requirements:
318
+ - Capture WHAT was discovered, WHERE (which files / directories / symbols), and the KEY CONCLUSIONS — do not list tool calls mechanically
319
+ - Keep actionable facts the main agent needs to continue: code locations, function names, file paths, structure, and open questions the exploration raised
320
+ - Drop raw tool-output noise, repeated lines, and verbatim file dumps — keep only what must be remembered
321
+ - Be honest: mark anything not actually verified as "unverified"; do not present guesses as facts
322
+ - Use bullet points; aim for information completeness, not a hard word limit
323
+
324
+ Exploration log:
325
+ `
326
+
327
+ /** tool_calls name across both stored shapes ({function:{name}} and flat {name}). */
328
+ function toolCallName(tc) {
329
+ return tc?.function?.name ?? tc?.name ?? ""
330
+ }
331
+
332
+ /** Tool that produced a tool-result message (falls back to its owner assistant's tool_call). */
333
+ function toolResultName(msg, ownerToolCalls) {
334
+ if (typeof msg?.name === "string" && msg.name) return msg.name
335
+ const owner = (ownerToolCalls ?? []).find((tc) => tc.id === msg?.tool_call_id)
336
+ return owner ? toolCallName(owner) : ""
337
+ }
338
+
339
+ /**
340
+ * Find the pure-exploration "assistant(tool_calls)→tool…" pair blocks added since `start`.
341
+ * A block is explorable only when EVERY tool call AND every tool result in it is an exploration
342
+ * tool — mixed blocks (read + edit in one turn) stay untouched, or we'd orphan the edit pairing.
343
+ */
344
+ function findExplorationBlocks(history, start) {
345
+ const blocks = []
346
+ let i = start
347
+ while (i < history.length) {
348
+ const m = history[i]
349
+ if (m?.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
350
+ let j = i + 1
351
+ while (j < history.length && history[j]?.role === "tool") j++
352
+ const toolMsgs = history.slice(i + 1, j)
353
+ const allCallsExplore = m.tool_calls.every((tc) => EXPLORE_TOOLS.has(toolCallName(tc)))
354
+ const allResultsExplore = toolMsgs.length > 0 && toolMsgs.every((t) => EXPLORE_TOOLS.has(toolResultName(t, m.tool_calls)))
355
+ if (allCallsExplore && allResultsExplore) {
356
+ blocks.push({ start: i, end: j, messages: history.slice(i, j), toolCount: toolMsgs.length })
357
+ }
358
+ i = j
359
+ } else {
360
+ i++
361
+ }
362
+ }
363
+ return blocks
364
+ }
365
+
366
+ /** Serialize a batch of exploration messages for the summary LLM (same shape as compaction serialization). */
367
+ function serializeExplorationMessages(messages) {
368
+ const cap = 8000 // exploration results ARE the signal to distill — generous cap (quality-first, N1)
369
+ return messages
370
+ .map((m) => {
371
+ const toolNote = m.tool_calls ? ` [called tools: ${m.tool_calls.map(toolCallName).join(", ")}]` : ""
372
+ let text = ""
373
+ if (typeof m.content === "string") text = m.content
374
+ else if (Array.isArray(m.content)) text = m.content.filter((p) => p?.type === "text").map((p) => p.text ?? "").join(" ")
375
+ return `[${m.role}]${toolNote} ${text.slice(0, cap)}`
376
+ })
377
+ .join("\n")
378
+ }
379
+
380
+ /**
381
+ * Core (shared) distillation: replace this run's pure-exploration pair blocks with a single
382
+ * "[Exploration summary]" note placed where the first block was. Returns a NEW history array,
383
+ * or null when there is nothing to shrink (<3 exploration results / LLM failure). Pairing-safe:
384
+ * whole assistant→tool blocks are removed, so no orphan tool_calls/tool can survive.
385
+ */
386
+ async function distillExplorations(history, start, provider, signal) {
387
+ if (!Array.isArray(history) || history.length - start < 2) return null
388
+ const blocks = findExplorationBlocks(history, start)
389
+ const resultCount = blocks.reduce((n, b) => n + b.toolCount, 0)
390
+ if (resultCount < 3) return null
391
+
392
+ const serialized = blocks.map((b) => serializeExplorationMessages(b.messages)).join("\n")
393
+
394
+ let summary
395
+ try {
396
+ // Silent by design (D11): thinking:null and no onToken/onReasoning — this internal
397
+ // distillation must not stream to the frontend. signal propagates user cancellation.
398
+ const resp = await chat({ ...provider, thinking: null, reasoningEffort: null }, {
399
+ messages: [{ role: "user", content: EXPLORE_SUMMARY_PROMPT + serialized }],
400
+ signal,
401
+ })
402
+ summary = resp?.content
403
+ } catch {
404
+ return null // N3: never block the run's return or lose history — original results stay
405
+ }
406
+ if (!summary) return null
407
+
408
+ const drop = new Set()
409
+ for (const b of blocks) for (let k = b.start; k < b.end; k++) drop.add(k)
410
+ const note = { role: "user", content: "[Exploration summary]\n" + summary }
411
+ const next = []
412
+ let inserted = false
413
+ for (let k = 0; k < history.length; k++) {
414
+ if (drop.has(k)) {
415
+ if (!inserted) { next.push(note); inserted = true }
416
+ continue
417
+ }
418
+ next.push(history[k])
419
+ }
420
+ return next
421
+ }
422
+
423
+ /**
424
+ * End-of-run exploration distillation (runAgent's final return). Shrinks the MACHINE line
425
+ * (agent.history) only; agent._fullHistory is never touched. Triggers when this run added ≥3
426
+ * exploration tool results; on LLM failure it silently keeps the original history (N3).
427
+ * `callbacks` is accepted for call-site parity with the other lifecycle hooks — the distillation
428
+ * is silent by design and never streams (D11).
429
+ */
430
+ export async function summarizeRunExplorations(agent, callbacks, signal) {
431
+ const next = await distillExplorations(agent.history, agent._runStartHistoryLen ?? 0, agent.provider, signal)
432
+ if (!next) return
433
+ agent.history = next
434
+ // The machine line changed shape — the measured token baseline was for the pre-shrink context.
435
+ // Invalidate so the next compaction check re-estimates instead of over-counting stale history.
436
+ agent._lastPromptTokens = null
437
+ agent._usageAtLen = null
438
+ }
@@ -1,10 +1,14 @@
1
1
  Workflow — match the process to the task:
2
- - Complex (3+ steps, new features): Requirements Design Development Testing. Write a design doc. Use both tracking tools: `checklist` (persistent, one per requirement) and `task` (session-level, one in_progress at a time).
3
- - Medium (2-3 steps, refactoring): plan briefly, no design doc needed. Use `task` tool.
4
- - Small (typo, one-line fix): confirm understanding, change, verify. No design doc.
2
+ - Read the relevant docs before changing code at ANY tier: doc_search the topic, then locate the owning design doc via docs/design/README.md (the document map) and read it plus AGENTS.md if present.
3
+ - Use `task` to track work for EVERY tier one item in_progress at a time.
4
+ - Complex (3+ steps, new features): Read the docs → Requirements → Design → Development → Testing. Write a design doc. Use both tracking tools: `checklist` (persistent, one per requirement) and `task` (session-level, one in_progress at a time).
5
+ - Medium (2-3 steps, refactoring): Read the docs → Plan → Change → update the owning doc if you spotted a gap — a decision not yet recorded, or a doc now contradicting the code. No design doc needed. Use `task` tool.
6
+ - Small (typo, one-line fix): Read the docs → Change → Verify → update the owning doc if you spotted a gap — a decision not yet recorded, or a doc now contradicting the code. Use `task` tool. No design doc.
5
7
  - If unsure which tier, treat as complex. Under-planning costs more than over-planning.
8
+ - Never create a new doc for an existing board's topic — find the owner and amend it.
6
9
 
7
10
  Debugging strategy:
11
+ - Track the debug steps in `task` — reproduce → locate root cause → fix → verify, one in_progress.
8
12
  - Read the full error output — root cause is often at the end.
9
13
  - Verify against official docs before guessing.
10
14
  - Binary search: cut the problem in half, test which half has the fault.
@@ -24,4 +28,4 @@ Review discipline (standard mode only — engineering mode has its own review ti
24
28
  - **No "pre-existing" cop-out.** You own the whole code. "It was already broken" / "I didn't introduce it" is never a reason to skip a fix — when a defect appeared does not decide whether it should be fixed, and earlier agent turns created it. Rebut only on technical grounds, otherwise fix it.
25
29
  - **Do not bury 🔴.** A 🔴 you neither fix nor rebut blocks convergence. `Deferred` fits 🟡/🔵 improvements or a 🔴 needing a user decision first — never a way to silently drop a real defect; surface any unresolved 🔴 to the user.
26
30
  - Round 2 verifies the prior table + flags obvious new issues; round 3+ strictly verifies only the prior table (no new-issue hunting). Max 5 rounds total.
27
- - When the advisor reports all clear (no 🔴 remaining), run `verify`.
31
+ - When the advisor reports all clear (no 🔴 remaining), run `verify`.
@@ -9,11 +9,13 @@ Explore the codebase read-only, design the architecture, present the plan. When
9
9
  For tasks that match the Coding discipline's "complex" tier, plan mode is your design step; for "medium" tasks it's optional but recommended.
10
10
 
11
11
  Delegate well — spawn subagents for independent subtasks.
12
+ - Subagents run in an isolated context: their step-by-step read/grep never enters your history — only their final report comes back. Doing the same broad exploration inline floods your own window with noise and degrades your attention across turns.
12
13
  - Explore agents for parallel codebase search, plan agents for architecture design, coder agents for self-contained implementation.
13
14
  - When delegating an explore agent, state the thoroughness in the task description — quick / medium / thorough — graded by need; unspecified means the default.
14
- - Delegate breadth-first exploration; do precision edits yourself.
15
+ - Breadth-first exploration understanding that spans multiple files / directories (finding usages, mapping structure, reading a batch of files) — goes to an `explore` subagent, with thoroughness (quick / medium / thorough) annotated in the task.
16
+ - Read a file yourself only when you are about to edit it immediately: precise edits need precise lines inside your own working context — this is a precision exception, not a token-saving trick.
15
17
  - Never give parallel subagents tasks that edit the same files — conflicts waste everyone's time.
16
- - When a coder subagent finishes, verify its report: read the files it claims to have changed, run the tests — do not trust subagent reports blindly.
18
+ - When a coder subagent finishes, verify its work: read the files it claims to have changed and run the tests — do NOT redo the whole exploration you delegated, or you undo the delegation.
17
19
  - If a subagent fails or returns ambiguous results, don't spin: narrow the task and retry, or handle it yourself.
18
20
  - Escalate EARLY, on up-front ability judgment — if the task is beyond your comfortable ability, hand it to a stronger model (escalate) before burning attempts, not after.
19
21
  - When multiple subagent reports conflict, read the relevant code yourself to arbitrate — never merge conflicting claims.