@thincoder/core 0.10.3 → 0.10.5

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 (172) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +1 -1
  3. package/advisor/citations.mjs +29 -3
  4. package/advisor/compaction.mjs +31 -7
  5. package/advisor/history.mjs +8 -6
  6. package/advisor/loop.mjs +29 -27
  7. package/advisor/messages.mjs +2 -1
  8. package/advisor/run.mjs +9 -5
  9. package/advisor/timeline.mjs +26 -0
  10. package/advisor.mjs +4 -2
  11. package/agent/assemble.mjs +1 -1
  12. package/agent/chat-call.mjs +1 -1
  13. package/agent/child-marks.mjs +2 -2
  14. package/agent/completion.mjs +3 -1
  15. package/agent/digest-account.mjs +189 -0
  16. package/agent/dispatch-gates.mjs +8 -0
  17. package/agent/dispatch.mjs +39 -18
  18. package/agent/family-tools.mjs +27 -16
  19. package/agent/helpers.mjs +12 -3
  20. package/agent/light-round.mjs +66 -0
  21. package/agent/post-turn.mjs +8 -4
  22. package/agent/record-results.mjs +4 -0
  23. package/agent/run-stages.mjs +32 -7
  24. package/agent/run-start.mjs +50 -3
  25. package/agent/setup-reminders.mjs +1 -1
  26. package/agent/setup.mjs +18 -3
  27. package/agent/subagent-event.mjs +1 -1
  28. package/agent/suspension.mjs +37 -12
  29. package/agent/timers.mjs +56 -5
  30. package/agent/turn-loop.mjs +30 -5
  31. package/agent/write-gate.mjs +20 -32
  32. package/agent-tools/advisor-async.mjs +15 -7
  33. package/agent-tools/advisor-settle.mjs +2 -2
  34. package/agent-tools/advisor.mjs +34 -33
  35. package/agent-tools/async-discard.mjs +34 -0
  36. package/agent-tools/async-settle.mjs +32 -7
  37. package/agent-tools/audit-block.mjs +1 -1
  38. package/agent-tools/bash-async.mjs +1 -1
  39. package/agent-tools/batch-lifecycle.mjs +59 -17
  40. package/agent-tools/batch-paths.mjs +24 -4
  41. package/agent-tools/batch-skeleton.mjs +59 -43
  42. package/agent-tools/batch.mjs +39 -18
  43. package/agent-tools/browser-async.mjs +198 -0
  44. package/agent-tools/checkpoint.mjs +1 -1
  45. package/agent-tools/design-slots.mjs +232 -0
  46. package/agent-tools/design-token.mjs +20 -0
  47. package/agent-tools/digest-budget.mjs +1 -1
  48. package/agent-tools/escalate-async.mjs +4 -2
  49. package/agent-tools/manifest.mjs +193 -0
  50. package/agent-tools/parent-channel.mjs +7 -4
  51. package/agent-tools/review-facts.mjs +143 -16
  52. package/agent-tools/settings.mjs +1 -1
  53. package/agent-tools/skill.mjs +1 -1
  54. package/agent-tools/subagent-actions-query.mjs +13 -6
  55. package/agent-tools/subagent-async.mjs +31 -15
  56. package/agent-tools/subagent-panel.mjs +2 -1
  57. package/agent-tools/subagent-scheduler.mjs +8 -5
  58. package/agent-tools/subagent-spawn.mjs +30 -56
  59. package/agent-tools/subagent.mjs +21 -8
  60. package/agent-tools/timer.mjs +186 -12
  61. package/agent-tools/zero-write-watch.mjs +77 -0
  62. package/agent-tools.mjs +2 -1
  63. package/agent.mjs +2 -2
  64. package/attachments.mjs +1 -1
  65. package/config-io.mjs +18 -13
  66. package/config-migrate.mjs +13 -29
  67. package/config-presets.mjs +33 -29
  68. package/config.mjs +68 -25
  69. package/context-degrade.mjs +1 -1
  70. package/context-echo.mjs +1 -1
  71. package/context-push.mjs +2 -2
  72. package/context.mjs +1 -1
  73. package/conventions.mjs +28 -8
  74. package/declaration.mjs +18 -2
  75. package/escape.mjs +1 -1
  76. package/file-links.mjs +1 -1
  77. package/file-refs.mjs +1 -1
  78. package/history-window.mjs +1 -1
  79. package/i18n.mjs +7 -0
  80. package/ledger-cmd.mjs +62 -16
  81. package/ledger-db.mjs +6 -0
  82. package/ledger-surface.mjs +49 -5
  83. package/ledger-tools.mjs +158 -87
  84. package/ledger-variant-notice.mjs +47 -0
  85. package/ledger.mjs +11 -18
  86. package/log.mjs +1 -1
  87. package/manifest-discovery.mjs +1 -1
  88. package/manifest-schema.mjs +7 -5
  89. package/manifest.mjs +5 -3
  90. package/mcp/transport-stdio.mjs +1 -1
  91. package/mcp.mjs +1 -1
  92. package/memory/code-index.mjs +1 -1
  93. package/memory/code-search.mjs +1 -1
  94. package/memory/code-sync.mjs +1 -1
  95. package/memory/docs.mjs +6 -2
  96. package/memory/file-list.mjs +1 -1
  97. package/memory/file-walk.mjs +17 -5
  98. package/memory/maintain.mjs +410 -0
  99. package/memory/memory-tool.mjs +21 -6
  100. package/memory/schema.mjs +19 -7
  101. package/memory/sweep.mjs +8 -5
  102. package/memory/sync-tail.mjs +16 -10
  103. package/model-ref.mjs +16 -13
  104. package/model-specs.mjs +21 -9
  105. package/package.json +1 -1
  106. package/peer-instances.mjs +1 -1
  107. package/permission.mjs +33 -0
  108. package/process-probe.mjs +2 -2
  109. package/prompts/advisor-design.md +4 -2
  110. package/prompts/advisor-round1.md +1 -1
  111. package/prompts/advisor-round2.md +1 -1
  112. package/prompts/advisor-round3.md +1 -1
  113. package/prompts/common.md +38 -4
  114. package/prompts/consult-base.md +1 -1
  115. package/prompts/discipline-engineering.md +32 -15
  116. package/prompts/discipline-normal.md +46 -2
  117. package/prompts/persona-eng-designer.md +6 -6
  118. package/prompts/persona-engineering.md +18 -9
  119. package/prompts/persona-normal.md +2 -0
  120. package/prompts/subagent-base.md +3 -0
  121. package/provider/anthropic.mjs +14 -2
  122. package/provider/core.mjs +6 -0
  123. package/provider/errors.mjs +28 -3
  124. package/provider/google.mjs +22 -10
  125. package/provider/list-models.mjs +3 -2
  126. package/provider/responses-request.mjs +3 -1
  127. package/provider/responses.mjs +38 -4
  128. package/provider/sse.mjs +3 -0
  129. package/provider-flows.mjs +14 -15
  130. package/proxy-chunked.mjs +102 -0
  131. package/proxy-target.mjs +54 -0
  132. package/proxy-transport.mjs +298 -0
  133. package/proxy.mjs +17 -248
  134. package/session-gc.mjs +2 -2
  135. package/session-lifecycle.mjs +18 -14
  136. package/session-slot-claims.mjs +2 -2
  137. package/session-slots-manifest.mjs +1 -1
  138. package/session-slots.mjs +3 -3
  139. package/shell-identity.mjs +107 -0
  140. package/team-ledger.mjs +282 -0
  141. package/team.mjs +223 -0
  142. package/tool-docs/advisor.md +1 -1
  143. package/tool-docs/bash.md +2 -1
  144. package/tool-docs/batch.md +1 -1
  145. package/tool-docs/browser.md +48 -0
  146. package/tool-docs/delete.md +7 -5
  147. package/tool-docs/ledger.md +6 -0
  148. package/tool-docs/manifest.md +16 -0
  149. package/tool-docs/memory.md +1 -0
  150. package/tool-docs/notify_parent.md +5 -0
  151. package/tool-docs/process.md +4 -4
  152. package/tool-docs/question.md +1 -0
  153. package/tool-docs/subagent.md +14 -3
  154. package/tool-docs/team_ledger.md +9 -0
  155. package/tool-docs/timer.md +6 -0
  156. package/tool-docs/wait_for.md +1 -0
  157. package/tools/bash.mjs +9 -4
  158. package/tools/browser.mjs +161 -0
  159. package/tools/git.mjs +1 -1
  160. package/tools/index.mjs +4 -4
  161. package/tools/ops.mjs +26 -10
  162. package/tools/patch.mjs +8 -2
  163. package/tools/repomap.mjs +1 -1
  164. package/tools/write-path.mjs +8 -6
  165. package/traces/trace-cleanup.mjs +1 -1
  166. package/traces/trace-store.mjs +1 -1
  167. package/vision-reader.mjs +10 -16
  168. package/tool-docs/ledger_add.md +0 -1
  169. package/tool-docs/ledger_close.md +0 -1
  170. package/tool-docs/ledger_count.md +0 -1
  171. package/tool-docs/ledger_query.md +0 -1
  172. package/tool-docs/ledger_update.md +0 -1
