thincoder 0.12.66 → 0.12.68

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +23 -5
  3. package/bin/thincoder.mjs +43 -331
  4. package/package.json +2 -2
  5. package/src/acp/bridge.mjs +13 -12
  6. package/src/acp/handlers-session.mjs +12 -1
  7. package/src/cli/make-agent.mjs +44 -119
  8. package/src/cli/memory-command.mjs +45 -3
  9. package/src/cli/permission.mjs +1 -1
  10. package/src/command-interactive.mjs +186 -0
  11. package/src/command-table.mjs +185 -0
  12. package/src/completions.mjs +36 -12
  13. package/src/crash-reports.mjs +42 -20
  14. package/src/distill.mjs +3 -3
  15. package/src/heap-watch.mjs +5 -14
  16. package/src/prompt-injections.mjs +1 -1
  17. package/src/tui/agent-turn.mjs +114 -69
  18. package/src/tui/clipboard.mjs +74 -30
  19. package/src/tui/cmd-advisor.mjs +54 -13
  20. package/src/tui/cmd-clear.mjs +2 -2
  21. package/src/tui/cmd-config.mjs +26 -2
  22. package/src/tui/cmd-eng.mjs +1 -1
  23. package/src/tui/cmd-mcp.mjs +1 -1
  24. package/src/tui/cmd-new.mjs +2 -2
  25. package/src/tui/cmd-reindex.mjs +1 -1
  26. package/src/tui/cmd-session.mjs +13 -5
  27. package/src/tui/cmd-think.mjs +35 -17
  28. package/src/tui/cmd-timers.mjs +33 -0
  29. package/src/tui/conversation-writer.mjs +2 -2
  30. package/src/tui/display-budget.mjs +9 -9
  31. package/src/tui/fold-block.mjs +1 -1
  32. package/src/tui/index.mjs +23 -5
  33. package/src/tui/interaction.mjs +5 -3
  34. package/src/tui/key-handler-busy.mjs +17 -12
  35. package/src/tui/key-handler-ctrlc.mjs +5 -5
  36. package/src/tui/key-handler-edit.mjs +12 -8
  37. package/src/tui/key-handler-search.mjs +1 -1
  38. package/src/tui/key-handler.mjs +2 -2
  39. package/src/tui/key-modes.mjs +1 -1
  40. package/src/tui/layout.mjs +5 -5
  41. package/src/tui/ledger-surface.mjs +26 -5
  42. package/src/tui/lifecycle-records.mjs +199 -0
  43. package/src/tui/model-picker.mjs +16 -199
  44. package/src/tui/mouse.mjs +5 -5
  45. package/src/tui/pickers.mjs +6 -2
  46. package/src/tui/provider-admin.mjs +213 -0
  47. package/src/tui/queued-merge.mjs +12 -0
  48. package/src/tui/queued-pickup.mjs +34 -0
  49. package/src/tui/render-conversation.mjs +49 -4
  50. package/src/tui/render-frame.mjs +54 -11
  51. package/src/tui/render-loop.mjs +2 -3
  52. package/src/tui/render-segments.mjs +4 -4
  53. package/src/tui/render.mjs +1 -1
  54. package/src/tui/slash-commands.mjs +3 -0
  55. package/src/tui/startup.mjs +28 -3
  56. package/src/tui/subagent-blocks.mjs +37 -29
  57. package/src/tui/subagent-children.mjs +5 -5
  58. package/src/tui/subagent-freeze.mjs +43 -41
  59. package/src/tui/subagent-panel.mjs +11 -11
  60. package/src/tui/suspension-drive.mjs +173 -253
  61. package/src/tui/timer-watch.mjs +64 -0
  62. package/src/tui/tool-args.mjs +10 -71
  63. package/src/tui/tool-display.mjs +7 -7
  64. package/src/tui/tool-events.mjs +40 -35
  65. package/src/tui/tui-lifecycle.mjs +13 -4
  66. package/src/tui/tui-state.mjs +3 -2
  67. package/src/tui/turn-face.mjs +6 -3
  68. package/src/tui/wizard.mjs +7 -2
  69. package/src/tui/wrapped-spawn.mjs +3 -3
