@thincoder/core 0.9.4 → 0.10.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 (221) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +1 -1
  3. package/abort-provenance.mjs +10 -10
  4. package/advisor/citations.mjs +5 -5
  5. package/advisor/compaction.mjs +16 -15
  6. package/advisor/loop.mjs +14 -14
  7. package/advisor/messages.mjs +6 -6
  8. package/advisor/project-context.mjs +5 -5
  9. package/advisor/repos.mjs +3 -3
  10. package/advisor/run.mjs +23 -13
  11. package/advisor.mjs +4 -4
  12. package/agent/assemble.mjs +111 -0
  13. package/agent/chat-call.mjs +68 -0
  14. package/agent/child-marks.mjs +2 -2
  15. package/agent/completion.mjs +9 -2
  16. package/agent/continue-decision.mjs +25 -0
  17. package/agent/dispatch-gates.mjs +140 -0
  18. package/agent/dispatch-run.mjs +167 -0
  19. package/agent/dispatch.mjs +55 -294
  20. package/agent/family-tools.mjs +11 -11
  21. package/agent/helpers.mjs +63 -3
  22. package/agent/live-beat.mjs +49 -0
  23. package/agent/post-turn.mjs +4 -8
  24. package/agent/record-results.mjs +23 -20
  25. package/agent/relay-prefix.mjs +2 -2
  26. package/agent/run-stages.mjs +19 -15
  27. package/agent/run-start.mjs +111 -0
  28. package/agent/setup-reminders.mjs +9 -9
  29. package/agent/setup.mjs +32 -4
  30. package/agent/spawn-child.mjs +9 -9
  31. package/agent/suspension.mjs +90 -30
  32. package/agent/timers.mjs +113 -0
  33. package/agent/turn-loop.mjs +244 -0
  34. package/agent/write-gate.mjs +103 -6
  35. package/agent-tools/advisor-async.mjs +11 -11
  36. package/agent-tools/advisor-settle.mjs +17 -17
  37. package/agent-tools/advisor.mjs +38 -43
  38. package/agent-tools/async-discard.mjs +44 -1
  39. package/agent-tools/async-settle.mjs +23 -20
  40. package/agent-tools/audit-block.mjs +7 -7
  41. package/agent-tools/bash-async.mjs +281 -0
  42. package/agent-tools/batch-lifecycle.mjs +65 -34
  43. package/agent-tools/batch-paths.mjs +186 -0
  44. package/agent-tools/batch-skeleton.mjs +19 -1
  45. package/agent-tools/batch.mjs +22 -119
  46. package/agent-tools/checkpoint.mjs +121 -0
  47. package/agent-tools/child-permission.mjs +5 -3
  48. package/agent-tools/consult.mjs +58 -58
  49. package/agent-tools/context.mjs +4 -20
  50. package/agent-tools/design-token.mjs +3 -3
  51. package/agent-tools/digest-budget.mjs +1 -1
  52. package/agent-tools/eng.mjs +5 -5
  53. package/agent-tools/escalate-async.mjs +18 -13
  54. package/agent-tools/goal.mjs +3 -10
  55. package/agent-tools/panel-blocks.mjs +3 -3
  56. package/agent-tools/parent-channel.mjs +3 -13
  57. package/agent-tools/plan.mjs +3 -4
  58. package/agent-tools/read-history.mjs +12 -28
  59. package/agent-tools/recent-changes.mjs +4 -5
  60. package/agent-tools/review-facts.mjs +1 -1
  61. package/agent-tools/settings.mjs +47 -20
  62. package/agent-tools/skill.mjs +3 -5
  63. package/agent-tools/spawn-gates.mjs +3 -3
  64. package/agent-tools/subagent-actions-query.mjs +270 -0
  65. package/agent-tools/subagent-actions.mjs +29 -273
  66. package/agent-tools/subagent-async.mjs +36 -31
  67. package/agent-tools/subagent-panel.mjs +38 -9
  68. package/agent-tools/subagent-run.mjs +44 -29
  69. package/agent-tools/subagent-scheduler.mjs +29 -28
  70. package/agent-tools/subagent-spawn.mjs +57 -25
  71. package/agent-tools/subagent.mjs +46 -66
  72. package/agent-tools/task.mjs +3 -9
  73. package/agent-tools/timer.mjs +12 -12
  74. package/agent-tools/verify.mjs +14 -13
  75. package/agent-tools.mjs +0 -1
  76. package/agent.mjs +20 -351
  77. package/attachments.mjs +125 -0
  78. package/auto-think.mjs +1 -1
  79. package/config-presets.mjs +12 -7
  80. package/config-watch.mjs +83 -0
  81. package/config.mjs +17 -4
  82. package/context-degrade.mjs +81 -0
  83. package/context-echo.mjs +88 -0
  84. package/context-push.mjs +51 -0
  85. package/context.mjs +8 -187
  86. package/conventions.mjs +110 -161
  87. package/declaration.mjs +194 -0
  88. package/embedding.mjs +3 -1
  89. package/env-ref.mjs +81 -0
  90. package/explore-distill.mjs +2 -3
  91. package/file-links.mjs +53 -0
  92. package/file-refs.mjs +103 -0
  93. package/generate-title.mjs +28 -4
  94. package/git/checkpoint.mjs +4 -8
  95. package/history-window.mjs +17 -2
  96. package/i18n.mjs +2 -0
  97. package/index-discover.mjs +7 -7
  98. package/ledger-cmd.mjs +34 -126
  99. package/ledger-db.mjs +35 -7
  100. package/ledger-migrate.mjs +299 -0
  101. package/ledger-tools.mjs +188 -0
  102. package/ledger.mjs +11 -5
  103. package/log.mjs +12 -7
  104. package/manifest-discovery.mjs +131 -0
  105. package/manifest-schema.mjs +166 -0
  106. package/manifest.mjs +18 -217
  107. package/mcp.mjs +4 -1
  108. package/memory/code-search.mjs +149 -0
  109. package/memory/code-sync.mjs +89 -230
  110. package/memory/core.mjs +47 -32
  111. package/memory/docs.mjs +63 -262
  112. package/memory/file-list.mjs +89 -0
  113. package/memory/file-walk.mjs +7 -4
  114. package/memory/memory-tool.mjs +229 -0
  115. package/memory/schema.mjs +18 -3
  116. package/memory/sweep.mjs +289 -0
  117. package/memory/sync-tail.mjs +81 -0
  118. package/memory-status.mjs +69 -0
  119. package/memory.mjs +3 -0
  120. package/model-specs.mjs +131 -66
  121. package/notify-policy.mjs +51 -0
  122. package/package.json +1 -1
  123. package/peer-claims.mjs +263 -0
  124. package/peer-domains.mjs +100 -71
  125. package/peer-instances.mjs +9 -10
  126. package/process-probe-exec.mjs +187 -0
  127. package/process-probe.mjs +27 -175
  128. package/prompt-overlays.mjs +11 -10
  129. package/prompts/advisor-design.md +2 -2
  130. package/prompts/advisor-round2.md +1 -1
  131. package/prompts/advisor-round3.md +1 -1
  132. package/prompts/common.md +19 -7
  133. package/prompts/discipline-engineering.md +57 -16
  134. package/prompts/discipline-normal.md +6 -3
  135. package/prompts/persona-coder.md +0 -1
  136. package/prompts/persona-eng-coder.md +1 -5
  137. package/prompts/persona-eng-designer.md +1 -2
  138. package/prompts/persona-engineering.md +41 -11
  139. package/prompts/persona-explore.md +0 -1
  140. package/prompts/persona-normal.md +10 -1
  141. package/prompts/persona-plan.md +3 -4
  142. package/prompts/subagent-base.md +14 -0
  143. package/provider/core.mjs +11 -48
  144. package/provider/list-models.mjs +3 -0
  145. package/provider/responses-request.mjs +237 -0
  146. package/provider/responses.mjs +6 -228
  147. package/provider-flows.mjs +248 -0
  148. package/queued.mjs +90 -0
  149. package/rules.mjs +209 -2
  150. package/session-gc.mjs +67 -6
  151. package/session-guard.mjs +27 -7
  152. package/session-lifecycle.mjs +90 -18
  153. package/session-rename.mjs +5 -1
  154. package/session-slot-claims.mjs +230 -0
  155. package/session-slot-scan.mjs +299 -0
  156. package/session-slot-verify.mjs +304 -0
  157. package/session-slot-write.mjs +74 -8
  158. package/session-slots-manifest.mjs +163 -236
  159. package/session-slots.mjs +99 -71
  160. package/session.mjs +30 -10
  161. package/shell-candidates.mjs +78 -0
  162. package/text-budget.mjs +2 -2
  163. package/think-off.mjs +49 -0
  164. package/token-ttl.mjs +15 -4
  165. package/tool-args.mjs +73 -0
  166. package/tool-docs/advisor.md +9 -0
  167. package/tool-docs/batch.md +1 -0
  168. package/tool-docs/code_search.md +1 -0
  169. package/tool-docs/consult_start.md +5 -0
  170. package/tool-docs/consult_stop.md +3 -0
  171. package/tool-docs/context.md +7 -0
  172. package/tool-docs/doc_search.md +1 -0
  173. package/tool-docs/edit.md +2 -13
  174. package/tool-docs/eng.md +1 -0
  175. package/tool-docs/execute.md +1 -9
  176. package/tool-docs/git.md +25 -51
  177. package/tool-docs/goal.md +6 -0
  178. package/tool-docs/ledger_add.md +1 -0
  179. package/tool-docs/ledger_close.md +1 -0
  180. package/tool-docs/ledger_count.md +1 -0
  181. package/tool-docs/ledger_query.md +1 -0
  182. package/tool-docs/ledger_update.md +1 -0
  183. package/tool-docs/main_history.md +1 -0
  184. package/tool-docs/memory.md +10 -0
  185. package/tool-docs/notify_parent.md +9 -0
  186. package/tool-docs/peer_instances.md +1 -0
  187. package/tool-docs/plan.md +5 -0
  188. package/tool-docs/process.md +7 -3
  189. package/tool-docs/question.md +1 -0
  190. package/tool-docs/read_history.md +7 -0
  191. package/tool-docs/recent_changes.md +1 -0
  192. package/tool-docs/repo_outline.md +1 -0
  193. package/tool-docs/settings.md +6 -0
  194. package/tool-docs/skill.md +5 -0
  195. package/tool-docs/subagent.md +31 -0
  196. package/tool-docs/task.md +7 -0
  197. package/tool-docs/timer.md +6 -0
  198. package/tool-docs/verify.md +1 -0
  199. package/tool-docs/wait_for.md +2 -1
  200. package/tools/bash.mjs +28 -4
  201. package/tools/edit-batch.mjs +10 -11
  202. package/tools/edit-diff.mjs +33 -25
  203. package/tools/exec-run.mjs +102 -4
  204. package/tools/execute.mjs +5 -2
  205. package/tools/file.mjs +11 -12
  206. package/tools/git-run.mjs +1 -1
  207. package/tools/git.mjs +0 -24
  208. package/tools/glob-dialect.mjs +3 -3
  209. package/tools/index.mjs +2 -1
  210. package/tools/linter.mjs +2 -1
  211. package/tools/node-child.mjs +21 -0
  212. package/tools/ops.mjs +50 -13
  213. package/tools/patch.mjs +4 -2
  214. package/tools/repomap.mjs +20 -9
  215. package/tools/search.mjs +2 -2
  216. package/tools/shared.mjs +9 -7
  217. package/tools/web.mjs +5 -2
  218. package/tools/write-path.mjs +1 -1
  219. package/traces/trace-store.mjs +17 -14
  220. package/vision-reader.mjs +76 -0
  221. package/agent-tools/batch-segment.mjs +0 -18