@@ -0,0 +1,189 @@
1
+ /**
2
+ * agent/digest-account.mjs — 消化账务单件(AGENT-LOOP-ASYNC-POOL.md §6.31 · 批 digest-accounting ·
3
+ * 台账 #930):投递 ≠ 销账——报告不许无声消失。
4
+ *
5
+ * 三角色(§6.31.2):**投递**(注入但留容器)∥ **见账**(消化轮收尾逐条机检账目覆盖)∥
6
+ * **销账**(覆盖才离容器 + 释放 report)。本档承载:常量(`DA_RETRY_LIMIT`)/ 回合分类
7
+ * (`isDigestRound`)/ 投递门控(`shouldDeliverEntry`)/ 账目合同(`coversDigestId` 解析 ∥
8
+ * `digestAccountRequirement` 要求行)/ 见账 pass(`accountDigestRound`——销账 ∥ 重投标记 ∥
9
+ * 升级)/ 读面(`unsettledRows` · `unsettledRow` · `unsettledCount`)/ 落痕清位 ∕ 取件助手
10
+ * (`armAccountRound` · `harvestAccountOutput`)。
11
+ *
12
+ * 载体(§6.31.2):`_daSession`(会话标记——挂起会话置 / 复位,`agent/suspension.mjs`)与
13
+ * `_unsettledDigests`(升级账本——经 `async-settle.mjs` 写助手);账目态挂条目字段
14
+ * (`_daDelivered` / `_daRetry` / `_daFailures`)。**fallback 面**(无 `_daSession`——headless /
15
+ * 直连 `runAgent`)零账目动作(不销账 / 不重投 / 不升级):缺省条逐字沿用取尽语义(先离容后注入);
16
+ * 已投递未销账条同判据跳过且留容器(§6.31 出口缝② · 父侧裁定 2026-10-05)。
17
+ *
18
+ * 边界(§6.31.12):不做处理质量机检(只保「不许无声」);升级账本无 TTL(会话期面——
19
+ * 中止清、idle 存续)。
20
+ */
21
+ import { logEvent } from "../log.mjs"
22
+ import { pushReal } from "../context.mjs"
23
+ import { carrierField, releaseSettledEntry, appendUnsettledDigest } from "../agent-tools/async-settle.mjs"
24
+ import { DIGEST_ACCOUNT_DOMAIN } from "./helpers.mjs"
25
+
26
+ /** 同条重投上限(§6.31.5 KD-DA4):重投 ≤ DA_RETRY_LIMIT ⇒ 总投递 ≤ 3 次;超限 ⇒ 升级。 */
27
+ export const DA_RETRY_LIMIT = 2
28
+
29
+ /**
30
+ * 回合分类单点(§6.31.2):账目只属**消化轮**(autoTurn ∧ 非上行唤醒轮 ∧ 非 timer 轮)——
31
+ * timer / 上行轮投递不销账、不重投(其账目在后续消化轮)。
32
+ */
33
+ export function isDigestRound(opts) {
34
+ return opts?.autoTurn === true && opts?.upstreamTurn !== true && opts?.timerTurn !== true
35
+ }
36
+
37
+ /**
38
+ * 投递门控(§6.31.4 单点谓词;批 async-delivery-formalize 2026-10-12 · §6.34 收窄):
39
+ * - `!_daDelivered` ∧ 本回合 = 系统轮(`autoTurn`)⇒ 投(首次投递——**用户回合零投递**);
40
+ * - `_daRetry` ∧ 本回合 = 消化轮 ⇒ 投(重投经消化轮回路);
41
+ * - 其余 ⇒ 跳过(防重复注入)。
42
+ * 注:`autoTurn` 判据由调用方(`run-start.mjs` 会话态支)传入;回退面(非会话态支)不经本谓词。
43
+ */
44
+ export function shouldDeliverEntry(entry, digestRound, autoTurn) {
45
+ if (entry?._daDelivered !== true) return autoTurn === true
46
+ return entry._daRetry === true && digestRound === true
47
+ }
48
+
49
+ /** 账目要求行(§6.31.3):核常量(helpers.mjs)+ 未销账 id 清单动态拼(`<ids>` 占位替换)。 */
50
+ export function digestAccountRequirement(ids) {
51
+ return DIGEST_ACCOUNT_DOMAIN.replace("<ids>", ids.map((id) => `#${id}`).join(", "))
52
+ }
53
+
54
+ /**
55
+ * 账目覆盖机检(§6.31.3):输出文本内**存在一行**同时含标记 `[digest-ack` 与 `#<id>`
56
+ * (id 词界 = 后随非数字)⇒ 该条覆盖;disposition / 要点 / 原因**不参与机检**。
57
+ * 无标记 / 无 id / 部分覆盖各按本条判(零工具轮可满足——纯文本判据)。
58
+ * 注:行内扫**全部**出现位(长 id 首现不吻合不弃行——如 id=1 行内先见 `#12` 后见 `#1`)。
59
+ */
60
+ export function coversDigestId(output, id) {
61
+ const key = `#${id}`
62
+ for (const line of String(output ?? "").split(/\r?\n/)) {
63
+ const marker = line.indexOf("[digest-ack")
64
+ if (marker < 0) continue
65
+ for (let from = marker; ;) {
66
+ const at = line.indexOf(key, from)
67
+ if (at < 0) break
68
+ const after = line[at + key.length]
69
+ if (after === undefined || !/[0-9]/.test(after)) return true
70
+ from = at + 1 // 词界不吻合 ⇒ 续扫下一出现位(同长 id 前缀不吞后随正例)
71
+ }
72
+ }
73
+ return false
74
+ }
75
+
76
+ /**
77
+ * 见账 pass(§6.31.5——消化轮收尾 `finalizeAgentTurn` 单点调用;调用方已判 `isDigestRound ∧
78
+ * `_daSession` ∧ 非会话停止径):
79
+ * - 投递账 = pending ∩ `_daDelivered`;覆盖 ⇒ 离容器(splice)+ `releaseSettledEntry`(含 report 释放);
80
+ * - 未覆盖 ⇒ `_daFailures += 1`;≤ `DA_RETRY_LIMIT` ⇒ `_daRetry = true`(下轮重投);超限 ⇒ **升级**
81
+ * (离容器 + 入 `_unsettledDigests` + 恰一条提醒(pushReal)+ 恰一条 `ev:unsettled`)。
82
+ * 缺痕(output == null——抛错 / 撞帽 / 未收口)⇒ 全条未覆盖(安全方向——假阴可重投;假阳是事故本相)。
83
+ */
84
+ export function accountDigestRound(carrier) {
85
+ const pending = carrierField(carrier, "_pendingAsyncResults")
86
+ if (!Array.isArray(pending)) return { covered: 0, retried: 0, unsettled: 0 }
87
+ const output = harvestAccountOutput(carrier)
88
+ const covered = []
89
+ const retried = []
90
+ const upgraded = []
91
+ for (const entry of [...pending]) {
92
+ if (entry?._daDelivered !== true) continue
93
+ if (output != null && coversDigestId(output, entry.id)) { covered.push(entry); continue }
94
+ entry._daFailures = (entry._daFailures ?? 0) + 1
95
+ if (entry._daFailures <= DA_RETRY_LIMIT) { entry._daRetry = true; retried.push(entry) }
96
+ else upgraded.push(entry)
97
+ }
98
+ for (const entry of covered) {
99
+ const i = pending.indexOf(entry)
100
+ if (i >= 0) pending.splice(i, 1)
101
+ releaseSettledEntry(entry)
102
+ }
103
+ for (const entry of upgraded) {
104
+ const i = pending.indexOf(entry)
105
+ if (i >= 0) pending.splice(i, 1)
106
+ appendUnsettledDigest(carrier, {
107
+ id: String(entry.id),
108
+ role: entry.role ?? "subagent",
109
+ failures: entry._daFailures,
110
+ report: entry.report ?? null,
111
+ at: Date.now(),
112
+ })
113
+ }
114
+ // 升级:恰一条提醒(机器线 + 人读线——`pushReal`)+ 恰一条留痕(同点;字段单源 = LOGGING.md §6.2)。
115
+ // 注:`ids` 以 join 串落盘——`logEvent` 只收标量(数组字段会被静默丢弃——先例 `ev:discarded`)。
116
+ if (upgraded.length > 0) {
117
+ const ids = upgraded.map((entry) => String(entry.id))
118
+ pushReal(carrier, { role: "user", content: unsettledReminder(upgraded.length, ids) })
119
+ logEvent("ev:unsettled", { n: upgraded.length, ids: ids.join(", ") })
120
+ }
121
+ return { covered: covered.length, retried: retried.length, unsettled: upgraded.length }
122
+ }
123
+
124
+ /** 升级提醒行(§6.31.5——逐字 = 父侧定稿:含 n / 轮数(= 重投上限 + 1)/ id 清单与接手指引)。 */
125
+ export function unsettledReminder(n, ids) {
126
+ return `[System reminder: ${n} background report(s) were delivered but never accounted after ${DA_RETRY_LIMIT + 1} digest rounds — automatic re-delivery has stopped and they are marked UNSETTLED: ${ids.map((id) => `#${id}`).join(", ")}. Their content was delivered in this conversation; process them there or re-spawn the work. Readable via subagent action:'status'.]`
127
+ }
128
+
129
+ /**
130
+ * 读面(§6.31.6——status 段 ∥ 端面残余行共用单点):行 = `{ id, role, state, attempts }`;
131
+ * state ∈ `awaiting-digest`(在途未投递 ∥ 已投递未判)/ `retrying`(判过未覆盖)/
132
+ * `unsettled`(已升级——随携 `preview` ≤400 字符)。
133
+ */
134
+ export function unsettledRows(carrier) {
135
+ const rows = []
136
+ const pending = carrierField(carrier, "_pendingAsyncResults")
137
+ for (const entry of Array.isArray(pending) ? pending : []) {
138
+ const failures = entry?._daFailures ?? 0
139
+ rows.push({
140
+ id: String(entry.id),
141
+ role: entry.role ?? "subagent",
142
+ state: failures > 0 ? "retrying" : "awaiting-digest",
143
+ attempts: failures,
144
+ })
145
+ }
146
+ const ledger = carrierField(carrier, "_unsettledDigests")
147
+ for (const u of Array.isArray(ledger) ? ledger : []) {
148
+ rows.push({
149
+ id: String(u.id),
150
+ role: u.role ?? "subagent",
151
+ state: "unsettled",
152
+ attempts: u.failures ?? 0,
153
+ preview: String(u.report ?? "").slice(0, 400),
154
+ })
155
+ }
156
+ return rows
157
+ }
158
+
159
+ /** 单查 id 同解析(§6.31.6 单查序第三 / 四站):命中 pending / 升级账本 ⇒ 行形同上;未命中 ⇒ null。 */
160
+ export function unsettledRow(carrier, id) {
161
+ const key = String(id)
162
+ return unsettledRows(carrier).find((row) => row.id === key) ?? null
163
+ }
164
+
165
+ /** 端面残余行判据(§6.31.6):pending 中 `_daFailures > 0` 条数(升级条已离容器——由升级提醒承载)。 */
166
+ export function unsettledCount(carrier) {
167
+ const pending = carrierField(carrier, "_pendingAsyncResults")
168
+ let n = 0
169
+ for (const entry of Array.isArray(pending) ? pending : []) if ((entry?._daFailures ?? 0) > 0) n += 1
170
+ return n
171
+ }
172
+
173
+ /** 落痕清位(§6.31.5——`run-start.mjs` 会话态起跑即清;fallback 零动作):父字段 ∥ history 双清。
174
+ * 注:核内写点落**父字段**(`turn-loop.mjs` 的 `agent._lastRunOutput`——逐轮追加,本批 #939
175
+ * 收正;抗 `history` 整体替换——`context.mjs` `applyCompression`);`history` 级为兼容读形
176
+ * (`harvestAccountOutput` 回退分支)——该痕不属跨 run 字段集,§6.31.5 边界句。 */
177
+ export function armAccountRound(carrier) {
178
+ if (!carrier || typeof carrier !== "object") return
179
+ carrier._lastRunOutput = null
180
+ if (carrier.history && typeof carrier.history === "object") carrier.history._lastRunOutput = null
181
+ }
182
+
183
+ /** 落痕取件(§6.31.5——载体吸收读:父字段优先 / 回退 `history`);读值 = 本 run 多轮累积文本(#939);
184
+ * 缺痕(本 run 零输出落痕——零模型轮 / 起跑即断)⇒ null。 */
185
+ export function harvestAccountOutput(carrier) {
186
+ const own = carrier?._lastRunOutput
187
+ if (own !== undefined && own !== null) return own
188
+ return carrier?.history?._lastRunOutput ?? null
189
+ }
@@ -76,6 +76,11 @@ export function isSubagentReadonlyAction(toolName, args) {
76
76
  const action = args?.action
77
77
  return action === "list" || action === "get"
78
78
  }