package/CHANGELOG.md CHANGED
@@ -1,3 +1,47 @@
1
+ ## [0.12.68] — 2026-10-03
2
+
3
+ > 0.12.67 → 0.12.68(连续号——发布时定号)
4
+
5
+ ### Added
6
+
7
+ - **空闲唤醒**:挂起会话可被定时器唤醒——到点自动续跑(可见面 + 中止适配;timer-wake #443 ∕ #444)。
8
+ - **台账健康信号**:台账库异常 ⇒ TUI 警告行(空值以 `—` 占位)。
9
+ - **heap 快照采集运行期开关**(`diagnostics.heapSnapshot`——默认关)。
10
+ - **`PROJECT-MANIFEST.json` 声明面**:codePaths ∕ index ∕ advisor 三键入驻(`conventions.json` 退役);工程写门豁免 aux 路径(test/tests/scripts、`.thincoder/tmp`)。
11
+
12
+ ### Changed
13
+
14
+ - **核依赖升级**:`@thincoder/core` `^0.9.5` → `^0.10.1`(公开仓读取 ∥ 会话锚解析 ∥ 多仓机制 ∥ 纪律面随核生效)。
15
+ - **交互整批(parity B1–B10 + 走查轮)**:滚动退出 ∥ 挂起队列 ∥ 状态栏 ∥ 渲染性能 ∥ i18n ∥ 设置 ∥ 复制 ∥ 卡顿指示 ∥ 发送忙碌队列。
16
+ - **测试面重整**:旧套件族退役(runner 骨架保留——空 = 绿);单测 = 批内件(随批档)。
17
+
18
+ ### Fixed
19
+
20
+ - **tech-debt 轮 6 ∕ 8**:slash 直通 ∥ 模态门 ∥ sticky bit 重臂 ∥ 级联清除 ∥ aborted reason ∥ advisor 槽守卫 ∥ 台账写者 ∥ timer 支收敛。
21
+ - **批档 CRLF 容忍 + 粘贴生命周期**(#732 ∕ #733)。
22
+ - **会话台账读写守卫**(项目相对守卫面 + 读守卫)。
23
+
24
+ ## [0.12.67] — 2026-09-25
25
+
26
+ > 0.12.66 → 0.12.67(连续号——发布时定号)
27
+
28
+ ### Added
29
+
30
+ - **忙时排队扩面(多槽 + 合并消费 + 可见面)**:Enter 排队自单槽升为**队列**(上限 8 条)——排队期会话流尾显示**待发送块**(逐条原文,每条 ≤3 行、超限尾标);状态栏 `enterHint` **四态分流**(已排队 N 条 / 普通 busy / 挂起内用户回合 / 挂起内系统轮——后两态分别「当前步骤结束后自动发送」/「本轮结束后优先发送」);消费 = **步边界领取为主**(不打断在飞工具;`[sending queued message]` 回执),连续非 `/` 条目**合并为一条消息、一次回合**(每批 ≤8 条 ∧ ≤2000 字符;`/cmd` 逐条保序);回合尾兜底与驱动级取批两条既有通道零改。
31
+ - **台账迁移 / 审计命令**:`thincoder ledger migrate --dry-run | --confirm`(跨端双库键归一合并——备份后导入、源库回收不删)+ `thincoder ledger audit`(库目录只读审计与处置建议)。
32
+
33
+ ### Changed
34
+
35
+ - **核依赖升级**:`@thincoder/core` `^0.9.4` → `^0.9.5`(规格扩面 + 意图认领 + 台账键归一随核生效)。
36
+ - **会诊 effort 菜单**:`none` 双行消除(自有两 `none` 与枚举 `none` 叠加——列表构造单源化)。
37
+ - **advisor effort off 语义归一**:菜单选 off ⇒ 统一 `think_off` 形(不再直赋 `reasoningEffort: "none"`),回执准确。
38
+
39
+ ### Fixed
40
+
41
+ - **edit 工具入参守卫**(ACP 桥同收):非数组 `edits` ⇒ 成形错误,不再裸 `TypeError`。
42
+ - **子代理工具表** `question` 排除(CLI 提示词注入面同步)。
43
+ - 多处 TUI 文句 / 指针收正(挂起面 / 子代理面板 / 工具显示 / 台账面板)。
44
+
1
45
  ## [0.12.66] — 2026-09-22
2
46
 
3
47
  > 0.12.65 → 0.12.66(连续号——发布时定号)
@@ -189,6 +233,10 @@
189
233
  ② **工具作用域限制全部移除**(§10.1):resolveInCwd 去边界断言(信任模型 + 权限门禁为唯一防线,与 bash 一致);git workdir / execute scriptFile / file_ops 目录限制一并移除;工具描述与提示词 "confined to workspace" 措辞清理(两端 byte-identical)
190
234
  ③ **模型上下文长度可配置**(PROVIDER.md §15):`providers[].context`(K 单位,如 128 = 128K)覆盖 MODEL_SPECS——providerSpec 拷贝覆盖不污染共享 spec;压缩阈值(auto ×0.6)/ TOKEN 窗口 / 状态栏显示 / advisor 预算全链路跟随;数字字符串("128")两端统一归一;非法值忽略 + 警告一次;CLI /model 管理流 + VS Code settings.json 配置界面
191
235
 
236
+ ## [0.12.58] — 2026-09-02
237
+
238
+ - 维护性发布(本期无单独变更条目)
239
+
192
240
  ## [0.12.57] — 2026-09-02
193
241
 
194
242
  ### Added
@@ -350,6 +398,10 @@
350
398
 
351
399
  - 新增 `test/session-eng-advisor.test.mjs`:/eng 双写断言(slot+config)、applySession slot 恢复(true/false/显式 false 压过 config true)、旧槽位无字段回退 config 锁定、saveSession 每 turn 往返、/advisor guard 双写(model 等仍 config-scoped)
352
400
 
401
+ ## [0.12.50] — 2026-08-29
402
+
403
+ - 维护性发布(本期无单独变更条目)
404
+
353
405
  ## [0.12.49] — 2026-08-29
354
406
 
355
407
  ### Added
package/README.md CHANGED
@@ -17,8 +17,8 @@ resolved from the npm registry for releases).
17
17
  - **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)
18
18
  - **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
19
19
  guided by "structure → intent → details"