@@ -1,136 +1,27 @@
1
1
  /**
2
2
  * agent/dispatch.mjs — two-phase tool call execution
3
+ * 2026-09-28 拆分(Module Split Policy——498/499 贴 500 硬限跨档):门面谓词 ∕ 记账外提
4
+ * `./dispatch-gates.mjs`(四谓词 + logToolError + noteExecutedMutation)、单条执行体外提
5
+ * `./dispatch-run.mjs`(runPreparedItem——原 runOne 闭包,verbatim + 形参代入);
6
+ * `executeToolCalls` 签名与对外缝零改(本档导出面零变)。
3
7
  */
4
- import { logEvent, errText, headText } from "../log.mjs"
5
- import { offloadToolResult, FILE_MUTATORS } from "./helpers.mjs"
8
+ import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
6
9
  import { runHooks } from "../hooks.mjs"
7
- import { snapshotForUndo } from "../undo-stack.mjs"
8
- import { isCodePath, loadConventions } from "../conventions.mjs"
9
- // R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3b):写工具钩子——peerCollabNote(执行前冲突
10
- // 检测——软提示不阻止)+ recordPeerWrites(成功后累积本回合写足迹——回合末 flush)。
11
- import { PEER_WRITE_TOOLS, peerCollabNote, recordPeerWrites } from "../peer-domains.mjs"
12
- import { writeFileSync, mkdirSync, existsSync } from "node:fs"
13
- import { join, resolve, relative } from "node:path"
14
- import { homedir } from "node:os"
15
- // §29 fix A(AGENT-LOOP.md §29——2026-09-07):FILE_MUTATORS 的 mutation-seq 记账从
16
- // 批后提交(record-results noteMutations)移到执行成功即刻——唯一记账点(取代批后段
17
- // + agent.mjs 中断分支记账——不双计)——同消息 [写 + async advisor launch] 时 launch 前
18
- // 完成的写在 launchSeq 之前落地 → settle 不再误判 stale(§29 症状根因)。
19
- import { noteMutations } from "../agent-tools/advisor-async.mjs"
10
+ import { isCodePath, loadProjectDeclaration } from "../conventions.mjs"
11
+ import { resolve, relative } from "node:path"
20
12
  import { anyLiveDesignSlot } from "../token-ttl.mjs"