79
+ // manifest read 只读格(`MANIFEST.md` §2.10 · 台账 #1098——settings list/get 同分类;init / write 保持侧效门)
80
+ if (toolName === "manifest") {
81
+ const action = args?.action
82
+ return action === "read"
83
+ }
79
84
  // #9(TOOLS.md §6.19):process 动作分级——list 保持只读分类(planMode 放行/免审批/可批并行);
80
85
  // kill 是破坏性动作(审批门 + planMode 拒)。
81
86
  if (toolName === "process") return args?.action !== "kill"
@@ -87,6 +92,9 @@ export function isSubagentReadonlyAction(toolName, args) {
87
92
  if (action === "observe") return true
88
93
  // AGENT-LOOP-SUBAGENT.md §6.7.2 panel view 面(freeze 缺省/空 = 视图请求——readonly;非空 freeze 归控制类)
89
94
  if (action === "panel" && (args.freeze === undefined || args.freeze === null || String(args.freeze) === "")) return true
95
+ // DESIGN-TOKEN-SETTLEMENT.md §10 F-SL1(2026-10-07 批 ledger-tool):design-slots = 只读清点
96
+ // (同 status / observe——planMode 放行 / 免审批 / digest 放行;工程模式父侧门在执行器内)。
97
+ if (action === "design-slots") return true
90
98
  return false
91
99
  }