20
- - **Model adaptation** ⭐: top-tier only, latest only. Built-in flagship models from twenty-one providers — DeepSeek / Kimi / Kimi For Coding / GLM / GLM Coding Plan (智谱 Coding) / Qwen / Qwen Token Plan / MiMo (小米) / MiMo Token Plan (小米套餐) / MiniMax / OpenAI / Claude / Gemini /
21
- Grok / Mistral / Volcengine Ark (豆包) / Hunyuan (腾讯混元) / Tencent TokenHub (腾讯混元网关) / SiliconFlow (硅基流动) / OpenRouter / Groq. No legacy model compatibility. Auto-matched context windows, truncation-resume protocols (prefix/partial), thinking-mode APIs (thinking.type /
20
+ - **Model adaptation** ⭐: top-tier only, latest only. Built-in flagship models from twenty-two providers — DeepSeek / Kimi / Kimi For Coding / GLM / GLM Coding Plan (智谱 Coding) / Qwen / Qwen Token Plan / MiMo (小米) / MiMo Token Plan (小米套餐) / MiniMax / OpenAI / Claude / Gemini /
21
+ Grok / Mistral / Volcengine Ark (豆包) / Hunyuan (腾讯混元) / Tencent TokenHub (腾讯混元网关) / Huawei Cloud MaaS (华为云 MaaS) / SiliconFlow (硅基流动) / OpenRouter / Groq. No legacy model compatibility. Auto-matched context windows, truncation-resume protocols (prefix/partial), thinking-mode APIs (thinking.type /
22
22
  reasoning_effort), reasoning_content echo strategies (reasoningEcho), output limits, temperature range clamping — all deeply adapted.
23
23
  - **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
24
24
  - **Memory system**: three layers (personal/project/team), FTS5 + vector RRF hybrid retrieval, git-friendly markdown format
@@ -80,6 +80,8 @@ thincoder
80
80
  First launch walks you through a setup wizard: arrow keys to pick a provider (built-in presets or a custom endpoint) → enter API key → optionally enter an embedding key (SiliconFlow, enables vector memory search, skippable) → arrow keys to pick a model — no hand-editing config
81
81
  files. Adjust anytime with `/model` (add/remove providers, set keys, custom endpoints) or `/config embedkey`. `chat`/`distill` also offer in-place interactive setup when no key is configured in a terminal (in pipes/CI they exit with an error and instructions).
82
82
 
83
+ Any OpenAI-compatible endpoint works without a built-in preset: pick **add provider → custom** in `/model` (name + baseURL + model), or hand-write the entry in `providers[]`. Regional endpoint variants work the same way — e.g. Huawei Cloud MaaS `https://api-ap-southeast-1.modelarts-maas.com/openai/v1`.
84
+
83
85
  You can also hand-write `~/.thincoder/config.json` (see "Configuration" below), then:
84
86
 
85
87
  ```bash
@@ -91,6 +93,7 @@ thincoder memory put --type=rule --title="code style" --content="no semicolons"
91
93
  thincoder memory search "code style"
92
94
  thincoder memory list
93
95
  thincoder memory remove 1
96
+ thincoder memory sweep
94
97
 
95
98
  # Team memory (optional, available after configuring memory.team)
96
99
  thincoder sync # pull the team repo and rebuild the index
@@ -110,7 +113,8 @@ encoding/command issues), `/think` (thinking mode toggle and reasoning effort),
110
113
  sessions), `/reindex` (rebuild the index), `/extract` (extract knowledge from the current session), `/restore` (restore checkpoint), `/clear`, `/exit`. High-frequency commands support abbreviations: `/h` `/x` `/m` `/p` `/t` `/c` `/n`. Typing `/` shows live matching hints in the
111
114
  status bar. Model picker supports search/filter — type to narrow down results.
112
115
 
113
- Configuration comes exclusively from `~/.thincoder/config.json` — no environment-variable configuration is supported.
116
+ Configuration comes exclusively from `~/.thincoder/config.json` — the single source of truth. Sensitive value fields (e.g. a provider's `apiKey` or `websearch.apiKey`)
117
+ may hold a `${env:VAR}` reference, resolved at use time — the value is then taken from the environment variable. There is no environment-variable-only configuration channel and no env-only override.
114
118
 
115
119
  > **Kimi note**: Kimi has **two separate platforms with non-interchangeable API keys** — Moonshot (`https://api.moonshot.cn/v1`, keys `sk-...`, platform.moonshot.cn) and **Kimi For Coding** (`https://api.kimi.com/coding/v1`, keys `sk-kimi-...`, platform.kimi.com, model ID `k3`).
116
120
  > Use the `kimi` preset for Moonshot and `kimi-code` for Kimi For Coding — putting one platform's key on the other's endpoint fails with 401 (a hint is appended when the key/baseURL look mismatched).
@@ -148,6 +152,7 @@ Configuration comes exclusively from `~/.thincoder/config.json` — no environme
148
152
  "subagentModel": null, // default subagent provider/model override: "provider:model" | provider name | model name; null = inherit parent provider. Per-call: subagent tool `model` arg
149
153
  "subagentModels": {}, // per-type override: { "explore": "...", "plan": "...", "coder": "...", "eng-coder": "...", "eng-designer": "..." }; priority: tool model arg > this > subagentModel > parent provider
150
154
  "compactThreshold": 100000, // context compaction threshold (approx. tokens)
155
+ "timerWake": true, // timer idle wake: expired timers are delivered on the spot and start a turn when the host is idle — supported face = CLI / VSC / desktop foreground ends (headless chat / ACP: step-boundary delivery only); false = step-boundary delivery only
151
156
  },
