@thincoder/core 0.10.13 → 0.11.1

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 (164) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/advisor/messages.mjs +4 -2
  3. package/advisor/notice.mjs +1 -1
  4. package/advisor/project-context.mjs +121 -11
  5. package/agent/completion.mjs +13 -1
  6. package/agent/digest-account.mjs +73 -46
  7. package/agent/dispatch-run.mjs +1 -1
  8. package/agent/dispatch.mjs +29 -21
  9. package/agent/family-tools.mjs +9 -5
  10. package/agent/helpers.mjs +1 -94
  11. package/agent/offload.mjs +88 -0
  12. package/agent/record-results.mjs +1 -6
  13. package/agent/run-stages.mjs +69 -48
  14. package/agent/run-start.mjs +8 -8
  15. package/agent/setup.mjs +3 -11
  16. package/agent/suspension.mjs +1 -1
  17. package/agent/turn-loop.mjs +32 -3
  18. package/agent/write-face.mjs +99 -0
  19. package/agent/write-gate.mjs +354 -5
  20. package/agent-tools/advisor.mjs +127 -11
  21. package/agent-tools/async-settle.mjs +2 -2
  22. package/agent-tools/bash-async.mjs +8 -2
  23. package/agent-tools/batch-closeout.mjs +206 -0
  24. package/agent-tools/batch-gate.mjs +287 -73
  25. package/agent-tools/batch-ledger-bind.mjs +183 -0
  26. package/agent-tools/batch-ledger-link.mjs +73 -0
  27. package/agent-tools/batch-lifecycle.mjs +113 -367
  28. package/agent-tools/batch-paths.mjs +45 -40
  29. package/agent-tools/batch-read.mjs +1 -1
  30. package/agent-tools/batch-reopen.mjs +142 -0
  31. package/agent-tools/batch-skeleton.mjs +275 -80
  32. package/agent-tools/batch.mjs +243 -50
  33. package/agent-tools/context.mjs +6 -3
  34. package/agent-tools/design-slots.mjs +24 -24
  35. package/agent-tools/design-token.mjs +96 -15
  36. package/agent-tools/doc-check.mjs +18 -2
  37. package/agent-tools/eng.mjs +1 -1
  38. package/agent-tools/escalate-async.mjs +3 -3
  39. package/agent-tools/manifest.mjs +8 -5
  40. package/agent-tools/read-history.mjs +5 -5
  41. package/agent-tools/review-facts.mjs +31 -13
  42. package/agent-tools/spawn-gates.mjs +10 -14
  43. package/agent-tools/ssh-session.mjs +120 -111
  44. package/agent-tools/ssh-transfer.mjs +210 -0
  45. package/agent-tools/ssh-transport.mjs +277 -46
  46. package/agent-tools/subagent-actions-query.mjs +1 -1
  47. package/agent-tools/subagent-actions.mjs +1 -1
  48. package/agent-tools/subagent-async.mjs +3 -2
  49. package/agent-tools/subagent-scheduler.mjs +43 -9
  50. package/agent-tools/subagent-spawn.mjs +68 -9
  51. package/agent-tools/subagent.mjs +16 -14
  52. package/client-identity.mjs +22 -6
  53. package/config-presets.mjs +5 -3
  54. package/context.mjs +34 -3
  55. package/declaration.mjs +2 -2
  56. package/doc-check/anchors.mjs +128 -28
  57. package/doc-check/index.mjs +68 -15
  58. package/doc-check/line-refs.mjs +112 -0
  59. package/doc-check/paragraph.mjs +18 -5
  60. package/explore-distill.mjs +1 -1
  61. package/i18n.mjs +1 -1
  62. package/index-discover.mjs +4 -1
  63. package/ledger-close-proof.mjs +19 -37
  64. package/ledger-cmd.mjs +71 -195
  65. package/ledger-db.mjs +29 -28
  66. package/ledger-migrate.mjs +4 -4
  67. package/ledger-read.mjs +10 -8
  68. package/ledger-surface.mjs +1 -1
  69. package/ledger-tools.mjs +6 -6
  70. package/ledger-variant-notice.mjs +1 -1
  71. package/ledger-write-ops.mjs +373 -0
  72. package/ledger.mjs +2 -2
  73. package/manifest-discovery.mjs +2 -2
  74. package/manifest-schema.mjs +4 -0
  75. package/memory/memory-tool.mjs +5 -2
  76. package/memory/schema.mjs +5 -26
  77. package/package.json +1 -1
  78. package/path-resolve.mjs +217 -0
  79. package/permission.mjs +4 -0
  80. package/prompt-overlays.mjs +5 -0
  81. package/prompts/advisor-design.md +9 -6
  82. package/prompts/advisor-round1.md +4 -3
  83. package/prompts/advisor-round2.md +6 -5
  84. package/prompts/advisor-round3.md +6 -5
  85. package/prompts/common.md +23 -10
  86. package/prompts/consult-base.md +2 -1
  87. package/prompts/discipline-engineering.md +29 -48
  88. package/prompts/discipline-normal.md +10 -5
  89. package/prompts/persona-eng-coder.md +8 -0
  90. package/prompts/persona-eng-designer.md +22 -12
  91. package/prompts/persona-engineering.md +81 -68
  92. package/prompts/persona-plan.md +2 -1
  93. package/prompts/persona-tester.md +55 -0
  94. package/provider/core.mjs +2 -2
  95. package/provider/google.mjs +15 -5
  96. package/provider/rate.mjs +11 -3
  97. package/provider/sse.mjs +15 -4
  98. package/session-identity.mjs +21 -0
  99. package/session-lifecycle.mjs +9 -4
  100. package/session-slots.mjs +9 -14
  101. package/session.mjs +5 -0
  102. package/skip-dirs.mjs +48 -0
  103. package/team-ledger.mjs +4 -4
  104. package/text-budget.mjs +4 -0
  105. package/token-window.mjs +90 -14
  106. package/tool-docs/advisor.md +2 -2
  107. package/tool-docs/apply_patch.md +2 -0
  108. package/tool-docs/bash.md +4 -0
  109. package/tool-docs/batch.md +85 -1
  110. package/tool-docs/browser.md +3 -1
  111. package/tool-docs/code_search.md +3 -1
  112. package/tool-docs/consult_start.md +6 -2
  113. package/tool-docs/consult_stop.md +3 -1
  114. package/tool-docs/delete.md +3 -0
  115. package/tool-docs/doc_check.md +43 -8
  116. package/tool-docs/doc_search.md +5 -1
  117. package/tool-docs/edit.md +22 -1
  118. package/tool-docs/execute.md +7 -1
  119. package/tool-docs/fetch.md +3 -1
  120. package/tool-docs/file_ops.md +3 -1
  121. package/tool-docs/git.md +9 -1
  122. package/tool-docs/goal.md +2 -0
  123. package/tool-docs/grep.md +1 -1
  124. package/tool-docs/hashline_edit.md +2 -0
  125. package/tool-docs/insert_after.md +3 -1
  126. package/tool-docs/ledger.md +5 -5
  127. package/tool-docs/lint.md +1 -0
  128. package/tool-docs/manifest-read.md +2 -1
  129. package/tool-docs/manifest.md +9 -2
  130. package/tool-docs/memory.md +11 -1
  131. package/tool-docs/notify_parent.md +1 -1
  132. package/tool-docs/plan.md +2 -0
  133. package/tool-docs/question.md +1 -1
  134. package/tool-docs/read_history.md +15 -3
  135. package/tool-docs/read_image.md +3 -1
  136. package/tool-docs/recent_changes.md +3 -1
  137. package/tool-docs/repo_outline.md +3 -1
  138. package/tool-docs/settings.md +10 -2
  139. package/tool-docs/skill.md +2 -0
  140. package/tool-docs/ssh.md +30 -17
  141. package/tool-docs/subagent.md +50 -13
  142. package/tool-docs/task.md +2 -0
  143. package/tool-docs/team_ledger.md +1 -1
  144. package/tool-docs/timer.md +7 -0
  145. package/tool-docs/verify.md +13 -1
  146. package/tool-docs/websearch.md +3 -1
  147. package/tool-docs/write.md +2 -0
  148. package/tools/bash.mjs +12 -2
  149. package/tools/edit-batch.mjs +40 -33
  150. package/tools/edit-diff.mjs +69 -1
  151. package/tools/execute.mjs +10 -4
  152. package/tools/file.mjs +17 -27
  153. package/tools/git.mjs +27 -1
  154. package/tools/ops.mjs +2 -1
  155. package/tools/patch.mjs +12 -3
  156. package/tools/process-doc-mark.mjs +139 -0
  157. package/tools/search.mjs +15 -6
  158. package/tools/shared.mjs +24 -1
  159. package/tools/ssh.mjs +48 -7
  160. package/tools/tree.mjs +4 -1
  161. package/tools/write-path.mjs +64 -3
  162. package/traces/trace-store.mjs +4 -3
  163. package/agent/light-round.mjs +0 -63
  164. package/agent/unbooked-pen.mjs +0 -62
