@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
package/agent/helpers.mjs CHANGED
@@ -85,6 +85,38 @@ const MAX_GIT_CHANGES_DISPLAY = 20
85
85
  export const OUTLINE_INJECT_PREFIX = "[System reminder: project dependency outline:"
86
86
  export const FILE_MUTATORS = new Set(["write", "edit", "insert_after", "apply_patch", "delete", "hashline_edit"])
87
87
 
88
+ /**
89
+ * 工具自报触达面提取**单源谓词**(`docs/core/design/TOOLS.md` §6.17 / D-TO12——批 GUARD-SCHEDULER ·
90
+ * 台账 #327):与 `FILE_MUTATORS` 同址(门禁 / 变更记账 / L3 足迹 / 作用域规则四条面的公共输入)。
91
+ * 契约(恒数组 · 恒零抛):
92
+ * - `args` 为 null **或非对象** ⇒ 一律规范化为 `{}` 再交钩子;
93
+ * - 有钩子 ⇒ **钩子裁决**;无钩子 ⇒ `file_ops` **动作感知**(D-TO13 · 台账 #333)——`action === "copy"`
94
+ * ⇒ `[args.dest]`(源仅读取,不属写域);`move` / `rename` / 未知或缺失 `action` ⇒ `[args.source,
95
+ * args.dest]`(保守双算);其余工具单参兜底(形态零变——缺 path 同样返含 `undefined` 的单元素
96
+ * 数组,由消费方按「未知路径 ⇒ 保守」自行判定);
97
+ * - 钩子 throw / 返回非数组 ⇒ `[]`;
98
+ * - **不过滤非字符串项**(未知路径的保守判据归门禁自身——`thincoder-cli/test/portability-classification.test.mjs`
99
+ * T-22 语义零变)。
100
+ * @param {Object|undefined} tool — 工具对象(可选钩子 `touchedPaths(args)`)
101
+ * @param {unknown} args — 工具入参(畸形入参不抛)
102
+ * @returns {unknown[]} 触达路径候选(可能含非字符串项——消费方过滤)
103
+ */
104
+ export function toolTouchPaths(tool, args) {
105
+ const a = args && typeof args === "object" ? args : {}
106
+ const hook = tool?.touchedPaths
107
+ if (typeof hook !== "function") {
108
+ if (tool?.name === "file_ops") return a.action === "copy" ? [a.dest] : [a.source, a.dest]
109
+ return [a.path]
110
+ }
111
+ try {
112
+ const out = hook(a)
113
+ return Array.isArray(out) ? out : []
114
+ } catch {
115
+ return []
116
+ }
117
+ }
118
+
119
+
88
120
  /** Escape XML special characters in a string for safe embedding in XML/HTML */
89
121
  export function escapeXml(s) {
90
122
  return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;")
@@ -210,7 +242,7 @@ export class ContinueError extends Error {
210
242
  }
211
243
  }
212
244
 
213
- /** §17 D-S6 auto-turn guard 标记集(核内单源):快照/回填两 helper 共用本清单——
245
+ /** AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6 auto-turn guard 标记集(核内单源):快照/回填两 helper 共用本清单——
214
246
  * 端侧宿主载体(`panel._guardCarry`)与核载体(`agent._inheritedGuard`)同清单。 */