92
100
  export function isSubagentControlAction(toolName, args) {
@@ -7,9 +7,12 @@
7
7
  */
8
8
  import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
9
9
  import { runHooks } from "../hooks.mjs"
10
- import { isCodePath, loadProjectDeclaration } from "../conventions.mjs"
10
+ import { isCodePath, declarationForTarget } from "../conventions.mjs"
11
+ import { manifestFilePath } from "../manifest.mjs"
11
12
  import { resolve, relative } from "node:path"
12
13
  import { anyLiveDesignSlot } from "../token-ttl.mjs"
14
+ // F-LC5(LIGHT-CHANNEL §2.8 · 2026-10-09):轻通道轮在盘谓词(代码笔通路——两件双在盘 ⇒ 父侧代码笔放行)。
15
+ import { lightRoundOpen } from "./light-round.mjs"
13
16
  // M4 写权门禁(模块设计 §2.1#2):冻结窗口判据组装(被审文件集 = 声明文档集 + 批次档
14
17
  // 的合流点)落 write-gate.mjs 单一权威源——本档只 import 消费(KD-M4-4 拆分点)。
15
18
  import { freezeWindowConflict, batchRecordWriteConflict } from "./write-gate.mjs"
@@ -83,28 +86,46 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
83
86
  // design token. The project can declare its own code paths (the manifest's
84
87
  // `codePaths`) so a non-src layout is not silently exempted. Mechanically
85
88
  // blocks "talk then code".
89
+ // F-LC5(LIGHT-CHANNEL §2.8 · 2026-10-09):轻通道轮在盘 = 机械开路——轮次台账行(在途 · title 携「收尾链
90
+ // 待跑」· 携指针)与所指轮档(§1 状态行「进行中」)双在盘期间父侧代码笔放行(谓词 fail-closed)。
91
+ // 查找面 = 会话锚 + 其直接子目录一层(纯向下——工作区锚会话同判)。
92
+ // F11(#1104 · PORTABILITY §3.9 / D20):辖域 = 目标所属项目——逐目标沿祖先链取最近带档
93
+ // 目录(`declarationForTarget`——nearest wins ∥ 纯向上 ∥ 发现梯不参与);出辖(祖先链无档)
94
+ // ⇒ 放行(门只管 manifest 树以内的内容)。相对形先按会话 cwd 解析(cwd 只是相对基,非声明源);
95
+ // 在辖目标按所属项目装载声明,项目内判定(code 段 ∥ 兜底 fail-closed ∥ doc·temp·aux·state 豁免)零改。
86
96
  // DESIGN-TOKEN-SETTLEMENT D3(2026-09-08):资格判据 = 权威槽"任一活槽存在"
87
97
  // (anyLiveDesignSlot——查内存 Map,miss 回读槽文件——单值镜像 `_engDesignToken`
88
98
  // 已退役,门禁不再读镜像——AC4)。
89
99
  if (agent.config?.agent?.engineering && depth === 0
90
100
  && !anyLiveDesignSlot(agent)
91
- && FILE_MUTATORS.has(toolCall.name)) {
101
+ && FILE_MUTATORS.has(toolCall.name)
102
+ && !(await lightRoundOpen(agent))) {
92
103
  const paths = toolTouchPaths(tool, args)
93
- const conv = loadProjectDeclaration(agent.cwd)
94
- // Unknown/missing paths (non-string, e.g. no path argument) are treated
95
- // as code — cannot tell what they touch, so block conservatively. Known
96
- // paths go through the single shared classifier: a declared code segment
97
- // (default: "src") at ANY depth, else anything that is not documentation.
98
- const touchesCode = paths.some((p) => typeof p !== "string" || isCodePath(p, conv))
99
- if (touchesCode) {
100
- // Undeclared project → point at the declaration file (§4.3 降级可见契约).
101
- const convNote = conv.declared
102
- ? ""
103
- : ` — this path was classified as product code by the default conventions (code paths: ${conv.codePaths.join(", ")}); declare project conventions in PROJECT-MANIFEST.json to adjust.`
104
+ // Unknown/missing paths (non-string / empty — nothing to judge) block conservatively.
105
+ // Known paths are judged per target: resolve against the session cwd first, then
106
+ // classify on the ABSOLUTE form against the target's OWNING PROJECT declaration
107
+ // (the segment face anchors at that project's root — D17). Out of jurisdiction ⇒ the
108
+ // target passes; non-string / empty targets keep blocking above (nothing to judge).
109
+ let blocked = false
110
+ let noteConv = null // first undeclared-and-blocked target's project (hint pointer — #1102)
111
+ for (const p of paths) {
112
+ if (typeof p !== "string" || p.trim() === "") { blocked = true; continue }
113
+ const abs = resolve(agent.cwd, p)
114
+ const conv = declarationForTarget(abs)
115
+ if (!conv || !isCodePath(abs, conv)) continue
116
+ blocked = true
117
+ if (!noteConv && !conv.declared) noteConv = conv
118
+ }
119
+ if (blocked) {
120
+ // Undeclared project → point at ITS declaration file (§4.3 降级可见契约; #1102).
121
+ const convNote = noteConv
122
+ ? ` — this path was classified as product code by the default conventions (code paths: ${noteConv.codePaths.join(", ")}); declare project conventions in ${manifestFilePath(noteConv.root)} to adjust.`
123
+ : ""
104
124
  prepared.push({
105
125
  toolCall, tool, denied: true,
106
126
  reason: "engineering design gate",
107
- hint: `Engineering mode: write the design document first(location per your project's document conventions), then call advisor with type='design' to review it, and wait for user approval. Implementation is done by eng-coder subagents.${convNote}`,
127
+ hint: `Engineering mode: write the design document first(location per your project's document conventions), then call advisor with type='design' to review it, and wait for user approval. Implementation is done by eng-coder subagents.${convNote} ` +
128
+ `If this is a light-channel code pen: the gate looks for the round from the session anchor and its direct subdirectories — book the round first (an in-flight batch record plus a 在途 ledger row carrying 「收尾链待跑」 pointing at the record); code pens pass once that is on disk.`,
108
129
  })
109
130
  continue
110
131
  }
@@ -113,14 +134,14 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
113
134
  // E(第 11 批·F17/ADVISOR-GUARDS.md §5 E-3d):D5 冻结窗口写前拦截——设计评审在途(点火 → 结算)期间,
114
135
  // 父侧对被审文件集(声明文档集 + 批次档)的写入会被拒绝:在途写使本轮结算 stale——pass 轮
115
136
  // = token 直接丢失(实证:第 10 批 id=20 整轮作废)。工具面 = FILE_MUTATORS ∪ `file_ops`
116
- // (E2——冻结腿覆盖面;批次档腿仍 = FILE_MUTATORS:与变更记账同集——不记入日志的写面既不判 stale 也不拦);判据与 reviewIsStale 同源、单一权威源 =
117
- // agent/write-gate.mjs 的 freezeWindowConflict(声明文档集腿 = inflightDesignReviewConflict
118
- // 同 docAbs / 同 normAbs;批次档腿 = run.batchDoc——M4 合流点,仅扫 running 未取消的设计条目)。
137
+ // (E2——冻结分支覆盖范围;批次档分支仍 = FILE_MUTATORS:与变更记账同集——不记入日志的写面既不判 stale 也不拦);判据与 reviewIsStale 同源、单一权威源 =
138
+ // agent/write-gate.mjs 的 freezeWindowConflict(声明文档集分支 = inflightDesignReviewConflict
139
+ // 同 docAbs / 同 normAbs;批次档分支 = run.batchDoc——M4 合流点,仅扫 running 未取消的设计条目)。
119
140
  // 位置:只读 / autoApprove 短路之前——审批不得绕过冻结;拒绝 = 可见 denied + 逃生门
120
141
  // (先 cancel → 改 → 重发)。
121
142
  if (FILE_MUTATORS.has(toolCall.name) || toolCall.name === "file_ops") {
122
143
  // #327:单源谓词(同集合内两处消费共用);#309 批次档写门与 D5 冻结窗同区、共用同一路径集。
123
- // E2(parity-b1 §2.5——缺省取核形):外门扩 `file_ops`(冻结窗覆盖面与端壳对齐——`file_ops`
144
+ // E2(parity-b1 §2.5——缺省取核形):外门扩 `file_ops`(冻结窗覆盖范围与端壳对齐——`file_ops`
124
145
  // 的 move ∕ copy ∕ rename 亦为写面);**批次档写门判据集不变**(= `FILE_MUTATORS`,file_ops 不入)。
125
146
  const absPaths = toolTouchPaths(tool, args)
126
147
  .filter((p) => typeof p === "string" && p)
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * 登记册**动态**载入:`agent-tools.mjs` 静态图经 consult/subagent 族可达核 agent 栈
15
15
  * (`../agent/setup.mjs` → `../memory.mjs` → `node:sqlite`)——静态引入会破端壳 W8 契约②
16
- * (W8 契约②判据(现载体 = 批件 `docs/batches/2026-09-29-residuals-round2.test.mjs`;单测树重建时回迁端侧单测档):端壳静态链不得到达 `node:sqlite`)。
16
+ * (W8 契约②判据(现载体 = 单元测试档 `docs/batches/2026-09-29-residuals-round2.test.mjs`;单元测试树重建时回迁端侧单元测试档):端壳静态链不得到达 `node:sqlite`)。
17
17
  * 核内先例 = 登记册头注「Loaded from agent.mjs via dynamic import to avoid ESM circular
18
18
  * dependencies」(本档先于 `agent/setup.mjs` 的调用点载入,同一语义)。
19
19
  */
@@ -22,14 +22,17 @@ export async function assembleFamilyTools({
22
22
  role = null, // string 子代理角色(eng-coder / eng-designer / coder / consult / explore / …)
23
23
  engineering = false, // boolean depth-0 role enum 注入用(工程模式)
24
24
  consultModels = [], // array consult 池([] ⇒ consult 工具不注册)
25
- batchDoc = null, // string 批次档绑定路径(eng 角色 → batchTool 绑定)
25
+ batchDoc = null, // string 批次档绑定路径(eng 角色恒挂 ∥ 普通面 coder 携绑定挂——batchTool 绑定)
26
26
  decorate = null, // object 端差面:{ subagent?, consultStart?, consultStop?, settings? }
27
27
  } = {}) {
28
28
  // CORE-UNIFICATION TOOLS #83:consult 家族随统一登记册自 `../agent-tools.mjs` 取用(单一来源)
29
- const { planTool, subagentTool, taskTool, skillTool, goalTool, verifyTool, recentChangesTool, timerTool, advisorTool, engTool, readHistoryTool, contextTool, batchTool, consultStartTool, consultStopTool, parentChannelTool } = await import("../agent-tools.mjs")
30
- // 写命令(主 agent 专用)——动态 import 且**仅 depth===0 载入**(ledger 链静态达 node:sqlite——
31
- // W8 契约②;子代理路径不注册 = 零载入——depth>0 解构得空、不引用即无副作用)
32
- const { ledgerAddTool, ledgerUpdateTool, ledgerCloseTool } = depth === 0 ? await import("../ledger.mjs") : {}
29
+ const { planTool, subagentTool, taskTool, skillTool, goalTool, verifyTool, recentChangesTool, timerTool, advisorTool, engTool, readHistoryTool, contextTool, batchTool, manifestTool, consultStartTool, consultStopTool, parentChannelTool } = await import("../agent-tools.mjs")
30
+ // 台账统一入口(2026-10-05 统一入口批——台账 #923):全量变体 = depth-0 段 ∥ 只读变体 = 全部
31
+ // depth>0 段。动态 import——ledger 链静态达 node:sqlite(W8 契约②);两变体同名单对象、schema 窄化。
32
+ const { ledgerTool, ledgerReadTool } = await import("../ledger.mjs")
33
+ // 团队台账核工具(2026-10-13 team-ledger 批——TEAM-LEDGER.md §2.5):全量五 action = depth-0 段 ∥ 只读
34
+ // 变体(query/count)= 全部 depth>0 段(写三 schema 级不可达——D-TL5)。动态 import 同律(沿上条)。
35
+ const { teamLedgerTool, teamLedgerReadTool } = await import("../team-ledger.mjs")
33
36
 
34
37
  // withPool: decorate the consult_start description with the CURRENT candidate pool
35
38
  // so the model knows which models it can pick (CLI parity with the plugin). The
@@ -141,9 +144,13 @@ export async function assembleFamilyTools({
141
144
  const depthOnly = depth === 0
142
145
  ? [decorate?.subagent ?? filteredSubagent, skillTool, goalTool, engTool, verifyTool, recentChangesTool, readHistoryTool, contextTool, advisorTool, batchTool(null),
143
146
  ...consultTools,
144
- // 台账写命令(M2——仅主 agent;查询面 ledger_count 住基础集 tools/index.mjs)。
145
- // fail-closed:子代理不挂载 = 写面机械不可达。
146
- ledgerAddTool, ledgerUpdateTool, ledgerCloseTool,
147
+ // 台账全量变体(五 action——仅主 agent 装配;fail-closed:子代理段只挂读变体 = 写面机械不可达)
148
+ ledgerTool,
149
+ // 团队台账全量变体(五 action——同律:子代理段只挂读变体;登录态门在工具内)
150
+ teamLedgerTool,
151
+ // manifest 工具(`MANIFEST.md` §2.10 · 2026-10-08 · 台账 #1098):主 agent 读写出口——
152
+ // **两模式同挂**(需求未设模式门);子代理各段零挂(未装配 = 权限面第一道)。
153
+ manifestTool,
147
154
  // 端差(decorate.settings——VSC depth-0 主 agent 面;缺省不追加):核默认形态里
148
155
  // settings 住**基础集**(`tools/index.mjs` `assembleBuiltinTools`),端侧自持清单
149
156
  // 无该面 ⇒ 端以 decorate 补位(收敛通道 = 将来去 decorate 项即归核位)。
@@ -161,18 +168,22 @@ export async function assembleFamilyTools({
161
168
  // BATCH-RECORD §4.3 挂载表(批次档生命周期工具化批):eng 两分支各挂主名 `batch`
162
169
  // (绑定段 append/status;目标 = spawn 绑定 `child._batchDoc`)——BATCH-RECORD.md §4.2 的 batch_segment
163
170
  // 挂载形态已随单名化收口退役;depth-0 主 agent 同表挂载
164
- // `batch`(D-BR18 扩权——create/close + append §1/§4/§6 + status §1(轮 2 裁定②:§4/§6
165
- // 状态面走普通文档写),目标走可选 path / 在飞扫描)。
171
+ // `batch`(D-BR18 扩权——create/close + append/status 域随模式(工程面 = append §1/§4/§6 ∥ status §1;普通面 =
172
+ // append §1/§2/§4/§5/§6 ∥ status §1/§2/§5)(轮 2 裁定②:§4/§6 状态面走普通文档写),目标走可选 path / 在飞扫描)。
173
+ // §4.16(2026-10-09):coder 分支**携绑定追加** `batchTool(batchDoc)`——未绑定不挂载
174
+ // (工具不在表 = 语法面不可达——同评审面先例);绑定值 = spawn 可选绑定 `child._batchDoc`。
166
175
  // SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 装配接线):子代理上行通道
167
176
  // (`notify_parent`)随 depth>0 段**前置**——4 处携带 = eng-coder / eng-designer / coder / 兜底段
168
177
  // (未列名 depth>0 role 落同一兜底段 ⇒ 亦装配;语义 =「depth>0 且非 consult 皆装配」);
169
178
  // 计数口径:`consult` 分支不入 ⇒ 「5 个插入点」读法已作废(实读 `thincoder-core/agent/family-tools.mjs:165-169`)。
170
179
  // consult 段不入(其角色语义 = 父发起的一次性会诊,父在其 settle 前不期望中途对话)。
171
- : engChildRole === "eng-coder" ? [parentChannelTool, advisorTool, verifyTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
172
- : engChildRole === "eng-designer" ? [parentChannelTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
173
- : role === "coder" ? [parentChannelTool, verifyTool, advisorTool]
174
- : role === "consult" ? [recentChangesTool]
175
- : [parentChannelTool]
180
+ // 台账只读变体(query / count)——全部 depth>0 角色段(含 consult ∕ 兜底;写三 schema 级不可达);
181
+ // 团队台账只读变体(team-ledger 批)同律。
182
+ : engChildRole === "eng-coder" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, advisorTool, verifyTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
183
+ : engChildRole === "eng-designer" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
184
+ : role === "coder" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, verifyTool, advisorTool, ...(batchDoc ? [batchTool(batchDoc)] : [])]
185
+ : role === "consult" ? [recentChangesTool, ledgerReadTool, teamLedgerReadTool]
186
+ : [parentChannelTool, ledgerReadTool, teamLedgerReadTool]
176
187
 
177
188
  // 固定段(装配序 = agent.tools → 固定段 → 家族段 → extraTools):timer 全模式全深度;
178
189
  // plan 随模式位——ENG-PLAN-EXCLUSION(FR31 ① · KD8 卸载而非「注册 + 报错」· KD11 全深度两端);
package/agent/helpers.mjs CHANGED
@@ -203,7 +203,7 @@ export function _clearGitFailureCooldownForTests(cwd) {
203
203
  gitFailureCooldowns.delete(cwd)
204
204
  }
205
205
 
206
- /** Git context 纯格式化(branch/log/status = trim 后原串)——独立导出供单测锁字节
206
+ /** Git context 纯格式化(branch/log/status = trim 后原串)——独立导出供单元测试锁字节
207
207
  * parity(现拼装逐字节保留——detached/dirty>20 截断/clean 三形态——VSC 镜像)。 */
208
208
  export function composeGitContext({ branch, log, status }) {
209
209
  const dirty = status ? status.split("\n").length : 0
@@ -377,12 +377,13 @@ export const SUBAGENT_TOOL_EXCLUSIONS = new Set(["question"])
377
377
  /** Apply the depth>0 exclusion set — always returns a NEW array (parent table untouched).
378
378
  * #9(TOOLS.md §6.19):`bash` 的 `async` 参仅 depth-0——子代副本删参(照 `family-tools.mjs:98`
379
379
  * 先例;返回值仍恒新数组、未命中条目原对象直传)。运行期第二道在 `tools/bash.mjs` execute
380
- * (照 `read_image`「schema 门 + 运行期第二道」双线先例)。 */
380
+ * (照 `read_image`「schema 门 + 运行期第二道」双线先例);批 browser-async-fix(#1045)同线
381
+ * 扩至 `browser` 的 `async` 参(运行期第二道在 `tools/browser.mjs` execute)。 */
381
382
  export function excludeSubagentTools(tools) {
382
383
  return tools
383
384
  .filter((t) => !SUBAGENT_TOOL_EXCLUSIONS.has(t.name))
384
385
  .map((t) => {
385
- if (t.name !== "bash" || !t.parameters?.properties || !("async" in t.parameters.properties)) return t
386
+ if (!(t.name === "bash" || t.name === "browser") || !t.parameters?.properties || !("async" in t.parameters.properties)) return t
386
387
  const { async: _asyncDrop, ...restProps } = t.parameters.properties
387
388
  return { ...t, parameters: { ...t.parameters, properties: restProps } }
388
389
  })
@@ -468,6 +469,14 @@ export const UPSTREAM_TURN_DOMAIN =
468
469
  export const TIMER_TURN_DOMAIN =
469
470
  "[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
471
 
472
+ /** Digest-accounting requirement line (AGENT-LOOP-ASYNC-POOL.md §6.31.3 · batch digest-accounting ·
473
+ * 2026-10-05):消化轮账目要求——两档同发(手动 / AUTO);manual 域文本只覆盖手动档 ⇒ 要求行
474
+ * 独立常量承载,避免 AUTO 轮无要求。`<ids>` = 未销账 id 清单占位(动态拼 = digest-account.mjs
475
+ * `digestAccountRequirement`);注入面 = transient(机器线独有)。Verbatim from the parent-side
476
+ * final text (single line — no newlines); content authority = parent side. */
477
+ export const DIGEST_ACCOUNT_DOMAIN =
478
+ "[System reminder: digest accounting — for every report listed below as not yet accounted, emit exactly one line: \"[digest-ack #<id>] digested — <key points>\" (handled) or \"[digest-ack #<id>] deferred — <reason>\" (not handled). Not yet accounted: <ids>. Do not omit any.]"
479
+
471
480
  /** Engineering-mode status injection — one reminder on EVERY transition (2026-08-25:
472
481
  * OFF is announced too — the model must know the gates lifted; silence after /eng-off
473
482
  * left it guessing. Covers TUI /eng, resume, and any path bypassing the eng tool.) */
@@ -0,0 +1,66 @@
1
+ /**
2
+ * agent/light-round.mjs — 轻通道代码笔通路谓词(F-LC5 · 设计 = `docs/core/design/LIGHT-CHANNEL.md` §2.8)。
3
+ *
4
+ * `lightRoundOpen(agent)`:闸读判据——**两件双在盘**才算轻通道轮在盘:
5
+ * ① 轮次台账行:`status = 在途` ∧ `title` 携「收尾链待跑」(轮型标记 = 闸凭据)∧ `task_book` 非空;
6
+ * ② 该指针指向的轮次批档:`§1` 状态行 = 「进行中」(判定器单源 = `readBatchStatusLine`)。
7
+ * 查找面(纯向下)= 会话锚 + 其直接子目录一层(候选枚举单源 = `ledgerChildren`——与族发现同级判据
8
+ * 同源;不递归 ∥ 不取同胞):逐候选查行 ∥ 以各候选为基解析指针(台账写门同式)。歧义候选
9
+ * (`projectRootView` 判 `ambiguous`——如工作区锚自身)⇒ 跳过该候选、照查其余(既定观察态,非读错);
10
+ * 候选读错 ∥ 不可判 ⇒ 停本候选(总判不因他候选读错而失——开路凭据仍是「两件校验全过」)。
11
+ * 语义:开门动作 ≡ 开轮两件落盘(「启动即挂账」本体——无独立于账的开门面);单件不放行
12
+ * (轮档独在 ∥ 行独在 ∥ 无标记 ∥ 行非在途 ∥ 指针失据 ∥ 档已收口 ⇒ 皆 false);收口即自闭
13
+ * (信号生命周期 = 账生命周期——零撤销动作 ∥ 零跨轮残留)。
14
+ * 读错 ∥ 不可判 ⇒ `false`(fail-closed——照拦;被拦时文案不因读错而变)。
15
+ * 只读:台账 = `ledgerQuery` **动态 import**(`ledger-db.mjs` 静态 import node:sqlite ⇒
16
+ * 消费侧静态 import 破 W8 契约②);批档 = 一次 `readFileSync`。
17
+ */
18
+ import { readFileSync } from "node:fs"
19
+ import { resolve } from "node:path"
20
+ import { projectRootView, resolveProjectRoot } from "../manifest.mjs"
21
+ import { resolveDeclaredRef } from "../declaration.mjs"
22
+ import { readBatchStatusLine } from "../agent-tools/batch-skeleton.mjs"
23
+
24
+ /** 轮型标记(§2.3 开轮形升格为闸凭据;全链批行无此标记 ⇒ 不构成信号)。 */
25
+ const ROUND_MARKER = "收尾链待跑"
26
+
27
+ /** 轻通道轮在盘谓词(异步 · 只读 · fail-closed)。契约见头注。
28
+ * @param {object} agent — 会话 agent(读 `cwd`)
29
+ * @returns {Promise<boolean>} true = 放行(在盘轮次);读错 ∥ 不可判 ⇒ false(照拦)
30
+ */
31
+ export async function lightRoundOpen(agent) {
32
+ try {
33
+ const cwd = agent?.cwd
34
+ if (typeof cwd !== "string" || cwd.trim() === "") return false
35
+ // W8 契约②:node:sqlite 链只经动态 import 进入(勿升静态)——候选枚举与逐候选查行同取本档公共面。
36
+ const { ledgerChildren, ledgerQuery } = await import("../ledger.mjs")
37
+ const anchor = resolve(cwd)
38
+ // 候选面 = 锚自身 + 直接子目录一层(纯向下;子项枚举单源 = `ledgerChildren`)。
39
+ const candidates = [anchor, ...ledgerChildren(anchor).map((p) => p.root)]
40
+ for (const cand of candidates) {
41
+ // 歧义候选(≥2 候选项目——`projectRootView`)⇒ 跳过、照查其余(歧义锚不充当项目 = AC-M2-19 同判;
42
+ // `openLedger` 歧义拒 = AC-M2-17——既定观察态,非读错)。
43
+ if (projectRootView(cand).state === "ambiguous") continue
44
+ let rows
45
+ try { rows = ledgerQuery({ cwd: cand, status: "在途" }) } catch { continue } // 候选读错 ⇒ 停本候选
46
+ if (!Array.isArray(rows) || rows.length === 0) continue
47
+ // 指针基准 = 台账写门同式(`ledger-cmd.mjs` assertTaskBookGate 同一子表达式)——以各候选为基。
48
+ const base = resolveProjectRoot(cand) ?? resolve(cand ?? ".")
49
+ for (const row of rows) {
50
+ if (!String(row?.title ?? "").includes(ROUND_MARKER)) continue // 无标记 ⇒ 非轻通道轮(全链批行)
51
+ const taskBook = row?.task_book
52
+ if (typeof taskBook !== "string" || taskBook.trim() === "") continue // 缺指针 ⇒ 单件不放行
53
+ const part = taskBook.split("§")[0].trim() // 台账写门同式:首个 § 前子串
54
+ if (part === "") continue
55
+ const hit = resolveDeclaredRef(base, part)
56
+ if (!hit.ok) continue // 指针失据 ⇒ 不放行
57
+ let src
58
+ try { src = readFileSync(hit.abs, "utf8") } catch { continue } // 档读不到 ⇒ 不放行
59
+ if (readBatchStatusLine(src) === "open") return true // 两件双在盘 ⇒ 放行
60
+ }
61
+ }
62
+ return false
63
+ } catch {
64
+ return false // 读错 ∥ 不可判 ⇒ fail-closed(照拦)
65
+ }
66
+ }
@@ -4,7 +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
+ import { takeExpiredTimers, injectTimerReminders, timerWakeEnabled } from "./timers.mjs"
8
8
 
9
9
  export const STALL_WINDOW_SIZE = 5
10
10
  export const STALL_THRESHOLD = 3
@@ -14,11 +14,15 @@ export const GOAL_BUDGET_WARN_RATIO = 0.75
14
14
  * Inject all post-turn events into agent history.
15
15
  * Must be called after every tool-execution turn within runAgent's loop.
16
16
  */
17
- export function injectPostTurn(agent, results, recentCallSigs, callbacks, turn) {
17
+ export function injectPostTurn(agent, results, recentCallSigs, callbacks, turn, { suspDriven = false } = {}) {
18
18
  // Expired timers — inject reminders when thinking budget is up
19
19
  // AGENT-LOOP-ASYNC-POOL.md §6.30.2:过滤 / 注入语义单源于 `agent/timers.mjs`(CLI 空闲闩 /
20
- // 挂起窗同读);本处 = 回合边界轮询调用点——行为零变(最近到期 / 出列幂等 / 逐字形态逐条不变)。
21
- injectTimerReminders(agent, takeExpiredTimers(agent))
20
+ // 挂起窗同读);本处 = 回合边界轮询调用点。
21
+ // 批 async-delivery-formalize(§6.34.2 行 5):驱动面 ∧ `timerWakeEnabled` ⇒ 不在工具轮收尾注入
22
+ // (到期件留在途零出列)——交付交空闲面(挂起窗 ∥ 前台空闲闩);开关关 ∨ 回退面 ⇒ 逐字保持。
23
+ if (!(suspDriven && timerWakeEnabled(agent))) {
24
+ injectTimerReminders(agent, takeExpiredTimers(agent))
25
+ }
22
26
 
23
27
  // Pending reminders
24
28
  if (agent._pendingReminders.length > 0) {
@@ -30,6 +30,8 @@ import { capHistoryImages } from "../provider/normalize.mjs"
30
30
  import { resolve } from "node:path"
31
31
  import { advisorRuns, stripApprovedSuffix } from "../agent-tools/advisor-async.mjs"
32
32
  import { looksLikeReviewOutput, advisorIncompleteMarker } from "../advisor/run.mjs"
33
+ // #949(评审 history 串台面):同步面投递水印单点(叶档——零环)。
34
+ import { noteReviewDelivered } from "../agent-tools/review-facts.mjs"
33
35
 
34
36
  let _reindexFile = null
35
37
 
@@ -118,6 +120,8 @@ export async function recordToolResults(agent, toolByName, results) {
118
120
  } else {
119
121
  const reviewId = agent._advisorSyncCalls?.get(toolCall.id)
120
122
  const run = reviewId !== undefined ? advisorRuns(agent).get(reviewId) : undefined
123
+ // #949:工具结果已入史(上方 pushReal)⇒ 本评审响应表取件下界落定(run 缺省零写)。
124
+ if (run) noteReviewDelivered(agent, run)
121
125
  // F16 同步面(第 13 批——ADVISOR-GUARDS.md §6):未完成尾 + 非设计面
122
126
  // (含类型不可判——run 缺失的 legacy 直调)⇒ 不置「已覆盖」(与 settle 面
123
127
  // 逐条 parity;设计评审保持计入——无代码面)。