@@ -0,0 +1,21 @@
1
+ /**
2
+ * session-identity.mjs — 进程会话 id 单源(零 import 叶档)——PROVIDER.md §6.17「会话标识头」。
3
+ *
4
+ * 为什么单立一叶:请求装配面 `client-identity.mjs` 是零依赖叶子,而每个 transport 都 import 它;
5
+ * 会话 id 又要「每次请求现取」。若从 `session-slots.mjs` 取,会把它的依赖链(config 读盘 ∥ 会话
6
+ * 存储 ∥ 进程探测共 8 档)连带拉进每个 transport 的 import 图。故本体住本档,`session-slots.mjs`
7
+ * 反向 re-export——既有 import 面零改。
8
+ *
9
+ * 语义:进程级惰性生成、零 IO、进程内稳定;形 `{pid}-{ts}-{rand6}`。它区分的是「哪一次进程会话」,
10
+ * 不是「槽位里的哪一份对话」(按对话槽分桶属另批穿参改造)。
11
+ */
12
+
13
+ let currentSessionId = null
14
+
15
+ /** Generate unique session ID for this process */
16
+ export function getSessionId() {
17
+ if (!currentSessionId) {
18
+ currentSessionId = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
19
+ }
20
+ return currentSessionId
21
+ }
@@ -36,8 +36,8 @@ import { loadSlotFile } from "./session.mjs"
36
36
  // 与 VSC `newSlot` / `loadSlotForWrite` 全新分支同一构造器;环 import 同上,函数体内运行时使用)。
37
37
  import { newSlotData } from "./session-slot-write.mjs"
38
38
  import { mergeAdjacentAssistantEchoes } from "./context.mjs"
39
- // 打开态读数公式(§6.24 判据句 2——与 CLI 状态行 / VSC 同式;零新公式,只组合)。
40
- import { historyPercent } from "./token-window.mjs"
39
+ // 打开态读数公式(§6.24 判据句 2——与 CLI 状态行 / VSC 同一实现点的投影;零新公式,只组合)。
40
+ import { historyPercent, restoreTokenSample, sampleRatio } from "./token-window.mjs"
41
41
  import { scheduleSessionGC } from "./session-gc.mjs"
42
42
  import { restoreEngTokens, tokenExpired } from "./token-ttl.mjs"
43
43
  // 槽事件(docs/core/design/SESSION-OBSERVABILITY.md §3.1):`slot:new` 走核日志单一通道。
@@ -210,10 +210,15 @@ export function applySession(agent, data, opts = {}) {
210
210
  agent.provider.reasoningEffort = data.effort
211
211
  }
212
212
  }
213
+ // §6.17.1 链二读面:估算校准样本随槽恢复——上行刚把 `provider` 按槽值落定,key 同源校验才有意义
214
+ // (四条校验在 `restoreTokenSample`:形状 / est 版本 / key 同源 / 下界 1;不合格 ⇒ 未校准档)。
215
+ restoreTokenSample(agent, data.estimateSample, agent.provider)
213
216
  return { switched, droppedDesignTokens: restored.droppedDesignTokens }
214
217
  }
215
218
  // ② 槽无 key ∥ 不在册 → D-S3 保留——静默保持现状(落 config 链:config 统一解析产物已是
216
219
  // `agent.provider`;不报错不纠正——两方都无效由调用侧复验 validateProvider 弹重选)
220
+ // §6.17.1 链二读面(B 支出口同 A 支):样本照旧随槽恢复(provider = config 链产物,key 同源校验照做)
221
+ restoreTokenSample(agent, data.estimateSample, agent.provider)
217
222
  return { switched: false, droppedDesignTokens: restored.droppedDesignTokens }
218
223
  }
219
224
 
@@ -401,7 +406,7 @@ export function slotOccupancy(cwd, slot) {
401
406
  * ⇒ 落链)∥ 无槽模型 ⇒ 现行模型链回落(`defaultModel` 属本渠道 ⇒ 其模型段;两档皆无 ⇒ `null`——
402
407
  * `resolveChannelModel`,2026-10-09 清除批:渠道单值模型退场,零静默回落);未命中 ⇒ `fallback`
403
408
  * (支 ②「静默保持现状」的装配口径 = `loadConfig().provider`);
404
- * ④ 公式 = `historyPercent`(`thincoder-core/token-window.mjs`——与 CLI 状态行 / VSC 同式)。
409
+ * ④ 公式 = 核 `historyReading`(与 CLI ∥ VSC 同一实现点的投影);第三参 = 槽样本经 `sampleRatio` 取的校准比。
405
410
  * **零副作用**:`data` / `providers` / `fallback` / `defaultModel` 皆入参(不读盘、不写盘、不改任何进程态);
406
411
  * `data` 非对象 ⇒ `0`(与空历史同值——「非正 ⇒ 零节点」显示门归端侧,沿 `historyPercent` 空历史口径)。 */
407
412
  export function sessionReading(data, { providers, fallback, defaultModel } = {}) {
@@ -417,5 +422,5 @@ export function sessionReading(data, { providers, fallback, defaultModel } = {})
417
422
  const provider = entry
418
423
  ? { ...entry, model: slotModel ?? resolveChannelModel(entry, defaultModel ?? null) }
419
424
  : fallback
420
- return historyPercent(line, provider)
425
+ return historyPercent(line, provider, sampleRatio(data.estimateSample, provider))
421
426
  }
package/session-slots.mjs CHANGED
@@ -6,10 +6,10 @@
6
6
  * init-block 批 · F-MI7 二次拆分:探测束接线后本档实读 517 行(破 490 判定线并越 500 硬限)
7
7
  * ⇒ 按 `CORE-UNIFICATION.md` §2.8.1 子表原行 3(已随 2026-10-08 批清账)**随批**外提清单 / 认领 / 属主面至
