@thincoder/core 0.9.5 → 0.10.2

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 (216) hide show
  1. package/CHANGELOG.md +49 -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 +12 -12
  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 +22 -12
  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 +54 -296
  20. package/agent/family-tools.mjs +5 -5
  21. package/agent/helpers.mjs +53 -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 +15 -11
  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 +89 -29
  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 +9 -9
  36. package/agent-tools/advisor-settle.mjs +12 -12
  37. package/agent-tools/advisor.mjs +36 -41
  38. package/agent-tools/async-discard.mjs +44 -1
  39. package/agent-tools/async-settle.mjs +21 -18
  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 +57 -18
  43. package/agent-tools/batch-paths.mjs +52 -8
  44. package/agent-tools/batch-skeleton.mjs +19 -1
  45. package/agent-tools/batch.mjs +12 -65
  46. package/agent-tools/checkpoint.mjs +121 -0
  47. package/agent-tools/child-permission.mjs +5 -3
  48. package/agent-tools/consult.mjs +56 -56
  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 +16 -11
  54. package/agent-tools/goal.mjs +3 -10
  55. package/agent-tools/panel-blocks.mjs +2 -2
  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 +21 -265
  66. package/agent-tools/subagent-async.mjs +25 -20
  67. package/agent-tools/subagent-panel.mjs +33 -4
  68. package/agent-tools/subagent-run.mjs +36 -21
  69. package/agent-tools/subagent-scheduler.mjs +28 -27
  70. package/agent-tools/subagent-spawn.mjs +45 -14
  71. package/agent-tools/subagent.mjs +33 -53
  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 +18 -355
  77. package/attachments.mjs +125 -0
  78. package/auto-think.mjs +1 -1
  79. package/config-presets.mjs +11 -6
  80. package/config-watch.mjs +83 -0
  81. package/config.mjs +29 -12
  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/history-window.mjs +17 -2
  95. package/i18n.mjs +2 -0
  96. package/index-discover.mjs +7 -7
  97. package/ledger-cmd.mjs +34 -126
  98. package/ledger-db.mjs +10 -2
  99. package/ledger-tools.mjs +188 -0
  100. package/ledger.mjs +9 -4
  101. package/log.mjs +12 -7
  102. package/manifest-discovery.mjs +131 -0
  103. package/manifest-schema.mjs +166 -0
  104. package/manifest.mjs +18 -217
  105. package/mcp.mjs +4 -1
  106. package/memory/code-search.mjs +149 -0
  107. package/memory/code-sync.mjs +89 -230
  108. package/memory/core.mjs +47 -32
  109. package/memory/docs.mjs +63 -262
  110. package/memory/file-list.mjs +89 -0
  111. package/memory/file-walk.mjs +7 -4
  112. package/memory/memory-tool.mjs +229 -0
  113. package/memory/schema.mjs +18 -3
  114. package/memory/sweep.mjs +289 -0
  115. package/memory/sync-tail.mjs +81 -0
  116. package/memory-status.mjs +69 -0
  117. package/memory.mjs +3 -0
  118. package/model-ref.mjs +96 -3
  119. package/model-specs.mjs +16 -7
  120. package/notify-policy.mjs +51 -0
  121. package/package.json +1 -1
  122. package/peer-claims.mjs +2 -2
  123. package/peer-domains.mjs +7 -11
  124. package/peer-instances.mjs +9 -10
  125. package/process-probe-exec.mjs +187 -0
  126. package/process-probe.mjs +27 -175
  127. package/prompt-overlays.mjs +11 -10
  128. package/prompts/advisor-design.md +2 -2
  129. package/prompts/advisor-round2.md +1 -1
  130. package/prompts/advisor-round3.md +1 -1
  131. package/prompts/common.md +16 -8
  132. package/prompts/discipline-engineering.md +57 -16
  133. package/prompts/discipline-normal.md +6 -3
  134. package/prompts/persona-coder.md +0 -1
  135. package/prompts/persona-eng-coder.md +1 -5
  136. package/prompts/persona-eng-designer.md +2 -3
  137. package/prompts/persona-engineering.md +32 -11
  138. package/prompts/persona-explore.md +0 -1
  139. package/prompts/persona-normal.md +1 -1
  140. package/prompts/persona-plan.md +3 -4
  141. package/prompts/subagent-base.md +14 -0
  142. package/provider/core.mjs +11 -48
  143. package/provider/list-models.mjs +3 -0
  144. package/provider/responses-request.mjs +237 -0
  145. package/provider/responses.mjs +6 -228
  146. package/provider-flows.mjs +248 -0
  147. package/queued.mjs +90 -0
  148. package/rules.mjs +209 -2
  149. package/session-guard.mjs +27 -7
  150. package/session-lifecycle.mjs +108 -27
  151. package/session-rename.mjs +5 -1
  152. package/session-slot-claims.mjs +230 -0
  153. package/session-slot-scan.mjs +299 -0
  154. package/session-slot-verify.mjs +304 -0
  155. package/session-slot-write.mjs +74 -8
  156. package/session-slots-manifest.mjs +163 -241
  157. package/session-slots.mjs +99 -71
  158. package/session.mjs +30 -10
  159. package/shell-candidates.mjs +78 -0
  160. package/text-budget.mjs +2 -2
  161. package/think-off.mjs +49 -0
  162. package/token-ttl.mjs +15 -4
  163. package/tool-args.mjs +73 -0
  164. package/tool-docs/advisor.md +9 -0
  165. package/tool-docs/batch.md +1 -0
  166. package/tool-docs/code_search.md +1 -0
  167. package/tool-docs/consult_start.md +5 -0
  168. package/tool-docs/consult_stop.md +3 -0
  169. package/tool-docs/context.md +7 -0
  170. package/tool-docs/doc_search.md +1 -0
  171. package/tool-docs/edit.md +2 -13
  172. package/tool-docs/eng.md +1 -0
  173. package/tool-docs/execute.md +1 -9
  174. package/tool-docs/git.md +25 -51
  175. package/tool-docs/goal.md +6 -0
  176. package/tool-docs/ledger_add.md +1 -0
  177. package/tool-docs/ledger_close.md +1 -0
  178. package/tool-docs/ledger_count.md +1 -0
  179. package/tool-docs/ledger_query.md +1 -0
  180. package/tool-docs/ledger_update.md +1 -0
  181. package/tool-docs/main_history.md +1 -0
  182. package/tool-docs/memory.md +10 -0
  183. package/tool-docs/notify_parent.md +9 -0
  184. package/tool-docs/peer_instances.md +1 -0
  185. package/tool-docs/plan.md +5 -0
  186. package/tool-docs/process.md +7 -3
  187. package/tool-docs/read_history.md +7 -0
  188. package/tool-docs/recent_changes.md +1 -0
  189. package/tool-docs/repo_outline.md +1 -0
  190. package/tool-docs/settings.md +6 -0
  191. package/tool-docs/skill.md +5 -0
  192. package/tool-docs/subagent.md +31 -0
  193. package/tool-docs/task.md +7 -0
  194. package/tool-docs/timer.md +6 -0
  195. package/tool-docs/verify.md +1 -0
  196. package/tool-docs/wait_for.md +2 -1
  197. package/tools/bash.mjs +28 -4
  198. package/tools/edit-batch.mjs +4 -4
  199. package/tools/edit-diff.mjs +4 -23
  200. package/tools/exec-run.mjs +102 -4
  201. package/tools/execute.mjs +5 -2
  202. package/tools/file.mjs +4 -9
  203. package/tools/git-run.mjs +1 -1
  204. package/tools/git.mjs +0 -24
  205. package/tools/index.mjs +2 -1
  206. package/tools/linter.mjs +2 -1
  207. package/tools/node-child.mjs +21 -0
  208. package/tools/ops.mjs +50 -13
  209. package/tools/patch.mjs +4 -2
  210. package/tools/repomap.mjs +20 -9
  211. package/tools/shared.mjs +8 -6
  212. package/tools/web.mjs +5 -2
  213. package/tools/write-path.mjs +1 -1
  214. package/traces/trace-store.mjs +17 -14
  215. package/vision-reader.mjs +76 -0
  216. package/agent-tools/batch-segment.mjs +0 -18