21
13
  // M4 写权门禁(模块设计 §2.1#2):冻结窗口判据组装(被审文件集 = 声明文档集 + 批次档
22
14
  // 的合流点)落 write-gate.mjs 单一权威源——本档只 import 消费(KD-M4-4 拆分点)。
23
- import { freezeWindowConflict } from "./write-gate.mjs"
24
-
25
- const ERRORS_DIR = join(homedir(), ".thincoder", "tool-errors")
26
-
27
- /**
28
- * Persist a tool error to ~/.thincoder/tool-errors/YYYY-MM-DD/HHmmss-toolName.log
29
- * Only called for actual execution failures and malformed invocations.
30
- * Skipped for intentional denials (plan mode, user reject).
31
- */
32
- function logToolError(toolName, args, error) {
33
- try {
34
- const now = new Date()
35
- const ymd = now.toISOString().slice(0, 10)
36
- const ts = now.toISOString().replace(/:/g, "").replace(/\..+/, "").replace("T", "-")
37
- const dir = join(ERRORS_DIR, ymd)
38
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
39
- const file = join(dir, `${ts}-${toolName.replace(/[/\\]/g, "_")}.log`)
40
- const entry = [
41
- `time: ${now.toISOString()}`,
42
- `tool: ${toolName}`,
43
- `args: ${JSON.stringify(args, null, 2).slice(0, 2000)}${JSON.stringify(args, null, 2).length > 2000 ? "… (truncated)" : ""}`,
44
- `error: ${error?.message ?? String(error)}`,
45
- error?.stack ? `stack:\n${error.stack}` : "",
46
- ].filter(Boolean).join("\n") + "\n"
47
- writeFileSync(file, entry, "utf8")
48
- } catch {
49
- // Log failure itself must not crash the agent
50
- }
51
- }
52
-
53
- /**
54
- * §19 action-level classification (AGENT-LOOP.md §19 D-M1): the merged subagent
55
- * tool expresses spawn (side effect) and status (read-only query) through
56
- * its `action` parameter — the tool-level readonly flag can no longer express both.
57
- * dispatch Phase-1/Phase-2 classifies per action: status behaves as readonly
58
- * (planMode pass / no permission ask / batchable), spawn keeps its non-readonly
59
- * gates, escalate runs non-readonly AND serially (the retired escalate tool had no
60
- * parallel flag — zero behavior change under the merged surface).
61
- * §19.5 cancel (19.5.2b round2 #4): CONTROL-class exemption — cancel only
62
- * stops, never starts. isSubagentControlAction feeds the SAME two gate sites as
63
- * readonly (planMode pass / no permission ask — never joins a batch approval
64
- * group / no handler → not denied — digest 内 cancel 放行).
65
- * §19.6 panel (round1 #5): view 面归只读类(同 status——planMode 放行、免
66
- * 审批、可批并行);freeze 面归控制类(同 cancel——planMode 放行、免权限审批、
67
- * 批审批不入组、digest 内放行)。freeze 存在(非空 key)即控制类——否则只读类。
68
- */
69
- function isSubagentReadonlyAction(toolName, args) {
70
- // §6 memory 工具面重构(MEMORY.md §6 D-M5):memory search/list 是只读动作——与
71
- // subagent status 同分类(planMode 放行/免审批——Phase-2 批并行只认工具级
72
- // readonly/parallel,memory 无 parallel → 按非只读串行,见 MEMORY.md §6.4 实现注)。
73
- // 动作级判定——不能按工具名(同一 memory 工具的 put/delete/clear 保持侧效门)。
74
- if (toolName === "memory") {
75
- const action = args?.action
76
- return action === "search" || action === "list"
77
- }
78
- // SETTINGS-TOOL.md(2026-09-05):settings list/get 是只读动作(memory search/list 同分类——
79
- // planMode 放行/免审批);set 保持侧效门。
80
- if (toolName === "settings") {
81
- const action = args?.action
82
- return action === "list" || action === "get"
83
- }
84
- if (toolName !== "subagent" || !args || typeof args !== "object") return false
85
- const action = args.action
86
- // §19.8: check 动作已删除——只读面仅剩 status(planMode 放行/免权限审批/可批并行)
87
- if (action === "status") return true
88
- // SUBAGENT-OBSERVE-SEND:observe = readonly 查询(同 status——digest/planMode 放行)
89
- if (action === "observe") return true
90
- // §19.6 panel view 面(freeze 缺省/空 = 视图请求——readonly;非空 freeze 归控制类)
91
- if (action === "panel" && (args.freeze === undefined || args.freeze === null || String(args.freeze) === "")) return true
92
- return false
93
- }
94
- function isSubagentControlAction(toolName, args) {
95
- if (toolName !== "subagent") return false
96
- if (args?.action === "cancel") return true
97
- // SUBAGENT-OBSERVE-SEND:send = 控制类豁免(同 cancel——父回合内显式调用即授权——
98
- // 写子输入队列属父对子轻量引导,非产品代码写——免审批、planMode 放行、digest 内放行)
99
- if (args?.action === "send") return true
100
- // §19.6 panel freeze 面(D-P3 门控在 executor——只读/控制分类在此)
101
- if (args?.action === "panel" && args.freeze !== undefined && args.freeze !== null && String(args.freeze) !== "") return true
102
- return false
103
- }
104
- /**
105
- * §2.6 token 链终消费制(2026-09-07——评审 #7d dispatch 分类):consume-design =
106
- * 非只读控制动作——planMode 拒绝(不入 readonly/control 豁免——与其他非只读动作同门)、
107
- * 免权限审批、不入批审批分组(无文件写——控制类直行——只停既有状态不起新副作用)。
108
- * 与 cancel 的不同:cancel 是控制类豁免(planMode 放行),consume-design 按设计
109
- * planMode 拒绝——故不并入 isSubagentControlAction,单独谓词只接权限豁免位。
110
- */
111
- function isSubagentConsumeDesignAction(toolName, args) {
112
- return toolName === "subagent" && args?.action === "consume-design"
113
- }
114
- function isSubagentEscalateAction(toolName, args) {
115
- return toolName === "subagent" && args?.action === "escalate"
116
- }
117
-
118
- /**
119
- * §29 fix A — 唯一记账点:FILE_MUTATORS 工具执行成功即刻记 mutation seq(abs 路径)。
120
- * 取代 record-results 批后段 + agent.mjs 中断分支的 noteMutations(不双计——中断+同批
121
- * launch 场景 seq 单计,AGENT-LOOP.md §29 T-A1i)。调用时机 = 写执行成功(非 Error 前缀
122
- * 结果——recordPeerWrites 同款门);routed(M2 ACP 客户端执行)成功同样记账。
123
- */
124
- function noteExecutedMutation(agent, tool, args) {
125
- let paths
126
- try {
127
- paths = tool.touchedPaths ? tool.touchedPaths(args ?? {}) : [args?.path]
128
- } catch { return }
129
- const abs = (paths ?? [])
130
- .filter((p) => typeof p === "string" && p)
131
- .map((p) => resolve(agent.cwd, p))
132
- if (abs.length > 0) noteMutations(agent, abs)
133
- }
15
+ import { freezeWindowConflict, batchRecordWriteConflict } from "./write-gate.mjs"
16
+ // 门面谓词 ∕ 记账(2026-09-28 拆分外提——见头注):动作级四谓词 + logToolError 住 ./dispatch-gates.mjs。
17
+ import {
18
+ logToolError, isSubagentConsumeDesignAction, isSubagentEscalateAction,
19
+ readonlyActionOf, controlActionOf, // E1(§2.5):工具面动作谓词钩子优先 ∕ 核名面谓词回落
20
+ } from "./dispatch-gates.mjs"
21
+ // 单条执行体(2026-09-28 拆分外提——见头注):runPreparedItem(原 runOne 闭包;本档两处调用点改指)。
22
+ import { runPreparedItem } from "./dispatch-run.mjs"
23
+ // B7 3a:作用域规则 JIT 注入(核单源——`../rules.mjs`;相位一末打点见下方缝注释)。
24
+ import { injectScopedRules } from "../rules.mjs"
134
25
 