8
8
  * `session-slots-manifest.mjs`(语义原样迁移;本档 re-export 该档导出保既有 import 面——
9
- * 消费档 import 路径与名面不动)。本档保留 = 存储原语(cwd 哈希 / 路径 / 原子写 / 进程
10
- * sessionId)· 端记录(end marker)· 列表面(listSlots——条目集 = 盘面实读;扫描面住
11
- * `session-slot-scan.mjs`)· 删槽 · 恢复决策面
12
- * (usableSlot / loadLegacyFile / resumeSlot)。
9
+ * 消费档 import 路径与名面不动)。本档保留 = 存储原语(cwd 哈希 / 路径 / 原子写)· 端记录
10
+ * (end marker)· 列表面(listSlots——条目集 = 盘面实读;扫描面住 `session-slot-scan.mjs`)·
11
+ * 删槽 · 恢复决策面(usableSlot / loadLegacyFile / resumeSlot)。进程 sessionId 的本体已迁
12
+ * 零 import 叶 `session-identity.mjs`(PROVIDER.md §6.17),本档 re-export 保名面。
13
13
  *
14
14
  * 端名缝(SESSION.md §6.20 D-SE50 · 2026-09-25 SLOT-END-PARAM 批):端名 = **进程内的单值
15
15
  * 声明**(`setSessionEnd` / `sessionEnd`;`END` 仅模块级初值——CLI 零声明即得「cli」)。