215
247
  export const INHERITED_GUARD_KEYS = [
216
248
  "_mutatedThisRun", "_verifiedThisRun", "_verifyPassed", "_calledAdvisorThisRun",
@@ -231,7 +263,7 @@ export function restoreGuard(target, snap) {
231
263
  for (const k of INHERITED_GUARD_KEYS) if (k in snap) target[k] = snap[k]
232
264
  }
233
265
 
234
- /** 跨段累计编号帧(TURN-CAP-CONTINUE.md §19.3——第 19 批 TURN-ACROSS-SEGMENTS):
266
+ /** 跨段累计编号帧(TURN-CAP-CONTINUE.md §4——第 19 批 TURN-ACROSS-SEGMENTS):
235
267
  * 唯一计算点(纯函数)——把链内累计序数换算成面向消费面的编号载荷。
236
268
  * - seq = 该轮链内累计序数(1 起——`agent._turnSeq` 每轮 +1,续跑不重置)
237
269
  * - turn = 段内轮号(0 起——段内帽判定的循环变量)
@@ -337,6 +369,25 @@ export function readonlyToolNames(tools) {
337
369
  return new Set(tools.filter((t) => t.readonly).map((t) => t.name))
338
370
  }
339
371
 
372
+ /** Depth>0 tool exclusions — subagents never get the interactive main-session tools (`question`:
373
+ * no user to ask; the routing guidance already lives in every persona file). Single source for
374
+ * the repo: the shell-side depth>0 filter consumes this set (design = TOOLS.md §6.16 / D-TO11). */
375
+ export const SUBAGENT_TOOL_EXCLUSIONS = new Set(["question"])
376
+
377
+ /** Apply the depth>0 exclusion set — always returns a NEW array (parent table untouched).
378
+ * #9(TOOLS.md §6.19):`bash` 的 `async` 参仅 depth-0——子代副本删参(照 `family-tools.mjs:98`
379
+ * 先例;返回值仍恒新数组、未命中条目原对象直传)。运行期第二道在 `tools/bash.mjs` execute
380
+ * (照 `read_image`「schema 门 + 运行期第二道」双线先例)。 */
381
+ export function excludeSubagentTools(tools) {
382
+ return tools
383
+ .filter((t) => !SUBAGENT_TOOL_EXCLUSIONS.has(t.name))
384
+ .map((t) => {
385
+ if (t.name !== "bash" || !t.parameters?.properties || !("async" in t.parameters.properties)) return t
386
+ const { async: _asyncDrop, ...restProps } = t.parameters.properties
387
+ return { ...t, parameters: { ...t.parameters, properties: restProps } }
388
+ })
389
+ }
390
+
340
391
  const MAX_INSTRUCTION_CHARS = 32_000
341
392
 
342
393
  /** Load AGENTS.md / project_rules.md from user home and project root.
@@ -374,7 +425,7 @@ export async function loadProjectInstructions(cwd) {
374
425
 
375
426
  /** Engineering mode reminder — shared with eng.mjs tool. */
376
427
  export const ENG_ON_REMINDER =
377
- "[System reminder: engineering mode is ON — design-before-code enforced. " +
428
+ "[System reminder: engineering mode is ON (session-scoped) — design-before-code enforced. " +
378
429
  "Workflow: Requirements doc → Design doc → advisor(type='design') → " +
379
430
  "user approval → eng-coder implementation. Code changes go through eng-coder " +
380
431
  "subagents only. Advisor calls are NOT per-turn-mandatory — call only at " +
@@ -408,6 +459,15 @@ export const AUTO_TURN_DIGEST_DOMAIN_ENG =
408
459
  export const UPSTREAM_TURN_DOMAIN =
409
460
  "[System reminder: auto-turn — a running subagent sent you an in-flight message (shown below). No user message is waiting. Decide it now and reply with subagent action:'send' (id + message) — the child consumes the reply at its next turn boundary and keeps working on the unaffected parts; if the message needs no answer, say so in one line and move on. If finished subagent reports are also present above, summarize them as usual in the same turn. Do not start new work: FORBIDDEN this turn (mechanically enforced): modifying files, bash/execute/verify, spawning subagents, asking questions — those need a real user message. End the turn once the reply is sent.]"
410
461
 
462
+ /** Timer wake-turn domain (AGENT-LOOP-ASYNC-POOL.md §6.30.3 D-TW4): a pending timer
463
+ * expired while the session was idle and the end's latch opened this turn automatically —
464
+ * neither the digest domain ("no one is waiting", organize-only) nor the up-stream domain
465
+ * (a subagent waiting for a reply) fits: the timer's own semantics is "act on what the
466
+ * timer was for". Verbatim from the parent-side final text (single line — no newlines).
467
+ * Content authority = parent side. */
468
+ export const TIMER_TURN_DOMAIN =
469
+ "[System reminder: auto-turn — a timer you set earlier has expired (reminder above). No user message is waiting. Act on what the timer was for now: pull the data or run the check rather than continuing to reason in the abstract. Stay within the work that was in flight when the timer was set; do not open unrelated new work. Wrap up as soon as the action is done or blocked.]"
470
+
411
471
  /** Engineering-mode status injection — one reminder on EVERY transition (2026-08-25:
412
472
  * OFF is announced too — the model must know the gates lifted; silence after /eng-off
413
473
  * left it guessing. Covers TUI /eng, resume, and any path bypassing the eng tool.) */
@@ -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 + §17 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).
@@ -217,7 +221,7 @@ export async function finalizeAgentTurn(agent, ctx) {
217
221
  }
218
222
  }
219
223
  agent._inAutoTurn = false
220
- // §17 D-S6: auto-turn guard marks survive into the next USER run (restored at its
224
+ // AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6: auto-turn guard marks survive into the next USER run (restored at its
221
225
  // !resume reset above). Normal ends only — abort discards; ContinueError lets the
222
226
  // auto-resumed run snapshot at its own end.
223
227
  if (autoTurn && !(signal?.aborted && !signal?.reason?.interrupt) && !(thrownError instanceof ContinueError)) {
@@ -236,13 +240,13 @@ export async function finalizeAgentTurn(agent, ctx) {
236
240
  * queued STAY — no allSettled wait. Results never lost without a session.
237
241
  * - suspDriven=true (the interaction layer runs suspensionSession after this
238
242
  * run): NO direct inject — settled entries STAY pooled (settled not consumed)
239
- * for the session's first sweepSettledToPending → digest turn (§17.5 — done
240
- * 条目留池等消化轮注入;status 在 sweep 前仍从池读——17.5.2 不变面(§19.8:check 已删——自动通道为唯一消费方))。
243
+ * for the session's first sweepSettledToPending → digest turn (AGENT-LOOP-ASYNC-POOL.md §6.8 — done
244
+ * 条目留池等消化轮注入;status 在 sweep 前仍从池读——17.5.2 不变面(AGENT-LOOP-SUBAGENT.md §6.7.5:check 已删——自动通道为唯一消费方))。
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 } = {}) {
@@ -250,14 +254,14 @@ async function collectSettledAsync(agent, { suspDriven = false } = {}) {
250
254
  if (maps.length === 0) return false
251
255
  const { maybeRefillAsync, injectAsyncResult } = await import("../agent-tools/subagent.mjs")
252
256
  maybeRefillAsync(agent) // start queued heads now that slots may have freed — no waiting
253
- if (suspDriven) return false // §17.5: settled stays pooled — the suspension session digests it
257
+ if (suspDriven) return false // AGENT-LOOP-ASYNC-POOL.md §6.8: settled stays pooled — the suspension session digests it
254
258
  let injectedAdvisor = false
255
259
  for (const map of maps) {
256
260
  for (const e of [...map.values()]) {
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
  /**