135
26
  /**
136
27
  * Two-phase execution:
@@ -143,7 +34,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
143
34
  // ---- Phase 1: serial preparation ----
144
35
  // Pre-gates run per tool (parse/planMode/engineering gates); non-readonly tools
145
36
  // that REACH the permission stage are collected into one batch — a single merged
146
- // ask covers the whole toolCalls array (§16 D-B1, "approve all / one by one /
37
+ // ask covers the whole toolCalls array (D-B1, "approve all / one by one /
147
38
  // deny"). Tools stopped by a pre-gate never join the batch (review #7).
148
39
  const prepared = []
149
40
  const permPending = [] // { toolCall, tool, args } — reached the permission stage
@@ -164,7 +55,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
164
55
  continue
165
56
  }
166
57
 
167
- if (agent.planMode && !tool.readonly && !isSubagentReadonlyAction(toolCall.name, args) && !isSubagentControlAction(toolCall.name, args)) {
58
+ if (agent.planMode && !tool.readonly && !readonlyActionOf(tool, toolCall.name, args) && !controlActionOf(tool, toolCall.name, args)) {
168
59
  prepared.push({ toolCall, tool, denied: true, reason: "plan mode" })
169
60
  continue
170
61
  }
@@ -186,11 +77,11 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
186
77
  // before the design review passed. Signaled by a live design slot (design-review
187
78
  // approval — persists in the session slot, survives across turns; _engDesignReviewed
188
79
  // is eng-coder-only and reset per run). Exemptions = the non-code classes of
189
- // src/conventions.mjs (documentation and temp scratch files) — writing a design
80
+ // thincoder-core/conventions.mjs (documentation and temp scratch files) — writing a design
190
81
  // document IS the design step. Anything inside a declared code segment (default:
191
82
  // src — incl. src/prompts/*.md) is product code, not documentation, and needs a
192
- // design token. The project can declare its own code paths (.thincoder/
193
- // conventions.json) so a non-src layout is not silently exempted. Mechanically
83
+ // design token. The project can declare its own code paths (the manifest's
84
+ // `codePaths`) so a non-src layout is not silently exempted. Mechanically
194
85
  // blocks "talk then code".
195
86
  // DESIGN-TOKEN-SETTLEMENT D3(2026-09-08):资格判据 = 权威槽"任一活槽存在"
196
87
  // (anyLiveDesignSlot——查内存 Map,miss 回读槽文件——单值镜像 `_engDesignToken`
@@ -198,8 +89,8 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
198
89
  if (agent.config?.agent?.engineering && depth === 0
199
90
  && !anyLiveDesignSlot(agent)
200
91
  && FILE_MUTATORS.has(toolCall.name)) {
201
- const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
202
- const conv = loadConventions(agent.cwd)
92
+ const paths = toolTouchPaths(tool, args)
93
+ const conv = loadProjectDeclaration(agent.cwd)
203
94
  // Unknown/missing paths (non-string, e.g. no path argument) are treated
204
95
  // as code — cannot tell what they touch, so block conservatively. Known
205
96
  // paths go through the single shared classifier: a declared code segment
@@ -209,7 +100,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
209
100
  // Undeclared project → point at the declaration file (§4.3 降级可见契约).
210
101
  const convNote = conv.declared
211
102
  ? ""
212
- : ` — this path was classified as product code by the default conventions (code paths: ${conv.codePaths.join(", ")}); declare project conventions in .thincoder/conventions.json to adjust.`
103
+ : ` — this path was classified as product code by the default conventions (code paths: ${conv.codePaths.join(", ")}); declare project conventions in PROJECT-MANIFEST.json to adjust.`
213
104
  prepared.push({
214
105
  toolCall, tool, denied: true,
215
106
  reason: "engineering design gate",
@@ -219,20 +110,28 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
219
110
  }
220
111
  }
221
112
 
222
- // E(第 11 批·F17/§14.14 E-3d):D5 冻结窗口写前拦截——设计评审在途(点火 → 结算)期间,
113
+ // E(第 11 批·F17/ADVISOR-GUARDS.md §5 E-3d):D5 冻结窗口写前拦截——设计评审在途(点火 → 结算)期间,
223
114
  // 父侧对被审文件集(声明文档集 + 批次档)的写入会被拒绝:在途写使本轮结算 stale——pass 轮
224
- // = token 直接丢失(实证:第 10 批 id=20 整轮作废)。工具面 = FILE_MUTATORS(与变更记账
225
- // 同集——不记入日志的写面既不判 stale 也不拦);判据与 reviewIsStale 同源、单一权威源 =
115
+ // = token 直接丢失(实证:第 10 批 id=20 整轮作废)。工具面 = FILE_MUTATORS ∪ `file_ops`
116
+ // (E2——冻结腿覆盖面;批次档腿仍 = FILE_MUTATORS:与变更记账同集——不记入日志的写面既不判 stale 也不拦);判据与 reviewIsStale 同源、单一权威源 =
226
117
  // agent/write-gate.mjs 的 freezeWindowConflict(声明文档集腿 = inflightDesignReviewConflict
227
118
  // 同 docAbs / 同 normAbs;批次档腿 = run.batchDoc——M4 合流点,仅扫 running 未取消的设计条目)。
228
119
  // 位置:只读 / autoApprove 短路之前——审批不得绕过冻结;拒绝 = 可见 denied + 逃生门
229
120
  // (先 cancel → 改 → 重发)。
230
- if (FILE_MUTATORS.has(toolCall.name)) {
231
- let touched = []
232
- try { touched = tool.touchedPaths ? tool.touchedPaths(args) : [args.path] } catch { touched = [] }
233
- const absPaths = (touched ?? [])
121
+ if (FILE_MUTATORS.has(toolCall.name) || toolCall.name === "file_ops") {
122
+ // #327:单源谓词(同集合内两处消费共用);#309 批次档写门与 D5 冻结窗同区、共用同一路径集。
123
+ // E2(parity-b1 §2.5——缺省取核形):外门扩 `file_ops`(冻结窗覆盖面与端壳对齐——`file_ops`
124
+ // 的 move ∕ copy ∕ rename 亦为写面);**批次档写门判据集不变**(= `FILE_MUTATORS`,file_ops 不入)。
125
+ const absPaths = toolTouchPaths(tool, args)
234
126
  .filter((p) => typeof p === "string" && p)
235
127
  .map((p) => resolve(agent.cwd, p))
128
+ if (FILE_MUTATORS.has(toolCall.name)) {
129
+ const crossBatch = batchRecordWriteConflict(agent, depth, absPaths)
130
+ if (crossBatch) {
131
+ prepared.push({ toolCall, tool, denied: true, reason: "cross-batch record write", hint: crossBatch.message })
132
+ continue
133
+ }
134
+ }
236
135
  const conflict = freezeWindowConflict(agent, absPaths)
237
136
  if (conflict) {
238
137
  prepared.push({
@@ -246,7 +145,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
246
145
 
247
146
  // Readonly tools (and autoApprove — the short-circuit, unchanged for the
248
147
  // whole batch too) skip the permission stage entirely.
249
- // §18 D-E3 task-domain authorization (spawn-time): an eng-coder child's
148
+ // AGENT-LOOP-SUBAGENT.md §6.7.6 D-E3 task-domain authorization (spawn-time): an eng-coder child's
250
149
  // tools skip the permission ASK stage exactly like autoApprove — granted by
251
150
  // the parent spawn (approved design + task = authorization; subagent.mjs
252
151
  // sets _engTaskAuthorized on the child). Everything EARLIER in Phase 1
@@ -254,7 +153,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
254
153
  // — the exemption never widens what reaches this stage (round4 #3, T-E14).
255
154
  // PreToolUse hooks still run below. Non-eng-coder children keep the manual
256
155
  // parent ask (human in the loop).
257
- if (tool.readonly || isSubagentReadonlyAction(toolCall.name, args) || isSubagentControlAction(toolCall.name, args) || isSubagentConsumeDesignAction(toolCall.name, args) || agent.autoApprove || agent._engTaskAuthorized) {
156
+ if (tool.readonly || readonlyActionOf(tool, toolCall.name, args) || controlActionOf(tool, toolCall.name, args) || isSubagentConsumeDesignAction(toolCall.name, args) || agent.autoApprove || agent._engTaskAuthorized) {
258
157
  if (!(await runHooks("PreToolUse", { agent, toolName: toolCall.name, toolArgs: args }))) {
259
158
  prepared.push({ toolCall, tool, denied: true, reason: "blocked by PreToolUse hook" })
260
159
  continue
@@ -267,7 +166,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
267
166
  permPending.push({ toolCall, tool, args })
268
167
  }
269
168
 
270
- // ---- Permission stage: one merged ask for the whole batch (§16 D-B1) ----
169
+ // ---- Permission stage: one merged ask for the whole batch (D-B1) ----
271
170
  // >1 non-readonly tools in the same toolCalls array → a single
272
171
  // onBatchPermissionRequest({ tools, count }) ask; verdicts:
273
172
  // "approveAll" → batch-scope allowance (autoApprove style, NOT persistent)
@@ -292,7 +191,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
292
191
  else if (batchAllowed === false) allowed = false
293
192
  else if (callbacks.onPermissionRequest) {
294
193
  allowed = await (async () => {
295
- // D2 (AGENT-LOOP.md §7.2): announce the wait BEFORE prompting — the TUI
194
+ // D2 (AGENT-LOOP-SUBAGENT.md §6.7.2): announce the wait BEFORE prompting — the TUI
296
195
  // subagent block header flips to "等待审批" so a waiting child is visibly
297
196
  // different from a stalled one. Depth>0 only (the parent TUI shows its own
298
197
  // permission panel). turn n/max = the child's live turn counters.
@@ -322,172 +221,34 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
322
221
  }
323
222
  }
324
223
 
325
- // ---- Phase 2: order-preserving execution ----
326
- const runOne = async (item) => {
327
- if (item.error) return { ...item, result: `Error: ${item.error}`, ok: false }
328
- if (item.denied) {
329
- const reason = item.reason === "plan mode"
330
- ? "Error: plan mode is active — only read-only tools are allowed. Exit plan mode first."
331
- : item.reason === "engineering design gate"
332
- ? `Error: design review required before any file modification. ${item.hint}`
333
- : item.reason === "d5 freeze window"
334
- ? `Error: ${item.hint}`
335
- : item.reason === "denied by user"
336
- ? "Error: permission denied by user"
337
- : item.reason === "blocked by PreToolUse hook"
338
- ? "Error: blocked by PreToolUse hook"
339
- : "Error: no permission handler configured — this tool requires user approval but the current context doesn't support interaction (e.g. subagent or non-TUI mode)"
340
- return { ...item, result: reason, ok: false }
341
- }
342
- // 2026-08-31 工具顺手度(用户批准"做吧"):dispatch 拦截工具执行期间的
343
- // console.log/console.error——工具的探查/调试输出(原本只到终端、模型看不到)
344
- // 收集后附在工具结果后回显给模型。bash 工具的输出走子进程回显(onOutput),
345
- // 不走 dispatch console——拦截安全。嵌套 dispatch(subagent)各自拦截/恢复,
346
- // 捕获分离(父恢复原始后子的拦截期间父捕获停止、子恢复后父继续)——正确。
347
- // 声明在 try 之外:catch 块(异常路径)也要访问(报错前的探查输出回显)。
348
- const capturedConsole = []
349
- // LOGGING(LOGGING.md):tool:* 事件——仅真实执行(pre-gate 拦截项在下方早退分支不入事件)。
350
- // 参数值永不落盘(NF-L3——工具事件不记 args);child=子代理 id(agent._logId,spawn 时 stamp)。
351
- const toolT0 = Date.now()
352
- const toolName = item.toolCall.name
353
- logEvent("tool:call", { tool: toolName, child: agent?._logId })
354
- try {
355
- // R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3b):结构化写工具执行前查 conflicts
356
- // (命中他实例 hot 域 → 结果附软提示——决策⑥ A 不阻止;一次目录 stat——N3 度量);
357
- // 足迹累积(D-L3a——"检测+记录一次完成")延后到执行成功(实际写过的文件)。
358
- const isPeerWriteTool = PEER_WRITE_TOOLS.has(toolName)
359
- const peerNote = isPeerWriteTool ? peerCollabNote(agent.cwd, item.tool, item.args) : null
360
- // Snapshot for undo before side-effect tools (setupOutputPanel already fired in Phase 1)
361
- if (!item.tool?.readonly && item.args) {
362
- snapshotForUndo(agent, item.toolCall.name, item.args, agent.cwd)
363
- }
364
- // M2 ACP: route fs tools through the client (IDE buffer / diff review).
365
- // toolRouter returns { handled: true, result } to short-circuit execution.
366
- if (callbacks.toolRouter) {
367
- const routed = await callbacks.toolRouter(item.toolCall.name, item.args)
368
- if (routed?.handled) {
369
- const routedOk = !String(routed.result).startsWith("Error:")
370
- const routedResult = peerNote && routedOk ? `${routed.result}\n${peerNote}` : routed.result
371
- if (isPeerWriteTool && routedOk) recordPeerWrites(agent, item.tool, item.args)
372
- // §29 fix A:routed 写成功(客户端执行)同样执行期即刻记账(唯一记账点)
373
- if (routedOk && FILE_MUTATORS.has(toolName)) noteExecutedMutation(agent, item.tool, item.args)
374
- callbacks.onToolResult?.(item.toolCall.name, routedResult, item.toolCall.id)
375
- logEvent("tool:done", { tool: toolName, ms: Date.now() - toolT0, head: headText(routedResult, 200), child: agent?._logId })
376
- return { ...item, result: routedResult, ok: true }
377
- }
378
- }
379
- const origConsoleLog = console.log
380
- const origConsoleErr = console.error
381
- console.log = (...a) => capturedConsole.push(a.map(String).join(" "))
382
- console.error = (...a) => capturedConsole.push("[err] " + a.map(String).join(" "))
383
- let rawResult
384
- // ctx 对象提升为变量(§7.2.3):subagent 阻塞 execute 返回前在 ctx 上留
385
- // _subagentKey(relayPrefix 去尾)——runOne 在 execute 返回后读它作 onToolResult
386
- // 第 4 参(普通工具/错误路径无此字段——undefined 兼容既有签名)。每次工具调用
387
- // 独立 ctx——并行同名工具(批并行 runOne)各自带自己的 key,互不串扰。
388
- const toolCtx = {
389
- cwd: agent.cwd,
390
- agent,
391
- depth,
392
- signal,
393
- callbacks,
394
- // §11.2 D-24b: per-call id — the advisor tool marker-keys its launch so
395
- // recordToolResults can split async-ack accounting from sync settles.
396
- _toolCallId: item.toolCall.id,
397
- onOutput: (chunk) => callbacks.onToolOutput?.(item.toolCall.name, chunk, item.toolCall.id),
398
- onQuestion: callbacks.onQuestion,
399
- onPermissionRequest: callbacks.onPermissionRequest,
400
- }
401
- try {
402
- // SUBAGENT-OBSERVE-SEND D1(评审 #1):in-flight 当前工具记账——工具执行期间在
403
- // agent 上留 _inflightTools Set(子代理 observe 从 dispatch 状态读——卡在长工具
404
- // 调用时 history 无新回合、恰需此信号);finally 清除。批并行工具同入 Set(observe
405
- // 如实返回多个在跑工具)。主会话同样记账——无害(无人读)。
406
- const inflight = agent._inflightTools ?? (agent._inflightTools = new Set())
407
- inflight.add(toolName)
408
- try {
409
- rawResult = await item.tool.execute(item.args, toolCtx)
410
- } finally {
411
- inflight.delete(toolName)
412
- }
413
- } finally {
414
- console.log = origConsoleLog
415
- console.error = origConsoleErr
416
- }
417
- if (rawResult === undefined) throw new Error(`Tool "${item.toolCall.name}" returned undefined — all tools must return a string value`)
418
- const raw = String(rawResult)
419
- // 写成功(非 "Error:" 字符串结果)→ 足迹计入本回合集合(flush 在 finalizeAgentTurn)
420
- if (isPeerWriteTool && !raw.startsWith("Error:")) {
421
- recordPeerWrites(agent, item.tool, item.args)
422
- }
423
- // §29 fix A:FILE_MUTATORS 执行成功即刻记账(唯一记账点——取代 record-results 批后
424
- // 段 + agent.mjs 中断分支——不双计)——同批 launch 前的写在 launchSeq 之前落地 →
425
- // async advisor settle 不误判 stale(同批 launch 后写仍保守 stale——T-A2/T-24b9)。
426
- if (FILE_MUTATORS.has(toolName) && !raw.startsWith("Error:")) {
427
- noteExecutedMutation(agent, item.tool, item.args)
428
- }
429
- // Multimodal tools keep the raw result (base64 images ride the multimodal
430
- // channel); everything else offloads oversized text to disk. Flag-driven, not
431
- // name-driven (consult P3, 2026-08-30).
432
- const result = item.tool?.multimodal ? raw : await offloadToolResult(raw, item.toolCall.id)
433
- // 2026-08-31:工具执行期间捕获的 console 输出附在结果后回显(模型视野)
434
- const resultWithConsole = capturedConsole.length > 0
435
- ? `${result}\n[console during ${item.toolCall.name}]\n${capturedConsole.join("\n")}`
436
- : result
437
- // R10 L3:冲突软提示附在工具结果末尾(模型可见——不阻止写)
438
- const resultForModel = peerNote && !raw.startsWith("Error:")
439
- ? `${resultWithConsole}\n${peerNote}`
440
- : resultWithConsole
441
- callbacks.onToolResult?.(item.toolCall.name, resultForModel, item.toolCall.id, toolCtx._subagentKey)
442
- // PostToolUse hooks: fire-and-forget (result not awaited on hook failure)
443
- runHooks("PostToolUse", { agent, toolName: item.toolCall.name, toolArgs: item.args, result: raw }).catch(() => {})
444
- logEvent("tool:done", { tool: toolName, ms: Date.now() - toolT0, head: headText(resultForModel, 200), child: agent?._logId })
445
- return { ...item, result: resultForModel, ok: true }
446
- } catch (error) {
447
- // Persist to ~/.thincoder/tool-errors/ for post-mortem; only pass message to the model (stack traces confuse LLMs and may leak paths)
448
- logToolError(item.toolCall.name, item.args, error)
449
- // User interrupt (Ctrl+C / Ctrl+I) must propagate, not become a tool error:
450
- // swallowing it here would make the parent keep looping while the user
451
- // asked to stop — worst case with subagents, where the child runs its
452
- // whole turn budget and the interrupt appears to do nothing.
453
- if (signal?.aborted) throw error
454
- // LOGGING(2026-09-03 code review #4):中止先于事件——用户停不落 tool:error
455
- //(vscode execute-tools parity;阻塞子代理 child:error 同款抑制)
456
- logEvent("tool:error", { tool: toolName, ms: Date.now() - toolT0, err: errText(error, 200), child: agent?._logId })
457
- runHooks("PostToolUseFailure", { agent, toolName: item.toolCall.name, toolArgs: item.args, error }).catch(() => {})
458
- // Build contextual error: tool name + key args so the model can reason about what went wrong
459
- const ctxParts = []
460
- if (item.args.path) ctxParts.push(`path=${item.args.path}`)
461
- if (item.args.pattern) ctxParts.push(`pattern=${item.args.pattern}`)
462
- if (item.args.command) ctxParts.push(`cmd=${item.args.command.slice(0, 80)}`)
463
- const ctx = ctxParts.length > 0 ? ` [${ctxParts.join(", ")}]` : ""
464
- // 2026-08-31:异常路径同样回显捕获的 console(工具报错前的探查输出最有价值)
465
- const consolePart = capturedConsole.length > 0
466
- ? `\n[console during ${item.toolCall.name}]\n${capturedConsole.join("\n")}`
467
- : ""
468
- return { ...item, result: `Error: ${error.message}${ctx}${consolePart}`, ok: false }
469
- }
224
+ // ---- B7 3a JIT 缝(相位一末 ∕ 相位二前——原 VSC「派发前」语义;`depth === 0` 门)--------
225
+ // 作用域规则注入:打点 = `prepared` 非拒项 `{tool, args}`(即将执行集);注入源 = 本 run 尾块
226
+ // 读点缓存 `agent._rules.scoped`(核 `prepareRun` 同点写入),去重域 = 会话。子代不注入。
227
+ if (depth === 0) {
228
+ injectScopedRules(agent, agent.history, prepared.filter((p) => p.tool && p.args).map(({ tool, args }) => ({ tool, args })))
470
229
  }
471
230
 
231
+ // ---- Phase 2: order-preserving execution ----
232
+
472
233
  const results = []
473
234
  let batch = []
474
235
  const flush = async () => {
475
236
  if (batch.length === 0) return
476
- results.push(...await Promise.all(batch.map(runOne)))
237
+ results.push(...await Promise.all(batch.map((item) => runPreparedItem(item, agent, depth, signal, callbacks))))
477
238
  batch = []
478
239
  }
479
240
  for (const item of prepared) {
480
241
  // escalate action keeps the retired escalate tool's serial placement (no
481
- // parallel flag): it flushes the batch and runs alone in call order (§19 —
242
+ // parallel flag): it flushes the batch and runs alone in call order (AGENT-LOOP-SUBAGENT.md §6.7 —
482
243
  // spawn stays parallel; status classifies as readonly and batch freely).
483
244
  if (item.tool && !item.tool.readonly
484
245
  && (!item.tool.parallel || isSubagentEscalateAction(item.tool.name, item.args))) {
485
246
  await flush()
486
- results.push(await runOne(item))
247
+ results.push(await runPreparedItem(item, agent, depth, signal, callbacks))
487
248
  } else {
488
249
  batch.push(item)
489
250
  }
490
251
  }
491
252
  await flush()
492
253
  return results
493
- }
254
+ }
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * 登记册**动态**载入:`agent-tools.mjs` 静态图经 consult/subagent 族可达核 agent 栈
15
15
  * (`../agent/setup.mjs` → `../memory.mjs` → `node:sqlite`)——静态引入会破端壳 W8 契约②
16
- * (`thincoder-vscode/test/engine-floor-guard.test.mjs`:端壳静态链不得到达 `node:sqlite`)。
16
+ * (W8 契约②判据(现载体 = 批件 `docs/batches/2026-09-29-residuals-round2.test.mjs`;单测树重建时回迁端侧单测档):端壳静态链不得到达 `node:sqlite`)。
17
17
  * 核内先例 = 登记册头注「Loaded from agent.mjs via dynamic import to avoid ESM circular
18
18
  * dependencies」(本档先于 `agent/setup.mjs` 的调用点载入,同一语义)。
19
19
  */