152
157
  "memory": {
153
158
  // Path fields (dbPath / projectDir / team.dir / shell) expand a leading ~ (~, ~/, ~\) to the home directory at load time
@@ -196,7 +201,7 @@ src/
196
201
  key handling, mouse/clipboard, the cmd-* command family, subagent panels
197
202
  tui.mjs re-export shim → src/tui/index.mjs
198
203
  upgrade.mjs version check and self-upgrade
199
- test/ node:test offline unit tests (npm test)
204
+ test/ suite entry (npm test) — integration set; unit tests = batch-local files (docs/batches/) — current state: ../docs/core/design/TESTING.md §2.3
200
205
  scripts/ repo-side scripts — release check, doc-impact, syntax/endpoint probes,
201
206
  real-environment verification (compaction, team sync)
202
207
  ```
@@ -213,7 +218,7 @@ Key design decisions:
213
218
  ## Development
214
219
 
215
220
  ```bash
216
- npm test # offline unit tests (node:test, with local mock servers)
221
+ npm test # repo suite (integration set, offline) — empty manifest after the 2026-09-28 reset; unit tests = docs/batches/ — current state: ../docs/core/design/TESTING.md §2.3
217
222
  node scripts/verify-compress.mjs # real-API verification of context compaction (needs valid config)
218
223
  node scripts/verify-team.mjs # team memory A->git->B full-chain verification (local git, offline)
219
224
  ```
@@ -226,6 +231,19 @@ Code conventions: pure `.mjs`, no semicolons, no third-party npm dependencies al
226
231
 
227
232
  ## Changelog
228
233
 
234
+ ### 0.12.68 (2026-10)
235
+ - **Idle wake** — suspended sessions wake to timers: they resume on schedule (visibility surface + abort accommodation).
236
+ - **Ledger health signals** — an unhealthy ledger database now shows a warning line in the TUI (empty values render as `—`).
237
+ - **Interaction batch (parity B1–B10)** — scroll exits, suspend queue, status line, render performance, i18n, settings, copy, stall indicator, send-while-busy queue.
238
+ - **Core `^0.10.1`** — public-repo reading (declared sources), session-anchor resolution for multi-repo workspaces, the multi-repo operation mechanism, and discipline-surface fixes ride in with the core.
239
+ - **Fixes** — tech-debt rounds 6/8 (slash passthrough, modal gate, sticky-bit re-arm, cascade clear, aborted reason, advisor slot guard, ledger writer); batch CRLF tolerance and paste lifecycle; ledger read/write guards.
240
+
241
+ ### 0.12.67 (2026-09)
242
+ - **Visible busy queue (multi-slot)** — queue up to 8 messages while a turn runs: a "to send" block shows each queued message in the stream, the status bar has four states, and batches are picked up at step boundaries (without interrupting in-flight tools) and merged into one message per turn (`/cmd` entries keep their order). New ledger commands: `thincoder ledger migrate --dry-run|--confirm` and `thincoder ledger audit`.
243
+ - **Effort menus fixed** — the consult effort picker no longer shows a duplicated `none`; advisor effort "off" normalizes to `think_off` with an accurate receipt.
244
+ - **Core `^0.9.5`** — wider model coverage (qwen-flash / qwen-vl-max / claude-fable-5.1 / gemini-3.1-pro / step-3.7-flash / kimi-code rows), the `cacheMode` dead field removed, deepseek preset aligned to 384K, plus peer intent claims and cross-platform ledger key fixes.
245
+ - **Fixes** — the edit tool now returns a formed error for non-array `edits` (ACP bridge too); `question` is excluded from subagent tool tables.
246
+
229
247
  ### 0.12.66 (2026-09)
230
248
  - **MiMo V2.6 support** — the core `^0.9.4` dependency registers MiMo V2.6 (pro / flash / pro-ultraspeed: 1M context, 131K output, vision, auto caching) and repoints the `mimo` / `mimoplan` presets to `mimo-v2.6-pro`.
231
249
 
package/bin/thincoder.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  * thincoder — CLI entry point
5
5
  * thincoder Launch the interactive TUI
6
6
  * thincoder chat "..." One-shot agent run (tools enabled, streamed)
7
- * thincoder memory <sub> Memory management: list / search / put / remove
7
+ * thincoder memory <sub> Memory management: list / search / put / remove / sweep(sweep:origin 级库治理)
8
8
  * thincoder upgrade Update to the latest version from npm
9
9
  * thincoder completion <sh> Shell completion: bash / zsh / fish
10
10
  * thincoder session gc Session dir GC: --dry-run report / --confirm delete cold projects (SESSION.md §6.12)
@@ -15,48 +15,56 @@
15
15
  */
16
16
 
17
17
  import { readFileSync } from "node:fs"
18
- import { isAbsolute, join } from "node:path"
19
- import { runAgent } from "@thincoder/core/agent.mjs"
20
18
  import { loadConfig, configPath } from "@thincoder/core/config.mjs"
21
19
  import { tracesRoot } from "@thincoder/core/traces/trace-store.mjs"
22
20
  import { scheduleTraceCleanup } from "@thincoder/core/traces/trace-cleanup.mjs"
23
- import { createMemory, syncDir } from "@thincoder/core/memory.mjs"
24
- // 批 1 CORE-DEFECT-FIXES B3:onWait 相位值域 + 文案单源(消费面禁各自枚举——PROVIDER.md §6.20)
25
- import { waitStatusText } from "@thincoder/core/provider/wait-status.mjs"
26
- import { assembleAgent, attachManifest, teamConfig, gitAuthor, validateProvider } from "../src/cli/make-agent.mjs"
27
- import { memoryCommand } from "../src/cli/memory-command.mjs"
28
- import { setupWizard } from "../src/cli/setup-wizard.mjs"
29
- import { summarize, askPermission } from "../src/cli/permission.mjs"
30
- import { distillCommand } from "../src/cli/distill-command.mjs"
31
- import { prepareCrashReporting, recentCrashHint, writeCrashRecord } from "../src/crash-reports.mjs"
32
- import { setTuiActive, restoreTerminalAfterCrash } from "../src/tui/tui-lifecycle.mjs"
21
+ import { prepareCrashReporting, writeCrashRecord } from "../src/crash-reports.mjs"
22
+ import { setTuiActive, restoreTerminalAfterCrash, _setCleanupOutPathForTest } from "../src/tui/tui-lifecycle.mjs"
33
23
  import { spawnTuiWrapped } from "../src/tui/wrapped-spawn.mjs"
34
24
  import { configurePromptInjections } from "@thincoder/core/prompt-files.mjs"
35
25
  import { CLI_PROMPT_INJECTIONS } from "../src/prompt-injections.mjs"
26
+ // 拆档批 R4(2026-09-28):命令分发表外提——分发骨架 + 八薄命令族 + help ∕ version =
27
+ // `src/command-table.mjs`;交互长驻三命令(chat ∕ tui ∕ acp)= `src/command-interactive.mjs`。
28
+ import { runCommandTable } from "../src/command-table.mjs"
36
29
 
37
30
  // U2(CORE-UNIFICATION §2.6.3 专项补⑦ / §2.13.8(六)):CLI 端锚取值表——**进程入口、任何装配之前**注册一次(调用期应用 ⇒ 无导入序要求)。
38
31
  // 漏配 = 锚字面静默进模型 ⇒ 入口面用例(test/integration/cli-prompt-entry.test.mjs)显式覆盖本调用路径。
39
32
  configurePromptInjections(CLI_PROMPT_INJECTIONS)
40
33
 
41
- const [command, ...args] = process.argv.slice(2)
34
+ // CONFIG.md §6.2 ④:内部 argv 标志(生产零路径)。`--tui-wrapped`(包装门——注入点 =
35
+ // src/tui/wrapped-spawn.mjs)在 bin 顶部自剥离(不进命令解析);测试钩逐旗标
36
+ // (`--test-crash` / `--test-tui-active` / `--test-cleanup-out=<路径>`)在下方崩溃钩处按原始 argv
37
+ // (位置无关)判定后抛错接管——先于命令分派,永不作为命令被解释。
38
+ const _argvRaw = process.argv.slice(2)
39
+ const _tuiWrapped = _argvRaw.includes("--tui-wrapped")
40
+ const [command, ...args] = _argvRaw.filter((a) => a !== "--tui-wrapped")
42
41
  // TUI-STDERR-CAPTURE(F-1):TUI 启动(tui/无命令)默认包装——父 spawn 子 tee stderr 落盘(外部
43
42
  // 终止/native abort——第 4 类崩溃面——诊断默认捕获)。须在 prepareCrashReporting 前(父不预建/不设
44
- // report——子进程做——R25 保留)。env 门已设(包装内子进程)或包装失败 → 直行现逻辑(尽力面)。
45
- if ((command === undefined || command === "tui") && !process.env.THINCODER_TUI_WRAPPED) {
46
- if (spawnTuiWrapped()) await new Promise(() => {}) // 包装成功 → 挂起(tee/收尾退出全在 wrapped-spawn——不达下方 switch)
43
+ // report——子进程做——R25 保留)。argv 门已设(包装内子进程)或包装失败 → 直行现逻辑(尽力面)。
44
+ if ((command === undefined || command === "tui") && !_tuiWrapped) {
45
+ if (spawnTuiWrapped()) await new Promise(() => {}) // 包装成功 → 挂起(tee/收尾退出全在 wrapped-spawn——不达下方命令分发)
47
46
  }
48
47
  const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version
49
48
 
49
+ // CONFIG.md §6.2 ①(用户面两开关——默认各向):判定单点 = 本入口——读配置键
50
+ // `diagnostics.{heapSnapshot,heapWatch}` 一次,经显式参数传入下方两消费者;读抛 ⇒ 各向默认
51
+ // (watch 开 ∥ 快照关——fail-closed)。
52
+ let _diagnostics = {}
53
+ try {
54
+ _diagnostics = loadConfig().diagnostics ?? {}
55
+ } catch { /* 配置缺失/损坏 → 各向默认(watch 开 ∥ 快照关——fail-closed) */ }
56
+
50
57
  // R25(F-R25b):crash-reports 预建 + process.report 启用——入口最前(一切重活前——缩编程
51
58
  // 期窗口)——V8 OOM/原生 fatal 自动写 report.*.json(实现批实测:目录缺失时 Node 静默不写
52
59
  // ——预建为必要动作)。失败不阻断启动(尽力面)。
53
- prepareCrashReporting()
60
+ prepareCrashReporting({ heapSnapshot: _diagnostics.heapSnapshot === true })
54
61
  // TUI-OOM-ROOTCAUSE(CRASH-REPORTS.md §8.3 武装点——全命令同源单点):堆遥测/看门狗——
55
62
  // 60s 采样 + 双档(70/85%)比例边缘预警(stderr + TUI 行 + 事件日志);定时器 unref
56
- // (一次性命令自然退出零阻塞);默认开 / THINCODER_HEAP_WATCH 关值集可不启。失败面全吞。
63
+ // (一次性命令自然退出零阻塞);默认开(配置键 `diagnostics.heapWatch` 取 `false` 可不启)。
64
+ // 失败面全吞。
57
65
  try {
58
66
  const { startHeapWatch } = await import("../src/heap-watch.mjs")
59
- startHeapWatch()
67
+ startHeapWatch({ enabled: _diagnostics.heapWatch !== false })
60
68
  } catch { /* 看门狗启动失败不阻断启动(尽力面) */ }
61
69
 
62
70
  // R25(F-R25a)异常钩子升级:原"只 console.error 一行(TUI 全屏下不可见)"→ ① 落盘
@@ -91,12 +99,15 @@ function handleFatal(type, error) {
91
99
  process.on("uncaughtException", (error) => handleFatal("uncaughtException", error))
92
100
  process.on("unhandledRejection", (error) => handleFatal("unhandledRejection", error))
93
101
 
94
- // R25 测试门(T-R25a.1/a.2——子进程 env 注入——生产零路径):THINCODER_TEST_CRASH=1 抛
95
- // 未捕获异常走完整崩溃序列;THINCODER_TEST_TUI_ACTIVE=1 模拟 TUI 活动态(同一 setter——
96
- // 测试缝同源);THINCODER_TEST_CLEANUP_OUT 指向文件时恢复序列写入该文件(tui-lifecycle 读)。
97
- if (process.env.THINCODER_TEST_CRASH === "1") {
98
- if (process.env.THINCODER_TEST_TUI_ACTIVE === "1") setTuiActive(true)
99
- throw new Error("R25 test crash (THINCODER_TEST_CRASH)")
102
+ // R25 测试门(T-R25a.1/a.2——子进程 argv 注入——生产零路径):`--test-crash` 抛未捕获异常
103
+ // 走完整崩溃序列;`--test-tui-active` 模拟 TUI 活动态(同一 setter——测试缝同源);
104
+ // `--test-cleanup-out=<路径>` 指到文件时恢复序列写入该文件(tui-lifecycle 缝——启动时读点)。
105
+ // 判定面 = 原始 argv(位置无关——与旧 env 门同语义:旗标可居首/居尾,不依赖命令位)。
106
+ const _cleanupOutArg = _argvRaw.find((a) => a.startsWith("--test-cleanup-out="))
107
+ if (_cleanupOutArg) _setCleanupOutPathForTest(_cleanupOutArg.slice("--test-cleanup-out=".length))
108
+ if (_argvRaw.includes("--test-crash")) {
109
+ if (_argvRaw.includes("--test-tui-active")) setTuiActive(true)
110
+ throw new Error("R25 test crash (--test-crash)")
100
111
  }
101
112
 
102
113
  const USAGE = `thincoder - thin coding agent
@@ -111,6 +122,7 @@ Usage:
111
122
  thincoder memory search <query> Search memory
112
123
  thincoder memory put --type=<t> --title=<t> --content=<c> [--tags=<t>]
113
124
  thincoder memory remove <id> Remove an entry
125
+ thincoder memory sweep [--origin <o>] [--dry-run|--confirm]
114
126
  thincoder sync Sync team memory repo (pull --rebase + reindex)
115
127
  thincoder reindex Rebuild the local index from markdown sources
116
128
  thincoder distill <file> [--yes] [--layer=<s>]
@@ -122,6 +134,8 @@ Usage:
122
134
  thincoder session index [--status | --rebuild]
123
135
  Derived session index (disposable, rebuilt on demand): --status reports sessions /
124
136
  messages / tool calls + coverage + size; --rebuild reindexes every session file
137
+ thincoder ledger migrate --dry-run | --confirm Ledger variant-key merge (dry-run report / backup then import; source recycled)
138
+ thincoder ledger audit Read-only ledger dir audit (classify every db + suggestions)
125
139
  thincoder upgrade Update to the latest version from npm
126
140
  thincoder completion <sh> Generate shell completion script (bash / zsh / fish)
127
141
  thincoder -v, --version Print version
@@ -159,308 +173,6 @@ if (command === undefined || command === "tui" || command === "chat" || command
159
173
  } catch { /* 拍挂点失败静默——不阻断启动 */ }
160
174
  }