@@ -53,20 +53,15 @@ import {
53
53
  import { scanSlotMetas } from "./session-slot-scan.mjs"
54
54
  // 读面懒核实面(§6.25 判据句 3 落点 B——不可信清单调度:纯内存登记 + 启动窗外延迟拍);静态环同形。
55
55
  import { scheduleVerify } from "./session-slot-verify.mjs"
56
+ // 进程会话 id(PROVIDER.md §6.17 会话标识头):本体住零 import 叶 `session-identity.mjs`——本档的
57
+ // 依赖链会经请求装配面被拉进每个 transport 的 import 图,故迁出。本档 import 取用(usableSlot ∥
58
+ // resumeSlot 的属主判据)+ 下行 re-export 保既有 import 面零改(消费档路径与名面不动)。
59
+ import { getSessionId } from "./session-identity.mjs"
56
60
  export {
57
61
  slotDigest, loadManifest, saveManifest, ownerPid, ownerPids,
58
62
  allocateFresh, claimSlot, activeSlot, ledgerHealth,
59
63
  } from "./session-slots-manifest.mjs"
60
-
61
- let currentSessionId = null
62
-
63
- /** Generate unique session ID for this process */
64
- export function getSessionId() {
65
- if (!currentSessionId) {
66
- currentSessionId = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
67
- }
68
- return currentSessionId
69
- }
64
+ export { getSessionId }
70
65
 
71
66
  /** Normalize cwd for hashing: uppercase Windows drive letter so both ends
72
67
  * (CLI's process.cwd() vs VS Code's uri.fsPath, which lowercases it) agree. */
package/session.mjs CHANGED
@@ -27,6 +27,8 @@ import { digestTs } from "./session-slots-manifest.mjs"
27
27
  // F2 轮转守卫自 2026-09-08 迁至 session-guard.mjs(session-slots 再越 500 行硬限拆分)
28
28
  import { guardForeignSlotFile } from "./session-guard.mjs"
29
29
  import { engTokenSlotFields } from "./token-ttl.mjs"
30
+ // §6.17.1 链二写面(估算校准样本随会话槽走):本档只取写出值,读面在 `session-lifecycle.mjs`(applySession)。
31
+ import { tokenSampleOf } from "./token-window.mjs"
30
32
  // TUI-OOM-ROOTCAUSE 批(SESSION.md §6.14):人读线记录存储(磁盘为准 + 内存窗口)。
31
33
  // slimForDisplay/isLegacyTransient 迁入 store 模块(§6.14 记录存储)——本文件 re-export 保持
32
34
  // 既有 import 面(session-slots.mjs 引 isLegacyTransient)。依赖方向单向:store 零项目内依赖。
@@ -143,6 +145,9 @@ export function saveSession(agent, opts = {}) {
143
145
  advisor: agent.config?.advisor ?? null,
144
146
  pendingReminders: agent._pendingReminders ?? [],
145
147
  sessionStart: agent._sessionStart ?? null,
148
+ // §6.17.1 链二写面:估算校准样本 `{key,num,den,est}`——空样本落 null(不造空对象);
149
+ // 只随回合尾保存走,不新增写路径、不逐样本落盘。
150
+ estimateSample: tokenSampleOf(agent),
146
151
  }
147
152
  // 2026-09-05 §6.10 D-4:首认领(_slot 为 null——如 /session 切换后首保存、“查看对方活槽 →
148
153
  // 保存 fork 新槽”的落盘槽跟随)写本端记录——marker = 端内最后认领者;粘性 _slot 期间
package/skip-dirs.mjs ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * skip-dirs.mjs — 目录排除名单**零依赖叶档**(本批 E12:四份名单合一份)。
3
+ *
4
+ * 四份并存曾是漂移源:`index-discover.mjs:28`(8 项)· `tools/shared.mjs:27`(6 项,名 `IGNORED_DIRS`)·
5
+ * `tools/tree.mjs:13`(13 项)· `memory/schema.mjs:48-69`(39 项 + 折叠集 + 前缀族)。同一个问题
6
+ * (「哪些目录不是人要读的东西」)四个答案;索引面与检索面各剪各的,同一批文件在两个面上一进一出。
7
+ * 单源 = 本档,四个消费面只 import 名单,不各自持有副本。
8
+ *
9
+ * **名单只此一份**:`SKIP_DIRS`(42 项)答「索引 / 遍历 / 搜索时跳过什么」——剪掉的是噪音。
10
+ * 面判不读任何排除名单:面只有两值(文档面放行 ∥ 其余产品面拦),「什么算产物」不参与面判定。
11
+ *
12
+ * 三条处置(理由写在项旁,防后人「顺手补回去」):
13
+ * - `bin` **不列**:`thincoder-cli/bin/` 有 2 个 `.mjs` 是 CLI 入口、产品代码;列进名单会把入口
14
+ * 漏出索引与检索面。
15
+ * - `turbo` 并入 `.turbo`:`tools/tree.mjs` 旧名单里的 `turbo`(无点)是笔误,另三份都是 `.turbo`,
16
+ * 工作区递归查 `*turbo*` 零命中该目录。
17
+ * - `.thincoder` / `.vs` / `.idea` 本批补入:工具临时目录与 IDE 配置——进索引是噪音。
18
+ *
19
+ * 零依赖:本档只出常量,无 import——四处引它不成环、不加重量(`memory/schema.mjs` 引 `node:sqlite`、
20
+ * `conventions.mjs` 的依赖链被 `declaration.mjs` → `manifest.mjs` 拖着走,两条都不适合当单源落点)。
21
+ */
22
+
23
+ /** 索引 / 遍历 / 搜索的剪枝全集(42 项)。精确段匹配(`memory/file-walk.mjs` 另叠折叠与前缀口径)。 */
24
+ export const SKIP_DIRS = new Set([
25
+ // 产物 / 依赖(19)
26
+ "node_modules", ".git", "dist", "build", ".turbo", "coverage",
27
+ "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".svelte-kit",
28
+ "vendor", "Pods", "bower_components", "third_party", "obj", "out",
29
+ // Windows 用户目录(14——永不含项目代码)
30
+ "AppData", "Application Data", "Desktop", "Documents", "Downloads",
31
+ "Music", "Pictures", "Videos", "OneDrive", "Contacts", "Favorites",
32
+ "Links", "Saved Games", "Searches",
33
+ // 其他非代码目录(6——Library = macOS 用户目录;go = Go workspace;三项 Windows 系统目录)
34
+ "Library", "go", "Program Files", "Program Files (x86)", "Windows", "$Recycle.Bin",
35
+ // 本批补(3——工具临时目录 / IDE 配置;见头注)
36
+ ".thincoder", ".vs", ".idea",
37
+ ])
38
+
39
+ /** win32 大小写折叠子集(18 项——那台文件系统不区分大小写,`Node_Modules` 仍要剪)。
40
+ * `.git` 不在:它由 `memory/file-walk.mjs` 的点段规则覆盖,不属本族。 */
41
+ export const SKIP_DIRS_FOLD = new Set([
42
+ "node_modules", "dist", "build", ".turbo", "coverage",
43
+ "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".svelte-kit",
44
+ "vendor", "Pods", "bower_components", "third_party", "obj", "out",
45
+ ])
46
+
47
+ /** 目录名前缀族(`bazel-<workspace>` 无法枚举——workspace 名任意)。 */
48
+ export const SKIP_DIR_PREFIXES = ["bazel-"]
package/team-ledger.mjs CHANGED
@@ -26,8 +26,8 @@ const READ_ACTIONS = ["query", "count"]
26
26
  /** 六态(服务端 CHECK 同集——ledger/LEDGER.md §3)。 */
27
27
  const STATUSES = ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"]
28
28
 
29
- /** 工具 cwd 供值面(缺省 → 当前项目根)。 */
30
- const cwdOf = (ctx) => ctx?.agent?.cwd ?? ctx?.cwd ?? process.cwd()
29
+ /** 工具 cwd 供值面(零进程缺省:`ctx.agent.cwd` > `ctx.cwd`;两者皆无 ⇒ 当场失败,不落 `process.cwd()`)。 */
30
+ const cwdOf = (ctx) => ctx?.agent?.cwd ?? ctx?.cwd
31
31
 
32
32
  // ── 仓标记派生(§2.2) ───────────────────────────────────────────────────────
33
33
 
@@ -174,7 +174,7 @@ const ACTION_SPECS = {
174
174
  properties: {
175
175
  id: { type: "number", description: "条目编号(全局)" },
176
176
  title: { type: "string", description: "标题(缺省 = 不变)" },
177
- detail: { type: "string", description: "正文(缺省 = 不变;追认核销前须非空)" },
177
+ detail: { type: "string", description: "正文(缺省 = 不变)" },
178
178
  owner: { type: "string", description: "负责人(交接 = 换负责人;缺省 = 不变)" },
179
179
  status: { type: "string", enum: STATUSES, description: "目标状态(迁移表合法后继——非法由服务端 400)" },
180
180
  },
@@ -201,7 +201,7 @@ const UNION_PARAMETERS = {
201
201
  status: { type: "string", enum: STATUSES, description: "状态(query ∥ count 过滤——六态之一;update 目标态——迁移表合法后继;close ∈ {已核销, 已废弃})" },
202
202
  owner: { type: "string", description: "负责人(query ∥ count 过滤——用户名精确匹配;add ∥ update 设值)" },
203
203
  title: { type: "string", description: "标题(add 必填 ∥ update 可改——trim 非空 ∥ ≤200)" },
204
- detail: { type: "string", description: "正文(add ∥ update——可空 ∥ ≤4000;追认核销须非空)" },
204
+ detail: { type: "string", description: "正文(add ∥ update——可空 ∥ ≤4000)" },
205
205
  id: { type: "number", description: "条目编号(update ∥ close 必填——全局编号)" },
206
206
  },
207
207
  additionalProperties: false,
package/text-budget.mjs CHANGED
@@ -51,6 +51,10 @@ export function appendCappedText(prev, add, { hard, head = 0, tail = 0, marker =
51
51
  * straddling the boundary leaves a LONE high surrogate (U+D800-DBFF) that strict UTF-16
52
52
  * decoders reject. When the cut point lands on a high surrogate, step back one code unit
53
53
  * (the pair is dropped whole).
54
+ *
55
+ * 核内唯一定义(D2):`thincoder-core/agent/offload.mjs`(双端 preview 头切片)与
56
+ * `thincoder-core/agent/setup.mjs`(doc_search 预览)经 import 复用本函数——核内不再有私有复本。
57
+ * 入参先归一(`String(text ?? "")`):非字符串入参归一后按同一规则切片,不抛(既有导出契约)。
54
58
  */
55
59
  export function safeSliceUTF16(text, max) {
56
60
  const t = String(text ?? "")
package/token-window.mjs CHANGED
@@ -4,11 +4,12 @@
4
4
  * 迁出三族(语义零改——原档越限在即,本批按 §6.16.8 拆出):① 计量面(IMAGE_TOKEN_ESTIMATE + `estimateTokens`);
5
5
  * ② 尾族(KEEP_HEAD 头注 + TAIL_BUDGET_FRACTION / SUMMARY_TOKEN_ESTIMATE / TAIL_FLOOR_MESSAGES + `keepTailSize` + `tailBudgetTokens`);
6
6
  * ③ 切分与配对修复族(`splitHistory` + `repairedTailStart` + `tightenTailByBudget`)。
7
- * 新增族:`contextUsage`(与 `compressIfNeeded` 的 token 判定**同一函数**)· `historyPercent`(与两端状态行**同式**)·
7
+ * 新增族:`contextUsage`(与 `compressIfNeeded` 的 token 判定**同一函数**)· `historyReading` / `historyPercent`(状态行
8
+ * 百分比——与 CLI ∥ VSC **同源**:一个实现点,端侧零自算式,消费面全是本档的投影)·
8
9
  * `collectStaleToolOutputs`(陈旧工具输出合格集)+ 门槛常量 `PRUNE_MIN_TOKENS`(**与合格集同住本档** ⇒ 消费方单向取用、零回指)。
9
- * 依赖面 = `provider/rate.mjs`(`estimateText`)+ `config.mjs`(`providerSpec` / `resolveCompactThreshold`)——**零 import 环**。
10
+ * 依赖面 = `provider/rate.mjs`(`estimateText` / `ESTIMATOR_VERSION`)+ `config.mjs`(`providerSpec` / `resolveCompactThreshold`)——**零 import 环**。
10
11
  */
11
- import { estimateText } from "./provider/rate.mjs"
12
+ import { estimateText, ESTIMATOR_VERSION } from "./provider/rate.mjs"
12
13
  import { providerSpec, resolveCompactThreshold } from "./config.mjs"
13
14
 
14
15
  const IMAGE_TOKEN_ESTIMATE = 2000 // rough estimate for image content tokens (CLI legacy 256 underestimated real image costs, delaying compaction)
@@ -40,7 +41,7 @@ const KEEP_HEAD = 0 // No dedicated head: earliest messages may be a COMPLETED e
40
41
  // × window − SUMMARY_TOKEN_ESTIMATE ≈1K, §6.9) tightens it over pair-safe boundaries when compaction runs,
41
42
  // never below TAIL_FLOOR_MESSAGES; ordinary sessions never reach it (D-T4: the trigger ratio lives in `config.mjs` — untouched here).
42
43
  const TAIL_BUDGET_FRACTION = 0.15
43
- const SUMMARY_TOKEN_ESTIMATE = 1000 // §6.9: summary output target ~1K tokens — reserved from the 15%
44
+ export const SUMMARY_TOKEN_ESTIMATE = 1000 // §6.9: summary output target ~1K tokens — reserved from the 15%;§6.17 可达性门的 `note` 项同取本常量
44
45
  const TAIL_FLOOR_MESSAGES = 10 // §6.4④ D-T2: the tail keeps ≥10 verbatim messages — floor beats budget
45
46
  export function keepTailSize(provider, historyLen) {
46
47
  // provider is guaranteed at every call site (runAgent always builds one); providerSpec
@@ -139,7 +140,7 @@ export function contextUsage(agent, extras = {}) {
139
140
  const historyTokens = estimateTokens(history)
140
141
  const measured = agent?._lastPromptTokens != null
141
142
  // §6.3 / §6.17 估算自校准:估算值乘 `_tokRatio`;`_lastPromptTokens` 是服务端真值,不乘。
142
- const ratio = agent?._tokRatio ?? 1
143
+ const ratio = tokenRatioOf(agent) // 取比单源(同下 `historyReading` 的第三参来源)
143
144
  return {
144
145
  basis: measured ? "measured" : "estimated",
145
146
  total: measured
@@ -155,18 +156,25 @@ export function contextUsage(agent, extras = {}) {
155
156
  }
156
157
  }
157
158
 
159
+ /** 估算样本键(§6.17.1 链二校验③单源):`format:model`——采样面与恢复面同一判据(切模型自清)。 */
160
+ export function tokenKey(provider) {
161
+ return `${provider?.format ?? "openai"}:${provider?.model ?? ""}`
162
+ }
163
+
158
164
  /**
159
165
  * 估算自校准采样(§6.17「估算自校准」)——池化比:`num += usage.prompt_tokens`、`den += estPrompt`
160
166
  * (分子分母同取输入侧口径;`estPrompt` 由发送面随 result 带回,与净判同一个值)。
161
- * `key = format:model` 变 ⇒ 归零重采(切模型自清,不接四个刷新点);`_tok` / `_tokRatio` 只由本函数写,
162
- * `/new` 与 run 装配复位。无样本 ⇒ 比 = 1(估算维持原口径)。
163
- * 作用面 = `contextUsage` 两支承(§6.3);实测基线 `_lastPromptTokens` 不乘。
167
+ * `tokenKey()` 变 ⇒ 归零重采(切模型自清,不接四个刷新点);`_tok` / `_tokRatio` 的**采样侧**只由本函数写
168
+ * (恢复侧 = `restoreTokenSample`,清空 = `/new`——`session-lifecycle.mjs`);run 装配**不复位**
169
+ * (池化比要跨消息累计,见 `agent/setup.mjs`)。
170
+ * 无样本 ⇒ 比 = 1(估算维持原口径)。作用面 = `contextUsage` 两支承(§6.3);实测基线 `_lastPromptTokens` 不乘。
171
+ * 落槽形见 §6.17.1 链二:`est` = 估算口径版本位,跨进程恢复时与本值不符的样本不复用。
164
172
  */
165
173
  export function recordTokenSample(agent, provider, estPrompt, usage) {
166
174
  const prompt = Number(usage?.prompt_tokens)
167
175
  if (!agent || !(prompt > 0) || !(estPrompt > 0)) return
168
- const key = `${provider?.format ?? "openai"}:${provider?.model ?? ""}`
169
- if (agent._tok?.key !== key) agent._tok = { key, num: 0, den: 0 }
176
+ const key = tokenKey(provider)
177
+ if (agent._tok?.key !== key) agent._tok = { key, num: 0, den: 0, est: ESTIMATOR_VERSION }
170
178
  agent._tok.num += prompt
171
179
  agent._tok.den += estPrompt
172
180
  // 下界 = 1:已知偏差方向是估算偏低;比 < 1 会把全部估算支缩小、重开「该压没压」原始故障,
@@ -175,11 +183,79 @@ export function recordTokenSample(agent, provider, estPrompt, usage) {
175
183
  }
176
184
 
177
185
  /**
178
- * 状态行百分比(§6.16.4「与状态行 ctx% 的单源关系」)——与 CLI 状态行(`render-frame.mjs` 式)与 VSC `ctxPercentForHistory` **同式**;
179
- * stats 报出的就是用户看到的那个百分比(不新增第三口径);空历史 ⇒ 0(状态行的 `ctxPct > 0` 显示门在端侧)。
186
+ * 样本写出面(§6.17.1 链二写面):会话槽 `estimateSample` 的值——`agent._tok` 为空 ⇒ `null`
187
+ * (落 null,不造空对象);`est` 缺省补当前口径版本(老进程内的样本对象无该位)。
188
+ */
189
+ export function tokenSampleOf(agent) {
190
+ const tok = agent?._tok
191
+ if (!tok || typeof tok.key !== "string") return null
192
+ return { key: tok.key, num: tok.num, den: tok.den, est: tok.est ?? ESTIMATOR_VERSION }
193
+ }
194
+
195
+ /**
196
+ * 样本读回面(§6.17.1 链二):槽里是盘上用户数据 ⇒ 判定走 `validSample`(同一份校验,不在此复述),
197
+ * 不过则回到未校准档(`_tok = null` / `_tokRatio = 1`,不抛——坏样本不得拦住会话恢复)。
198
+ * 返回是否复用了样本。
199
+ */
200
+ export function restoreTokenSample(agent, sample, provider) {
201
+ const ok = validSample(sample, provider)
202
+ agent._tok = ok
203
+ agent._tokRatio = ok ? Math.max(1, ok.num / ok.den) : 1
204
+ return ok !== null
205
+ }
206
+
207
+ /**
208
+ * 样本判定(§6.17.1 链二四条校验——**唯一实现面**):形状与数值域(`num` / `den` / `est` 有限、`den > 0`)·
209
+ * 口径版本(`est === ESTIMATOR_VERSION`——口径变了,旧样本的 `den` 与新估算不同量纲)· 键匹配
210
+ * (`key === tokenKey(provider)`——恢复后模型已换 ⇒ 不复用)。全过 ⇒ `{ key, num, den, est }`;任一不过 ⇒ `null`
211
+ * (回到未校准档,不抛——坏样本不得拦住会话恢复 / 读数)。恢复面 `restoreTokenSample` 与读数面 `sampleRatio`
212
+ * 同取本函数:同一判定,不写第二份。④ 比 < 1 不必单独判——由 `max(1, …)` 兜底。
180
213
  */
181
- export function historyPercent(history, provider) {
182
- return Math.round((estimateTokens(history ?? []) / providerSpec(provider).context) * 100)
214
+ function validSample(sample, provider) {
215
+ if (!sample || typeof sample !== "object" || Array.isArray(sample)) return null
216
+ const { key, num, den, est } = sample
217
+ if (typeof key !== "string" || !Number.isFinite(num) || !Number.isFinite(den) || !Number.isFinite(est) || !(den > 0)) return null
218
+ if (est !== ESTIMATOR_VERSION) return null
219
+ if (key !== tokenKey(provider)) return null
220
+ return { key, num, den, est }
221
+ }
222
+
223
+ /**
224
+ * 核读数(§6.16.4「与状态行 ctx% 的单源关系」)——这个量**只有本函数一个实现点**,五处消费面全是它的投影:
225
+ * stats 第 6 行 · 打开态读数 · 桌面 `ev:usage` · VSC `specs.mjs` · CLI 状态行(经 `ctxCacheOf` 缓存)。
226
+ * 一点出两值:`tokens` = 校准后的历史估算 `round(estimateTokens(history) × ratio)`、`pct` = 它 ÷ 窗口、
227
+ * `window` = `providerSpec(provider).context`——同屏「context X% Yk」必须同尺,两值各自算一次就是两把尺;
228
+ * `window` 随读带出,消费面不再二次派生同一个量。
229
+ * `ratio` **无缺省**:忘传 ⇒ `estimateTokens × undefined` = `NaN` ⇒ `tokens` / `pct` 皆 `NaN`。三面显示门
230
+ * 都把非正值读作「不显示」(CLI `ctxPct > 0` · VSC 分子为假回 `null` · 桌面 `usageFrameWanted` 不发帧)
231
+ * ⇒ 读数**缺席**(不会是错数);给缺省 1 则相反——忘传处静默显示未校准读数,看着完全正常而差一整个校准比。
232
+ * 两种形状里只有后者会被当作事实读走,所以宁可缺席。
233
+ * 空历史 ⇒ 0(状态行的 `ctxPct > 0` 显示门在端侧,不入本函数面)。
234
+ */
235
+ export function historyReading(history, provider, ratio) {
236
+ const window = providerSpec(provider).context
237
+ const tokens = Math.round(estimateTokens(history ?? []) * ratio)
238
+ return { tokens, pct: Math.round((tokens / window) * 100), window }
239
+ }
240
+
241
+ /** 状态行百分比薄转口(名面与 export 不变):`historyReading` 的 `pct`——与 stats ∥ CLI ∥ VSC **同源**
242
+ * (各处全是这一实现点的投影,端侧零自算式)。第三参 `ratio` 同 `historyReading`:无缺省。 */
243
+ export function historyPercent(history, provider, ratio) {
244
+ return historyReading(history, provider, ratio).pct
245
+ }
246
+
247
+ /** 估算校准比(§6.17)——`agent._tokRatio` 的**唯一**读出点(中性值 1 的唯一出处):
248
+ * 无样本 ⇒ 1。合法的 1 只有两种:新会话无样本(`_tok` 为 `null`)· 打开态槽无 `estimateSample`;
249
+ * agent 不在场(`undefined` / `null`)也回 1,该档 = 未校准读数,由消费面显式断言,不靠默认值蒙过。 */
250
+ export function tokenRatioOf(agent) {
251
+ return agent?._tokRatio ?? 1
252
+ }
253
+
254
+ /** 槽样本 ⇒ 校准比(§6.17.1 链二读数面):判定与 `restoreTokenSample`(恢复面)同一份 `validSample`,
255
+ * 好样本 ⇒ `max(1, num / den)`,坏样本 ⇒ `1`(下界 1 与 `recordTokenSample` 同——偏差方向已知是估算偏低)。 */
256
+ export function sampleRatio(sample, provider) {
257
+ const s = validSample(sample, provider)
258
+ return s ? Math.max(1, s.num / s.den) : 1
183
259
  }
184
260
 
185
261
  /** 陈旧工具输出门槛(§6.16.3 判据③):低于门槛的替换是净增 token(stub 本身有长度)——常量与合格集同住本档。 */
@@ -1,9 +1,9 @@
1
1
  Run an independent review of your work. `type` is REQUIRED — one of exactly two values, no default and no silent fallback.
2
2
 
3
- - `type="design"` — design review: reviews design / requirement documents BEFORE implementation. Pass `documents=[...]` (the explicit list), plus `batchDoc` when a batch record is in flight.
3
+ - `type="design"` — design review: reviews design / requirement documents BEFORE implementation. Pass `documents=[...]` and `batchDoc` — the batch record this review belongs to. **`batchDoc` is required**: a design review without it is refused before launch, because the token it would mint has no batch to close it.
4
4
  - `type="code"` — code review: reviews the code you changed AFTER implementation. Pass `paths=[...]` to scope files/directories; `documents=[...]` adds acceptance-criteria context.
5
5
 
6
- `documents` — one rule: a relative path resolves against the session project root (e.g. `docs/core/design/ADVISOR-GUARDS.md`); an absolute path is used as-is.
6
+ `documents` — one rule: a relative path resolves against the session project root (e.g. `docs/<area>/<name>.md`); an absolute path is used as-is.
7
7
 
8
8
  The relative form needs the session anchor to be inside a project. When the anchor is a workspace root (several projects below it, no manifest of its own) there is no single project root to resolve against, and a relative path is refused with `no-project-root` — write the absolute path there.
9
9
 
@@ -13,3 +13,5 @@ Notes:
13
13
  - If a hunk's context matches multiple locations it is rejected — add more surrounding context lines
14
14
  - Deleting files is not supported — use the delete tool
15
15
  - For single-file edits, edit is simpler; for full rewrites, write is simpler
16
+
17
+ **`ledger`** — the in-flight ledger row this call serves: `"#N"` or `"N"`. In engineering mode the parent agent must pass it — a call that does not say which row it serves is refused.
package/tool-docs/bash.md CHANGED
@@ -37,3 +37,7 @@ Notes:
37
37
  - After commands that change files (git checkout, npm install, etc.), repo_outline and code_search may be stale — re-run them to get current results.
38
38
  - Prefer read/glob/grep/ls for file operations inside the project — file tools and bash reach the same paths (no directory restriction).
39
39
  - NEVER use bash to write or modify files (echo/sed/printf > file, cat << EOF, etc.). Use write/edit/insert_after/apply_patch instead — they handle encoding, escaping, and EOL conventions correctly.
40
+
41
+ **`ledger`** — the in-flight ledger row this call serves: `"#N"` or `"N"`. In engineering mode the parent agent must pass it — a call that does not say which row it serves is refused, and read-only commands are judged the same way (no command-line classification).
42
+
43
+ **`project`** — the project this call belongs to: the absolute path of the project root (the directory carrying PROJECT-MANIFEST.json). Engineering mode: pass it together with `ledger`. Pure declaration: it does not change the execution directory.
@@ -1 +1,85 @@
1
- Batch-record lifecycle tool (一段一作者 — six append-only sections, one author each). actions: create (depth-0 only — write a new six-section skeleton record; pass path + topic + source, plus optional ledger / board that fill the record's 台账 header line at creation) · append (add text to the END of YOUR section — existing lines are never rewritten or deleted) · status (update YOUR section's `**状态行**:` line — the value must carry exactly ONE legal keyword of your section's STATUS_WORDS entry, decorated only by surrounding symbols / a trailing ISO date / whitespace; prose goes to the separate `note` field, which lands as a parenthetical). Writing §1 已收口 closes the record and runs the closeout-chain gate: §2 / §3 / §4 / §6 must each carry content, or §1 declares it inapplicable with `> 不适用:§N —— <reason>` (§2 / §3 / §4 only; §5 is exempt; §6 never). Reopening a frozen record = §1 → 进行中 with `note` = the reason: the tool rewrites the §1 status line and appends a 「重开记录」 line — no other existing line is touched. · close (depth-0 only — freeze the record: §1 → 「已收口 <date>」, every further write is refused except the reopen conjunction; runs the same closeout-chain gate as status — an incomplete chain is refused, naming the missing sections). Closing a record — the close action, or status writing §1 已收口 — also writes the ledger: this record's unsettled rows (task_book → this record, status 待讨论 / 待设计 / 在途, trigger ≠ 认账不排期 — a policy call leaves its row untouched, and a row already 条件 is skipped with zero writes) get trigger → 条件, with the resume sentence 「原批 <record> 已收口,须另起批次接手」 landing in resume_when (a non-empty resume_when keeps its value — the sentence is appended to evidence instead); each changed / skipped row is named in a `[ledger reanchor]` receipt block (a ledger write failure — busy store / no derivable project root — is named there too and never blocks the freeze). Identity fixes what you may write (段白名单): main agent (depth 0) — create/close; append/status domains are mode-dependent: engineering — append §1/§4/§6, status §1; normal — append §1/§2/§4/§5/§6, status §1/§2/§5 (the main agent holds the designer seat and records direct implementation); eng-designer — §2; eng-coder — §5; a normal-mode coder bound via batchDoc — §5; design review (review binding) — §3. A write outside your own section is refused. Skeleton placeholders (dead literals — `#<编号>` / `<板块>` and the older `<BATCH-ID>` / `<讨论来源>`) in the record header's structural lines block append/status until filled: create with ledger / board fills them at creation; if omitted, the main agent fills the header by file edit right after create (citing these literals in a section's prose is fine — only the header is checked); `<§N 模板占位:…>` template lines are legal and never blocked. Target record: sub-agents and design reviews have NO path parameter in practice — the record arrives via the spawn binding / the review instance key (passing path is refused); depth-0 MAY pass path (若传则须可读), omitting it picks the unique in-flight record (0 or ≥2 in flight ⇒ refuse — pass path). A design review's append is stamped by the tool with a `### 轮次 N(评审子代理)` heading — N is tool-counted; do not write your own heading (it would be dropped). Credential values are stripped mechanically before writing (never write a token or designId value). Document-check gate: appending §2 / §5 as a sub-agent and closing a record (or setting §1 to 已收口) run the doc-check gate before the write — REFUSED ⇒ the write is refused and the record's bytes stay unchanged (do not close a record after a refused append); ERROR ⇒ a closeout is refused while an append still lands with an ERROR receipt line; no PROJECT-MANIFEST.json ⇒ the gate does not run and the receipt says so. Failures are hard and visible (no silent fallback): if a write is refused or fails, say so in your report — “§× 未写入”.
1
+ Batch-record lifecycle tool (一段一作者 — six sections, one author each; the author has two actions on that one section: append adds text at its end, edit rewrites its lines in place; the section's status line has a single writer, `status`).
2
+
3
+ actions: create (depth-0 only — write a new six-section skeleton record; pass path + topic + source, plus optional ledger / board that fill the record's 台账 header line at creation;
4
+
5
+ ledger:"#N" takes over an existing row, and omitting ledger opens a new row only when no unsettled row exists on the same board — otherwise create is refused with the candidate list and you pass newDebt:"<why this is a new debt>" or ledger:"#<id>")
6
+
7
+ · append (add text to the END of YOUR section — the lines already there are left byte-for-byte as they stand)
8
+
9
+ A §3 append must carry exactly one verdict on its own line — `VERDICT: pass` or `VERDICT: changes-required`; the localized whole-line forms count too (`结论:通过` / `结论:不通过`, and their 需修改 variants), and the Chinese forms take an optional trailing 。.
10
+
11
+ A §3 write that reads as no verdict line, or as both lines at once, is refused with nothing written — no round number is consumed, so resend the same text with the verdict line added.
12
+
13
+
14
+ · edit — replace one anchored range inside YOUR OWN section (any section you may append to; your identity decides which)
15
+
16
+ oldText must match exactly once, byte for byte (line endings included), inside the section body; newText replaces that range, and an empty newText deletes it.
17
+
18
+ Refused with nothing written: a blank oldText, zero hits, two or more hits (the matching lines are named), an anchor crossing the section boundary, and any anchor or replacement that touches or introduces a `**状态行**:` line (the status line has one writer: `status`).
19
+
20
+ On §3 an incoming `### 轮次 N(评审子代理)` line is dropped from newText — the round stamp is tool-generated only.
21
+
22
+ Two gates hang on `append` alone and do not apply to `edit`: the §5 ordering gate (an edit needs an existing anchor, so it cannot write what does not exist yet) and the §3 verdict line (that gate reads the block you append; rewriting words inside an existing block changes no machine criterion).
23
+
24
+
25
+ · status (update YOUR section's `**状态行**:` line — the value must carry exactly ONE legal keyword of your section's STATUS_WORDS entry, decorated only by surrounding symbols / a trailing ISO date / whitespace).
26
+
27
+ Prose goes to the separate `note` field, which lands as a parenthetical.
28
+
29
+ A §3 pass round sets that section's status line in the same write — `status` on §3 is an optional rewrite, never a required step.
30
+
31
+ Writing §1 已收口 closes the record and runs the closeout-chain gate: every section must carry content (§5 exempt) — the `不适用` declaration is retired, so any `> 不适用:§N` line in §1 is refused outright.
32
+
33
+ Reopening a frozen record = §1 → 进行中 with `note` = the reason: the tool rewrites the §1 status line and appends a 「重开记录」 line — no other existing line is touched.
34
+
35
+ · close (depth-0 only — freeze the record: §1 → 「已收口 <date>」, every further write is refused except the reopen conjunction; runs the same closeout-chain gate as status — an incomplete chain is refused, naming the missing sections).
36
+
37
+ Closing a record — the close action, or status writing §1 已收口 — also writes the ledger: this record's unsettled rows (task_book → this record, status 待讨论 / 待设计 / 在途, trigger ≠ 认账不排期 — a policy call leaves its row untouched, and a row already 条件 is skipped with zero writes) get trigger → 条件,
38
+
39
+ with the resume sentence 「原批 <record> 已收口,须另起批次接手」 landing in resume_when (a non-empty resume_when keeps its value — the sentence is appended to evidence instead);
40
+
41
+ each changed / skipped row is named in a `[ledger reanchor]` receipt block (a ledger write failure — busy store / no derivable project root — is named there too and never blocks the freeze).
42
+
43
+ Identity fixes what you may write (段白名单): main agent (depth 0) — create/close;
44
+
45
+ append/status domains are mode-dependent: engineering — append §1/§4/§6, status §1; normal — append §1/§2/§4/§5/§6, status §1/§2/§5 (the main agent holds the designer seat and records direct implementation);
46
+
47
+ edit reads the same whitelist as append — one table, two actions: the section you may append to is the section you may rewrite in place. Depth-0 gains §2 and §5 on edit in normal mode for the reason it gains them on append (the main agent holds the designer and implementer seats).
48
+
49
+ eng-designer — §2; eng-coder — §5; a normal-mode coder bound via batchDoc — §5; design review (review binding) — §3.
50
+
51
+ A write outside your own section is refused.
52
+
53
+ Skeleton placeholders (dead literals — `#<编号>` / `<板块>` and the older `<BATCH-ID>` / `<讨论来源>`) in the record header's structural lines block append/status until filled:
54
+
55
+ create with ledger / board fills them at creation; if omitted, the main agent fills the header by file edit right after create (citing these literals in a section's prose is fine — only the header is checked);
56
+
57
+ `<§N 模板占位:…>` template lines are legal and never blocked.
58
+
59
+ Target record: sub-agents and design reviews have NO path parameter in practice — the record arrives via the spawn binding / the review instance key (passing path is refused); depth-0 MAY pass path (若传则须可读), omitting it picks the unique in-flight record (0 or ≥2 in flight ⇒ refuse — pass path).
60
+
61
+ A design review's append is stamped by the tool with a `### 轮次 N(评审子代理)` heading — N is tool-counted; do not write your own heading (it would be dropped).
62
+
63
+ Credential values are stripped mechanically before writing (never write a token or designId value).
64
+
65
+ Document-check gate: every append and every edit runs it before the write — any section (§1–§6), depth-0 and sub-agent and design review alike — and so does closing a record (or setting §1 to 已收口).
66
+
67
+ On `edit` the judged set splits by hit class: paragraph / line length is judged on the newText you pass; the anchor classes (档缺 / 节缺 / 引缺) are judged on the whole record as it will stand after your edit.
68
+
69
+ A dead citation anywhere in the record therefore refuses an unrelated edit — and replacing that very line is the way through, which is why the record is judged as it will stand, not as it sits.
70
+
71
+ On append it judges three things: the text you are appending now, the markdown files already touched in this run, and the record itself — but only the first two can refuse the write.
72
+
73
+ A hit in the text you are appending or in a touched file ⇒ REFUSED: nothing lands, the record's bytes stay unchanged (do not close a record after a refused append).
74
+
75
+ A hit in a section that has already landed is named in the receipt with its section number and author and never blocks the write — another author's landed lines cannot be rewritten here; the close face is where it is enforced.
76
+
77
+ On close (and on status writing §1 已收口) every hit in the range refuses, each one named as `§N(作者:…)` when it sits inside the record.
78
+
79
+ An append receipt reads `[doc-check gate] DOC-CHECK GATE: PASS · 范围 N 档` — N = the documents actually judged, the record itself always one of them.
80
+
81
+ ERROR ⇒ a closeout is refused while an append still lands with an ERROR receipt line;
82
+
83
+ no PROJECT-MANIFEST.json ⇒ the gate does not run and the receipt says so.
84
+
85
+ Failures are hard and visible (no silent fallback): if a write is refused or fails, say so in your report — “§× 未写入”.
@@ -1,4 +1,6 @@
1
- Drive a real browser the tool starts itself: a headless (default) or visible system Edge/Chrome in an isolated profile under `~/.thincoder/browser/profile`. Multi-step and stateful: `navigate`, `snapshot`, then act on the element references the snapshot returns. Not a takeover — it never attaches to a browser you already have open and needs no extension.
1
+ Drive a real browser the tool starts itself: a headless (default) or visible system Edge/Chrome in an isolated profile under `~/.thincoder/browser/profile`.
2
+
3
+ Multi-step and stateful: `navigate`, `snapshot`, then act on the element references the snapshot returns. Not a takeover — it never attaches to a browser you already have open and needs no extension.
2
4
 
3
5
  **Route to browser instead of fetch:**
4
6
  - content that only exists after JS runs, or behind interaction (login, click, form, scroll) → browser
@@ -1 +1,3 @@
1
- Search the project's source code for relevant code. Use this to find functions, classes, or code patterns across the codebase. Returns matching code chunks with file paths and line numbers. Prefer doc_search for the intended design (design docs, conventions); code_search for the implementation as written. For what was said in a session (conversation/chat history), use read_history.
1
+ Search the project's source code for relevant code. Use this to find functions, classes, or code patterns across the codebase. Returns matching code chunks with file paths and line numbers.
2
+
3
+ Prefer doc_search for the intended design (design docs, conventions); code_search for the implementation as written. For what was said in a session (conversation/chat history), use read_history.
@@ -1,5 +1,9 @@
1
1
  Start a parallel multi-model consultation (会诊) for a hard problem you are stuck on (repeated failures, no headway). Call it directly when the user asks for 会诊 / consult — an explicit user request applies even if you are not 'stuck'.
2
2
 
3
- Several configured models (agent.consultModels — at most 5; an unconfigured pool is an explicit error) analyze the same problem INDEPENDENTLY and in parallel. Non-blocking: returns immediately with a consult id; the consultants keep running in the background across turns. When EVERY model has replied (or failed), the full verdict text is delivered to you automatically — as a system reminder at the next run start, or digested on its own while the session is idle — judge and adopt each opinion yourself with your own tools (opinions are suggestions, not gates). To stop a session early (user changed their mind / wants the tokens back), call consult_stop(id) — a stopped session delivers no digest.
3
+ Several configured models (agent.consultModels — at most 5; an unconfigured pool is an explicit error) analyze the same problem INDEPENDENTLY and in parallel. Non-blocking: returns immediately with a consult id; the consultants keep running in the background across turns.
4
4
 
5
- The brief should carry the symptom, what you already tried (the failure trail), and entry-point files; do NOT paste raw error logs — consultants pull the main session history themselves via their main_history tool.
5
+ When EVERY model has replied (or failed), the full verdict text is delivered to you automatically — as a system reminder at the next run start, or digested on its own while the session is idle — judge and adopt each opinion yourself with your own tools (opinions are suggestions, not gates).
6
+
7
+ To stop a session early (user changed their mind / wants the tokens back), call consult_stop(id) — a stopped session delivers no digest.
8
+
9
+ The brief should carry the symptom, what you already tried (the failure trail), and entry-point files; do NOT paste raw error logs — consultants pull the main session history themselves via their main_history tool.
@@ -1,3 +1,5 @@
1
- Cancel a still-running consultation session (会诊) — the user changed their mind, the problem resolved, or you want the tokens back. Aborts every consultant that is still running; a stopped session delivers NO digest (its already-collected partial replies are dropped). Sessions that finished on their own are no longer cancellable — their verdict text is delivered automatically.
1
+ Cancel a still-running consultation session (会诊) — the user changed their mind, the problem resolved, or you want the tokens back. Aborts every consultant that is still running; a stopped session delivers NO digest (its already-collected partial replies are dropped).
2
+
3
+ Sessions that finished on their own are no longer cancellable — their verdict text is delivered automatically.
2
4
 
3
5
  Returns JSON {abandoned: <pending count>, cancelled: true} — or {error: "unknown consult id"} (already finished/cancelled).
@@ -1,4 +1,5 @@
1
1
  Delete a file, or an empty directory. Use when the agent created a temporary or junk file or empty directory that should be cleaned up, or when the user explicitly asks to delete something.
2
+
2
3
  Refuses to delete git-tracked files as a safety measure — tracked files should be edited or removed via bash with explicit user confirmation.
3
4
 
4
5
  **Route to delete instead of bash:** `del file` / `rm file` → delete (single files); an empty directory → delete (empty directories only). Non-empty directories have no delete path — stop and report to the user instead of hand-rolling a recursive shell delete.
@@ -13,3 +14,5 @@ Notes:
13
14
  - Directories: empty ones only — a non-empty directory is refused (a natural fuse; nothing recursive is ever removed); delete nested empty trees bottom-up
14
15
  - Symbolic links are removed as links — the link itself is deleted, never followed (its target is untouched)
15
16
  - Returns `Deleted <path>` (or `Error: ...` on failure / tracked / non-empty refusal).
17
+
18
+ **`ledger`** — the in-flight ledger row this call serves: `"#N"` or `"N"`. In engineering mode the parent agent must pass it — a call that does not say which row it serves is refused.