@@ -63,7 +63,7 @@ export async function assembleFamilyTools({
63
63
  properties: {
64
64
  ...subagentTool.parameters.properties,
65
65
  role: { ...subagentTool.parameters.properties.role, ...subagentRoles },
66
- // §19: escalate 动作的候选池 = consultModels(缺省池首 / 指定 provider:model)。
66
+ // AGENT-LOOP-SUBAGENT.md §6.7: escalate 动作的候选池 = consultModels(缺省池首 / 指定 provider:model)。
67
67
  // 池装饰挂在 action 属性描述(原 escalate 工具注册时 withPool 同款意图——模型
68
68
  // 需要知道可选候选人)。escalate 在工程模式禁用——装饰只对正常模式有意义。
69
69
  action: (consultModels.length && !engineering)
@@ -77,18 +77,18 @@ export async function assembleFamilyTools({
77
77
  },
78
78
  } : subagentTool
79
79
 
80
- // §18 D-E3 + ENGINEERING-MODE.md §2.15 D(第 2 批——参数化复用,不并列第二个 IIFE):
80
+ // AGENT-LOOP-SUBAGENT.md §6.7.6 D-E3 + ENGINEERING-MODE.md §2.15 D(第 2 批——参数化复用,不并列第二个 IIFE):
81
81
  // 工程子代理(depth>0;eng-coder = 偏差审计 / eng-designer = 自己勘察)get a restricted
82
82
  // spawn channel — role enum limited to explore, NO async parameter (sync only) and action
83
- // pinned to spawn(§19 D-M3 restricted-variant action gate——escalate/check/status are
83
+ // pinned to spawn(AGENT-LOOP-SUBAGENT.md §6.7 D-M3 restricted-variant action gate——escalate/check/status are
84
84
  // refused here at the schema level too;the mechanical re-check lives in subagent.mjs
85
- // execute → the §19 action gate + gateEngCoderSpawn (spawn-child.mjs) — schema enums are
85
+ // execute → the AGENT-LOOP-SUBAGENT.md §6.7 action gate + gateEngCoderSpawn (spawn-child.mjs) — schema enums are
86
86
  // advisory, providers don't enforce them)。描述文案按父角色分流(审计 vs 勘察)。
87
87
  const engChildRole = depth > 0 && (role === "eng-coder" || role === "eng-designer") ? role : null
88
88
  const engChildSubagent = engChildRole
89
89
  ? (() => {
90
90
  const props = { ...subagentTool.parameters.properties }
91
- // §19 review hygiene: the child channel is spawn-only sync explore — drop
91
+ // AGENT-LOOP-SUBAGENT.md §6.7 review hygiene: the child channel is spawn-only sync explore — drop
92
92
  // async, the check/status params (id/n), the eng-coder token params
93
93
  // (designToken/designId are meaningless for a read-only spawn; the parent
94
94
  // spawn already carried the token) and batchDoc (an audit child derives no
@@ -130,9 +130,9 @@ export async function assembleFamilyTools({
130
130
  : null
131
131
 
132
132
  // consult 工具仅在配置时注册(consultModels 空池时注册会让模型调用后吃一个错误回合)——
133
- // §19: escalate 已并入常驻 subagent 的 action:"escalate"(无空池注册问题——动作在
133
+ // AGENT-LOOP-SUBAGENT.md §6.7: escalate 已并入常驻 subagent 的 action:"escalate"(无空池注册问题——动作在
134
134
  // 池空时返回既有错误语义,工程模式 fail-closed 在 execute 内拒绝)。
135
- // §25 D-R17a: consult_check 已退役(digest 自动注入是唯一消费通道)——consult 家族
135
+ // CONSULTATION.md §6.2 D-R17a: consult_check 已退役(digest 自动注入是唯一消费通道)——consult 家族
136
136
  // 只剩 2 工具(consult_start/consult_stop——setup 注册点与描述面同步清零)。
137
137
  const consultTools = consultModels.length
138
138
  ? [decorate?.consultStart ?? withPool(consultStartTool), decorate?.consultStop ?? consultStopTool]
@@ -155,12 +155,12 @@ export async function assembleFamilyTools({
155
155
  // the system prompt names verify (system.md) and advisor (discipline.md) — without them an
156
156
  // escalate hit "unknown tool" and fell back to bash node --check / npm test to
157
157
  // self-verify (2026-08-16 deepseek escalate diagnosis; plugin parity).
158
- // eng-coder: advisor + verify + the §18 audit-only subagent channel (D-E3).
158
+ // eng-coder: advisor + verify + the AGENT-LOOP-SUBAGENT.md §6.7.6 audit-only subagent channel (D-E3).
159
159
  // eng-designer (§2.15 D): the survey-only subagent channel alone — no advisor
160
160
  // (it does not fire reviews) and no verify (its deliverable is documents, not code).
161
161
  // BATCH-RECORD §4.3 挂载表(批次档生命周期工具化批):eng 两分支各挂主名 `batch`
162
- // (绑定段 append/status;目标 = spawn 绑定 `child._batchDoc`)——§2.20.3 的 batch_segment
163
- // 挂载形态已随单名化收口退役(过渡别名 §4.14 不入生产挂载面);depth-0 主 agent 同表挂载
162
+ // (绑定段 append/status;目标 = spawn 绑定 `child._batchDoc`)——BATCH-RECORD.md §4.2 的 batch_segment
163
+ // 挂载形态已随单名化收口退役;depth-0 主 agent 同表挂载
164
164
  // `batch`(D-BR18 扩权——create/close + append §1/§4/§6 + status §1(轮 2 裁定②:§4/§6
165
165
  // 状态面走普通文档写),目标走可选 path / 在飞扫描)。
166
166
  // SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 装配接线):子代理上行通道