@@ -2,9 +2,11 @@
2
2
  * subagent-run.mjs — async spawn 执行器 executeAsyncSpawn(2026-09-05 module-split:
3
3
  * subagent.mjs 726 > 500 硬限——execute 的 async 分支 verbatim 迁入(闭包变量参数化:
4
4
  * parent/ctx/role/args + buildSpawnChild 产物),语义零变——subagent.mjs execute 调
5
- * 用(只此一处);§20 补位/排队刷新仍来自 subagent-scheduler.mjs。
5
+ * 用(只此一处);AGENT-LOOP-SUBAGENT.md §6.9 补位/排队刷新仍来自 subagent-scheduler.mjs。
6
6
  */
7
7
 
8
+ import { createHash } from "node:crypto"
9
+ import { basename } from "node:path"
8
10
  import { ASYNC_POOL_LIMITS, poolDomainOf, poolLimitsFor, runningPoolCount } from "./subagent-async.mjs"
9
11
  import { runChildPipeline } from "./subagent-async.mjs"
10
12
  import { logEvent } from "../log.mjs"
@@ -15,6 +17,12 @@ import {
15
17
  } from "./subagent-scheduler.mjs"
16
18
  // ASYNC-RESULT-CONTAINER.md D3/D6:settle 公共收尾单点 + child signal 构建单点
17
19
  import { bindChildController, buildChildSignal, settleAsyncEntry } from "./async-settle.mjs"
20
+ import { registerTurnCapCheckpoint } from "./checkpoint.mjs"
21
+
22
+ /** #309 任务书文本摘要(12 hex——**不留全文**,NF-L3 同族):条目自携与双点留痕的标识面。 */
23
+ function taskSeal(text) {
24
+ return createHash("sha256").update(String(text ?? "")).digest("hex").slice(0, 12)
25
+ }
18
26
 
19
27
  /**
20
28
  * SUBAGENT-OBSERVE-SEND D2:注入队列回合边界消费核心——把 entry._injected 全部消息按普通
@@ -32,12 +40,12 @@ export function drainInjectedQueue(entry, agent) {
32
40
  }
33
41
 
34
42
  /**
35
- * Async branch (AGENT-LOOP.md §15 D-A1/D-A6): spawn without waiting.
43
+ * Async branch (AGENT-LOOP-SUBAGENT.md §6.7.3 D-A1/D-A6): spawn without waiting.
36
44
  * The child runs the EXACT blocking pipeline (runChildPipeline — relay /
37
45
  * turn-cap / permission / MIN_REPORT_CHARS / mergeChildMutations all unchanged),
38
46
  * but the parent does not await it: the promise is parked in _asyncSubagents and
39
47
  * consumed by the auto channel (AGENT-LOOP-SUBAGENT.md §6.7.5 — turn-end collection / suspension digest;
40
- * the check action is gone). Slot queue (§11.1 D-24a/R14 — 分域): the entry carries a
48
+ * the check action is gone). Slot queue (AGENT-LOOP-ASYNC-POOL.md §6.10 — D-24a/R14 分域): the entry carries a
41
49
  * pool domain (_pool = poolDomainOf(role)); running count < limit[its domain]
42
50
  * (agent.poolLimits — default engCoder 4 / other 4) → start now; ≥ → enqueue
43
51
  * (status "queued", position = queue index) — never rejected, never requiring the
@@ -57,7 +65,7 @@ export function executeAsyncSpawn(parent, ctx, role, args, child, input, childOp
57
65
  consumeSubagentToken(parent, id, "async spawn", role)
58
66
  const entry = {
59
67
  id, role, relayPrefix,
60
- // §11.1 D-24a/R14:池域字段(域判定单一事实源——poolDomainOf——role 枚举见
68
+ // AGENT-LOOP-ASYNC-POOL.md §6.10 D-24a/R14:池域字段(域判定单一事实源——poolDomainOf——role 枚举见
61
69
  // subagent.mjs ROLES;未知角色归 other)——running 计数/补位按域过滤。
62
70
  _pool: poolDomainOf(role),
63
71
  status: "queued", // 下面按等待态/槽位重定(避免两处判断漂移)
@@ -73,19 +81,27 @@ export function executeAsyncSpawn(parent, ctx, role, args, child, input, childOp
73
81
  // AGENT-LOOP-SUBAGENT.md §6.7.2 D-M6 (round2 #2):条目级 AbortController——cancel 定向 abort 本
74
82
  // 条目(runAgent signal 链);Ctrl+C 全停语义不变(基信号 abort 逐链传播)。
75
83
  controller: null,
76
- // §20 D-SD2 域元数据(AGENT-LOOP.md §20):running ∪ queued 条目全带——
84
+ // D-SD2 域元数据(AGENT-LOOP-SUBAGENT.md §6.9):running ∪ queued 条目全带——
77
85
  // _files(归一化绝对路径)/ _dependsOn(字符串 id)——冲突检测与补位判据
78
86
  // 的事实源;无调度参数 spawn 两字段皆空(legacy——不参与冲突检测——零改动)。
79
87
  _files: files,
80
88
  _dependsOn: dependsOn,
89
+ // #309(AGENT-LOOP-SUBAGENT.md §6.29.2——可诊断性):条目**自携**绑定档与任务书摘要
90
+ // (启动路径按条目闭包取值——入队/补位不再有第二个取值面);`child:spawn` / `child:start`
91
+ // 双点同键面留痕(基名 + 摘要,零内容)——事后可对账「哪条任务书在哪个 id 下起跑」。
92
+ _batchDoc: child?._batchDoc ?? null,
93
+ _taskSeal: taskSeal(input),
81
94
  _lastQueuedSig: null, // ⟦ev⟧queued 去重 sig(refreshQueuedTokens)
82
95
  // SUBAGENT-OBSERVE-SEND D2:父侧 send 注入队列——父 action:'send' push 消息,子
83
96
  // 回合边界经 consumeInjected 回调消费(drain + pushReal 成 user 回合);settle 收尾
84
97
  // 时仍残留 = 未投递(消息入队后子在下一回合边界前 settle)→ 附 settle 报告提示。
85
98
  _injected: [],
86
99
  }
87
- // §20 D-SD3 准入落点:等待态(依赖未满足/域冲突/depc)→ queued(waiting-deps——
88
- // 不占槽不启动——即使槽空);纯槽满(kind slot)→ 按域计数判定(§11.1 D-24a:
100
+ // 留痕面基名(零内容——§6.29.2):绑定档绝对路径 → 基名;无绑定(非工程族)⇒ null(字段不落)。
101
+ const batchDocBase = entry._batchDoc ? basename(entry._batchDoc) : null
102
+
103
+ // AGENT-LOOP-SUBAGENT.md §6.9 D-SD3 准入落点:等待态(依赖未满足/域冲突/depc)→ queued(waiting-deps——
104
+ // 不占槽不启动——即使槽空);纯槽满(kind slot)→ 按域计数判定(§6.10 D-24a:
89
105
  // runningIn(domain) < limit(domain)——跨域互不阻塞——每次入池判定时读配置)。
90
106
  const blockers = describeBlockers(parent, entry)
91
107
  if (blockers.kind === "slot") {
@@ -102,7 +118,7 @@ export function executeAsyncSpawn(parent, ctx, role, args, child, input, childOp
102
118
  // D6 buildChildSignal 单点(ASYNC-RESULT-CONTAINER.md)。
103
119
  const ctrl = new AbortController()
104
120
  entry.controller = ctrl
105
- // §20.3 站点 #10(第 24 批):hop 逐跳保 reason;#98 链结单点(interrupt 豁免面——
121
+ // AGENT-LOOP-SUBAGENT.md §6.12 站点 #10(第 24 批):hop 逐跳保 reason;#98 链结单点(interrupt 豁免面——
106
122
  // Ctrl+I 不逐链中止池内子代理)。
107
123
  bindChildController(ctrl, buildChildSignal(parent, ctx))
108
124
  // AGENT-LOOP-SUBAGENT.md §6.7.2 D-M5:turn 镜像拦截层(callbacks 包装层——在既有
@@ -118,7 +134,7 @@ export function executeAsyncSpawn(parent, ctx, role, args, child, input, childOp
118
134
  }
119
135
  return parentOnToken(t)
120
136
  }
121
- // §27 R23 D-R23c1:嵌套 wrapper 标记随镜像层透传(wrapChildCallbacks 产物带
137
+ // docs/cli/design/TUI.md §6.8.2 R23 D-R23c1:嵌套 wrapper 标记随镜像层透传(wrapChildCallbacks 产物带
122
138
  // _relayPrefix——本层重包后丢失会让异步子代理(默认 async——depth-0)内嵌 spawn
123
139
  // 的生成侧补发射失效(emitNestedChildEvent 判据)——同步复制标记保语义)。
124
140
  trackOpts.onToken._relayPrefix = parentOnToken._relayPrefix ?? null
@@ -128,7 +144,7 @@ export function executeAsyncSpawn(parent, ctx, role, args, child, input, childOp
128
144
  entry.position = undefined
129
145
  entry.startedAt = Date.now()
130
146
  // AGENT-LOOP-SUBAGENT.md §6.7.2 D-SF1(round3 #1):绑定子代理对象引用——绑定时刻 = 实际启动时
131
- // (queued 条目 spawn-ack 时刻尚无子代理对象——§20 D-SD3b);绑定对象 = 子代理
147
+ // (queued 条目 spawn-ack 时刻尚无子代理对象——§6.9 D-SD3b);绑定对象 = 子代理
132
148
  // 对象(不是 _touchedFiles 数组引用——per-run 记账在 prepareRun 重置——数组
133
149
  // 引用会陈旧——对象引用保证 status 查询时实时读——杀前一刻最新)。
134
150
  entry.childAgent = child
@@ -147,19 +163,18 @@ export function executeAsyncSpawn(parent, ctx, role, args, child, input, childOp
147
163
  ctx.callbacks?.onToken?.(relayPrefix + "⟦ev⟧async\x1e")
148
164
  // Deferred [model] emit: the TUI block is created at ACTUAL start.
149
165
  ctx.callbacks?.onToken?.(relayPrefix + "[model]" + (childProvider.model ?? ""))
150
- // Turn-cap on background children NEVER pops the continue panel (D-A3):
151
- // §15 D-A3 exception (2026-09-02 unified rule, AGENT-LOOP.md §2): in an
152
- // engineering && AUTO session the child auto-resumes — the user authorized
153
- // unattended runs, no one is at the panel. Every other tier auto-declines
154
- // and the partial-work report carries the cap reason. §18 D-E2 relies on
155
- // this exception as the turn-cap fallback for the default-async eng-coder
156
- // delivery (the internal protocol does not raise the 100-turn cap).
166
+ // #309 §6.29.2 双点留痕第二点(条目**实际启动点**——与 child:spawn 同键面):哪条任务书在哪个 id 下起跑。
167
+ logEvent("child:start", { role, id: `${role}#${id}`, batchDocBase, taskSeal: entry._taskSeal })
168
+ // Turn-cap on background children no longer auto-resumes (TURN-CAP-CONTINUE.md §1 #7 · 2026-09-26):
169
+ // a capped async child registers a parent-side checkpoint and stays suspended
170
+ // (no panel, no re-dispatch) until the parent answers send / cancel — AUTO included.
171
+ // The partial-work report carries TURN_CAP_MARK when the cap stopped the run.
157
172
  runChildPipeline(child, input, trackOpts, { ...childRunOpts, signal: entry.controller.signal, consumeInjected }, {
158
173
  parent, role, args,
159
- askContinue: () => Promise.resolve(Boolean(parent.config?.agent?.engineering && parent.autoApprove)),
174
+ askContinue: (e) => registerTurnCapCheckpoint(child, entry, { turn: e.turn, signal: entry.controller.signal }),
160
175
  })
161
176
  .then((report) => { entry.report = report })
162
- // §20.3 第 3 条合成器(第 24 批):原 message 前缀逐字保留 + 来源后缀
177
+ // §6.12 第 3 条合成器(第 24 批):原 message 前缀逐字保留 + 来源后缀
163
178
  .catch((err) => { entry.error = deathLine(err, entry.controller?.signal) })
164
179
  .finally(() => {
165
180
  // SUBAGENT-OBSERVE-SEND D3(send→settle 竞态):settle 收尾时 _injected 仍残留
@@ -187,11 +202,11 @@ export function executeAsyncSpawn(parent, ctx, role, args, child, input, childOp
187
202
  parent._asyncSubagents.set(String(id), entry)
188
203
  // LOGGING(LOGGING.md):child:spawn(async——注册即事件;status 记 queued/running 分流;
189
204
  // 实际启动由补位 start() 触发——运行中由子内 llm/tool 事件可见)
190
- logEvent("child:spawn", { role, id: `${role}#${id}`, kind: "async", status: entry.status, ms: 0 })
205
+ logEvent("child:spawn", { role, id: `${role}#${id}`, kind: "async", status: entry.status, ms: 0, batchDocBase, taskSeal: entry._taskSeal })
191
206
  if (entry.status === "queued") {
192
207
  parent._asyncQueue.push(entry)
193
208
  entry.position = parent._asyncQueue.length
194
- // §20 D-SD3b:排队 spawn 返回即建面板 waiting 块(⟦ev⟧queued 事件——spawn 侧
209
+ // §6.9 D-SD3b:排队 spawn 返回即建面板 waiting 块(⟦ev⟧queued 事件——spawn 侧
195
210
  // 发——TUI routeSubToken 消费建块/更新头;启动后 ⟦ev⟧async 转 running——同 key
196
211
  // 不重建)。refreshQueuedTokens 同时校正既有排队条目的位置/等待态头。
197
212
  refreshQueuedTokens(parent, ctx.callbacks?.onToken)
@@ -1,9 +1,9 @@
1
1
  /**
2
- * subagent-scheduler.mjs — §20 子 agent 任务调度器 + 文件域组(2026-09-05 自
3
- * subagent-async.mjs 拆分——Module Split Policy §20.9——纯迁移零行为变化——
4
- * AGENT-LOOP.md §20 D-SD1..SD5 + §21.1 D-SL1 环形死锁修正)。
2
+ * subagent-scheduler.mjs — 子 agent 任务调度器 + 文件域组(2026-09-05 自
3
+ * subagent-async.mjs 拆分——Module Split Policy——纯迁移零行为变化——
4
+ * AGENT-LOOP-SUBAGENT.md §6.9 D-SD1..SD5 + D-SL1 环形死锁修正)。
5
5
  * 内容:normalizeFileList / filesOverlap / effectiveFiles(SCHEDULER-DYNAMIC-DOMAIN
6
- * 动态域——声明 ∪ running touched)/ depInfo / describeBlockers / detectStall(§21.1
6
+ * 动态域——声明 ∪ running touched)/ depInfo / describeBlockers / detectStall(
7
7
  * P-SL2 停滞机械检测)/ queueRunnable / assertNoDepCycle / dependentLabels /
8
8
  * refreshQueuedTokens / maybeRefillAsync。
9
9
  * ASYNC_POOL_LIMITS/poolLimitsFor/runningPoolCount 回引自 subagent-async.mjs(主体保有——
@@ -24,7 +24,7 @@ import { poolLimitsFor, runningPoolCount, ASYNC_POOL_LIMITS } from "./subagent-a
24
24
  import { carrierField, getAsyncPool, tombstoneOf } from "./async-settle.mjs"
25
25
 
26
26
  // ═══════════════════════════════════════════════════════════════════════════
27
- // §20 子 agent 任务调度器(AGENT-LOOP.md §20——D-SD1..SD5 + 20.4 处置注)
27
+ // 子 agent 任务调度器(AGENT-LOOP-SUBAGENT.md §6.9——D-SD1..SD5)
28
28
  // 池条目域元数据(D-SD2:entry._files/_dependsOn——running ∪ queued 全带)、准入
29
29
  // (D-SD3:域冲突/依赖未满足 → queued 等位)、补位扫描(D-SD4:最早可启动——
30
30
  // 依赖全满足 + 域无冲突——waiting 越行不阻塞 slot 位)、释放规则(D-SD5——round2
@@ -35,14 +35,14 @@ import { carrierField, getAsyncPool, tombstoneOf } from "./async-settle.mjs"
35
35
 
36
36
  /** 文件域归一化(round1 #5——路径归一化再交集):相对 cwd 解析为绝对路径 + 去重;
37
37
  * 非字符串/空项静默跳过(声明错误 = false-negative 明示风险——v1 边界)。
38
- * §20.8 D-F1.1(2026-09-04):目录声明检测——fail-closed——尾斜杠形态 / 指向既有目录
38
+ * D-F1.1(2026-09-04):目录声明检测——fail-closed——尾斜杠形态 / 指向既有目录
39
39
  * → throw(含路径——错误字符串英文定稿)——目录声明静默绕过冲突检测的通道闭合;
40
40
  * 调用方(subagent.mjs spawn 入口)catch → 错误即工具结果(模型可见——无静默)。
41
- * 已知限制(§20.8 未编号段——评审 #4):不存在的目录声明(无尾斜杠 + 目录未创建)仍通过——不处理。
42
- * §28 R26 F-R26b(2026-09-07):父侧维护文件黑名单——归一化后 basename 全名匹配 +
41
+ * 已知限制(评审 #4):不存在的目录声明(无尾斜杠 + 目录未创建)仍通过——不处理。
42
+ * R26(2026-09-07):父侧维护文件黑名单——归一化后 basename 全名匹配 +
43
43
  * 大小写不敏感(todo.md/changelog.md 精确——路径任意层含 docs/、根、.thincoder/)——
44
44
  * 命中 → throw(fail-closed——先于调度器准入——无排队残留——错误即工具结果);
45
- * 提示列出全部违规条目——英文模板逐字定稿(AGENT-LOOP.md §28)。
45
+ * 提示列出全部违规条目——英文模板逐字定稿(AGENT-LOOP-SUBAGENT.md §6.9「父侧文件拦截(R26)」)。
46
46
  * M5 F6(2026-09-17):黑名单随同迁入 spawn-gates.mjs(rejectEngineeringFilePaths——族 = ledger.db / CHANGELOG + scripts/**;老台账 md 族随 M2 退役)——本函数只留调用(错误文案逐字保留)。 */
47
47
  export function normalizeFileList(files, cwd) {
48
48
  const out = []
@@ -102,7 +102,7 @@ export function effectiveFiles(e) {
102
102
  }
103
103
 
104
104
  /**
105
- * §20 依赖终态查询(单点事实——池条目 / pending(挂起期 settle 移交——注入前)/
105
+ * AGENT-LOOP-SUBAGENT.md §6.9 依赖终态查询(单点事实——池条目 / pending(挂起期 settle 移交——注入前)/
106
106
  * 终态墓碑(自动通道注入消费——consumed;取消/失败——D-SD5 分支)):
107
107
  * - ok = settle 成功(报告已产出)/ consumed(自动通道注入消费——T-SD14 视为满足)
108
108
  * - pending = running/queued 未终态(等启动/等完成)
@@ -131,7 +131,7 @@ export function depInfo(parent, id) {
131
131
  return { state: "unknown", role: null }
132
132
  }
133
133
 
134
- /** §20 等待态派生(无存储——refill/status/面板/spawn 返回同一事实源)。kind:
134
+ /** AGENT-LOOP-SUBAGENT.md §6.9 等待态派生(无存储——refill/status/面板/spawn 返回同一事实源)。kind:
135
135
  * - slot = 无阻塞(依赖全满足 + 域无冲突)——纯槽满等位(可启动——等 slot)
136
136
  * - wait = 依赖未完成 / 域冲突(running ∪ queued——D-SD3 同界——self 除外)
137
137
  * - depc = 依赖取消/失败(round2 #3——非 AUTO 锁住——需父显式处置;AUTO 视为可启动)
@@ -159,7 +159,7 @@ export function describeBlockers(parent, entry) {
159
159
  const hitDeclared = filesOverlap(myFiles, e._files ?? [])
160
160
  const hit = hitDeclared ?? filesOverlap(myFiles, effectiveFiles(e))
161
161
  if (!hit) continue
162
- // §21.1 D-SL1.2(环形死锁修正——与 queueRunnable 同界——展示一致):后入
162
+ // AGENT-LOOP-SUBAGENT.md §6.9 D-SL1.2(环形死锁修正——与 queueRunnable 同界——展示一致):后入
163
163
  // 者(spawn 序晚于我——数字 id 比较)不列——只列"会真正阻断我的"(running
164
164
  // 任意序 + 先入 queued);列后入者 = 误导"等一个其实等不到的人"。
165
165
  if (e.status === "queued" && Number(e.id) > Number(entry.id)) continue
@@ -176,7 +176,7 @@ export function describeBlockers(parent, entry) {
176
176
  return { kind: "slot", detail: "" }
177
177
  }
178
178
 
179
- /** §21.1 P-SL2(D-SL2——混合边环形等待停滞机械检测——AGENT-LOOP.md §21.1 扩展注):
179
+ /** P-SL2(D-SL2——混合边环形等待停滞机械检测——AGENT-LOOP-SUBAGENT.md §6.9):
180
180
  * 判据(收窄——零误报优先——宁可漏报不可误打断):池内 running = 0 && queued ≥ 2 &&
181
181
  * 无 dependency-cancelled 标记条目 && 每 queued 的 blocker(files 冲突者 + 未 settle
182
182
  * 依赖目标——与 describeBlockers/queueRunnable 同界——"真正会阻断我的"语义:files 只
@@ -185,7 +185,7 @@ export function describeBlockers(parent, entry) {
185
185
  * 条目一条阻塞链——沿首个 blocker 走到首个重复节点(闭环)——每节点自带"等谁 + 为何等"
186
186
  * 理由注(`X(reason) → Y` 形态——X 等 Y 因 reason);不满足 → null。
187
187
  * 不报(F-SL2.2 收窄判据):running 锚点存在(依赖链/文件串行正常排队);depc 滞留
188
- * (外部决策可解——§20 NF-SD 滞留有意——cancel 先入者即释放);单 queued;blocker
188
+ * (外部决策可解——§6.9 NF-SD 滞留有意——cancel 先入者即释放);单 queued;blocker
189
189
  * 外逃(unknown 依赖目标/池外条目);blocker 空(runnable——settle/cancel 驱动 refill
190
190
  * 会启动——非停滞)。自然流程中 wait 边恒指向先入者(依赖必须先前 spawn——unknown 拒 +
191
191
  * 文件冲突只阻断后入者——id 序)——混合环仅人工注入可构造(T-SD5 同族防御断言)——
@@ -239,7 +239,7 @@ export function detectStall(parent) {
239
239
  }
240
240
  // 链组装:每 queued 条目一条——沿首个 blocker 走到首个重复节点(闭环)——每节点
241
241
  // 自带"我等谁 + 为何等"段(`X(reason) → Y`——本节点 blocker 理由注于本节点旁——
242
- // 读取无歧义:X 等 Y 因为 reason——dependsOn 边不误读为目标属性——§21.1 扩展注
242
+ // 读取无歧义:X 等 Y 因为 reason——dependsOn 边不误读为目标属性——§6.9 扩展注
243
243
  // 示例同信息形态——闭环以起始节点重复闭合(末节点不再注——闭合自明)。
244
244
  const chains = []
245
245
  for (const start of queued) {
@@ -271,10 +271,10 @@ export function entryTerminal(entry) {
271
271
  return entry?.cancelled === true || entry?.done === true
272
272
  }
273
273
 
274
- /** §20 D-SD4 补位判据:依赖全满足(AUTO 下 depc 放行)+ 域无冲突(running 任意序 +
275
- * queued 先入者——§21.1 D-SL1.1 序判定:同文件串行 = 先入者先启动、后入者等先入者
274
+ /** AGENT-LOOP-SUBAGENT.md §6.9 D-SD4 补位判据:依赖全满足(AUTO 下 depc 放行)+ 域无冲突(running 任意序 +
275
+ * queued 先入者——§6.9 D-SL1.1 序判定:同文件串行 = 先入者先启动、后入者等先入者
276
276
  * ——不自锁;先入者启动后以 running 身份继续挡住后入者——self 除外)。
277
- * 已知限制(§21.1 评审 #4——与 §20 NF-SD 同语义——滞留有意义不静默):先入者被
277
+ * 已知限制(§6.9 评审 #4——与 §6.9 NF-SD 同语义——滞留有意义不静默):先入者被
278
278
  * depc 锁定时(依赖取消/失败且非 AUTO——永不自动启动),后入者滞留等它——cancel
279
279
  * 先入者即释放(父显式可清;AUTO 档 depc 视为可启动——不滞留)。 */
280
280
  export function queueRunnable(parent, entry) {
@@ -292,7 +292,7 @@ export function queueRunnable(parent, entry) {
292
292
  if (e === entry) continue
293
293
  if (e.status !== "running" && e.status !== "queued") continue
294
294
  if (!filesOverlap(myFiles, effectiveFiles(e))) continue // 动态域:声明 ∪ running touched
295
- // §21.1 D-SL1.1 序判定:queued 仅"先入者"(spawn 序早于我——数字 id 比较)阻断;
295
+ // §6.9 D-SL1.1 序判定:queued 仅"先入者"(spawn 序早于我——数字 id 比较)阻断;
296
296
  // 后入者不阻断——先入者先启动——两个 queued 同文件不再互等(环形死锁修正)。
297
297
  // 防御(评审 #3——id 形态):池条目 id 为数字递增(_subAgentCounter——已核实);
298
298
  // 异常形态 Number() 得 NaN → 比较 false → 不跳过 → 保守阻断(宁可多等——
@@ -304,7 +304,7 @@ export function queueRunnable(parent, entry) {
304
304
  return true
305
305
  }
306
306
 
307
- /** §20 D-SD5 环防御(round2 #5——自然流程不可达:unknown id 拒 + spawn 序天然无环——
307
+ /** §6.9 D-SD5 环防御(round2 #5——自然流程不可达:unknown id 拒 + spawn 序天然无环——
308
308
  * 仅人工向池注入可构造——防御断言定位):从新 spawn 的依赖集出发沿池内条目
309
309
  * _dependsOn 边做路径 DFS——路径上重复访问(可达环)→ 拒绝(A→B→A 永不自启——
310
310
  * 错误明确——T-SD5)。运行/排队条目皆可成环节点;池小(≤4 槽 + 有限队列)深度有限。 */
@@ -337,7 +337,7 @@ export function dependentLabels(parent, depId) {
337
337
  return out
338
338
  }
339
339
 
340
- /** §20 D-SD3b 排队态面板刷新(⟦ev⟧queued 事件族——TUI routeSubToken 消费):对全部
340
+ /** §6.9 D-SD3b 排队态面板刷新(⟦ev⟧queued 事件族——TUI routeSubToken 消费):对全部
341
341
  * queued 条目重算等待态并发射变化(去重 sig——kind/position/detail 全变才发)——
342
342
  * 调用点 = 一切队列突变与等待态变迁(spawn 入队 / settle 后补位与依赖转移 / cancel
343
343
  * 出队 / 自动通道消费)。position = 队列序(D-A1 既有——cancel 前移同源)。
@@ -362,12 +362,11 @@ export function refreshQueuedTokens(parent, onToken) {
362
362
  }
363
363
 
364
364
  /**
365
- * Slot-queue refill (AGENT-LOOP.md §15 D-A1/D-A6 + §20 D-SD4 + §11.1 D-24a/R14 分域):
365
+ * Slot-queue refill (AGENT-LOOP-SUBAGENT.md §6.7.3 D-A1/D-A6 + §6.9 D-SD4 + AGENT-LOOP-ASYNC-POOL.md §6.10 D-24a/R14 分域):
366
366
  * start queue heads while a running slot is free — called from every settle
367
- * (completion frees a slot) and from the turn-end collection's refill loop. §20:
368
- * 队列现可混合 waiting-deps 与 slot-queued——扫描选"依赖全满足 + 域无冲突"的最早
367
+ * (completion frees a slot) and from the turn-end collection's refill loop. 队列现可混合 waiting-deps 与 slot-queued——扫描选"依赖全满足 + 域无冲突"的最早
369
368
  * 条目启动(waiting 越行不阻塞槽位;多任务同时解除按 queued 序逐个启动)。
370
- * §11.1 D-24a(R14):槽位判定按域——条目 _pool 域内 running 数 < 该域上限才启动
369
+ * D-24a/R14(分域):槽位判定按域——条目 _pool 域内 running 数 < 该域上限才启动
371
370
  * (同域仍 4——纯单域队列行为与旧 shift 完全一致;跨域总量 8——各域独立腾槽,
372
371
  * 互不阻塞)。配置每次补位时读(poolLimitsFor——变更即生效下个补位)。
373
372
  */
@@ -397,13 +396,15 @@ export function maybeRefillAsync(parent) {
397
396
  * next = max(counter ?? 0, poolMax) + 1——取号后 counter 同步(首取号初始化)。id 作用
398
397
  * 域 = 进程内(不做槽持久化——reload 后池清块消失,新进程从 1 无冲突——设计范围边界)。
399
398
  * spawn(subagent-spawn.mjs async 分支)、escalate(escalate-async.mjs)、async-advisor
400
- * 池(advisor-async.mjs——§11.2 跨池共号)共用;executeAsyncSpawn 直读 counter(分配与
399
+ * 池(advisor-async.mjs——§6.10 跨池共号)、**后台 bash 任务池**(bash-async.mjs——#9:
400
+ * `_bgTasks` 同入扫描域——VSC 形计数器不随 run 存活,缺此扫描会与在途 bg 条目撞号)共用;
401
+ * executeAsyncSpawn 直读 counter(分配与
401
402
  * 消费同步——无 await 间隙)。CLI 两池键均为字符串(set(String(id))——解析分支防御保留)。
402
403
  * @returns {number} 全池唯一的下一 id(单调——进程内)
403
404
  */
404
405
  export function nextSubagentId(parent) {
405
406
  let poolMax = 0
406
- for (const pool of [carrierField(parent, "_asyncSubagents"), carrierField(parent, "_asyncAdvisors")]) {
407
+ for (const pool of [carrierField(parent, "_asyncSubagents"), carrierField(parent, "_asyncAdvisors"), carrierField(parent, "_bgTasks")]) {
407
408
  if (!pool || pool.size === 0) continue
408
409
  for (const k of pool.keys()) {
409
410
  const n = typeof k === "number" ? k : Number.parseInt(k, 10)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * subagent-spawn.mjs — spawn 路径装配(2026-09-05 module-split:subagent.mjs 726
3
3
  * > 500 硬限——spawn 前置 helpers(effectiveSubagentModel/resolveDesignSlot——审计块
4
- * 构造器 2026-09-22 起另立 ./audit-block.mjs)+ §20 调度参数准入(prepareScheduling)+ child 装配
4
+ * 构造器 2026-09-22 起另立 ./audit-block.mjs)+ AGENT-LOOP-SUBAGENT.md §6.9 调度参数准入(prepareScheduling)+ child 装配
5
5
  * (buildSpawnChild)verbatim 迁入(仅闭包变量参数化),语义零变;executeAsyncSpawn
6
6
  * 另在 subagent-run.mjs。subagent.mjs execute 经本文件 import 调用。
7
7
  * 2026-09-07:executeConsumeDesignAction 消费执行器(token 链终消费制——与 spawn 侧
@@ -11,12 +11,13 @@
11
11
  * (`child._spawnSystemBlock` 单点绑定——消费点 = 核 `prepareRun`),`input` 只留任务书。
12
12
  */
13
13
 
14
+ import { basename, isAbsolute, relative } from "node:path"
14
15
  import {
15
16
  createAgent,
16
17
  readonlyToolNames, escapeXml, excludeSubagentTools,
17
18
  } from "../agent.mjs"
18
19
  import { allocRelay, wrapChildCallbacks, relayPrefixOf } from "../agent/spawn-child.mjs"
19
- // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §23.3.1):子代理人读线窗口常量单源(store 零依赖)。
20
+ // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15):子代理人读线窗口常量单源(store 零依赖)。
20
21
  import { RECORD_WINDOW_MESSAGES } from "../session-store.mjs"
21
22
  import { validateDesignToken } from "./advisor.mjs"
22
23
  import { tokenExpired, removeDesignTokenSlot, reconcileEngTokensFromSlot, persistEngTokens } from "../token-ttl.mjs"
@@ -33,7 +34,35 @@ import { validateTaskBookFields } from "./spawn-gates.mjs"
33
34
  import { buildAuditBlock } from "./audit-block.mjs"
34
35
  // 批次档路径解析单源(BATCH-RECORD.md §4.15 · 台账 #287):门内解析改经叶档(读面非抛形),
35
36
  // 与 append / create 同源——门判据仍「参数在 + 路径可读」,错误文案逐字零变。
36
- import { resolveBatchReadPath } from "./batch-paths.mjs"
37
+ import { resolveBatchReadPath, batchDocBases } from "./batch-paths.mjs"
38
+ // LOGGING(LOGGING.md):#309 形三观测留痕走同一事件通道。
39
+ import { logEvent } from "../log.mjs"
40
+
41
+ /** #309 任务书 token 判据(`AGENT-LOOP-SUBAGENT.md` §6.29.3):`.md` 结尾的连续路径型字面
42
+ * (含盘符形态——裸名 `AGENTS.md` 无分隔符且非绝对,见调用点过滤)。 */
43
+ const MD_PATH_TOKEN = /[A-Za-z0-9_@.:\\/-]+\.md\b/g
44
+
45
+ /** #309 形三·task↔batchDoc 观测留痕(§6.29.3——**不阻断**,spawn 照常放行):任务书文本提及
46
+ * **他批存在档** ⇒ `logEvent` 留痕一条(键面 = 两侧基名,零任务书内容);只提绑定档 / 无路径型
47
+ * `.md` 字面 ⇒ 零事件。token 逐条经 `resolveBatchReadPath` 解析(路径单源),命中 = 可读 ∧
48
+ * 落基底内 ∧ ≠ 绑定档(同一档多 token 按解析后绝对路径去重)。 */
49
+ function logBatchDocRefs(parent, role, task, boundAbs) {
50
+ const cwd = parent?.cwd ?? process.cwd()
51
+ const text = typeof task === "string" ? task : ""
52
+ const key = (p) => (process.platform === "win32" ? p.toLowerCase() : p)
53
+ const bases = batchDocBases(cwd) // 歧义锚 ⇒ 候选项目基底并集(#828——入基底判据覆盖面随之对齐)
54
+ const seen = new Set()
55
+ for (const m of text.matchAll(MD_PATH_TOKEN)) {
56
+ const token = m[0]
57
+ if (!/[/\\]/.test(token) && !isAbsolute(token)) continue // 裸名(AGENTS.md 形态)不取
58
+ const abs = resolveBatchReadPath(cwd, token)
59
+ if (!abs || key(abs) === key(boundAbs ?? "")) continue // 不可读 / 就是绑定档 ⇒ 零事件
60
+ const inBase = bases.some((b) => { const r = relative(b, abs); return r && !r.startsWith("..") && !isAbsolute(r) })
61
+ if (!inBase || seen.has(key(abs))) continue // 他 .md 引用(设计档 / 需求档等)/ 同档重复
62
+ seen.add(key(abs))
63
+ logEvent("child:batchdoc-ref", { role, batchDocBase: basename(boundAbs ?? ""), refBase: basename(abs) })
64
+ }
65
+ }
37
66
 
38
67
  /**
39
68
  * Effective subagent model override for a role (CLI parity shared with VS Code):
@@ -76,7 +105,7 @@ export function resolveDesignSlot(parent, designIdArg) {
76
105
  const rec = reconcileEngTokensFromSlot(parent)
77
106
  if (rec && rec !== slots) slots = rec
78
107
  }
79
- // F2d (§29.1): both refusal branches carry the HELD id list — after a
108
+ // F2d: both refusal branches carry the HELD id list — after a
80
109
  // persistence restore the parent has no digest to look the id up in; the error
81
110
  // list is the only discovery path (ids are not credentials — the token is).
82
111
  const size = slots instanceof Map ? slots.size : 0
@@ -145,14 +174,14 @@ export function executeConsumeDesignAction(args, ctx) {
145
174
  return `design slot consumed — designId ${designId ?? "(single-design session)"} is closed out; a further eng-coder spawn for this design is mechanically rejected, and any new work (including deviation fixes) requires a fresh advisor(type='design') review and token.`
146
175
  }
147
176
 
148
- // ── §20 spawn 调度参数准入(AGENT-LOOP.md §20 D-SD1/D-SD3 + 20.4 round2 #5/#7)──
177
+ // ── spawn 调度参数准入(AGENT-LOOP-SUBAGENT.md §6.9 D-SD1/D-SD3)────────────
149
178
  // files/dependsOn 声明即契约(v1:不做任务书文本自动解析——不可靠)。缺省(两者皆
150
179
  // 缺)= 既有语义零改动(不参与冲突检测/无校验——legacy spawn 零开销直通)。
151
180
  // 校验序:参数形态 → 依赖 unknown id(非 consumed 墓碑——T-SD10)→ 依赖环可达
152
181
  // (T-SD5——防御断言:自然流程不可达)→ 等待态判定。判定结果:wait/depc 阻塞 →
153
182
  // async 入 queued 等位(spawn 返回带 reason——D-SD3b);**sync spawn(async:false)
154
183
  // 命中阻塞 → 明确错误——不队列化 sync——sync 语义零变更(round2 #7——T-SD13)**。
155
- /** §20 准入(参数化提取——2026-09-05 module-split):返回归一化 { files, dependsOn }。
184
+ /** 准入(参数化提取——2026-09-05 module-split):返回归一化 { files, dependsOn }。
156
185
  * filesRaw 目录声明 fail-closed(检测器 throw → 错误即工具结果 JSON)。 */
157
186
  export function prepareScheduling(parent, filesRaw, dependsRaw, wantAsync) {
158
187
  const files = []
@@ -192,7 +221,7 @@ export function prepareScheduling(parent, filesRaw, dependsRaw, wantAsync) {
192
221
  }
193
222
 
194
223
  /**
195
- * §20 准入通过后的 child 装配(2026-09-05 module-split——自 execute 参数化提取,
224
+ * AGENT-LOOP-SUBAGENT.md §6.9 准入通过后的 child 装配(2026-09-05 module-split——自 execute 参数化提取,
196
225
  * 原 318-488 段 verbatim——语义零变)。副作用保留:relay 取号(sync 支 allocRelay——
197
226
  * `[model]` 出生声明归 SYNC-CANCEL 单点,#133)/ 子代理 _logId / _engTaskInput 携带 /
198
227
  * _spawnSystemBlock 绑定(台账 #23——spawn 固块单点)全部在此发生。返回阻塞/异步两路径共用的
@@ -218,6 +247,8 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
218
247
  if (!given) throw refusal()
219
248
  batchDocAbs = resolveBatchReadPath(parent.cwd ?? process.cwd(), given)
220
249
  if (!batchDocAbs) throw refusal(" (given path is not a readable file)")
250
+ // #309 形三:任务书中的他批批次档引用观测留痕(不阻断——放行不受影响)。
251
+ logBatchDocRefs(parent, role, args.task, batchDocAbs)
221
252
  }
222
253
 
223
254
  // M5 F2(ENGINEERING-MODE-V2-MODULE-DELEGATION §2.2):任务书六强制字段门——
@@ -308,19 +339,19 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
308
339
  overlay,
309
340
  role,
310
341
  })
311
- // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §23.3.1):子代理人读线窗口置位(常量单源
342
+ // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15):子代理人读线窗口置位(常量单源
312
343
  // session-store.mjs)——机制复用主 agent 同路径(context.mjs pushReal 驱逐);
313
344
  // 每个 child 原先各自一份永不压缩的 _fullHistory(勘察 C2 乘数面)。
314
345
  child._historyWindow = RECORD_WINDOW_MESSAGES
315
346
 
316
347
  // BATCH-RECORD.md §2 批次档段写入通道绑定(第 4 批):工程角色(eng-coder/eng-designer)把批次档
317
- // 绝对路径记在 child 上——setup 挂载 batch_segment 时读它。无路径参数的工具靠这条
348
+ // 绝对路径记在 child 上——setup 挂载 `batch` 时读它。无路径参数的工具靠这条
318
349
  // 绑定决定目标档(spawn 门已保证「参数在 + 路径可读」)。
319
350
  if (engineeringRole) child._batchDoc = batchDocAbs
320
351
 
321
352
  // Token-verified design review → child is authorized to modify files without re-reviewing
322
353
  if (role === "eng-coder") child._engDesignReviewed = true
323
- // §18 D-E3 task-domain authorization: approved design + spawn task = authorization.
354
+ // AGENT-LOOP-SUBAGENT.md §6.7.6 D-E3 task-domain authorization: approved design + spawn task = authorization.
324
355
  // The child's OWN tools skip ONLY the onPermissionRequest ask (autoApprove
325
356
  // equivalent — dispatch.mjs permission stage); every other gate (JSON parse /
326
357
  // unknown tool / planMode / design-token) still applies (T-E14). Non-eng-coder
@@ -335,7 +366,7 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
335
366
  child._engDesignId = args.designId ?? null
336
367
  }
337
368
 
338
- // §18.5 子代理零 git(D-AG1——2026-09-04 用户裁定):explore/plan 一律不注入
369
+ // §6.7.4 子代理零 git(D-AG1——2026-09-04 用户裁定):explore/plan 一律不注入
339
370
  // git 上下文——子代理证据链 = 任务书 ∪ 磁盘当前状态(read/glob/grep)∪(审计时)
340
371
  // _touchedFiles,无一项来自 git;注入的全工作区脏状态快照与任务域无关,会误导
341
372
  // 审计/探索("status 里这个文件算不算超清单?")。注入分支整体删除(B 方案
@@ -354,7 +385,7 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
354
385
  // 本行让子代理"拿到本档路径")。行文不含 summarizeEngTaskBook 的三组段 marker
355
386
  // (Docs involved / Files list / Acceptance criteria)——段匹配不受影响。仅工程角色注入。
356
387
  if (engineeringRole) spawnBlocks.push(`Batch record (batchDoc): ${batchDocAbs}`)
357
- // §18 D-E2 ③ (round4 #4, T-E13/T-E15): an eng-coder audit spawn's task book is
388
+ // AGENT-LOOP-SUBAGENT.md §6.7.6 D-E2 ③ (round4 #4, T-E13/T-E15): an eng-coder audit spawn's task book is
358
389
  // the eng-coder's OWN spawn task — mechanically kept as _engTaskInput by the
359
390
  // parent spawn and injected as the D-TS5 A2 mechanical summary (design docs /
360
391
  // affected-file list / acceptance criteria verbatim, verbose context dropped)
@@ -371,10 +402,10 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
371
402
  if (spawnBlocks.length > 0) child._spawnSystemBlock = spawnBlocks.join("\n\n")
372
403
 
373
404
  // Relay content/reasoning/tool/output to the parent TUI via the unified spawn-child
374
- // pipeline (AGENT-LOOP.md §7.2 D3). Prefix includes a unique id: parallel child agents
405
+ // pipeline (AGENT-LOOP-SUBAGENT.md §6.7.2 D3). Prefix includes a unique id: parallel child agents
375
406
  // with the same role stay independent and don't overwrite each other.
376
407
  // Format: role#id/ → onToken("coder#2/writing..."), onToolCall("coder#2/read", args)
377
- // Async id allocation (AGENT-LOOP.md §15 D-A1): reserve the relay counter at
408
+ // Async id allocation (AGENT-LOOP-SUBAGENT.md §6.7.3 D-A1): reserve the relay counter at
378
409
  // spawn time — the returned id must be stable while the item sits in the queue.
379
410
  // The [model] token (TUI block creation) is DEFERRED to actual start so queued
380
411
  // children don't paint an empty panel block ("queued 态不显示").