@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
@@ -0,0 +1,49 @@
1
+ /**
2
+ * agent/live-beat.mjs — 存活投影心跳(拍间隔 ∕ 单拍 ∕ 起 ∕ 停 —— 幂等 · 清点)——「桌面处理流 · VSC 对齐」批 R6 上提产物。
3
+ *
4
+ * 上提源(迁移前坐标)= VSC `thincoder-vscode/src/extension/panel-messages.mjs:45`(`LIVE_HEARTBEAT_MS = 2000`)
5
+ * `:50`(单拍 `liveHeartbeatBeat`)`:62-75`(起 ∕ 停 `startLiveHeartbeat` ∕ `stopLiveHeartbeat`);
6
+ * 桌面先例 = `thincoder-desktop/src/main/subagent-face.mjs`(拍体逐键清点)。**纯搬 + 转口,零语义改**:
7
+ * 拍间隔 ∕ 单拍 ∕ 起停 ∕ 幂等 ∕ 清点入核。两处 VSC 端面判据**随拍体留端**(非本档语义):
8
+ * 「未就绪不拍」前置(`panel._wvReady`)与拍日志(`ev:subreassert`)。
9
+ *
10
+ * 拍体(`beat` 注入)= 各端存活再断言本体:VSC = `reassertLiveChildren(panel)`(`thincoder-vscode/src/
11
+ * extension/suspension.mjs:148` 投影面);桌面 = 逐键 `bridge(key).reassertLive(agent)`(只发在飞实例)。
12
+ * 回值 = 本拍投递条数(**清点**读数 —— 拍体自清点,本档原样透传)。
13
+ * **`setInterval` ∕ `unref` 转注入缝**:`timer(fn, ms) ⇒ handle` ∕ `clear(handle)` 由调用方注入
14
+ * (缺省 = 平台 `setInterval` ∕ `clearInterval`;句柄 `unref` = 可选面 —— 在场即调,不阻进程退出)。
15
+ * 消费面 = 桌面 `thincoder-desktop/src/main/subagent-face.mjs` · VSC `panel-messages.mjs` 拍家族——已随 parity-b4
16
+ * 迁移轮改指本档(2026-09-29,双写窗口收口;两处 VSC 端面判据随拍体留端)。
17
+ */
18
+
19
+ /** 拍间隔(毫秒)——单源(VSC 同值 `panel-messages.mjs:45`):起拍即按此周期。 */
20
+ export const LIVE_HEARTBEAT_MS = 2000
21
+
22
+ /**
23
+ * 起 ∕ 停 ∕ 单拍(幂等):
24
+ * - `beat()` = 单拍直驱(回值 = 本拍投递条数 —— 测试直驱面 ∕ 手动补拍);
25
+ * - `start()` = 起拍(**单拍幂等**:已起 ⇒ 返既有句柄、零重起);回值 = 定时器句柄;
26
+ * - `stop()` = 停拍(未起 ⇒ 零动作);停后 `start()` 可再起(新句柄 —— 起 ∕ 停两向)。
27
+ * `timer` ∕ `clear` = 定时器注入缝(缺省 = 平台全局;假钟注入 ⇒ 平 node 直测)。`beat` 缺 ∕ 非函数 ⇒ 抛
28
+ * (fail-loud —— 拍体未接线不得静默空转成「零投拍」)。
29
+ */
30
+ export function createLiveBeat({ beat, timer = (fn, ms) => setInterval(fn, ms), clear = (handle) => clearInterval(handle) } = {}) {
31
+ if (typeof beat !== "function") {
32
+ throw new TypeError("createLiveBeat: beat seam missing — inject the beat function (the projection must not silently idle)")
33
+ }
34
+ let handle = null
35
+ return {
36
+ beat,
37
+ start() {
38
+ if (handle !== null) return handle
39
+ handle = timer(beat, LIVE_HEARTBEAT_MS)
40
+ handle?.unref?.() // 不阻进程退出(句柄可选面)
41
+ return handle
42
+ },
43
+ stop() {
44
+ if (handle === null) return
45
+ clear(handle)
46
+ handle = null
47
+ },
48
+ }
49
+ }
@@ -4,6 +4,7 @@
4
4
  * stall detection, and goal status tracking.
5
5
  */
6
6
  import { escapeXml, tryCanonicalize, DEFAULT_GOAL_TURNS } from "./helpers.mjs"
7
+ import { takeExpiredTimers, injectTimerReminders } from "./timers.mjs"
7
8
 
8
9
  export const STALL_WINDOW_SIZE = 5
9
10
  export const STALL_THRESHOLD = 3
@@ -15,14 +16,9 @@ export const GOAL_BUDGET_WARN_RATIO = 0.75
15
16
  */