161
175
 
162
- switch (command) {
163
- case "chat": {
164
- const auto = args.includes("--auto")
165
- const prompt = args.filter((a) => a !== "--auto").join(" ").trim()
166
- if (!prompt) {
167
- console.error('Usage: thincoder chat [--auto] "<prompt>"')
168
- exitSoon(1)
169
- break
170
- }
171
-
172
- // R25(F-R25c):非交互/chat 模式——上次异常终止提示写 stderr 一行(无匹配不提示)
173
- const crashNotice = recentCrashHint()
174
- if (crashNotice) console.error(crashNotice)
175
-
176
- const agent = await assembleAgent()
177
- // SESSION.md §6.8 D-S4(F4)+ MODEL-MERGE-SESSION:headless 无 TUI —— 可读错误 + 退出码 1,
178
- // 不弹 UI、不崩溃;文案改引 defaultModel(F-6 引导 A headless 面——/config → 默认模型)
179
- if (agent._providerInvalid) {
180
- const dm = agent.config?.defaultModel
181
- console.error(`[error] 未配置有效模型(defaultModel "${dm ?? "(not set)"}":${agent._providerInvalidReason})。请运行 thincoder 进入 TUI 设置(/config → 默认模型;或 /model 选择后以会话槽生效),或编辑 ${configPath}`)
182
- exitSoon(1)
183
- break
184
- }
185
- if (!agent.provider.apiKey) {
186
- if (!process.stdin.isTTY) {
187
- console.error(noKeyMessage())
188
- exitSoon(1)
189
- break
190
- }
191
- const p = await setupWizard()
192
- if (!p) {
193
- exitSoon(1)
194
- break
195
- }
196
- agent.provider = p
197
- agent.activeProvider = p.name
198
- // Wizard may have configured an embedding key: attach vector search
199
- const fresh = loadConfig()
200
- if (fresh.embedding?.apiKey && agent.memory && !agent.memory.embedder) {
201
- const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
202
- agent.memory.embedder = createEmbedder(fresh.embedding)
203
- }
204
- }
205
- if (auto) agent.autoApprove = true
206
- // Accumulate token usage, output to stderr at the end (don't pollute stdout pipe)
207
- const usageTotal = { prompt: 0, completion: 0, cacheHit: 0, cacheMiss: 0 }
208
- try {
209
- await runAgent(agent, prompt, {
210
- onToken: (text) => process.stdout.write(text),
211
- onWait: (ev) => {
212
- const s = waitStatusText(ev)
213
- if (s) console.error(`[rate-limit] ${s}`)
214
- },
215
- onToolCall: (name, toolArgs) => {
216
- console.error(`\n[tool] ${name} ${summarize(toolArgs)}`)
217
- },
218
- onToolResult: (name, result) => {
219
- const preview = result.length > 200 ? result.slice(0, 200) + "..." : result
220
- console.error(`[done] ${name} -> ${preview.split("\n")[0]}`)
221
- },
222
- onToolOutput: (name, chunk) => process.stderr.write(chunk),
223
- onCompress: () => console.error(`\n[context] Context too long, auto-compacted (early conversation summarized by LLM)`),
224
- onTaskUpdate: (items) => {
225
- const done = items.filter((i) => i.status === "done").length
226
- const current = items.find((i) => i.status === "in_progress")
227
- console.error(`[task] ${done}/${items.length}${current ? ` ▶ ${current.title}` : ""}`)
228
- },
229
- onUsage: (usage) => {
230
- usageTotal.prompt += usage.prompt_tokens ?? 0
231
- usageTotal.completion += usage.completion_tokens ?? 0
232
- usageTotal.cacheHit += usage.prompt_cache_hit_tokens ?? 0
233
- usageTotal.cacheMiss += usage.prompt_cache_miss_tokens ?? 0
234
- },
235
- onPermissionRequest: (name, toolArgs) => (agent.autoApprove ? true : askPermission(name, toolArgs)),
236
- })
237
- process.stdout.write("\n")
238
- if (usageTotal.prompt > 0) {
239
- const cacheTotal = usageTotal.cacheHit + usageTotal.cacheMiss
240
- const hitPart = cacheTotal > 0 ? ` cache-hit ${Math.round((usageTotal.cacheHit / cacheTotal) * 100)}%` : ""
241
- console.error(`[usage] prompt ${usageTotal.prompt} + completion ${usageTotal.completion}${hitPart}`)
242
- }
243
- } catch (error) {
244
- // 用 name 判断而非 instanceof:不依赖"与 runAgent 同一个模块实例"这一隐式约定
245
- if (error.name === "ContinueError") {
246
- console.error(`\n[paused] Agent stopped after ${error.turn} turns. Run in TUI to continue.`)
247
- exitSoon(0)
248
- } else {
249
- console.error(`\n[error] ${error.message}`)
250
- exitSoon(1)
251
- }
252
- }
253
- break
254
- }
255
-
256
- case "memory": {
257
- const config = loadConfig()
258
- const memory = createMemory({ dbPath: config.memory.dbPath })
259
- if (config.embedding?.apiKey) {
260
- const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
261
- memory.embedder = createEmbedder(config.embedding)
262
- }
263
- if (config.memory.projectDir) {
264
- memory.projectOrigin = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)
265
- }
266
- const code = await memoryCommand(memory, args)
267
- if (code) exitSoon(code)
268
- break
269
- }
270
-
271
- case "sync": {
272
- const config = loadConfig()
273
- const team = teamConfig(config)
274
- if (!team) {
275
- console.error("Team memory not configured. Set memory.team in ~/.thincoder/config.json:")
276
- console.error(' "team": { "name": "myteam", "repo": "git@github.com:org/team-memory.git" }')
277
- exitSoon(1)
278
- break
279
- }
280
- const memory = createMemory({ dbPath: config.memory.dbPath })
281
- const { ensureClone, pullTeam } = await import("@thincoder/core/git/gitmem.mjs")
282
- try {
283
- const cloned = await ensureClone(team)
284
- if (cloned) console.log(`Cloned team repo to ${team.dir}`)
285
- await pullTeam(team.dir)
286
- const stats = await syncDir(memory, { layer: "team", dir: team.dir })
287
- console.log(`Synced. Index: +${stats.added} ~${stats.updated} -${stats.removed}`)
288
- } catch (error) {
289
- console.error(`[error] ${error.message}`)
290
- exitSoon(1)
291
- }
292
- break
293
- }
294
-
295
- case "distill": {
296
- const code = await distillCommand(args, exitSoon)
297
- if (code) exitSoon(code)
298
- break
299
- }
300
-
301
- case "reindex": {
302
- const config = loadConfig()
303
- const memory = createMemory({ dbPath: config.memory.dbPath })
304
- // files 层(project/team 的 markdown 索引)全量重建;索引是易失品,真相在 markdown
305
- memory.db.prepare(`DELETE FROM files`).run()
306
- const cwd = process.cwd()
307
- let total = { added: 0, removed: 0 }
308
- if (config.memory.projectDir) {
309
- const s = await syncDir(memory, { layer: "project", dir: isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(cwd, config.memory.projectDir) })
310
- total.added += s.added
311
- }
312
- const team = teamConfig(config)
313
- if (team) {
314
- const { ensureClone } = await import("@thincoder/core/git/gitmem.mjs")
315
- await ensureClone(team)
316
- const s = await syncDir(memory, { layer: "team", dir: team.dir })
317
- total.added += s.added
318
- }
319
- console.log(`Reindexed ${total.added} markdown entries (project${team ? " + team" : ""}). Vectors will be lazily regenerated on next search.`)
320
- break
321
- }
322
-
323
- case "tui":
324
- case undefined: {
325
- // 恢复上次的会话(同一项目目录);provider 按保存的名字切回(用户上次可能换过模型)
326
- // 2026-09-05 §6.10(R4):恢复决策按本端记录 resumeSlot(D-2 ①②③)——manifest active
327
- // 只作"无记录端"的一次性继承源,不再作本端恢复第一依据(D-6)。
328
- // 2026-09-18(装配门禁批 #30 · KD-M1-13 / KD-M1-15):resumeSlot **前移至装配之前**——
329
- // 装配期模式门判据 = 会话权威值(槽优先 + config 回退);恢复记录经 slotData 形参进
330
- // 装配钩子(同进程同一调用前移 ⇒ 零新增写动作;钩子内不调 resumeSlot——非纯读)。
331
- const { resumeSlot, applySession, sessionDescriptor } = await import("@thincoder/core/session.mjs")
332
- // F-MI7:resumeSlot = async(探测束不阻塞事件循环)——此处必须 await
333
- const { slot, data } = await resumeSlot(process.cwd())
334
- const agent = await assembleAgent({ slotData: data })
335
- // SESSION.md §6.8 D-S1:TUI 路径在 startTUI 前清空无效 provider——空 provider 不流入 runAgent
336
- // (崩溃源:chat() 缺 model → 网关 400 或 fetch("undefined/...") TypeError)
337
- if (agent._providerInvalid) agent.provider = null
338
- const config = loadConfig()
339
- if (data) {
340
- // applySession 内部已按槽复合重算 compactThreshold(auto 时)——不再需要 switched 分支
341
- // TUI-OOM-ROOTCAUSE(SESSION.md §6.14):传 slot → 绑定记录存储(身份核验 + 对账)
342
- // ——人读线 = 尾窗(磁盘为准);未传 = 模式 F(全量数组)
343
- applySession(agent, data, { slot })
344
- // SESSION.md §6.11(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
345
- // 信号——仅本启动 resume 路径设一次(真进程重启恢复盘上会话);/session 切换与 ACP
346
- // 加载走同一 applySession 收敛但不设(不误报进程重启)。prepareRun 发句即清——进程
347
- // 内一次。resumed:yes 独立由 applySession 的恢复事件武装(data.history 非空)。
348
- agent._processRestartPending = true
349
- }
350
- // D-3 钉槽:applySession 清 _slot 后立即钉回恢复槽——首保存必落恢复槽(消除
351
- // "load → 首回合保存"窗口内并发方翻 active 导致的静默迁移);/new 经 resetSessionState
352
- // 清 _slot(newSession 已认领 + 写记录,语义保留)
353
- agent._slot = slot
354
- // M1 重估点(会话起点②——applySession 之后 · KD-M1-13 取值点②):CLI 的工程模式权威值 =
355
- // 会话槽(/eng 只写槽不写 config.json);此时 agent.config.agent.engineering 已由
356
- // applySession 按槽订正 ⇒ 与装配期判据同值 ⇒ 幂等重估(值同 ⇒ 无副作用)。
357
- attachManifest(agent)
358
- // D-S3 优先级补全:applySession 可能已用会话中的有效 provider 修复(config 无效 + 会话有效)——
359
- // 修复后复验清除标记,仅当两者都无效才弹重选(validateProvider 幂等)
360
- if (agent._providerInvalid) validateProvider(agent)
361
- // MCP 连接失败在 TUI alt-buffer 下 stderr 不可见,注入为下一条 user 消息后的提醒
362
- if (agent._mcpWarnings?.length) {
363
- agent._pendingReminders = agent._pendingReminders ?? []
364
- agent._pendingReminders.push(
365
- `[System reminder: ${agent._mcpWarnings.length} MCP server(s) failed to connect at startup:\n` +
366
- agent._mcpWarnings.map((w) => ` - ${w}`).join("\n") +
367
- `\nYou can try reconnecting with /mcp connect <name>.]`
368
- )
369
- }
370
- const { startTUI } = await import("../src/tui.mjs")
371
- try {
372
- await startTUI(agent, {
373
- projectDir: config.memory.projectDir ? (isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)) : null,
374
- team: teamConfig(config),
375
- author: gitAuthor(),
376
- restored: data ? sessionDescriptor(agent, data) : data,
377
- // R25(F-R25c):TUI 启动显示一行"上次运行异常终止"提示(showStartup 渲染——无匹配不传)
378
- crashNotice: recentCrashHint() ?? undefined,
379
- })
380
- } catch (error) {
381
- console.error(`[error] ${error.message}`)
382
- exitSoon(1)
383
- }
384
- break
385
- }
386
-
387
- case "completion": {
388
- const shell = args[0]
389
- if (!["bash", "zsh", "fish"].includes(shell)) {
390
- console.error(`Usage: thincoder completion <bash|zsh|fish>`)
391
- exitSoon(1)
392
- break
393
- }
394
- // §11.2 交付行数债(2026-09-08——500 行硬限触碰执行):直写段逐字迁出
395
- // src/completions.mjs(脚本字节不变——printCompletion 发射)
396
- const { printCompletion } = await import("../src/completions.mjs")
397
- printCompletion(shell)
398
- break
399
- }
400
-
401
- case "upgrade": {
402
- const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))
403
- const local = pkg.version
404
- const { checkForUpdate } = await import("../src/upgrade.mjs")
405
- const result = await checkForUpdate(local)
406
- if (!result) {
407
- console.error("[upgrade] Unable to query npm registry — check your network connection and that npm is installed")
408
- exitSoon(1)
409
- break
410
- }
411
- if (!result.newer) {
412
- console.log(`ThinCoder ${local} is already the latest.`)
413
- } else {
414
- console.log(`Upgrading: ${local} → ${result.latest}`)
415
- const { execSync } = await import("node:child_process")
416
- execSync("npm install -g thincoder@latest", { stdio: "inherit" })
417
- console.log(`Upgraded to ${result.latest}`)
418
- }
419
- break
420
- }
421
-
422
- case "acp": {
423
- const { runAcpServer, runAcpLogin } = await import("../src/acp.mjs")
424
- // §11.2 改法 4:`args` 语义 = 追加到已配置的 agent 调用——`--login` = 终端认证流程,
425
- // 不进 stdio 服务模式(不带该旗标时行为零变);退出码语义 = 0 成功 / 非 0 失败。
426
- if (args.includes("--login")) {
427
- exitSoon(await runAcpLogin())
428
- break
429
- }
430
- await runAcpServer()
431
- break
432
- }
433
-
434
- case "session": {
435
- // SESSION.md §6.12 / §6.17:会话目录 GC 显式面(冷 cwd 90 天面 + 三合取存量组全量面)——
436
- // 双端同面(2026-09-21 端差注销:VS Code 端命令 `thincoder.sessionGc` 走同一核数据面)。
437
- // SESSION.md §6.19 D-SE46(2026-09-22 会话索引批):`session index` = 派生索引库显式面
438
- // (--status 读数 / --rebuild 全量重扫)——同一 `session` 分发族(壳侧分发,实现在核)。
439
- if (args[0] === "index") {
440
- const { runSessionIndex } = await import("@thincoder/core/session-index-cmd.mjs")
441
- process.exitCode = await runSessionIndex(args)
442
- break
443
- }
444
- const { runSessionGc } = await import("@thincoder/core/session-gc.mjs")
445
- process.exitCode = await runSessionGc(args)
446
- break
447
- }
448
-
449
- case "--help":
450
- case "-h": {
451
- process.stdout.write(USAGE)
452
- break
453
- }
454
-
455
- case "--version":
456
- case "-v": {
457
- console.log(VERSION)
458
- break
459
- }
460
-
461
- default: {
462
- console.error(`Unknown command: ${command}\n`)
463
- process.stdout.write(USAGE)
464
- exitSoon(1)
465
- }
466
- }
176
+ // 命令分发(2026-09-28 拆档批 R4:分发表外提 `src/command-table.mjs`)——依赖经 ctx 注入:
177
+ // USAGE ∕ VERSION ∕ 两 helper = 本进程入口装配面(消费方:分发骨架与交互长驻三命令)。
178
+ await runCommandTable(command, args, { usage: USAGE, version: VERSION, exitSoon, noKeyMessage })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.66",
3
+ "version": "0.12.68",
4
4
  "description": "Thin coding agent - zero third-party dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -20,7 +20,7 @@
20
20
  "node": ">=24"
21
21
  },
22
22
  "dependencies": {
23
- "@thincoder/core": "^0.9.4"
23
+ "@thincoder/core": "^0.10.1"
24
24
  },
25
25
  "files": [
26
26
  "bin/",