berry-agent 0.1.0-alpha.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 (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/.api-emit.stamp +1 -0
  4. package/dist/.build-meta.json +6 -0
  5. package/dist/agent/events.js +1 -0
  6. package/dist/agent/index.js +2 -0
  7. package/dist/agent/loop.js +139 -0
  8. package/dist/agent/queue.js +119 -0
  9. package/dist/agent/stream.js +62 -0
  10. package/dist/agent/tools-batch.js +221 -0
  11. package/dist/agent/types.js +8 -0
  12. package/dist/api/berry-agent-llm.d.ts +18 -0
  13. package/dist/api/berry-agent.d.ts +9 -0
  14. package/dist/api/surface.json +1632 -0
  15. package/dist/api/tsconfig.paths.json +12 -0
  16. package/dist/api/typebox-compile.d.ts +2 -0
  17. package/dist/api/typebox-value.d.ts +2 -0
  18. package/dist/api/typebox.d.ts +2 -0
  19. package/dist/browser/cdp.js +256 -0
  20. package/dist/browser/codes.js +31 -0
  21. package/dist/browser/discover.js +107 -0
  22. package/dist/browser/engine.js +169 -0
  23. package/dist/browser/index.js +17 -0
  24. package/dist/browser/install.js +342 -0
  25. package/dist/browser/page.js +321 -0
  26. package/dist/browser/service.js +158 -0
  27. package/dist/browser/tools.js +198 -0
  28. package/dist/browser/types.js +81 -0
  29. package/dist/channels/ask-queue.js +82 -0
  30. package/dist/channels/commands.js +129 -0
  31. package/dist/channels/engine/cell.js +143 -0
  32. package/dist/channels/engine/diff.js +143 -0
  33. package/dist/channels/engine/engine.js +416 -0
  34. package/dist/channels/engine/index.js +8 -0
  35. package/dist/channels/engine/input-keys.js +86 -0
  36. package/dist/channels/engine/input.js +596 -0
  37. package/dist/channels/engine/memory-io.js +71 -0
  38. package/dist/channels/engine/process-io.js +38 -0
  39. package/dist/channels/engine/types.js +17 -0
  40. package/dist/channels/engine/width.js +122 -0
  41. package/dist/channels/index.js +15 -0
  42. package/dist/channels/registry.js +56 -0
  43. package/dist/channels/sdk/admit.js +29 -0
  44. package/dist/channels/sdk/backend.js +102 -0
  45. package/dist/channels/sdk/cursor.js +56 -0
  46. package/dist/channels/sdk/index.js +20 -0
  47. package/dist/channels/sdk/jsonl.js +208 -0
  48. package/dist/channels/sdk/protocol.js +24 -0
  49. package/dist/channels/sdk/schema.js +91 -0
  50. package/dist/channels/sdk/wire-core.js +446 -0
  51. package/dist/channels/service.js +169 -0
  52. package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
  53. package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
  54. package/dist/channels/tui/autocomplete/popup.js +148 -0
  55. package/dist/channels/tui/autocomplete/provider.js +7 -0
  56. package/dist/channels/tui/autocomplete/token.js +68 -0
  57. package/dist/channels/tui/backend/ansi-rows.js +211 -0
  58. package/dist/channels/tui/backend/main-screen.js +207 -0
  59. package/dist/channels/tui/backend/osc.js +91 -0
  60. package/dist/channels/tui/backend/transcript.js +230 -0
  61. package/dist/channels/tui/backend/tui-backend.js +915 -0
  62. package/dist/channels/tui/editor/editor-model.js +543 -0
  63. package/dist/channels/tui/editor/editor-view.js +140 -0
  64. package/dist/channels/tui/editor/editor.js +275 -0
  65. package/dist/channels/tui/editor/undo-stack.js +35 -0
  66. package/dist/channels/tui/editor/visual-lines.js +126 -0
  67. package/dist/channels/tui/editor/word-nav.js +106 -0
  68. package/dist/channels/tui/history/history-viewer.js +403 -0
  69. package/dist/channels/tui/index.js +3 -0
  70. package/dist/channels/tui/layout.js +167 -0
  71. package/dist/channels/tui/markdown/blocks.js +111 -0
  72. package/dist/channels/tui/markdown/inline.js +75 -0
  73. package/dist/channels/tui/markdown/markdown.js +182 -0
  74. package/dist/channels/tui/memory/memory-viewer.js +540 -0
  75. package/dist/channels/tui/overlay/alt-screen.js +105 -0
  76. package/dist/channels/tui/overlay/overlay.js +57 -0
  77. package/dist/channels/tui/overlay/select-confirm.js +156 -0
  78. package/dist/channels/tui/panels/todo-panel.js +54 -0
  79. package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
  80. package/dist/channels/tui/scroll/scroll-view.js +254 -0
  81. package/dist/channels/tui/status/status-line.js +76 -0
  82. package/dist/channels/tui/text.js +38 -0
  83. package/dist/channels/tui/theme.js +41 -0
  84. package/dist/channels/types.js +11 -0
  85. package/dist/channels/ui-core.js +209 -0
  86. package/dist/checkpoint/capture.js +45 -0
  87. package/dist/checkpoint/codes.js +33 -0
  88. package/dist/checkpoint/command.js +82 -0
  89. package/dist/checkpoint/gate.js +48 -0
  90. package/dist/checkpoint/index.js +20 -0
  91. package/dist/checkpoint/restore.js +172 -0
  92. package/dist/checkpoint/store.js +247 -0
  93. package/dist/checkpoint/types.js +4 -0
  94. package/dist/checkpoint/walk.js +146 -0
  95. package/dist/compaction/ccr-tools.js +112 -0
  96. package/dist/compaction/ccr.js +84 -0
  97. package/dist/compaction/codes.js +21 -0
  98. package/dist/compaction/index.js +18 -0
  99. package/dist/compaction/policy.js +200 -0
  100. package/dist/compaction/service.js +422 -0
  101. package/dist/compaction/slots.js +121 -0
  102. package/dist/compaction/types.js +10 -0
  103. package/dist/context/codes.js +40 -0
  104. package/dist/context/events.js +160 -0
  105. package/dist/context/index.js +13 -0
  106. package/dist/context/logger.js +136 -0
  107. package/dist/context/scope.js +182 -0
  108. package/dist/context/workspace.js +104 -0
  109. package/dist/contracts/agent-events.d.ts +73 -0
  110. package/dist/contracts/agent-events.js +16 -0
  111. package/dist/contracts/api.d.ts +249 -0
  112. package/dist/contracts/api.js +299 -0
  113. package/dist/contracts/approval.d.ts +37 -0
  114. package/dist/contracts/approval.js +9 -0
  115. package/dist/contracts/env-ref.d.ts +41 -0
  116. package/dist/contracts/env-ref.js +32 -0
  117. package/dist/contracts/errors.d.ts +56 -0
  118. package/dist/contracts/errors.js +266 -0
  119. package/dist/contracts/events.d.ts +70 -0
  120. package/dist/contracts/events.js +329 -0
  121. package/dist/contracts/index.d.ts +32 -0
  122. package/dist/contracts/index.js +26 -0
  123. package/dist/contracts/llm.d.ts +275 -0
  124. package/dist/contracts/llm.js +12 -0
  125. package/dist/contracts/messages.d.ts +63 -0
  126. package/dist/contracts/messages.js +65 -0
  127. package/dist/contracts/redact.d.ts +46 -0
  128. package/dist/contracts/redact.js +223 -0
  129. package/dist/contracts/tools.d.ts +197 -0
  130. package/dist/contracts/tools.js +33 -0
  131. package/dist/contracts/types.d.ts +302 -0
  132. package/dist/contracts/types.js +50 -0
  133. package/dist/contracts/ui.d.ts +122 -0
  134. package/dist/contracts/ui.js +15 -0
  135. package/dist/conversation/agent-service.js +61 -0
  136. package/dist/conversation/approval-wiring.js +82 -0
  137. package/dist/conversation/backoff.js +38 -0
  138. package/dist/conversation/codes.js +47 -0
  139. package/dist/conversation/control-tools.js +102 -0
  140. package/dist/conversation/control.js +210 -0
  141. package/dist/conversation/driver.js +922 -0
  142. package/dist/conversation/index.js +12 -0
  143. package/dist/conversation/model-visible.js +169 -0
  144. package/dist/conversation/open-tools.js +126 -0
  145. package/dist/conversation/reseed.js +132 -0
  146. package/dist/conversation/sessions.js +215 -0
  147. package/dist/conversation/todo.js +203 -0
  148. package/dist/conversation/types.js +35 -0
  149. package/dist/conversation/wiring.js +204 -0
  150. package/dist/credentials/codes.js +59 -0
  151. package/dist/credentials/commands.js +186 -0
  152. package/dist/credentials/env-ref.js +34 -0
  153. package/dist/credentials/index.js +28 -0
  154. package/dist/credentials/migration.js +18 -0
  155. package/dist/credentials/oauth.js +266 -0
  156. package/dist/credentials/refresh.js +152 -0
  157. package/dist/credentials/secrets.js +118 -0
  158. package/dist/credentials/types.js +30 -0
  159. package/dist/exec/bash.js +279 -0
  160. package/dist/exec/codes.js +37 -0
  161. package/dist/exec/env.js +99 -0
  162. package/dist/exec/environment.js +39 -0
  163. package/dist/exec/git-guard.js +623 -0
  164. package/dist/exec/index.js +24 -0
  165. package/dist/exec/registry.js +120 -0
  166. package/dist/exec/spawn.js +281 -0
  167. package/dist/exec/tail.js +77 -0
  168. package/dist/exec/types.js +5 -0
  169. package/dist/goal/codes.js +33 -0
  170. package/dist/goal/command.js +175 -0
  171. package/dist/goal/fold.js +121 -0
  172. package/dist/goal/gates.js +122 -0
  173. package/dist/goal/index.js +22 -0
  174. package/dist/goal/migration.js +48 -0
  175. package/dist/goal/service.js +501 -0
  176. package/dist/goal/todo-tool.js +174 -0
  177. package/dist/goal/types.js +1 -0
  178. package/dist/goal/update-tool.js +46 -0
  179. package/dist/host/approval-cmd.js +276 -0
  180. package/dist/host/assembly.js +1146 -0
  181. package/dist/host/boot-failures.js +104 -0
  182. package/dist/host/budget-advisory.js +74 -0
  183. package/dist/host/budget-broadcast.js +50 -0
  184. package/dist/host/builtins.js +54 -0
  185. package/dist/host/cli.js +544 -0
  186. package/dist/host/codes.js +120 -0
  187. package/dist/host/config-schema.js +195 -0
  188. package/dist/host/conversation-stack.js +654 -0
  189. package/dist/host/core-plugins.js +1685 -0
  190. package/dist/host/credentials-cmd.js +42 -0
  191. package/dist/host/disclosure.js +30 -0
  192. package/dist/host/dispatch.js +105 -0
  193. package/dist/host/doors-cmd.js +154 -0
  194. package/dist/host/dump-config.js +44 -0
  195. package/dist/host/hook-dispatch-guard.js +27 -0
  196. package/dist/host/import-gate.js +153 -0
  197. package/dist/host/index.js +54 -0
  198. package/dist/host/issue-session.js +241 -0
  199. package/dist/host/loader.js +372 -0
  200. package/dist/host/main.js +161 -0
  201. package/dist/host/manifest.js +381 -0
  202. package/dist/host/mcp-entry.js +53 -0
  203. package/dist/host/plugin-boot.js +952 -0
  204. package/dist/host/plugin-context.js +672 -0
  205. package/dist/host/plugin-install.js +480 -0
  206. package/dist/host/plugin-reload.js +124 -0
  207. package/dist/host/plugin-store.js +462 -0
  208. package/dist/host/plugin-tools.js +359 -0
  209. package/dist/host/plugin-uninstall.js +257 -0
  210. package/dist/host/plugins-cmd.js +302 -0
  211. package/dist/host/plugins-command.js +130 -0
  212. package/dist/host/plugins-config.js +195 -0
  213. package/dist/host/prompt-sections.js +133 -0
  214. package/dist/host/run-entry.js +544 -0
  215. package/dist/host/runtime.js +209 -0
  216. package/dist/host/scheduler-tick.js +294 -0
  217. package/dist/host/serve-daemon.js +501 -0
  218. package/dist/host/serve-entry.js +279 -0
  219. package/dist/host/session-anchor.js +41 -0
  220. package/dist/host/sessions-cmd.js +219 -0
  221. package/dist/host/sessions-face.js +47 -0
  222. package/dist/host/settings-store.js +112 -0
  223. package/dist/host/signals.js +69 -0
  224. package/dist/host/single-instance.js +105 -0
  225. package/dist/host/subagent-factory.js +291 -0
  226. package/dist/host/testkit/harness.js +283 -0
  227. package/dist/host/testkit/index.js +14 -0
  228. package/dist/host/testkit/matrix.js +302 -0
  229. package/dist/host/tool-policy-store.js +177 -0
  230. package/dist/host/triggers.js +212 -0
  231. package/dist/host/tui-entry.js +216 -0
  232. package/dist/host/webui-bridge.js +165 -0
  233. package/dist/issue/codes.js +38 -0
  234. package/dist/issue/filter.js +146 -0
  235. package/dist/issue/github.js +116 -0
  236. package/dist/issue/index.js +17 -0
  237. package/dist/issue/mount.js +70 -0
  238. package/dist/issue/poll.js +58 -0
  239. package/dist/issue/service.js +341 -0
  240. package/dist/issue/tools.js +70 -0
  241. package/dist/issue/types.js +34 -0
  242. package/dist/issue/webhook.js +126 -0
  243. package/dist/llm/codes.js +77 -0
  244. package/dist/llm/complete.js +196 -0
  245. package/dist/llm/events.js +15 -0
  246. package/dist/llm/index.js +24 -0
  247. package/dist/llm/inflight.js +65 -0
  248. package/dist/llm/model-id.js +58 -0
  249. package/dist/llm/provider-face.d.ts +32 -0
  250. package/dist/llm/provider-face.js +34 -0
  251. package/dist/llm/recovery.js +146 -0
  252. package/dist/llm/runtime.js +46 -0
  253. package/dist/llm/stream-fn.js +147 -0
  254. package/dist/lsp/codes.js +31 -0
  255. package/dist/lsp/connection.js +147 -0
  256. package/dist/lsp/frame.js +110 -0
  257. package/dist/lsp/index.js +16 -0
  258. package/dist/lsp/inject.js +58 -0
  259. package/dist/lsp/instance.js +264 -0
  260. package/dist/lsp/service.js +522 -0
  261. package/dist/lsp/tools.js +53 -0
  262. package/dist/lsp/types.js +201 -0
  263. package/dist/mcp/bridge.js +222 -0
  264. package/dist/mcp/codes.js +21 -0
  265. package/dist/mcp/index.js +19 -0
  266. package/dist/mcp/jsonrpc.js +230 -0
  267. package/dist/mcp/service.js +125 -0
  268. package/dist/mcp/tools.js +145 -0
  269. package/dist/mcp/types.js +108 -0
  270. package/dist/memory/cite.js +131 -0
  271. package/dist/memory/codes.js +50 -0
  272. package/dist/memory/command.js +71 -0
  273. package/dist/memory/consolidate.js +219 -0
  274. package/dist/memory/cycle.js +109 -0
  275. package/dist/memory/dao.js +878 -0
  276. package/dist/memory/diff.js +238 -0
  277. package/dist/memory/extract.js +181 -0
  278. package/dist/memory/fts.js +58 -0
  279. package/dist/memory/index.js +42 -0
  280. package/dist/memory/inject.js +354 -0
  281. package/dist/memory/merge.js +178 -0
  282. package/dist/memory/migration.js +132 -0
  283. package/dist/memory/pollution.js +59 -0
  284. package/dist/memory/port.js +385 -0
  285. package/dist/memory/review.js +201 -0
  286. package/dist/memory/scan.js +89 -0
  287. package/dist/memory/tools.js +508 -0
  288. package/dist/memory/types.js +140 -0
  289. package/dist/obs/codes.js +38 -0
  290. package/dist/obs/db.js +115 -0
  291. package/dist/obs/index.js +20 -0
  292. package/dist/obs/rollup.js +61 -0
  293. package/dist/obs/service.js +363 -0
  294. package/dist/obs/session-tools.js +282 -0
  295. package/dist/obs/session-view.js +225 -0
  296. package/dist/obs/tool.js +97 -0
  297. package/dist/obs/types.js +1 -0
  298. package/dist/persist/audit.js +63 -0
  299. package/dist/persist/aux.js +39 -0
  300. package/dist/persist/codes.js +35 -0
  301. package/dist/persist/index.js +30 -0
  302. package/dist/persist/load-history.js +50 -0
  303. package/dist/persist/migrations.js +37 -0
  304. package/dist/persist/paths.js +108 -0
  305. package/dist/persist/persistence.js +229 -0
  306. package/dist/persist/schema.js +99 -0
  307. package/dist/persist/secret-box.js +95 -0
  308. package/dist/persist/store.js +706 -0
  309. package/dist/persist/write-behind.js +238 -0
  310. package/dist/safety/approval.js +171 -0
  311. package/dist/safety/bwrap.js +123 -0
  312. package/dist/safety/codes.js +57 -0
  313. package/dist/safety/danger.js +505 -0
  314. package/dist/safety/gate.js +226 -0
  315. package/dist/safety/index.js +24 -0
  316. package/dist/safety/presets.js +73 -0
  317. package/dist/safety/roots.js +139 -0
  318. package/dist/safety/sandbox.js +214 -0
  319. package/dist/safety/seatbelt.js +88 -0
  320. package/dist/safety/sensitive.js +34 -0
  321. package/dist/safety/tool-policy.js +203 -0
  322. package/dist/safety/types.js +13 -0
  323. package/dist/scheduler/codes.js +54 -0
  324. package/dist/scheduler/cron-backend.js +135 -0
  325. package/dist/scheduler/engine.js +280 -0
  326. package/dist/scheduler/gates.js +60 -0
  327. package/dist/scheduler/index.js +25 -0
  328. package/dist/scheduler/migration.js +22 -0
  329. package/dist/scheduler/runner.js +171 -0
  330. package/dist/scheduler/schedule.js +193 -0
  331. package/dist/scheduler/service.js +283 -0
  332. package/dist/scheduler/tick.js +139 -0
  333. package/dist/scheduler/types.js +1 -0
  334. package/dist/sdk/http.js +865 -0
  335. package/dist/sdk/index.js +22 -0
  336. package/dist/sdk/mcp.js +289 -0
  337. package/dist/sdk/plugin-route-registry.js +126 -0
  338. package/dist/sdk/plugin-routes.js +149 -0
  339. package/dist/sdk/security.js +116 -0
  340. package/dist/sdk/types.js +32 -0
  341. package/dist/session/budget.js +159 -0
  342. package/dist/session/codes.js +40 -0
  343. package/dist/session/derive.js +180 -0
  344. package/dist/session/event-data.js +1 -0
  345. package/dist/session/fork.js +72 -0
  346. package/dist/session/import-gates.js +148 -0
  347. package/dist/session/index.js +18 -0
  348. package/dist/session/recover.js +103 -0
  349. package/dist/session/session.js +279 -0
  350. package/dist/session/snapshot.js +107 -0
  351. package/dist/skills/agents.js +242 -0
  352. package/dist/skills/codes.js +51 -0
  353. package/dist/skills/discovery.js +266 -0
  354. package/dist/skills/frontmatter.js +215 -0
  355. package/dist/skills/index.js +28 -0
  356. package/dist/skills/load.js +118 -0
  357. package/dist/skills/manage.js +201 -0
  358. package/dist/skills/registry.js +138 -0
  359. package/dist/skills/render.js +124 -0
  360. package/dist/skills/sections.js +171 -0
  361. package/dist/skills/types.js +21 -0
  362. package/dist/subagent/codes.js +50 -0
  363. package/dist/subagent/declarative.js +55 -0
  364. package/dist/subagent/index.js +19 -0
  365. package/dist/subagent/notify.js +51 -0
  366. package/dist/subagent/provide.js +7 -0
  367. package/dist/subagent/registry.js +149 -0
  368. package/dist/subagent/service.js +338 -0
  369. package/dist/subagent/surface.js +44 -0
  370. package/dist/subagent/tool.js +153 -0
  371. package/dist/subagent/types.js +25 -0
  372. package/dist/tools/apply-patch.js +164 -0
  373. package/dist/tools/codes.js +109 -0
  374. package/dist/tools/fs.js +501 -0
  375. package/dist/tools/index.js +17 -0
  376. package/dist/tools/observed.js +99 -0
  377. package/dist/tools/pipeline.js +176 -0
  378. package/dist/tools/protected-read.js +54 -0
  379. package/dist/tools/registry.js +250 -0
  380. package/dist/tools/search.js +451 -0
  381. package/dist/tools/worktree.js +327 -0
  382. package/dist/web/codes.js +37 -0
  383. package/dist/web/gate.js +43 -0
  384. package/dist/web/hygiene.js +195 -0
  385. package/dist/web/index.js +16 -0
  386. package/dist/web/service.js +207 -0
  387. package/dist/web/ssrf-guard.js +44 -0
  388. package/dist/web/tool.js +76 -0
  389. package/dist/web/types.js +10 -0
  390. package/dist/webui/assets/index-B-t2O8gh.js +22 -0
  391. package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
  392. package/dist/webui/index.html +14 -0
  393. package/dist/webui/index.js +14 -0
  394. package/dist/webui/server.js +549 -0
  395. package/dist/webui/types.js +38 -0
  396. package/examples/README.md +26 -0
  397. package/examples/minimal-code-plugin/entry.js +43 -0
  398. package/examples/minimal-code-plugin/package.json +12 -0
  399. package/examples/pure-skill-pack/package.json +14 -0
  400. package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
  401. package/package.json +86 -0
@@ -0,0 +1,922 @@
1
+ /**
2
+ * ConversationDriver 本体(02 §2.3 对话本体 / 04 §2-§4 运行时骨架)——
3
+ * loop 的驱动侧:组装回调注入 agent loop、durable 接线、turn 级 auto-retry
4
+ * (04 §3.3)与溢出兜底(04 §3.4)。
5
+ *
6
+ * 批 11c 纵切面:durable 接线 + runTurns 重试循环 + 溢出兜底 + 待发队列的
7
+ * 通道机制面(busy→steer 顶注 / idle→followUp 起跑 / 搁浅件种子续跑)。
8
+ * 批 11d 纵切面:三通道路由正式收口(inject 停摆落账不唤醒 / busy steer
9
+ * 合批 / 唤醒预算批消费位记账)+ 取消模型(dismantled 停摆旗标 + per-run
10
+ * 控制器——abort())+ resume 冷启动续接(构造折叠 + 重播种 + header resume)。
11
+ * 批 11e 纵切面:open 域工具与审批三件装配(assembleOpenTools 消费)。
12
+ * 批 11f 纵切面:环境披露段注入(04 §11)+ onRunSettled 终态回调(ctx.agent
13
+ * 服务订阅面)+ 子代理审批挂起通知注入面(04 §10);host 装配根接线归批 12。
14
+ * 批 v 纵切面(2026-09-11 运行时断言批):「模型可见即已记录」请求关口总拍
15
+ * (05 §1.2 对拍断言)——onTransformContext 入口 timeline 活数组 vs 日志独立
16
+ * 重建恒开对拍,红 = 驱动 bug fail-loud(暗通道/翻译失真两类病灶)。
17
+ *
18
+ * 重试编舞(04 §3.3 六条):
19
+ * ① 遮蔽 + 落账一次 append——surfaceOp 随 llm/retry(phase=scheduled) 信封;
20
+ * 区间 = [错误 assistant seq, durable 高水位](伴生组与垫底 turn/end 一并
21
+ * 盖住,防悬空 toolUse 进续入上下文);
22
+ * ② 重播种走私有路径(reseedTimeline)——不清 deliverMeta、不复位 header 落账
23
+ * 差分幂等位(header resume 形由 wiring 重建标记承载);
24
+ * ③ 重试循环在 runTurns 内部——run 未终结语义上 isRunning 恒真,零新事件型;
25
+ * ④ 退避 = 指数 + 等比半幅抖动,sleep 挂 abort signal(打断即 aborted 落账);
26
+ * ⑤ attempt 计数生命周期 = 单次 runTurns 调用(进入即零、成功返回即弃);
27
+ * ⑥ 只 retry transient 桶(classifyError 注入缺席 = 一切 non-retryable 保守)。
28
+ * 溢出兜底(04 §3.4):遮蔽(llm/retry reason=overflow 复用遮蔽信封)→
29
+ * compactForOverflow → compacted 则重播种续入;名额 1/1 独立分账;注入缺席 =
30
+ * 溢出直接终态(05 §2.3 门三道第三道的装配面兑现)。
31
+ */
32
+ import { startRun } from '../agent/index.js';
33
+ import { PendingMessageQueue } from '../agent/index.js';
34
+ import { isStandardMessage, parseEventSource } from '../contracts/index.js';
35
+ import { abortableSleep, retryDelay } from './backoff.js';
36
+ import { DurableWiring } from './wiring.js';
37
+ import { AGENT_PRE_STEP_EVENT, CONTEXT_TRANSFORM_EVENT, DEFAULT_RETRY_POLICY, MAX_CONSECUTIVE_WAKES, SESSION_LIFECYCLE_EVENT, } from './types.js';
38
+ import { reseedTimeline } from './reseed.js';
39
+ import { assertModelVisibleTimeline, degradationMask } from './model-visible.js';
40
+ import { todoSnapshotMessage } from './todo.js';
41
+ import { notifyRunSettled } from './agent-service.js';
42
+ /**
43
+ * 对话驱动(per-session 实例)。timeline 活数组是投影的镜像缓存(05 §1.3
44
+ * 单一事实源——每次入口先重播种对齐,不另立第二真相);durable 落账经
45
+ * DurableWiring 翻译,活体事件双腿转发(接线腿 + 外部汇腿)。
46
+ */
47
+ export class ConversationDriver {
48
+ session;
49
+ options;
50
+ wiring;
51
+ context;
52
+ /** loop 回调基座(signal 每次入口另注——per-run AbortController 的协作面) */
53
+ baseConfig;
54
+ /** 当前入口的 active config(transformContext 取请求时点 model——换装后取新值) */
55
+ activeConfig;
56
+ /** 待发队列(三通道暂存——busy steer 顶注 / idle followUp 起跑同池,通道由消费点定) */
57
+ queue = new PendingMessageQueue();
58
+ /** 在飞 runTurns(busy 判据 + submit 搭车面) */
59
+ currentRun;
60
+ /**
61
+ * 当前 run 后台道声明位(04 §5——run 级后台性,2026-09-13 全面复盘修复批):
62
+ * launch 随起跑 submit 置位、run 结算清位;请求组装期 budgetAdvisory 取值
63
+ * 器消费(前台 run 恒 false)。纯 run 级内存载体——不落 durable。
64
+ */
65
+ runLaneValue = false;
66
+ /** 当前 run 的协作中止控制器(abort() 触发——streamFn/工具执行/退避睡眠共挂) */
67
+ activeController;
68
+ /** 停摆旗标(02 §2.3 取消模型——会话级标记非 run 状态机成员;置位后一切投递转 inject) */
69
+ dismantledValue = false;
70
+ /**
71
+ * 排队 kick 计数(等位中的排队段在飞数——abort() 记忆位的射程判据):
72
+ * 大于零 = 本会话仍有等位中的 run,此时无在飞 run 的 abort() 请求有明确
73
+ * 打断对象(排队段),值得记忆到取位结算后消费。
74
+ */
75
+ queuedKickCount = 0;
76
+ /**
77
+ * 排队期中止请求记忆位(无在飞 run 时 abort() 置、排队段取位后消费):
78
+ * issue watchdog 等「排队期请求打断」的调用方意图不因排队窗丢失——修复前
79
+ * 排队期 abort() 为 no-op(activeController 尚未出生),run 起跑后预算帽
80
+ * 执法失效可超额跑完(2026-09-10 全面复盘真缺口修复)。
81
+ */
82
+ abortRequestedValue = false;
83
+ /** 连续后台唤醒计数(04 §4 唤醒预算——批消费位记账;前台 kick 复位) */
84
+ wakeStreak = 0;
85
+ /**
86
+ * a2a 链深内存位(03 §2.2 第十一面回合护栏——e-4 落码裁决:选计数位不选
87
+ * 血缘回溯,O(1) 受理面单点递推同效)。更新律:人面输入(treatedAsUser
88
+ * 源经 routeMessage)归 0、plugin: 源归 1、跨会话 send 经 deliverControl
89
+ * 专径携 depthHint 递推(送话方深度+1);崩溃重启归零(环物理断链——
90
+ * 护栏目的已达,不承诺跨进程精确保留)。
91
+ */
92
+ a2aDepthValue = 0;
93
+ /** 全量工具面快照(工具面切换的还原基准——backgroundTools 的对照面) */
94
+ fullTools;
95
+ /** 警示面(缺省 stderr——护栏不静默) */
96
+ warnFace;
97
+ /**
98
+ * 重试进行态探针(04 §3.3 注记 seam——批 13a):退避等待窗内非空、其余
99
+ * 时点 null;只读外推面 = retryState getter(SDK 心跳载荷唯一线面出口)。
100
+ */
101
+ retryProbeValue;
102
+ constructor(options) {
103
+ this.options = options;
104
+ this.session = options.session;
105
+ // context_transform 词汇自举注册(一词两册幂等——open-tools 同律):独立
106
+ // stack(无 bootPlugins)的驱动也要能发射;bootPlugins 预注册 41 词在前,
107
+ // 已注册词跳过不撞名(EVENT_DUPLICATE 防御)
108
+ if (!options.dispatch.isRegistered(CONTEXT_TRANSFORM_EVENT)) {
109
+ options.dispatch.registerEventNames([CONTEXT_TRANSFORM_EVENT]);
110
+ }
111
+ // session/lifecycle 词汇自举注册(04 §6 e-2——同上幂等双源):装配根
112
+ // boot 前预注册(插件装载期订阅可达)在前,此处兜底独立 stack 形
113
+ if (!options.dispatch.isRegistered(SESSION_LIFECYCLE_EVENT)) {
114
+ options.dispatch.registerEventNames([SESSION_LIFECYCLE_EVENT]);
115
+ }
116
+ // agent_pre_step 词汇自举注册(03 §2.4 message 层行——批 #99 发射腿):
117
+ // 同上幂等双源——goal 件装载期 ctx.on('agent_pre_step') 可达在前,此处
118
+ // 兜底独立 stack 形(无 bootPlugins 的驱动也要能发射)
119
+ if (!options.dispatch.isRegistered(AGENT_PRE_STEP_EVENT)) {
120
+ options.dispatch.registerEventNames([AGENT_PRE_STEP_EVENT]);
121
+ }
122
+ // resolveToolOwner 透传(tool/call 载荷 owner 位取数 seam——T9 案一批 t-1);
123
+ // 缺席 = 载荷不带 owner(wiring 侧可选带出形)
124
+ this.wiring = new DurableWiring(this.session, {
125
+ ...(options.resolveToolOwner !== undefined ? { resolveToolOwner: options.resolveToolOwner } : {}),
126
+ });
127
+ this.fullTools = options.tools !== undefined ? [...options.tools] : undefined;
128
+ this.warnFace = options.warn ?? ((message) => console.error(message));
129
+ this.context = {
130
+ ...(options.systemPrompt !== undefined ? { systemPrompt: options.systemPrompt } : {}),
131
+ ...(this.fullTools !== undefined ? { tools: [...this.fullTools] } : {}),
132
+ messages: this.reseededTimeline(),
133
+ };
134
+ this.baseConfig = {
135
+ streamFn: options.streamFn,
136
+ model: options.model,
137
+ ...(options.thinkingLevel !== undefined ? { thinkingLevel: options.thinkingLevel } : {}),
138
+ convertToLlm: options.convertToLlm,
139
+ transformContext: this.onTransformContext,
140
+ preModelRequest: this.onPreModelRequest,
141
+ getSteeringMessages: this.consumeInRun,
142
+ getFollowUpMessages: this.consumeInRun,
143
+ onEvent: this.onLiveEvent,
144
+ };
145
+ }
146
+ /**
147
+ * 用户输入入口(04 §4——三通道路由细节单源在 routeMessage;本入口只
148
+ * 组装 user 消息:source 缺省 'user'、backgroundWake 随选项透传;
149
+ * dedupeKey 同随选项透传 data.dedupeKey——幂等 admit 落账位 05 §3.5)。
150
+ */
151
+ submit(content, options) {
152
+ // session: 前缀 fail-loud 拒(e-4 provenance 盖章单源——05 §3.1):跨会话
153
+ // 操控注入的 source 只能由操控受理面铸(controlSourceOf——control.ts),
154
+ // 本入口不铸——前缀专属受理面铸,结构性杜绝绕受理面伪造送话身份
155
+ if (options?.source !== undefined && parseEventSource(options.source).kind === 'session') {
156
+ throw new Error(`submit 不受理 session: 前缀 source(跨会话操控注入走操控受理面——绕受理面伪造送话身份属调用方 bug)`);
157
+ }
158
+ const message = {
159
+ role: 'user',
160
+ content,
161
+ timestamp: Date.now(),
162
+ ...(options?.source !== undefined ? { source: options.source } : { source: 'user' }),
163
+ ...(options?.dedupeKey !== undefined ? { dedupeKey: options.dedupeKey } : {}),
164
+ };
165
+ // 后台道声明位随起跑透传(04 §5 run 级后台性——busy steer 搭在飞 run
166
+ // 时本位被忽略:车道随起跑 submit,不随搭车件)
167
+ return this.routeMessage(message, options?.backgroundWake, options?.backgroundLane === true);
168
+ }
169
+ /**
170
+ * 三通道路由单源(submit 与子代理审批挂起通知共用——04 §4):
171
+ * - dismantled 停摆 → **inject**:只落 durable user/message 不唤醒(随下次
172
+ * 启动 timeline 重播种带入——队列内存态崩即丢,durable 落账是唯一诚实承载);
173
+ * - busy → **steer**:入待发队列(唤醒位随条目透传)+ 搭在飞 run 结算;
174
+ * - idle → **followUp**:搁浅件合批消费 + 新输入作种子起跑。
175
+ * 唤醒预算(04 §4 maxConsecutiveWakes=3 批消费位记账):超帽唤醒件拒收
176
+ * (receipt + warn,不静默);前台输入起跑即复位计数。
177
+ */
178
+ routeMessage(message, backgroundWake, backgroundLane) {
179
+ // a2a 链深更新律(03 §2.2 第十一面回合护栏——e-4):人面输入归 0(用户
180
+ // 在场即重置链深)、plugin: 源归 1(插件直唤即第一跳);session: 源到不了
181
+ // 这里(submit 已拒 + deliverControl 专径另走)——compaction 等其余非用户
182
+ // 源不触碰(不是链上事件)
183
+ const parsedSource = parseEventSource(message.source ?? 'user');
184
+ if (parsedSource.treatedAsUser)
185
+ this.a2aDepthValue = 0;
186
+ else if (parsedSource.kind === 'plugin')
187
+ this.a2aDepthValue = 1;
188
+ // inject 通道:停摆期只落账(durable 写点单归接线器——不绕过直写 session)
189
+ if (this.dismantledValue) {
190
+ return Promise.resolve({ status: 'injected', seq: this.wiring.appendInjectedUser(message) });
191
+ }
192
+ if (this.currentRun !== undefined) {
193
+ // busy:入列(steer 顶注——唤醒位随条目透传,消费位合批/预算执法)+ 搭车
194
+ this.queue.enqueue(message, 'steer', backgroundWake !== undefined ? { backgroundWake } : undefined);
195
+ return this.currentRun;
196
+ }
197
+ // idle:搁浅件消费(超帽唤醒件在 consumeBatch 内拒收过滤)+ 新输入
198
+ const consumed = this.consumeBatch();
199
+ const newWake = backgroundWake === true;
200
+ if (newWake && this.wakeStreak >= MAX_CONSECUTIVE_WAKES) {
201
+ // 新唤醒件拒收:无搁浅余件 → 拒收回执;有搁浅前台件 → 照常起前台 run
202
+ this.warnWakeRefused();
203
+ if (consumed.items.length === 0)
204
+ return Promise.resolve({ status: 'wake-refused', reason: 'wake-budget' });
205
+ return this.kick(consumed.items.map((item) => item.message), false, false);
206
+ }
207
+ const seeds = [...consumed.items.map((item) => item.message), message];
208
+ // 车道随起跑 submit 声明位(搁浅件批次级近似——04 §5 修复批定形注)
209
+ return this.kick(seeds, newWake || consumed.wakeTriggered, backgroundLane);
210
+ }
211
+ /**
212
+ * 跨会话操控投递入口(03 §2.2 第十一面 send——e-4 落码裁决:不复用
213
+ * submit,因 busy 腿 submit 搭车在飞 run 结算,而跨会话调用方要**即席
214
+ * 回执**不等目标 run 终态)。三通道与 routeMessage 同型判定:
215
+ * - 停摆 → inject:durable 落账(回执携 seq);
216
+ * - busy → steer 入列:接 EnqueueReceipt 翻译(drop-oldest 被丢旧件呈报
217
+ * droppedMessageId——被丢件有撤回关联键才填,普通件无名不虚构);
218
+ * - idle → followUp 起跑:搁浅件合批 + 新件作种子,fire-and-forget(run
219
+ * 异常走 warn 面不静默——跨会话调用方已拿到回执,无人 await 这个
220
+ * promise)。**不置 backgroundWake**(e-4 落码裁决:a2a 委托是明确
221
+ * 意图的协作非自激励环——链深帽才是语义护栏;不置位保目标全量工具面)。
222
+ *
223
+ * @param depthHint a2a 链深递推值(受理面已算:送话方深度+1 / 插件道 1)——
224
+ * 本入口单点记账(受理序已过链深帽执法)。
225
+ */
226
+ deliverControl(message, messageId, depthHint) {
227
+ this.a2aDepthValue = depthHint;
228
+ // inject 通道:停摆期只落账(「随下次启动带入」的 durable 承载)
229
+ if (this.dismantledValue) {
230
+ return { status: 'delivered', messageId, seq: this.wiring.appendInjectedUser(message) };
231
+ }
232
+ if (this.currentRun !== undefined) {
233
+ // busy:steer 入列(撤回关联键随条目透传)+ 溢出回执翻译
234
+ const receipt = this.queue.enqueue(message, 'steer', { id: messageId });
235
+ if (!receipt.accepted)
236
+ return { status: 'dropped', reason: 'queue-full' };
237
+ return receipt.dropped?.id !== undefined
238
+ ? { status: 'queued', messageId, droppedMessageId: receipt.dropped.id }
239
+ : { status: 'queued', messageId };
240
+ }
241
+ // idle:搁浅件合批 + 新件作种子起跑(fire-and-forget)
242
+ const consumed = this.consumeBatch();
243
+ const seeds = [...consumed.items.map((item) => item.message), message];
244
+ // 跨会话操控不置后台道(e-4 落码裁决语义同 backgroundWake——明确意图的
245
+ // 协作非后台道编排,04 §5 修复批定形注)
246
+ void this.kick(seeds, consumed.wakeTriggered, false).catch((error) => {
247
+ this.warnFace(`操控投递起跑异常(会话 ${this.session.sessionId}):${String(error)}`);
248
+ });
249
+ return { status: 'delivered', messageId };
250
+ }
251
+ /**
252
+ * 在队撤回读面(03 §2.2 第十一面 withdraw):按撤回关联键移除在队件。
253
+ * @returns true = 撤到('withdrawn');false = 已出队/从未在队('delivered'
254
+ * 语义诚实呈报——停摆清队后同 false)。
255
+ */
256
+ withdrawQueued(messageId) {
257
+ return this.queue.withdraw(messageId) !== undefined;
258
+ }
259
+ /** a2a 链深读面(受理面送话方深度解析——03 §2.2 第十一面回合护栏) */
260
+ get a2aDepth() {
261
+ return this.a2aDepthValue;
262
+ }
263
+ /**
264
+ * 在队快照透传(e-5 操控收口——interrupt 回执 still_queued 数据源):
265
+ * 打断只打断当前 run,在队 steer 件保留在队(下次 followUp 作种子续跑)
266
+ * ——受理面据此铸「打断后队列余量」呈报。只读零副作用。
267
+ */
268
+ queuedItems() {
269
+ return this.queue.snapshot();
270
+ }
271
+ /**
272
+ * 子代理审批挂起通知(04 §10 审批挂起通知——11f 注入面;子代理机器是
273
+ * 调用方,one-shot 形态不注入由调用方裁量):background 子代理触发审批对
274
+ * 时向父会话注入一条 UserMessage(source='subagent-approval-pending',
275
+ * 载荷 = job 名 + 工具名/理由)。三通道与 submit 同型:父 run 在飞走
276
+ * steer、不在飞走 followUp 起跑。
277
+ *
278
+ * 恰一条幂等(04 §10 边界②):同一审批请求恰注入一次——dedupeKey =
279
+ * `subagent-approval:<approvalId>`,durable 日志已有同键通知即零动作
280
+ * (返回原落账 seq);「被答或 Job 终态后不追加撤回」由调用方遵守
281
+ * (结算通知随后自愈)。纯信息位(边界①):通知文案不携带任何应答面,
282
+ * 应答权钉死用户。
283
+ */
284
+ notifySubagentApprovalPending(input) {
285
+ const dedupeKey = `subagent-approval:${input.approvalId}`;
286
+ // 幂等扫描:durable 可见事件里已有同键通知 → 零动作('injected' = 已在
287
+ // 日志,非新落——回执语义与 inject 通道同形)
288
+ for (const event of this.session.events()) {
289
+ if (event.type !== 'user/message')
290
+ continue;
291
+ if (event.data.dedupeKey === dedupeKey) {
292
+ return Promise.resolve({ status: 'injected', seq: event.seq });
293
+ }
294
+ }
295
+ const reason = input.reason !== undefined ? `——${input.reason}` : '';
296
+ const message = {
297
+ role: 'user',
298
+ content: `子代理「${input.jobName}」等待审批:${input.toolName}${reason}(通知仅信息位——审批应答权在用户,请勿代答/代批)`,
299
+ timestamp: Date.now(),
300
+ source: 'subagent-approval-pending',
301
+ dedupeKey,
302
+ };
303
+ // 审批挂起通知 = 信息位注入非后台道编排——前台车道(04 §5 修复批定形注)
304
+ return this.routeMessage(message, undefined, false);
305
+ }
306
+ /**
307
+ * 拆解收尾(02 §2.3 取消模型——停摆旗标置位):打断在飞 run(per-run 控制器
308
+ * 协作面)+ 清待发队列(内存态不承诺跨进程——durable 真相只有已进 timeline
309
+ * 的消息)+ 置位停摆。此后一切投递转 inject(04 §4 第三通道)。
310
+ */
311
+ dismantle() {
312
+ this.dismantledValue = true;
313
+ this.abort();
314
+ this.queue.clear();
315
+ }
316
+ /** 停摆旗标读面(inject 路由判据——会话级标记非 run 状态机成员) */
317
+ get dismantled() {
318
+ return this.dismantledValue;
319
+ }
320
+ /**
321
+ * 协作中止:打断在飞 run(signal 透传流与工具)与退避睡眠(phase=aborted
322
+ * 落账)。无在飞 run 时若本会话仍有排队 kick(等位中)则记忆请求——排队段
323
+ * 取位后消费(零 LLM 收场),issue watchdog 等「排队期请求打断」的调用方
324
+ * 意图不因排队窗丢失;无排队时 no-op(idle 会话 abort 无对象——现状语义)。
325
+ */
326
+ abort() {
327
+ if (this.activeController !== undefined) {
328
+ this.activeController.abort();
329
+ return;
330
+ }
331
+ if (this.queuedKickCount > 0)
332
+ this.abortRequestedValue = true;
333
+ }
334
+ /** 是否有在飞 runTurns(busy 判据的读面) */
335
+ get running() {
336
+ return this.currentRun !== undefined;
337
+ }
338
+ /**
339
+ * 全量工具名快照(批 19c-1——子代理派生面的父面枚举读面:委派预检判据
340
+ * (全父面)+ 派生面交集的基准源。整形式 = 构造快照(含后续 shape 整形
341
+ * 后的面——子会话作祖父时以其实际面为基准);纯对话形 undefined =
342
+ * 不可枚举(白名单透传——fail-closed 全列)。只读快照不暴露工具体。
343
+ */
344
+ get toolNames() {
345
+ return this.fullTools !== undefined ? this.fullTools.map((tool) => tool.name) : undefined;
346
+ }
347
+ /**
348
+ * 重试进行态只读小面(04 §3.3 注记 seam——批 13a):退避等待窗内非空、
349
+ * 其余时点 null。SDK 线协议心跳载荷是该探针的唯一线面出口(03 §10.6 ②)
350
+ * ——非事件型、不进 durable;装配侧经结构共享消费(RetryProbe 归位
351
+ * contracts,零 import 边)。
352
+ */
353
+ get retryState() {
354
+ return this.retryProbeValue ?? null;
355
+ }
356
+ /** 活体事件双腿:durable 接线腿(同步序即落账序)+ 外部汇腿(channels 信封包装归批 12) */
357
+ onLiveEvent = (event) => {
358
+ this.wiring.onEvent(event);
359
+ if (this.options.onEvent !== undefined)
360
+ void this.options.onEvent(event);
361
+ };
362
+ /**
363
+ * 模型请求前瀑布分派(03 §2.4 agent_pre_step——批 #99 发射腿):loop
364
+ * while 体顶调用(steering 消费与 turn_start 之前——刹停零 dangling turn)。
365
+ * 刹停 = warn 报备 + 'stop'(run 以 stopReason 'stop' 收 completed);提醒
366
+ * 槽暂存 pendingReminders、于同请求的 transformContext 关口注入(瞬态不落
367
+ * durable);管线失败沿链传播(fail-closed——context_transform 同律)。
368
+ */
369
+ pendingReminders = [];
370
+ onPreModelRequest = async () => {
371
+ const input = { sessionId: this.session.sessionId, reminders: [] };
372
+ const out = await this.options.dispatch.waterfall(AGENT_PRE_STEP_EVENT, input);
373
+ if (out.stop !== undefined) {
374
+ this.warnFace(`agent_pre_step 刹停(${out.stop.reason})——本 turn 不起模型请求`);
375
+ return 'stop';
376
+ }
377
+ if (out.reminders.length > 0)
378
+ this.pendingReminders = out.reminders;
379
+ };
380
+ /**
381
+ * 请求组装最后关口:信封快照(边界制)在此落账——快照取原始 systemPrompt
382
+ * (04 §11 快照序钉死:先快照后注入,瞬态注入体不入快照不落日志)。
383
+ * 环境披露段(04 §11 迁层定形——2026-09-11 cache 经济批 RP1):五件文本块
384
+ * 注入消息尾瞬态 UserMessage 族首位(原 systemPrompt 尾拼装废止——git 摘要/
385
+ * 插件计数/日期三 volatile 件随自主运行日 commit/装载/跨日 churn 杀全部
386
+ * 前缀缓存;瞬态层每请求重算、不落日志、随请求即弃;缺席/返回 null = 无披露)。
387
+ * todo 回看注入(05 §1.1 跨 turn 每轮当前全表):UserMessage 追加于消息
388
+ * 尾——瞬态层(不进 timeline 活数组不落 durable,llmContext 每请求新建
389
+ * 零累积);空表不注入;fold 单源 = durable 日志可见事件(遮蔽感知),
390
+ * 冷启动 resume 首请求同覆盖。
391
+ */
392
+ onTransformContext = async (context) => {
393
+ // 请求关口总拍(05 §1.2——「模型可见即已记录」收口对拍):本入口是每次
394
+ // LLM 请求组装的唯一关口(stream.ts 组装序 convertToLlm → 此处),先对拍
395
+ // 持久层再注入瞬态层——下方各注入段(插件段/披露段/瀑布尾注)注入于对拍
396
+ // 之后,属规范既定豁免面不进 v1 射程
397
+ this.assertTimelineReconstructable();
398
+ const config = this.activeConfig;
399
+ if (config !== undefined) {
400
+ this.wiring.noteRequest({
401
+ config: {
402
+ model: config.model,
403
+ ...(config.thinkingLevel !== undefined ? { thinkingLevel: config.thinkingLevel } : {}),
404
+ },
405
+ systemPrompt: context.systemPrompt ?? '',
406
+ toolSchemas: context.tools ?? [],
407
+ });
408
+ }
409
+ // 插件提示词段注入位(批 19a 消费腿——03 §2.5 注册即生效面):每请求
410
+ // 重取物化(段集动态;sessionId 透传 builder——每会话懒冻结类段消费,
411
+ // cache 经济批 ca-2);披露段已迁瞬态层,本段区 = systemPrompt 会话内
412
+ // 恒定面的主体;空串/缺席 = 零段跳过
413
+ const sections = this.options.pluginSections !== undefined ? this.options.pluginSections(this.session.sessionId) : '';
414
+ let transformed = context;
415
+ if (sections !== '') {
416
+ transformed = {
417
+ ...transformed,
418
+ systemPrompt: context.systemPrompt !== undefined && context.systemPrompt !== ''
419
+ ? `${context.systemPrompt}\n\n${sections}`
420
+ : sections,
421
+ };
422
+ }
423
+ // context_transform 瀑布派发(03 §2.4——LLM 请求组装最后关口的插件管线):
424
+ // 载荷消息批取拷贝(handler 就地改写不外溢变换前引用);失败上抛携带
425
+ // 变换前原批(审计保输入——管线失败语义沿链传播,驱动不加吞)。todo 快照
426
+ // 注入位在瀑布之后恒最后(05 §1.1「todo 走瀑布最后」——注入序 diff →
427
+ // recall → todo)
428
+ const preTransformMessages = transformed.messages;
429
+ try {
430
+ const payload = {
431
+ sessionId: this.session.sessionId,
432
+ messages: [...preTransformMessages],
433
+ };
434
+ const out = await this.options.dispatch.waterfall(CONTEXT_TRANSFORM_EVENT, payload);
435
+ transformed = { ...transformed, messages: out.messages };
436
+ }
437
+ catch (err) {
438
+ throw Object.assign(err instanceof Error ? err : new Error(String(err)), {
439
+ originalMessages: [...preTransformMessages],
440
+ });
441
+ }
442
+ // 披露段 + agent_pre_step 提醒 + 预算预警 + goal 轮间沉淀(批 #99 + 批 H
443
+ // + cache 经济批 RP1):同属请求组装瞬态层不落 durable;注入序定律(04
444
+ // §5 H 注单源)= 披露段 → reminders → 预算预警 → goal 沉淀 → todo 恒
445
+ // 最后(05 §1.1)。提醒槽取后即清(跨请求不残留——一次 pre_step 暂存
446
+ // 对应一次请求组装)
447
+ const reminders = this.pendingReminders;
448
+ this.pendingReminders = [];
449
+ const transientTail = [];
450
+ // 披露段注入位(04 §11 迁层定形):消息尾瞬态族首位——瀑布之后注入
451
+ const disclosure = this.options.environmentDisclosure?.() ?? null;
452
+ if (disclosure !== null) {
453
+ transientTail.push({ role: 'user', content: disclosure, timestamp: Date.now() });
454
+ }
455
+ if (reminders.length > 0) {
456
+ transientTail.push({ role: 'user', content: reminders.join('\n'), timestamp: Date.now() });
457
+ }
458
+ // 预算预警注入(04 §5 三档软着陆——瞬态 UserMessage;分族判据 = origin
459
+ // 会话级两判 + 当前 run 后台道声明位〔修复批 run 级后台性〕,装配位闭包
460
+ // 铸造,本层只管注入位与序)
461
+ const advisory = this.options.budgetAdvisory?.(this.runLaneValue) ?? null;
462
+ if (advisory !== null) {
463
+ transientTail.push({ role: 'user', content: advisory, timestamp: Date.now() });
464
+ }
465
+ const deposit = this.options.goalDeposit?.() ?? null;
466
+ if (deposit !== null) {
467
+ transientTail.push({ role: 'user', content: deposit, timestamp: Date.now() });
468
+ }
469
+ if (transientTail.length > 0) {
470
+ transformed = { ...transformed, messages: [...transformed.messages, ...transientTail] };
471
+ }
472
+ // todo 快照注入位:null = 空表跳过(从未建表/用户已重置——不打扰上下文)
473
+ // goal 段升格(03 §10.5):goalScopeFor 供锚 → fold 边界升格 goal 生命周期
474
+ // 段(user/message 不重置、跨续跑轮存活);缺席 = run-scoped 现行为
475
+ const goalScope = this.options.goalScopeFor?.(this.session.sessionId) ?? undefined;
476
+ const snapshot = todoSnapshotMessage(this.session.events(), Date.now(), goalScope);
477
+ if (snapshot === null)
478
+ return transformed;
479
+ return { ...transformed, messages: [...transformed.messages, snapshot] };
480
+ };
481
+ /**
482
+ * run 存续期统一取件(loop 两取件点共用——steer 顶注 getSteeringMessages /
483
+ * 自然停候选 followUp getFollowUpMessages):消费位合批与唤醒预算执法都在
484
+ * 此。唤醒批 → streak+1 + 收窄工具面;前台批保持现面(run 内纯前台批不
485
+ * 复位 streak——复位只认前台 idle 起跑,用户在场才复位预算)。
486
+ */
487
+ consumeInRun = () => {
488
+ const { items, wakeTriggered } = this.consumeBatch();
489
+ if (wakeTriggered) {
490
+ this.wakeStreak += 1;
491
+ this.applyToolFace(true);
492
+ }
493
+ return items.map((item) => item.message);
494
+ };
495
+ /**
496
+ * 入口统一 kickoff(04 §4——lane 帽两段形,channels 消息语义批 m-2):
497
+ * ① **排队段(不计在飞)**——acquireRunSlot 在场先取位,帽满则挂起等位
498
+ * (宿主级 FIFO,闸件在 host/conversation-stack);submit 回执 promise
499
+ * 含排队等待(语义 = 起跑延迟,run 结算回执形不变);排队期
500
+ * currentRun 未置位——busy 判据 / interrupt 回执 / 后续消息路由皆读
501
+ * 已起跑形(冷读闸 M1 裁决「排队不计在飞」的执法位)。取位器缺席 =
502
+ * 无帽渐进增强(直通零破口)。
503
+ * ② **起跑段(launch)**——取位成功后进入:streak 记账 / 工具面定形 /
504
+ * lifecycle 起拍广播 / currentRun 咬合;run 终态(launch promise 结算)
505
+ * 后 finally 释放位——「run 终态释放位」(04 §4)。
506
+ * steer/inject 腿不经本闸:busy 腿入列搭车(routeMessage 直入 queue)、
507
+ * 停摆腿 durable 落账(routeMessage 直落 inject)——两腿不产生新 run。
508
+ */
509
+ kick(seeds, wakeTriggered, backgroundLane) {
510
+ const gate = this.options.acquireRunSlot;
511
+ // 缺席直通:零包装零排队(既有「busy 回执 === 在飞 run promise」引用恒等
512
+ // 与「受理即已落账」同步段不破——渐进增强零破口含同步段)
513
+ if (gate === undefined)
514
+ return this.launch(seeds, wakeTriggered, backgroundLane);
515
+ // 在场同步试位:帽内有位直通(tryAcquire 零微任务边界——受理回执时种子
516
+ // 落账已完成,投影一致性同缺席形);终态释放挂 settled 两路(run 回执
517
+ // promise 引用不变——busy 恒等同律保持)
518
+ const fast = gate.tryAcquire();
519
+ if (fast !== undefined) {
520
+ const settled = this.launch(seeds, wakeTriggered, backgroundLane);
521
+ void settled.then(fast, fast);
522
+ return settled;
523
+ }
524
+ // 排队段(不计在飞):帽满挂起等位(宿主级 FIFO);取位 promise 结算后
525
+ // 才进起跑段——排队期 currentRun 未置位(冷读闸 M1 裁决执法位)
526
+ return (async () => {
527
+ this.queuedKickCount += 1;
528
+ try {
529
+ const release = await gate.acquire();
530
+ // —— 取位后重验三查(2026-09-10 全面复盘真缺口修复)——
531
+ // 等位期间宿主状态可能已迁移;取位结算不能盲信起跑前快照,重验后再起跑。
532
+ // ① 停摆/中止重验:排队期 dismantle(finish/dispose/subagent 终态停摆
533
+ // 同置 dismantledValue)或 abort() 记忆(无在飞时记忆位)则零 LLM
534
+ // 收场:还位 + 种子转 inject 落账(durable 真相在——「随下次启动
535
+ // timeline 重播种带入」与 routeMessage 停摆腿同语义;种子落账序在
536
+ // 起跑后的 message_end——不起跑则必须转 inject 承载)+ aborted 终态
537
+ // 回执(run 未起跑的诚实终态)。修复前:dismantle 后已排队 run 取位
538
+ // 照常起跑烧 LLM——issue 预算帽/日池对排队期 run 完全失效。
539
+ if (this.dismantledValue || this.abortRequestedValue) {
540
+ this.abortRequestedValue = false;
541
+ release();
542
+ this.persistSeedsAsInjected(seeds);
543
+ return { status: 'aborted' };
544
+ }
545
+ // ② 单 run 不变量重验(04 §4「帽下串行不合批」执法位):另一排队 run
546
+ // 已先取位起跑(currentRun 已置——真闸件 FIFO 连续移交两位时后者必
547
+ // 见前者置位)则本批不起 run:种子转 steer 入列(消费位 = 在飞 run
548
+ // 取件点/尾声 drained 续跑)+ 搭车在飞 run 回执 + 立即还位(本单元
549
+ // 不占帽——在飞 run 持自己的位)。修复前:帽≥2 下同会话双 run 并发
550
+ // 起跑——currentRun 被覆写(abort 只达其一)+ durable turn 边界记账
551
+ // 腐坏。注:enqueue 与在飞 run 尾声 drained 判定间存在极窄微任务窗
552
+ // ——若搭车件错过 drained,作搁浅件自愈(下次 idle 输入 consumeBatch
553
+ // 带入——与 steer 正常搁浅同形)。
554
+ if (this.currentRun !== undefined) {
555
+ for (const seed of seeds) {
556
+ this.queue.enqueue(seed, 'steer', wakeTriggered ? { backgroundWake: true } : undefined);
557
+ }
558
+ const ride = this.currentRun;
559
+ release();
560
+ return ride;
561
+ }
562
+ try {
563
+ return await this.launch(seeds, wakeTriggered, backgroundLane);
564
+ }
565
+ finally {
566
+ // run 终态释放位(幂等保护在闸件——release() 双调安全)
567
+ release();
568
+ }
569
+ }
570
+ finally {
571
+ this.queuedKickCount -= 1;
572
+ }
573
+ })();
574
+ }
575
+ /**
576
+ * 排队收场种子的 durable 承载(排队段零 LLM 收场配套——2026-09-10 复盘真
577
+ * 缺口修复):逐条转 inject 落账保住 durable 真相。种子调用源单源
578
+ * routeMessage/deliverControl(全 UserMessage);非 user 形不可达——防御
579
+ * 丢弃并 warn 报备(不静默)。
580
+ */
581
+ persistSeedsAsInjected(seeds) {
582
+ for (const seed of seeds) {
583
+ if (isStandardMessage(seed) && seed.role === 'user') {
584
+ this.wiring.appendInjectedUser(seed);
585
+ }
586
+ else {
587
+ this.warnFace(`排队收场种子非用户消息(role=${String(seed.role)})——durable 无承载位,丢弃报备`);
588
+ }
589
+ }
590
+ }
591
+ /**
592
+ * 起跑段(lane 取位后——原 kickoff 体):streak 记账(04 §4 批消费位:
593
+ * 唤醒起跑 +1、前台起跑归零——用户在场即复位预算);工具面随之定形——
594
+ * 唤醒 run 收窄、前台全量;登记在飞 promise、结算链第一拍清位(调用方
595
+ * await 的就是清理后的 promise——resolved 后 running 必已归 false,无
596
+ * 观察窗口)。
597
+ */
598
+ launch(seeds, wakeTriggered, backgroundLane) {
599
+ if (wakeTriggered)
600
+ this.wakeStreak += 1;
601
+ else
602
+ this.wakeStreak = 0;
603
+ // 后台道声明位随起跑置位(04 §5 run 级后台性——本 run 存续期请求组装的
604
+ // 预警分族判据;结算链清位防跨 run 残留)
605
+ this.runLaneValue = backgroundLane;
606
+ this.applyToolFace(wakeTriggered);
607
+ // 记账窗锚(04 §5——批 #99):settle 时窗扫 [seqAtLaunch, settle) 的
608
+ // assistant/message 计数;捕获位在种子落账前(seeds 的 user/message 不入窗)
609
+ const eventsBefore = this.session.events();
610
+ const seqAtLaunch = eventsBefore.length > 0 ? eventsBefore[eventsBefore.length - 1].seq : 0;
611
+ // session/lifecycle 起拍广播(04 §6 e-2——fire-and-forget:活体投影不阻塞
612
+ // run 起跑;监听异常经 dispatch 隔离上报不回传)
613
+ void this.options.dispatch.emit(SESSION_LIFECYCLE_EVENT, {
614
+ sessionId: this.session.sessionId,
615
+ phase: 'run-started',
616
+ ...(wakeTriggered ? { wake: true } : {}),
617
+ });
618
+ const inner = this.runTurns(seeds);
619
+ const settled = inner.then((result) => {
620
+ if (this.currentRun === settled)
621
+ this.currentRun = undefined;
622
+ this.runLaneValue = false; // 车道随 run 结算清位(单 run 不变量下无跨 run 竞态)
623
+ this.noteRunSettled(seeds, seqAtLaunch, backgroundLane, result.status);
624
+ return result;
625
+ }, (error) => {
626
+ if (this.currentRun === settled)
627
+ this.currentRun = undefined;
628
+ this.runLaneValue = false;
629
+ this.noteRunSettled(seeds, seqAtLaunch, backgroundLane); // 崩溃路径——status 缺席不虚构
630
+ throw error;
631
+ });
632
+ this.currentRun = settled;
633
+ return settled;
634
+ }
635
+ /**
636
+ * run 结算记账腿(04 §5 双轨第一腿——批 #99 三入口统一:TUI/webui/issue/
637
+ * run CLI 全经 launch,一处嵌钩全入口覆盖):窗扫 durable assistant/message
638
+ * 计数 + 种子归因单源(kind 判据——'user'/'channel:*' 真人入口 true、
639
+ * 'schedule' tick 源机器轮 false)。嵌于 settled 链内分支(回执 promise 引用
640
+ * 恒等律不破);钩体自防炸(记账失败 warn 不改写 run 终态)。
641
+ */
642
+ noteRunSettled(seeds, seqAtLaunch, backgroundLane, status) {
643
+ if (this.options.onRunSettled === undefined)
644
+ return;
645
+ try {
646
+ let assistantMessages = 0;
647
+ for (const event of this.session.events()) {
648
+ if (event.seq > seqAtLaunch && event.type === 'assistant/message')
649
+ assistantMessages += 1;
650
+ }
651
+ const userInitiated = seeds.some((message) => {
652
+ if (!isStandardMessage(message) || message.role !== 'user')
653
+ return false;
654
+ // 「用户在场」判据 = 归因 kind(user / channel:* 真人入口)——schedule/
655
+ // budget-extended/subagent-* 机器注入位 treatedAsUser 虽 true(投影同视
656
+ // 用户话语——05 §3.1)但不算用户在场(04 §5 唤醒预算复位语义:机器
657
+ // 轮不复位)
658
+ const parsed = parseEventSource(message.source ?? 'user');
659
+ return parsed.kind === 'user' || parsed.kind === 'channel';
660
+ });
661
+ this.options.onRunSettled({
662
+ sessionId: this.session.sessionId,
663
+ assistantMessages,
664
+ seqFromLaunch: seqAtLaunch,
665
+ backgroundLane,
666
+ userInitiated,
667
+ ...(status !== undefined ? { status } : {}),
668
+ });
669
+ }
670
+ catch (err) {
671
+ this.warnFace(`run 结算记账失败(onRunSettled 自防炸——run 终态不受累):${err instanceof Error ? err.message : String(err)}`);
672
+ }
673
+ }
674
+ /**
675
+ * runTurns 本体(04 §3.3 重试循环宿主):一次调用 = 「起跑 + 若干次重试续入 +
676
+ * 搁浅件续跑」的完整编舞。attempt 计数生命周期 = 本调用(进入即零);
677
+ * transient 与 overflow 名额各自独立分账(04 §3.4 1/1)。
678
+ */
679
+ async runTurns(initialSeeds) {
680
+ const controller = new AbortController();
681
+ this.activeController = controller;
682
+ const retry = this.options.retry ?? DEFAULT_RETRY_POLICY;
683
+ let transientAttempt = 0;
684
+ let overflowAttempt = 0;
685
+ /** 终态结算值(finally 面的消费位——onRunSettled 订阅回调载荷) */
686
+ let settled;
687
+ try {
688
+ // 入口重播种:对齐投影(上次失败收场可能已遮蔽尾部——镜像先重建再起跑)
689
+ this.context.messages = this.reseededTimeline();
690
+ let result = await this.enterRun(initialSeeds, controller.signal);
691
+ while (true) {
692
+ if (result.status === 'failed') {
693
+ const assistant = lastErrorAssistant(this.context.messages);
694
+ if (assistant !== undefined) {
695
+ const bucket = this.options.classifyError?.(assistant) ?? 'non-retryable';
696
+ // —— transient 腿(04 §3.3)——
697
+ if (bucket === 'transient' && retry.enabled && transientAttempt < retry.maxRetries) {
698
+ transientAttempt += 1;
699
+ const delayMs = retryDelay(retry, transientAttempt);
700
+ this.occludeFailedTail('transient', transientAttempt, retry.maxRetries, delayMs);
701
+ this.context.messages = this.reseededTimeline();
702
+ // 重试探针窗开(04 §3.3 seam——退避等待期可见;心跳载荷唯一线面出口)
703
+ this.retryProbeValue = {
704
+ attempt: transientAttempt,
705
+ maxAttempts: retry.maxRetries,
706
+ nextAt: Date.now() + delayMs,
707
+ };
708
+ if (!(await abortableSleep(delayMs, controller.signal))) {
709
+ // 退避中被取消:phase=aborted 落账;run 终态保持已结算的 failed
710
+ this.session.append('llm/retry', {
711
+ attempt: transientAttempt,
712
+ maxAttempts: retry.maxRetries,
713
+ delayMs,
714
+ phase: 'aborted',
715
+ });
716
+ break;
717
+ }
718
+ // 窗关:续入即新流(活体层零新事件型——用户看到重跑,探针蒸发)
719
+ this.retryProbeValue = undefined;
720
+ result = await this.enterRun([], controller.signal);
721
+ continue;
722
+ }
723
+ // —— overflow 腿(04 §3.4:1/1 独立分账;注入缺席 = 直接终态零事件)——
724
+ if (bucket === 'overflow' && this.options.compactForOverflow !== undefined && overflowAttempt < 1) {
725
+ overflowAttempt += 1;
726
+ const delayMs = retryDelay(retry, overflowAttempt);
727
+ this.occludeFailedTail('overflow', overflowAttempt, retry.maxRetries, delayMs);
728
+ const outcome = await this.options.compactForOverflow(this.session);
729
+ if (outcome === 'compacted') {
730
+ this.context.messages = this.reseededTimeline();
731
+ result = await this.enterRun([], controller.signal);
732
+ continue;
733
+ }
734
+ // nothing(区间不足压无可压)/ failed(摘要通道失败)——续入必再溢出,诚实报败
735
+ this.appendExhausted('overflow', overflowAttempt, retry.maxRetries, outcome);
736
+ break;
737
+ }
738
+ // —— 达帽 / 不可重试:只在确有重试账面时落 exhausted(quota/auth 零重试零事件)——
739
+ if (bucket === 'transient' && transientAttempt > 0) {
740
+ this.appendExhausted('transient', transientAttempt, retry.maxRetries, 'exhausted', assistant.errorMessage);
741
+ }
742
+ else if (bucket === 'overflow' && overflowAttempt > 0) {
743
+ this.appendExhausted('overflow', overflowAttempt, retry.maxRetries, 'exhausted', assistant.errorMessage);
744
+ }
745
+ }
746
+ break;
747
+ }
748
+ // 非 failed 收场:completed 且队列有搁浅件(break 时未消费)→ 作种子续跑;
749
+ // aborted 不续(用户叫停语义——取消模型 11d 正式收口)。空消费防御:
750
+ // 搁浅件全被预算拒收时不续跑(startRun([]) 会触发无新消息的 LLM 调用)
751
+ if (result.status !== 'completed')
752
+ break;
753
+ const drained = this.continueConsumption();
754
+ if (drained.length === 0)
755
+ break;
756
+ result = await this.enterRun(drained, controller.signal);
757
+ }
758
+ settled = result;
759
+ return result;
760
+ }
761
+ finally {
762
+ if (this.activeController === controller)
763
+ this.activeController = undefined;
764
+ // 探针随 run 收口蒸发(防御位——aborted/exhausted 各 break 路不悬空)
765
+ this.retryProbeValue = undefined;
766
+ // run 终态 = 结算边界(04 §3):审批对收口(04 §9 turn 界闭合——未决
767
+ // ask 统一 unavailable;run 打断的在身 ask 已由 signal 链先收 cancel)
768
+ this.options.settleApprovals?.();
769
+ // onRunSettled 终态回调(ctx.agent 服务订阅面——03 §2.2;非总线词汇,
770
+ // 与审批收口同界;异常/上抛路径无终态即零回调)
771
+ if (settled !== undefined) {
772
+ notifyRunSettled(this.options.scope, { result: settled, sessionId: this.session.sessionId });
773
+ }
774
+ // session/lifecycle 终态拍广播(04 §6 e-2——finally 面恒发:崩溃路径也
775
+ // 收口〔只报 phase 不虚构终值〕;与 onRunSettled 同界但语义分立——彼系
776
+ // ctx.agent 订阅面携 RunResult 本体、此系活体投影零结果引用)
777
+ void this.options.dispatch.emit(SESSION_LIFECYCLE_EVENT, {
778
+ sessionId: this.session.sessionId,
779
+ phase: 'run-settled',
780
+ ...(settled !== undefined ? { status: settled.status } : {}),
781
+ });
782
+ }
783
+ }
784
+ /** 单次入口 startRun(seeds 空数组 = 纯续入——04 §2 入口两式) */
785
+ async enterRun(seeds, signal) {
786
+ const config = { ...this.baseConfig, signal };
787
+ this.activeConfig = config;
788
+ try {
789
+ return await startRun(this.context, config, [...seeds]);
790
+ }
791
+ finally {
792
+ if (this.activeConfig === config)
793
+ this.activeConfig = undefined;
794
+ }
795
+ }
796
+ /**
797
+ * 消费位合批(04 §4 合批规则单源):首件唤醒位 → 唤醒批取全量(合批窗口
798
+ * ——后续件不辨唤醒/前台全部并入);首件前台位 → one-at-a-time 取一件。
799
+ * 预算执法:超帽唤醒件在此拒收过滤(warn 不静默)——kept 空时调用方各自
800
+ * 空消费防御(followUp 空即收 / 续跑段不起空 run)。
801
+ */
802
+ consumeBatch() {
803
+ if (!this.queue.hasItems())
804
+ return { items: [], wakeTriggered: false };
805
+ const items = this.queue.drain();
806
+ if (items[0].backgroundWake === true) {
807
+ // 唤醒头触发合批窗口:余件全量并入本批
808
+ while (this.queue.hasItems())
809
+ items.push(...this.queue.drain());
810
+ }
811
+ const kept = items.filter((item) => {
812
+ if (item.backgroundWake === true && this.wakeStreak >= MAX_CONSECUTIVE_WAKES) {
813
+ this.warnWakeRefused();
814
+ return false;
815
+ }
816
+ return true;
817
+ });
818
+ return { items: kept, wakeTriggered: kept.some((item) => item.backgroundWake === true) };
819
+ }
820
+ /**
821
+ * 搁浅件续跑消费(runTurns 续跑段专用):面随本批触发者——唤醒批收窄并
822
+ * streak+1;前台批复原全量(不复位 streak——复位只认前台 idle 起跑)。
823
+ */
824
+ continueConsumption() {
825
+ const { items, wakeTriggered } = this.consumeBatch();
826
+ if (wakeTriggered)
827
+ this.wakeStreak += 1;
828
+ this.applyToolFace(wakeTriggered);
829
+ return items.map((item) => item.message);
830
+ }
831
+ /**
832
+ * 工具面切换(04 §4 合批收窄):唤醒 run = backgroundTools 供应面产出
833
+ * (**缺席 = 零工具**——「防后台 run 自由动用全部工具」最保守兑现);
834
+ * 前台 run = 全量 tools 快照(缺席 = 纯对话形态,摘除 tools 位)。
835
+ * 请求组装(stream)与工具批执行(tools-batch)都是请求时点读
836
+ * context.tools——换面即刻生效。
837
+ */
838
+ applyToolFace(wakeTriggered) {
839
+ if (wakeTriggered) {
840
+ this.context.tools = [...(this.options.backgroundTools?.() ?? [])];
841
+ }
842
+ else if (this.fullTools !== undefined) {
843
+ this.context.tools = [...this.fullTools];
844
+ }
845
+ else {
846
+ delete this.context.tools;
847
+ }
848
+ }
849
+ /** 唤醒预算拒收警示(护栏不静默——warn 面统一文案) */
850
+ warnWakeRefused() {
851
+ this.warnFace(`唤醒预算拒收:连续后台唤醒已达 ${MAX_CONSECUTIVE_WAKES} 帽(前台输入复位预算)`);
852
+ }
853
+ /**
854
+ * 遮蔽失败尾(04 §3.3 条 1:遮蔽 + 落账一次 append):区间 = [错误 assistant
855
+ * 锚 seq, durable 高水位],surfaceOp 随 llm/retry(phase=scheduled) 信封携带;
856
+ * sourceEventSeqs 全列区间 seq(溯源完整性校验)。校验红 = 驱动 bug,
857
+ * fail-loud 上抛(正门即执法位——05 §2.4 retry 区间合法规约)。
858
+ */
859
+ occludeFailedTail(reason, attempt, maxAttempts, delayMs) {
860
+ const start = this.wiring.lastAssistantSeq;
861
+ const events = this.session.events();
862
+ const end = events.length - 1;
863
+ if (start === undefined || end < start) {
864
+ // 防御位:错误 assistant 锚缺席(落账序漂移)——遮蔽不成不上重试,fail-loud
865
+ throw new Error(`retry 遮蔽锚缺席:lastAssistantSeq=${String(start)} 高水位=${end}(wiring 记账与落账序漂移——驱动 bug)`);
866
+ }
867
+ const sourceEventSeqs = [];
868
+ for (let seq = start; seq <= end; seq += 1)
869
+ sourceEventSeqs.push(seq);
870
+ this.session.appendWithSurfaceOp('llm/retry', {
871
+ attempt,
872
+ maxAttempts,
873
+ delayMs,
874
+ phase: 'scheduled',
875
+ ...(reason !== 'transient' ? { reason } : {}),
876
+ }, { op: 'replace', start, end }, sourceEventSeqs);
877
+ // 遮蔽即重建:下一请求 header 落 resume 形(timeline 重建的边界制判据)
878
+ this.wiring.markRebuildPending();
879
+ }
880
+ /** exhausted 落账(达帽放弃——errorMessage 随行;delayMs=0:无实退避发生) */
881
+ appendExhausted(reason, attempt, maxAttempts, outcome, errorMessage) {
882
+ this.session.append('llm/retry', {
883
+ attempt,
884
+ maxAttempts,
885
+ delayMs: 0,
886
+ phase: 'exhausted',
887
+ errorMessage: errorMessage ?? `retry 放弃:${outcome}`,
888
+ ...(reason !== 'transient' ? { reason } : {}),
889
+ });
890
+ }
891
+ /** 重播种(04 §3.3 条 2 私有路径):投影 → timeline 活数组(确定性时间源) */
892
+ reseededTimeline() {
893
+ const events = this.session.events();
894
+ return reseedTimeline(this.session.projection(), (seq) => events[seq]?.time ?? 0);
895
+ }
896
+ /**
897
+ * 请求关口总拍(05 §1.2 对拍断言——恒开无开关):timeline 活数组(loop
898
+ * 「入列↔emit」同步对偶维护的投影镜像缓存)vs 事件日志独立重建(投影 →
899
+ * reseedTimeline——与冷启动 resume / 重试重播种完全同路,两侧不共享中间
900
+ * 状态)。红 = 驱动 bug fail-loud:模型可见消息未经 durable 落账即入列
901
+ * (暗通道——含自定义角色零写点位)或落账翻译腿失真。比较 O(n) 与请求
902
+ * 序列化同阶(convertToLlm 全量转写已是每请求 O(n))——恒开零边际成本;
903
+ * 降级掩码从投影原始形判(预算刀截断豁免位——重建形已丢标记,见件注)。
904
+ */
905
+ assertTimelineReconstructable() {
906
+ const events = this.session.events();
907
+ const projection = this.session.projection();
908
+ const expected = reseedTimeline(projection, (seq) => events[seq]?.time ?? 0);
909
+ assertModelVisibleTimeline(this.context.messages, expected, degradationMask(projection));
910
+ }
911
+ }
912
+ /** timeline 尾扫最后一条错误 assistant(runTurns 重试判定的起点消息) */
913
+ function lastErrorAssistant(messages) {
914
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
915
+ const message = messages[i];
916
+ // 先收窄标准消息(CustomMessage.role 是宽 string,静态面可撞标准名)
917
+ if (!isStandardMessage(message) || message.role !== 'assistant')
918
+ continue;
919
+ return message.stopReason === 'error' ? message : undefined;
920
+ }
921
+ return undefined;
922
+ }