16
17
  export function injectPostTurn(agent, results, recentCallSigs, callbacks, turn) {
17
18
  // Expired timers — inject reminders when thinking budget is up
18
- if (agent._pendingTimers.length > 0) {
19
- const now = Date.now()
20
- const expired = agent._pendingTimers.filter((t) => t.expiresAt <= now)
21
- agent._pendingTimers = agent._pendingTimers.filter((t) => t.expiresAt > now)
22
- for (const t of expired) {
23
- agent.history.push({ role: "user", content: `[System reminder: ⏰ timer — ${t.message}]` })
24
- }
25
- }
19
+ // AGENT-LOOP-ASYNC-POOL.md §6.30.2:过滤 / 注入语义单源于 `agent/timers.mjs`(CLI 空闲闩 /
20
+ // 挂起窗同读);本处 = 回合边界轮询调用点——行为零变(最近到期 / 出列幂等 / 逐字形态逐条不变)。
21
+ injectTimerReminders(agent, takeExpiredTimers(agent))
26
22
 
27
23
  // Pending reminders
28
24
  if (agent._pendingReminders.length > 0) {
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { pushReal } from "../context.mjs"
27
27
  import { specForModel } from "../config.mjs"
28
- import { FILE_MUTATORS } from "./helpers.mjs"
28
+ import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
29
29
  import { resolve } from "node:path"
30
30
  import { advisorRuns, stripApprovedSuffix } from "../agent-tools/advisor-async.mjs"
31
31
  import { looksLikeReviewOutput, advisorIncompleteMarker } from "../advisor/run.mjs"
@@ -78,7 +78,7 @@ export async function recordToolResults(agent, toolByName, results) {
78
78
  // Direct file edit — code was changed. The prior advisor review and
79
79
  // verify are stale: a review that ran before the edit no longer
80
80
  // covers the current file state.
81
- // §29 fix A(AGENT-LOOP.md §29——2026-09-07):mutation-seq 记账已移到 dispatch
81
+ // fix A(2026-09-07):mutation-seq 记账已移到 dispatch
82
82
  // runOne 执行成功即刻(唯一记账点——取代本批后段 + agent.mjs 中断分支——不双计)——
83
83
  // 此处仅剩 guard 标志失效(顺序语义:批内同消息的 sync advisor 提交仍在其后置位)。
84
84
  agent._mutatedThisRun = true
@@ -100,7 +100,7 @@ export async function recordToolResults(agent, toolByName, results) {
100
100
  }
101
101
  if (toolCall.name === "verify") agent._verifiedThisRun = true
102
102
  if (toolCall.name === "advisor") {
103
- // §11.2 D-24b (settle accounting split — fix #2): an ASYNC launch returns an
103
+ // AGENT-LOOP-ASYNC-POOL.md §6.10 D-24b (settle accounting split — fix #2): an ASYNC launch returns an
104
104
  // ack and settles later — the settle callback owns its called/round/token
105
105
  // accounting. Only the SYNC path (depth>0 / explicit async:false) accounts
106
106
  // here — per-review instance round++ (marker-keyed by tool call id) + the
@@ -117,7 +117,7 @@ export async function recordToolResults(agent, toolByName, results) {
117
117
  } else {
118
118
  const reviewId = agent._advisorSyncCalls?.get(toolCall.id)
119
119
  const run = reviewId !== undefined ? advisorRuns(agent).get(reviewId) : undefined
120
- // F16 同步面(第 13 批 §15.2——ADVISOR-CONVERGENCE.md §15):未完成尾 + 非设计面
120
+ // F16 同步面(第 13 批——ADVISOR-GUARDS.md §6):未完成尾 + 非设计面
121
121
  // (含类型不可判——run 缺失的 legacy 直调)⇒ 不置「已覆盖」(与 settle 面
122
122
  // 逐条 parity;设计评审保持计入——无代码面)。
123
123
  const incomplete = advisorIncompleteMarker(String(result))
@@ -127,7 +127,7 @@ export async function recordToolResults(agent, toolByName, results) {
127
127
  run.round++
128
128
  agent._advisorRound = run.round
129
129
  // Prior of round 2+ = the last REVIEW-LOOKING output (run.mjs parity).
130
- // F2e (§29.1): the sync settle's engine-approved suffix (stored on the
130
+ // F2e: the sync settle's engine-approved suffix (stored on the
131
131
  // run by settleDesignReview) is stripped with exact truncation — the
132
132
  // prior never carries the raw token / designId.
133
133
  if (looksLikeReviewOutput(result)) {
@@ -145,23 +145,26 @@ export async function recordToolResults(agent, toolByName, results) {
145
145
  }
146
146
  }
147
147
  if (FILE_MUTATORS.has(toolCall.name)) {
148
- const args = JSON.parse(toolCall.arguments)
149
- const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
150
- for (const p of paths) {
151
- const abs = resolve(agent.cwd, p)
152
- if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
153
- if (agent.memory) {
154
- // Fire-and-forget: don't block the agent loop on indexing.
155
- // Reuses a single cached import; errors surface as pending reminders on next turn.
156
- if (!_reindexFile) {
157
- const mod = await import("../memory.mjs")
158
- _reindexFile = mod.reindexFile
148
+ try {
149
+ const args = JSON.parse(toolCall.arguments)
150
+ const paths = toolTouchPaths(tool, args)
151
+ for (const p of paths) {
152
+ if (typeof p !== "string" || !p) continue // 非字符串项不拖累合法路径(与 dispatch / VSC 消费面同口径)
153
+ const abs = resolve(agent.cwd, p)
154
+ if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
155
+ if (agent.memory) {
156
+ // Fire-and-forget: don't block the agent loop on indexing.
157
+ // Reuses a single cached import; errors surface as pending reminders on next turn.
158
+ if (!_reindexFile) {
159
+ const mod = await import("../memory.mjs")
160
+ _reindexFile = mod.reindexFile
161
+ }
162
+ _reindexFile(agent.memory, agent.cwd, abs).catch((e) => {
163
+ agent._pendingReminders.push(`[System reminder: background indexing failed for ${toolCall.name} on ${abs}: ${e.message}. This does not affect your work — the code index will catch up on next reindex.]`)
164
+ })
159
165
  }
160
- _reindexFile(agent.memory, agent.cwd, abs).catch((e) => {
161
- agent._pendingReminders.push(`[System reminder: background indexing failed for ${toolCall.name} on ${abs}: ${e.message}. This does not affect your work — the code index will catch up on next reindex.]`)
162
- })
163
166
  }
164
- }
167
+ } catch { /* #327:畸形 args(含 null)/ 畸形路径不再裸抛——touchedFiles 尽力而为 */ }
165
168
  }
166
169
  }
167
170
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
- * relay-prefix.mjs — relay 前缀(`role#id/`)文法**单一权威**(第 27 批 §12.3①——R-A2.3)。
2
+ * relay-prefix.mjs — relay 前缀(`role#id/`)文法**单一权威**(第 27 批——R-A2.3)。
3
3
  *
4
4
  * 零依赖(不 import 任何模块)——TUI(`src/tui/`)与 ACP(`src/acp/`)双向可导入,无环;
5
5
  * 生成侧 `src/agent/spawn-child.mjs` 再导出(枢纽)。文法 = 自
6
- * `src/tui/subagent-blocks.mjs` 逐字迁入(第 27 批 §12.3②——零语义改动)。
6
+ * `src/tui/subagent-blocks.mjs` 逐字迁入(第 27 批——零语义改动)。
7
7
  */
8
8
 
9
9
  /** `role#id/` prefix router — hyphen included since the eng-coder fix (2026-08-21). */
@@ -15,11 +15,12 @@ import { logEvent, errText } from "../log.mjs"
15
15
  // ASYNC-RESULT-CONTAINER.md D1:池 accessor(absorb 双池——advisor 独立池无队列)
16
16
  import { getAsyncPool, releaseSettledEntry } from "../agent-tools/async-settle.mjs"
17
17
  // 批 4 CLI-ASYNC-DISCARD(AGENT-LOOP-ASYNC-POOL.md §6.20):中止分支「只清已死」收尾单点
18
- import { discardAbortedPool, discardAbortedAdvisors } from "../agent-tools/async-discard.mjs"
18
+ // (#9:+ 后台 bash 任务族——逐条杀树 + 出池 + 墓碑)
19
+ import { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks } from "../agent-tools/async-discard.mjs"
19
20
  // R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3a):回合末域登记 flush(写工具钩子累积 →
20
21
  // 整写一次本实例 peers 文件——无写入跳过;失败容忍不抛)
21
22
  import { flushPeerDomains } from "../peer-domains.mjs"
22
- // 第 30 批 D1(Stop 钩子——AGENT-LOOP.md §21):主会话 run 终止事件(触发块见 finalizeAgentTurn)
23
+ // 第 30 批 D1(Stop 钩子——AGENT-LOOP.md §6.13):主会话 run 终止事件(触发块见 finalizeAgentTurn)
23
24
  import { runHooks } from "../hooks.mjs"
24
25
 
25
26
  /**
@@ -164,14 +165,14 @@ export async function finalizeAgentTurn(agent, ctx) {
164
165
  },
165
166
  }).catch(() => {})
166
167
  }
167
- // R17(AGENT-LOOP.md §25 D-R17a): consultation sessions are cross-turn
168
+ // R17(CONSULTATION.md §6.2): consultation sessions are cross-turn
168
169
  // background work now — NO unconditional turn-end cleanup (the old rule aborted
169
170
  // leftover consult children at every turn end because check-loop consumption was
170
171
  // turn-scoped). Sessions stay alive across normal turn ends (and ContinueError
171
172
  // resumes); the Ctrl+C abort branch below is the only path that aborts them
172
173
  // (cleanupConsultSessions — marked stopped → no digest), and the suspension
173
174
  // driver aborts them on its own abort unwind.
174
- // Async subagent turn-end handling (AGENT-LOOP.md §15 D-A3 + AGENT-LOOP-ASYNC-POOL.md §6.8 D-S1). Lifecycle:
175
+ // Async subagent turn-end handling (AGENT-LOOP-SUBAGENT.md §6.7.3 D-A3 + AGENT-LOOP-ASYNC-POOL.md §6.8 D-S1). Lifecycle:
175
176
  // - Ctrl+C (plain abort): children were aborted with the parent signal — discard dead
176
177
  // entries (tombstone/out-of-pool/notice — AGENT-LOOP-ASYNC-POOL.md §6.20; no stale
177
178
  // errors injected — user explicitly stopped); consultation sessions are cross-turn
@@ -188,12 +189,15 @@ export async function finalizeAgentTurn(agent, ctx) {
188
189
  if (signal?.aborted && !signal?.reason?.interrupt) {
189
190
  const subPool = getAsyncPool(agent, "subagent")
190
191
  const advPool = getAsyncPool(agent, "advisor")
191
- if ((subPool?.size ?? 0) > 0 || (advPool?.size ?? 0) > 0) {
192
- logEvent("ev:stopped", { poolN: (subPool?.size ?? 0) + (advPool?.size ?? 0), where: "turn-end-abort" })
192
+ const bgPool = getAsyncPool(agent, "bg") // #9:后台 bash 任务池(第三域)
193
+ if ((subPool?.size ?? 0) > 0 || (advPool?.size ?? 0) > 0 || (bgPool?.size ?? 0) > 0) {
194
+ logEvent("ev:stopped", { poolN: (subPool?.size ?? 0) + (advPool?.size ?? 0) + (bgPool?.size ?? 0), where: "turn-end-abort" })
193
195
  }
194
- // §6.20:只清已死条目(存活/已 settle 留池——收尾见 async-discard.mjs;原无条件清池已弃)。
196
+ // §6.20:只清已死条目(存活/已 settle 留池——收尾见 async-discard.mjs;原无条件清池已弃);
197
+ // #9:后台 bash 任务同面(杀 ⟺ 控制器已中止——逐条杀树 + 出池 + 墓碑)。
195
198
  discardAbortedPool(agent)
196
199
  discardAbortedAdvisors(agent)
200
+ discardAbortedBgTasks(agent)
197
201
  // R17: the consult family dies with the user stop (marked stopped — no digest).
198
202
  cleanupConsultSessions(agent)
199
203
  // ASYNC-RESULT-CONTAINER.md D2:pending 单容器——中止丢弃 consult/escalate 族停靠
@@ -204,7 +208,7 @@ export async function finalizeAgentTurn(agent, ctx) {
204
208
  // keep _asyncSubagents/_asyncAdvisors/_consultSessions — the resumed run continues them
205
209
  } else {
206
210
  const injectedAdvisor = await collectSettledAsync(agent, { suspDriven })
207
- // §11.2 D-24b: a normally-ended USER run with no review/child activity closes
211
+ // AGENT-LOOP-ASYNC-POOL.md §6.10 D-24b: a normally-ended USER run with no review/child activity closes
208
212
  // the OPEN code instances — the converged/abandoned thread must not burn the
209
213
  // 5-round cap of a later task (the next code review starts a fresh round 1).
210
214
  // Digest auto-turns are exempt (their disposition precedes the fix round).
@@ -241,8 +245,8 @@ export async function finalizeAgentTurn(agent, ctx) {
241
245
  * maybeRefillAsync runs in both modes (starts queued heads whose slot freed).
242
246
  * Single ownership: entries settled inside a suspension session were moved to
243
247
  * _pendingAsyncResults by the settle callback, so this only sees user-turn
244
- * settles (no double inject — D-S3 points ①/②). The ⟦ev⟧done freeze is NOT
245
- * emitted here — each settle callback emits it (§15 D-A3).
248
+ * settles (no double inject — D-S3 points ①/②). settle 回调只发 ⟦ev⟧settled;
249
+ * ⟦ev⟧done 冻结落消费窗(#746 · ASYNC-POOL §6.8)。
246
250
  * 2026-09-05 实践轮:自 agent.mjs 迁入(agent.mjs 内仅 finalize 引用——随收尾同迁)。
247
251
  */
248
252
  async function collectSettledAsync(agent, { suspDriven = false } = {}) {
@@ -257,7 +261,7 @@ async function collectSettledAsync(agent, { suspDriven = false } = {}) {
257
261
  if (!e.done) continue // still running — stays in the pool (D-S1)
258
262
  await injectAsyncResult(agent, e)
259
263
  if (e.role === "advisor") injectedAdvisor = true
260
- // TUI-OOM-ROOTCAUSE(§23.3.1 消费点①——回合尾收集):注入完成 → 释放条目持有
264
+ // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15 消费点①——回合尾收集):注入完成 → 释放条目持有
261
265
  releaseSettledEntry(e)
262
266
  map.delete(String(e.id))
263
267
  }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * agent/run-start.mjs — runAgent 起跑前段(2026-09-29 P2 三拆:agent.mjs runAgent 段 1
3
+ * verbatim 迁入——distill 等待 ∕ pendingAsync 注入 ∕ prepareRun ∕ 复位块 ∕ 域文本 ∕ 授权注释块)。
4
+ * 接口 = beginRun(agent, input, callbacks, opts) → 主循环装配面(`tools` 死绑定去——KD-8)。
5
+ */
6
+ import { prepareRun } from "./setup.mjs"
7
+ import {
8
+ restoreGuard, // digest D-S6 读侧单点(AGENT-LOOP-ASYNC-POOL.md §6.8;P2 机制层端差批 §2.18——键清单归核)
9
+ AUTO_TURN_DIGEST_DOMAIN,
10
+ AUTO_TURN_DIGEST_DOMAIN_ENG, // §6.15.3(F10 第三面):工程模式 digest 基座变体(task 指针改批次档 + 台账)
11
+ UPSTREAM_TURN_DOMAIN, // §6.27.12.8:上行唤醒轮域文本(手动档——ask 轮不沿用 digest 域文本)
12
+ TIMER_TURN_DOMAIN, // §6.30.3 D-TW4:timer 唤醒轮域文本(第三变体——到期 timer 自动开轮)
13
+ } from "./helpers.mjs"
14
+
15
+ export async function beginRun(agent, input, callbacks, {
16
+ depth = 0, signal, overrideTurns, resume = false, autoTurn = false, upstreamTurn = false, timerTurn = false,
17
+ extraTools = null, injections = null, turnDomainText = null, toolDecorate = undefined,
18
+ } = {}) {
19
+ // Previous run's async exploration distillation must settle before this run pushes
20
+ // input (SEND-STALL-DISTILL §2.2 N1) — await first, or its history replace wipes it.
21
+ if (agent._pendingDistill) {
22
+ const p = agent._pendingDistill
23
+ agent._pendingDistill = null
24
+ await p
25
+ }
26
+ // AGENT-LOOP-ASYNC-POOL.md §6.8 D-S3: suspension-settled async results inject before EVERY run's prepareRun
27
+ // (user + auto-turn); spliced = consumed. collectSettledAsync owns a different
28
+ // container, so no double-inject across the two consumption points.
29
+ // ASYNC-RESULT-CONTAINER.md D2 (2026-09-08):pending 单容器 `_pendingAsyncResults`
30
+ // +role——四族(subagent/advisor/escalate/consult)统一停靠;注入器按 role 分发
31
+ // (consult → injectConsultResult;其余 → injectAsyncResult——族分支同 CONSULTATION.md §6.2 D-R17a/b)
32
+ // ——单容器一处清,不再逐族三段。
33
+ const pendingAsync = agent._pendingAsyncResults
34
+ if (pendingAsync?.length) {
35
+ const { injectAsyncResult } = await import("../agent-tools/subagent.mjs")
36
+ const { injectConsultResult } = await import("../agent-tools/consult.mjs")
37
+ // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15 消费点②——run 起始 pending 注入):
38
+ // 注入完成后释放条目对子代理对象的持有(childAgent/report 置空——幂等 helper)。
39
+ const { releaseSettledEntry } = await import("../agent-tools/async-settle.mjs")
40
+ for (const e of pendingAsync.splice(0)) {
41
+ if (e.role === "consult") await injectConsultResult(agent, e)
42
+ else await injectAsyncResult(agent, e)
43
+ releaseSettledEntry(e)
44
+ }
45
+ }
46
+ agent._inAutoTurn = autoTurn // spawn gate for manual-tier digests (AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6/N3;上行唤醒轮同持 autoTurn——§6.27.12.4 ①)
47
+ const { maxTurns, threshold, toolSchemas, toolByName, systemPrompt } = await prepareRun(
48
+ // G1/G2(施工②):prompt 装配收口 prepareRun 内部(assemblePrompt——prompt-overlays.mjs
49
+ // 槽位常量,与子代理角色常量同源——单一权威锚 D1);本调用不再携带 prompt 常量。
50
+ agent, input, callbacks,
51
+ { depth, signal, overrideTurns, resume: resume || autoTurn, extraTools, injections, toolDecorate },
52
+ )
53
+
54
+ // Exploration-distillation boundary (CONTEXT-COMPACTION §5): prepareRun already
55
+ // pushed input + injections — appended from here counts as "this run's" work.
56
+ agent._runStartHistoryLen = agent.history.length
57
+
58
+ // Per-run bookkeeping reset — PRESERVED on `resume` (ContinueError continuation):
59
+ // mutation/guard continuity and the convergence budget must survive a continuation.
60
+ // §2.3 链内段数(TURN-CAP-CONTINUE.md §4):首段 1、每次续跑 +1——复位条件同 `_turnSeq`
61
+ // (链起点复位、续跑不回退);消费面 = 检查点 ask 载荷 / 池条目留痕二元。
62
+ agent._continueSegments = resume ? (agent._continueSegments ?? 1) + 1 : 1
63
+ // #417(撞帽载荷「本段零落盘轮数」——只报数:零阈值常量 / 零自动动作):段起点复位(首段 /
64
+ // 续跑新段——与 `_continueSegments` 同点);段内累加,采集点在回合环(与 `_turnSeq` 同源面)。
65
+ agent._zeroWriteTurns = 0
66
+ if (!resume) {
67
+ // 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §4):链内累计编号
68
+ // 只在链起点复位——续跑(resume:true)不重置、不回退(编号帧公式见 helpers.mjs
69
+ // turnFrame)。与下方 mutation/guard 复位同条件同点(全档唯一复位点)。
70
+ agent._turnSeq = 0
71
+ // AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6: an auto-turn's guard marks are inherited by the next USER run (not
72
+ // reset) so auto-turn changes never escape the guard silently.
73
+ const g = agent._inheritedGuard
74
+ if (g) {
75
+ restoreGuard(agent, g)
76
+ agent._inheritedGuard = null
77
+ } else {
78
+ agent._mutatedThisRun = false
79
+ agent._verifiedThisRun = false
80
+ agent._verifyPassed = undefined
81
+ agent._calledAdvisorThisRun = false
82
+ agent._touchedFiles = []
83
+ agent._verifyRetries = 0
84
+ agent._advisorRound = 0
85
+ agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
86
+ agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
87
+ agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
88
+ // F-CC2(§6.16.2):模型主动压缩的排队槽也是回合级——上一回合末尾未被安全点消费的请求不得跨回合生效
89
+ agent._pendingCompact = null
90
+ }
91
+ }
92
+ // digest D-S6 manual tier(AGENT-LOOP-ASYNC-POOL.md §6.8): action-domain reminder (system-driven turn — organize only).
93
+ // §6.27.12.4 ②: an up-stream wake turn answers a RUNNING subagent waiting for the reply — it
94
+ // must not reuse the digest text ("no one is waiting" is the opposite of the truth).
95
+ // §6.30.3 D-TW4: timer wake turn (third auto-turn variant) — 到期 timer 自动开轮,仅作域文本选择
96
+ // (`upstreamTurn` 优先不回归:ask 轮是「有人在等回复」,比 timer 轮更紧)。
97
+ if ((autoTurn || upstreamTurn) && !agent.autoApprove) {
98
+ // §2.5-A3(P4-I):端壳经 `opts.turnDomainText` 供「核基座 + 端 overlay」组合串(组合点 =
99
+ // 端 `turn-domains.mjs` composeTurnDomain);缺省(null)⇒ 核基座逐字零变(选择序不变)。
100
+ const domainBase = upstreamTurn ? UPSTREAM_TURN_DOMAIN : timerTurn ? TIMER_TURN_DOMAIN : (agent.config?.agent?.engineering === true ? AUTO_TURN_DIGEST_DOMAIN_ENG : AUTO_TURN_DIGEST_DOMAIN)
101
+ agent.history.push({ role: "user", content: turnDomainText ?? domainBase, transient: true })
102
+ }
103
+ // eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
104
+ // (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
105
+ // depth-0 parent never reads or writes it (the parent gate reads anyLiveDesignSlot; the
106
+ // depth-0 per-turn reset was removed 2026-09-08, ENG-SESSION-PROVIDER-CLEANUP D1.3).
107
+ // Design slots (_engDesignTokens Map) survive across turns (design review → approval →
108
+ // eng-coder spawn) — persisted to the session slot at settle time (DESIGN-TOKEN-
109
+ // SETTLEMENT D1); lifecycle: issued on a passing review, consumed by consume-design / TTL.
110
+ return { maxTurns, threshold, toolSchemas, toolByName, systemPrompt }
111
+ }
@@ -3,7 +3,7 @@
3
3
  * (SESSION.md §6.11 — 2026-09-06 需求池 R5/R8/R9/R11 合并设计; R10 L1 peer 注入同文件).
4
4
  *
5
5
  * One unified transient user reminder per turn covers the whole self-awareness
6
- * family: env identity (R8 — §6.10 D-1 END 常量先例:静态常量,不做 cmdline 判别),
6
+ * family: env identity (R8 — §6.10 D-1 端名先例:值 = 进程端名(§6.20 端名缝 sessionEnd()),不做 cmdline 判别),
7
7
  * engineering mode (R9), active model (R11), session slot (F1), restart awareness
8
8
  * (R5 resumed). §6.11(2026-09-08):resumed 按会话跟踪——恢复事件由 session.mjs
9
9
  * applySession 武装(agent._envResumed——载入历史非空 = 恢复);进程重启句独立走
@@ -29,16 +29,16 @@
29
29
  */
30
30
  import { statSync } from "node:fs"
31
31
  import { resolve } from "node:path"
32
- import { END } from "../session-slots.mjs"
32
+ import { sessionEnd } from "../session-slots.mjs"
33
33
  import { peerInstances } from "../peer-instances.mjs"
34
34
  import { specForModel } from "../config.mjs"
35
35
  import { manifestFilePath, readManifest, projectView } from "../manifest.mjs"
36
36
 
37
- /** env-state line builder — pure, unit-testable.
37
+ /** env-state line builder — 对入参 + 进程端名确定(§6.20 端名缝 sessionEnd();setSessionEnd 注入 ⇒ 单测可用)。
38
38
  * §6.11(F1):slot 字段入行——位置在 model 后 resumed 前(N3:无绑定 → 显式 null——
39
39
  * 不读 manifest active 共享指针——粘性 _slot 才是"本 agent 之槽")。 */
40
40
  export function envStateLine({ mode, model, slot, resumed }) {
41
- return `[System reminder: env: ${END}, mode: ${mode}, model: ${model}, slot: ${slot}, resumed: ${resumed ? "yes" : "no"}.]`
41
+ return `[System reminder: env: ${sessionEnd()}, mode: ${mode}, model: ${model}, slot: ${slot}, resumed: ${resumed ? "yes" : "no"}.]`
42
42
  }
43
43
 
44
44
  /**
@@ -63,16 +63,16 @@ export function pushEnvStateReminder(agent) {
63
63
  /** 情境行前缀——本行族识别符(摘旧行 / 会话重建认领同用;行形逐字见 manifestStateLine)。 */
64
64
  const MANIFEST_LINE_PREFIX = "[System reminder: project state: "
65
65
 
66
- /** phase → discipline 标签映射(判据单源 = 需求 v2 §9.1);未知值不在表内 → 无标签。 */
67
- const MANIFEST_DISCIPLINE = Object.freeze({ "initial-dev": "light", production: "strict" })
66
+ /** phase → rigor 标签映射(判据单源 = 需求 v2);未知值不在表内 → 无标签。 */
67
+ const MANIFEST_RIGOR = Object.freeze({ "initial-dev": "light", production: "strict" })
68
68
 
69
69
  /** 情境行构造 — pure, unit-testable(模块契约 `docs/core/design/MANIFEST.md` §2.6,与
70
70
  * envStateLine 同族)。行形逐字(**单字段**——2026-09-17 裁撤批后):
71
- * `[System reminder: project state: phase: <值> (discipline: <light|strict>).]`
71
+ * `[System reminder: project state: phase: <值> (rigor: <light|strict>).]`
72
72
  * 未知 phase → 只出值、不编判据(无标签)。 */
73
73
  export function manifestStateLine({ phase }) {
74
- const discipline = MANIFEST_DISCIPLINE[phase]
75
- return `${MANIFEST_LINE_PREFIX}phase: ${phase}${discipline ? ` (discipline: ${discipline})` : ""}.]`
74
+ const rigor = MANIFEST_RIGOR[phase]
75
+ return `${MANIFEST_LINE_PREFIX}phase: ${phase}${rigor ? ` (rigor: ${rigor})` : ""}.]`
76
76
  }
77
77
 
78
78
  /**
package/agent/setup.mjs CHANGED
@@ -10,14 +10,18 @@ import { search as memorySearch, docSearch } from "../memory.mjs"
10
10
  import { pushReal } from "../context.mjs"
11
11
  import { toOpenAISchema } from "../tools/index.mjs"
12
12
  import { loadSkills, formatSkillListing } from "../skills.mjs"
13
+ import { scopedRulesBlock } from "../rules.mjs"
13
14
  import { assemblePrompt } from "../prompt-overlays.mjs"
14
15
  import {
15
16
  escapeXml, repairHistory, listWorkDir,
16
17
  collectGitContext, loadProjectInstructions, OUTLINE_INJECT_PREFIX,
17
18
  DEFAULT_MAX_TURNS, ensureAutoReminder,
18
19
  } from "./helpers.mjs"
19
- import { pushEnvStateReminder, pushPeerReminder } from "./setup-reminders.mjs"
20
+ import { pushEnvStateReminder, pushPeerReminder, pushInjections } from "./setup-reminders.mjs"
20
21
  import { assembleFamilyTools } from "./family-tools.mjs"
22
+ import { normalizeOrigin } from "../memory/origin.mjs"
23
+ import { searchOrigins } from "../memory/code-search.mjs"
24
+ import { INDEX_ORIGIN_ROW_WARN } from "../memory/schema.mjs"
21
25
 
22
26
  const DEFAULT_COMPACT_THRESHOLD = 100_000
23
27
  const DOC_SEARCH_LIMIT = 5
@@ -41,6 +45,10 @@ const MEMORY_SEARCH_LIMIT = 3
41
45
  */
42
46
  export async function prepareRun(agent, input, callbacks, {
43
47
  depth = 0, signal, overrideTurns, resume, extraTools = null,
48
+ // P4-I(批次档 §2.5 A1 + 父侧 2026-09-29 裁定①):端壳装配缝——注入串 ∕ 工具装饰;
49
+ // 两项缺省(null ∕ undefined)⇒ 现行行为逐字零变(CLI ∕ desktop 不传即零变)。
50
+ // (A2 提示尾块载位已退役——B7 3a:核自持 `.cursor/rules` 尾块,见下方 depth-0 块。)
51
+ injections = null, toolDecorate = undefined,
44
52
  } = {}) {
45
53
  const maxTurns = overrideTurns ?? agent.config?.agent?.maxTurns ?? DEFAULT_MAX_TURNS
46
54
  const threshold = agent.config?.agent?.compactThreshold ?? DEFAULT_COMPACT_THRESHOLD
@@ -91,7 +99,8 @@ export async function prepareRun(agent, input, callbacks, {
91
99
  if (agent.memory && !agent.history.some((m) => typeof m.content === "string" && m.content.startsWith(OUTLINE_INJECT_PREFIX))) {
92
100
  try {
93
101
  const { buildSummary } = await import("../tools/repomap.mjs")
94
- const summary = await buildSummary(agent.memory.db, agent.cwd)
102
+ // §6.14 B4:origin 限定 + 文件数上界(越界 ⇒ 提示行 + 跳过大纲构建——见 repomap.mjs)
103
+ const summary = await buildSummary(agent.memory.db, agent.cwd, { origin: normalizeOrigin(agent.memory.codeOrigin) })
95
104
  if (summary && !summary.startsWith("(no indexed")) {
96
105
  agent.history.push({ role: "user", content: `${OUTLINE_INJECT_PREFIX}\n${summary}]`, transient: true })
97
106
  }
@@ -103,8 +112,16 @@ export async function prepareRun(agent, input, callbacks, {
103
112
  if (agent.memory && depth === 0) {
104
113
  const docs = await docSearch(agent.memory, input, { limit: DOC_SEARCH_LIMIT })
105
114
  if (docs.length > 0) {
106
- const count = agent.memory.db.prepare(`SELECT COUNT(*) AS n FROM doc_chunks`).get()?.n ?? 0
107
- const more = count > docs.length ? ` (${count} chunks indexed total — call doc_search if you need more)` : ""
115
+ // §6.14 B3 + §6.15:origin 集限定 + 有界计数(子查询 LIMIT = WARN+1 形——越界回「20000+」;跨 origin 全扫不再回升)
116
+ const origins = searchOrigins(agent.memory)
117
+ const bound = INDEX_ORIGIN_ROW_WARN + 1
118
+ const countFilter = origins.length === 1 ? " WHERE origin = ?" : origins.length > 1 ? ` WHERE origin IN (${origins.map(() => "?").join(", ")})` : ""
119
+ const countRow = agent.memory.db.prepare(
120
+ `SELECT COUNT(*) AS n FROM (SELECT 1 FROM doc_chunks${countFilter} LIMIT ${bound})`,
121
+ ).get(...origins)
122
+ const count = Number(countRow?.n ?? 0)
123
+ const countLabel = count >= bound ? `${INDEX_ORIGIN_ROW_WARN}+` : String(count)
124
+ const more = count > docs.length ? ` (${countLabel} chunks indexed total — call doc_search if you need more)` : ""
108
125
  agent.history.push({
109
126
  role: "user",
110
127
  content:
@@ -137,6 +154,9 @@ export async function prepareRun(agent, input, callbacks, {
137
154
  pushEnvStateReminder(agent)
138
155
  await pushPeerReminder(agent)
139
156
  }
157
+ // A1(§2.5——端壳编辑器上下文注入,序位 = env/peer 之后、time 之前):端采集内容经
158
+ // `pushInjections` 单点落机器线(transient + 同文去重);缺省 null ⇒ 零行为。
159
+ pushInjections(agent.history, injections)
140
160
 
141
161
  // Time grounding for EVERY agent depth AND every resume, pushed LAST (after the user
142
162
  // input): transient on the HUMAN line — dropped on persist; on the MACHINE line — kept
@@ -165,6 +185,9 @@ export async function prepareRun(agent, input, callbacks, {
165
185
  engineering: agent.config?.agent?.engineering === true,
166
186
  consultModels: agent.config?.agent?.consultModels ?? [],
167
187
  batchDoc: agent._batchDoc ?? null,
188
+ // 裁定①(P4-I · 父侧 2026-09-29):端壳装饰体透传(`assembleFamilyTools` 既有 `decorate`
189
+ // 参数面——缺省 undefined ⇒ 核默认形态逐字零变,CLI ∕ desktop 不传即零变)。
190
+ decorate: toolDecorate,
168
191
  })
169
192
  // 两段式:绑定值(基础集)→ 家族段 → caller 注入面(§2.3F 不相交义务在注入方)。
170
193
  const tools = [...agent.tools, ...familyTools, ...(Array.isArray(extraTools) ? extraTools : [])]
@@ -232,6 +255,11 @@ export async function prepareRun(agent, input, callbacks, {
232
255
  const skills = await loadSkills(agent.cwd)
233
256
  const listing = formatSkillListing(skills)
234
257
  if (listing) systemPrompt += `\n\n${listing}`
258
+ // B7 3a:`.cursor/rules` 常驻集尾块(原端壳 A2 载位退役——核自持;序位 = skills 清单之后)。
259
+ // 尾块 ∕ JIT 两缝同门 `depth === 0`(子代理不携——B1 §2.5-A2 在册态);同点缓存 `agent._rules`
260
+ // (JIT 判定只读该缓存——`loadScopedRules` 本 run 唯一一次目录读取)。
261
+ const scopedBlock = scopedRulesBlock(agent, agent.cwd)
262
+ if (scopedBlock) systemPrompt += scopedBlock
235
263
  }
236
264
 
237
265
  ensureAutoReminder(agent)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * spawn-child.mjs — 生成侧统一子代理管线(AGENT-LOOP.md §7.2 D3)。
2
+ * spawn-child.mjs — 生成侧统一子代理管线(AGENT-LOOP-SUBAGENT.md §6.7.2)。
3
3
  *
4
4
  * 收编 subagent/escalate/consult 三份机械同构的重复实现:
5
5
  * - allocRelay / emitRelayModel: relay 取号段 ∥ 出生声明(`[model]`)发射段——两段分离
@@ -20,8 +20,8 @@ import { specForModel } from "../config.mjs"
20
20
  import { ContinueError } from "../agent.mjs"
21
21
  import { relayPrefixOf } from "./relay-prefix.mjs"
22
22
  import { appendCappedText } from "../text-budget.mjs"
23
- // 第 27 批 §12.3①:relay 前缀文法单一权威模块(`src/agent/relay-prefix.mjs`)——
24
- // 生成侧枢纽再导出(TUI/ACP 消费方可经此导入;消费方按 §12.3① 直连模块亦可)。
23
+ // 第 27 批:relay 前缀文法单一权威模块(`src/agent/relay-prefix.mjs`)——
24
+ // 生成侧枢纽再导出(TUI/ACP 消费方可经此导入;消费方直连模块亦可)。
25
25
  export { RELAY_PREFIX_RE, parseRelayPath, relayPrefixOf } from "./relay-prefix.mjs"
26
26
 
27
27
  /** 事件 token 哨兵串(D1)——LLM 正常内容混淆概率极低;字段分隔用 RS (\x1e)。 */
@@ -35,7 +35,7 @@ const RS = "\x1e"
35
35
  export { TURN_CAP_MARK, STOPPED_MARK } from "./child-marks.mjs"
36
36
 
37
37
  /**
38
- * §18 D-E3 工程子代理内部 spawn 机械门(AGENT-LOOP.md §18 D-E2 round5 #2 后备 +
38
+ * D-E3 工程子代理内部 spawn 机械门(AGENT-LOOP-SUBAGENT.md §6.7.6 后备 +
39
39
  * ENGINEERING-MODE.md §2.15 D):eng-coder(偏差审计)与 **eng-designer(自己勘察)**
40
40
  * 同为受限通道——depth>0 且父角色 ∈ 集合时,内部 spawn 只允许 role='explore'、
41
41
  * async 强制同步。
@@ -109,7 +109,7 @@ export function makeRelay(parent, label, emit, model) {
109
109
  // Single source for the event grammar branch lists (consult P3, 2026-08-30):
110
110
  // stripEventToken (display) and stripEventTokensForCapture (capture) shared them
111
111
  // literally — extending the event set meant touching both regexes.
112
- // "done" = §15 D-A3 async-child completion event (emitted by the parent's
112
+ // "done" = AGENT-LOOP-SUBAGENT.md §6.7.3 D-A3 async-child completion event (emitted by the parent's
113
113
  // turn-end collection, not by children — listed so the grammar stays honest).
114
114
  const EVENT_PHASE = "turn|approval|done"
115
115
  const EVENT_TYPE = "llm|tool|approval|done"
@@ -139,7 +139,7 @@ export function stripEventTokensForCapture(text) {
139
139
  * - onToolOutput 带**已加前缀**的 name 走父 onToolOutput(name 形如
140
140
  * "coder#1/bash",消费端剥前缀路由进对应区块;chunk 对象/裸串原样透传)。
141
141
  * 父回调缺省时不包装(headless 嵌入)。
142
- * §27 R23 D-R23c1:包装产物在 onToken 上留 `_relayPrefix` 标记——同步收尾段据此
142
+ * docs/cli/design/TUI.md §6.8.2 R23 D-R23c1:包装产物在 onToken 上留 `_relayPrefix` 标记——同步收尾段据此
143
143
  * 判定"ctx.callbacks 已是嵌套 wrapper"(本 spawn 处于更深一层——eng-coder 内
144
144
  * explore)→ emitNestedChildEvent 补发射内层完成事件(T-R23c.2b 断言源)。
145
145
  */
@@ -163,7 +163,7 @@ export function wrapChildCallbacks(relayPrefix, parentCallbacks = {}) {
163
163
  }
164
164
 
165
165
  /**
166
- * §27 R23 D-R23c1(生成侧补发射——评审 #1 🅰):sync spawn 同步收尾时若父回调已是
166
+ * §6.8.2 R23 D-R23c1(生成侧补发射——评审 #1 🅰):sync spawn 同步收尾时若父回调已是
167
167
  * 嵌套 wrapper(onToken 带 `_relayPrefix` 标记——即本 spawn 的父本身是子代理,如
168
168
  * eng-coder 内 explore 审计)→ 发内层 done/stopped 事件(带完整嵌套前缀——wrapper
169
169
  * 链自动补外层前缀)→ 主 TUI routeSubToken 路由到子块定格。非嵌套(depth-0 直连主
@@ -178,7 +178,7 @@ export function emitNestedChildEvent(ctx, relayPrefix, kind) {
178
178
  return true
179
179
  }
180
180
 
181
- /** 捕获额度(TUI-OOM-ROOTCAUSE——AGENT-LOOP.md §23.3.1 表 1 候选 1:滞后水位截断):
181
+ /** 捕获额度(TUI-OOM-ROOTCAUSE——AGENT-LOOP.md §6.15:滞后水位截断):
182
182
  * `_capturedOutput` 是子代理流式文本的第二份全量拷贝(原无上限——勘察 C2);超 hard 即
183
183
  * 裁至头 16K + 标记 + 尾 48K(摊还 O(1))——消费面读时已各自 slice(0, 2000/4000),头尾
184
184
  * 保真覆盖;停止报告内联场景出现截断标记(可断言——D-SM1)。纯函数本体住
@@ -235,7 +235,7 @@ export async function runWithContinue(runner, child, input, callbacks, runOpts,
235
235
  // Review #4 fix: strip sentinel/control chars from the capture — `output` feeds
236
236
  // onDeclined's partial-output return, which lands in the PARENT LLM history where
237
237
  // the display-layer sanitizeDisplay backstop does not apply.
238
- // TUI-OOM-ROOTCAUSE(§23.3.1):捕获滞后水位截断——超 CAPTURE_CAP_OPTS.hard 裁至
238
+ // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15):捕获滞后水位截断——超 CAPTURE_CAP_OPTS.hard 裁至
239
239
  // 头 + 标记 + 尾(_capturedOutput 有界;续跑同闭包累积,语义一致)。
240
240
  const capture = callbacks?.onToken
241
241
  ? (t) => { output = appendCappedText(output, stripEventTokensForCapture(String(t)), CAPTURE_CAP_OPTS); child._capturedOutput = output; callbacks.onToken(t) }