@zhushanwen/subagent-core 0.2.0 → 0.5.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 (347) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-43ONBFZX.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-V3VHZ2VX.js +59 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
  18. package/dist/execution/engine/engines/zcode/constants.js +39 -7
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +18234 -1277
  24. package/dist/index.d.cts +5646 -673
  25. package/dist/index.d.ts +5646 -673
  26. package/dist/index.js +17828 -1277
  27. package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
  28. package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
  29. package/package.json +7 -4
  30. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  31. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  32. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  33. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  34. package/src/__tests__/manifest-store.test.ts +10 -9
  35. package/src/__tests__/record-store-cache.test.ts +0 -2
  36. package/src/__tests__/record-store-index.test.ts +1 -2
  37. package/src/__tests__/review-fix-loop-script.test.ts +93 -3
  38. package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
  39. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  40. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  41. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  42. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  43. package/src/__tests__/session-runner.test.ts +1 -1
  44. package/src/__tests__/smoke.test.ts +9 -2
  45. package/src/core/__tests__/host-services.test.ts +2 -2
  46. package/src/core/__tests__/logger.test.ts +1 -1
  47. package/src/core/error-message.ts +9 -0
  48. package/src/core/host-services.ts +21 -5
  49. package/src/core/notify-ports.ts +18 -5
  50. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  51. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  52. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  53. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  54. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  55. package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
  56. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
  57. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  58. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  59. package/src/execution/__tests__/delivery-methods.test.ts +77 -62
  60. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  61. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  62. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  63. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  64. package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
  65. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  66. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  67. package/src/execution/__tests__/execution-record.test.ts +217 -60
  68. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  69. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  70. package/src/execution/__tests__/finalize-record.test.ts +278 -16
  71. package/src/execution/__tests__/gc-timer.test.ts +57 -2
  72. package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
  73. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  74. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  75. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  76. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  77. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  78. package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
  79. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  80. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  83. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  84. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  85. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  86. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  87. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  88. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  89. package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
  90. package/src/execution/__tests__/output-collector.test.ts +117 -6
  91. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  92. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  93. package/src/execution/__tests__/record-store.test.ts +89 -5
  94. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  95. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  96. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  97. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  98. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  105. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
  106. package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
  107. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  108. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  109. package/src/execution/__tests__/session-pending.test.ts +471 -0
  110. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  111. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  112. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  113. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  114. package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
  115. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
  116. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  117. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  118. package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
  119. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  120. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  121. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  122. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  123. package/src/execution/__tests__/spawned-children.test.ts +155 -11
  124. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  125. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  126. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  127. package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
  128. package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
  129. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  130. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
  131. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
  132. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
  133. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  134. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  135. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  136. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  137. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  138. package/src/execution/__tests__/timeout-integration.test.ts +13 -12
  139. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  140. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  141. package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
  142. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  143. package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
  144. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  145. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  146. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  147. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  148. package/src/execution/agent-registry.ts +298 -30
  149. package/src/execution/agent-result-mapper.ts +3 -0
  150. package/src/execution/agents-assembly.ts +88 -0
  151. package/src/execution/cold-resurrect.ts +199 -0
  152. package/src/execution/concurrency-pool.ts +71 -9
  153. package/src/execution/dialog-queue.ts +101 -3
  154. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  155. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  156. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  158. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  159. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  160. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  161. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  162. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
  163. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +94 -3
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engine-discovery.ts +12 -16
  181. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  182. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  183. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  184. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
  185. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  186. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  187. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  188. package/src/execution/engine/engines/pi/reader.ts +11 -0
  189. package/src/execution/engine/engines/pi/registration.ts +4 -2
  190. package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
  191. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  192. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  193. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  194. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  195. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  197. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  198. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
  199. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  200. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  202. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  203. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  209. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  210. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
  211. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  212. package/src/execution/engine/engines/zcode/connection.ts +598 -0
  213. package/src/execution/engine/engines/zcode/constants.ts +147 -14
  214. package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
  215. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  216. package/src/execution/engine/engines/zcode/preparer.ts +22 -150
  217. package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
  218. package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
  219. package/src/execution/engine/host-task-spec.ts +32 -36
  220. package/src/execution/engine/model-validation.ts +177 -0
  221. package/src/execution/engine/port.ts +65 -7
  222. package/src/execution/engine/registry.ts +53 -0
  223. package/src/execution/engine/routing.ts +75 -1
  224. package/src/execution/engine/types.ts +30 -103
  225. package/src/execution/execution-record.ts +222 -144
  226. package/src/execution/finalize-record.ts +181 -85
  227. package/src/execution/idle-gc.ts +28 -0
  228. package/src/execution/lifecycle-manager.ts +27 -3
  229. package/src/execution/lifecycle-predicates.ts +1 -1
  230. package/src/execution/manifest-store.ts +53 -62
  231. package/src/execution/notifier.ts +10 -10
  232. package/src/execution/notify-host.ts +212 -0
  233. package/src/execution/notify-ledger.ts +117 -16
  234. package/src/execution/record-entry.ts +8 -2
  235. package/src/execution/record-store.ts +148 -88
  236. package/src/execution/round-settlement.ts +93 -0
  237. package/src/execution/session-file-gc.ts +96 -62
  238. package/src/execution/session-pending.ts +213 -62
  239. package/src/execution/session-reconstructor.ts +256 -143
  240. package/src/execution/sessions-index.ts +127 -102
  241. package/src/execution/settled-watchdog.ts +311 -0
  242. package/src/execution/subagent-actions-core.ts +686 -0
  243. package/src/execution/subagent-service.ts +1106 -907
  244. package/src/execution/subprocess-agent-runner.ts +144 -97
  245. package/src/execution/types.ts +9 -0
  246. package/src/execution/ui-request-handler-factory.ts +126 -75
  247. package/src/execution/ui-request-queue.ts +50 -19
  248. package/src/execution/worktree-git-ops.ts +397 -0
  249. package/src/execution/worktree-manager.ts +72 -10
  250. package/src/execution/worktree-registry.ts +6 -12
  251. package/src/index.ts +542 -9
  252. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  253. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
  254. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  255. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  257. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  258. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
  259. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  260. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  261. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  262. package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
  263. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  264. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  265. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  266. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  267. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  268. package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
  269. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  270. package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
  271. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  272. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  273. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  274. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  275. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  276. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
  277. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  278. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  279. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  280. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  281. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  282. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  283. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  284. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  285. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  286. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  287. package/src/orchestration/agent-opts-resolver.ts +8 -8
  288. package/src/orchestration/args-meta.ts +198 -0
  289. package/src/orchestration/args-validator.ts +53 -31
  290. package/src/orchestration/execute-agent-call.ts +25 -79
  291. package/src/orchestration/file-run-store.ts +327 -0
  292. package/src/orchestration/launcher.ts +160 -104
  293. package/src/orchestration/lifecycle.ts +371 -97
  294. package/src/orchestration/models/agent-call.ts +7 -7
  295. package/src/orchestration/models/budget.ts +5 -5
  296. package/src/orchestration/models/ports.ts +4 -4
  297. package/src/orchestration/models/run-runtime.ts +15 -15
  298. package/src/orchestration/models/trace.ts +9 -9
  299. package/src/orchestration/models/types.ts +111 -22
  300. package/src/orchestration/models/workflow-run.ts +28 -28
  301. package/src/orchestration/models/workflow-script.ts +4 -4
  302. package/src/orchestration/run-snapshot.ts +266 -0
  303. package/src/orchestration/script-generate.ts +154 -0
  304. package/src/orchestration/script-lint.ts +123 -90
  305. package/src/orchestration/worker-handle.ts +10 -10
  306. package/src/orchestration/worker-host.ts +10 -10
  307. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
  308. package/src/orchestration/worker-script-builder.ts +401 -346
  309. package/src/orchestration/workflow-files.ts +37 -11
  310. package/src/orchestration/workflow-run-summary.ts +69 -0
  311. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  312. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  313. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  314. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  315. package/src/shared/__tests__/injection-render.test.ts +518 -0
  316. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  317. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  318. package/src/shared/__tests__/resource-discovery.test.ts +261 -217
  319. package/src/shared/agent-ref.ts +143 -2
  320. package/src/shared/atomic-write.ts +323 -0
  321. package/src/shared/bounded-serialize.ts +154 -0
  322. package/src/shared/injection-render.ts +279 -0
  323. package/src/shared/meta-parser.ts +182 -68
  324. package/src/shared/model-ref.ts +15 -2
  325. package/src/shared/resource-discovery.ts +97 -204
  326. package/src/shared/resource-meta.ts +14 -0
  327. package/src/shared/schema-jsonify.ts +1 -1
  328. package/src/shared/xml-injection.ts +9 -9
  329. package/workflows/README.md +9 -9
  330. package/workflows/chain.js +4 -2
  331. package/workflows/map-reduce.js +5 -3
  332. package/workflows/parallel.js +5 -3
  333. package/workflows/review-fix-loop-utils.cjs +165 -92
  334. package/workflows/review-fix-loop-utils.d.cts +287 -0
  335. package/workflows/review-fix-loop.js +18 -7
  336. package/workflows/scatter-gather.js +4 -2
  337. package/dist/chunk-3VOERJPJ.js +0 -22
  338. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  339. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  340. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  341. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  342. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  343. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  344. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  345. package/src/execution/execute-options-mapper.ts +0 -115
  346. package/src/execution/session-runner.ts +0 -1789
  347. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -1,10 +1,16 @@
1
1
  /**
2
- * Workflow Extension — error-recovery
2
+ * Workflow Extension — worker-message-pump(原 error-recovery,D5-① 更名)
3
3
  *
4
- * Worker 失败处理 free functions(D-12)。
4
+ * Worker 消息泵 + 失败恢复 free functions(D-12)。承载四类职责:
5
+ * 1. 消息路由:handleWorkerMessage 分发 agent-call / workflow-call / return / error
6
+ * 2. IPC 序列化防御:postMessage 的 DataCloneError 拦截 + fallback 回发(W2)
7
+ * 3. retry/重建:worker/script 错误的指数退避重试 + rebuildRuntime(G3-001)
8
+ * 4. 终态化:finalizeRun ——「transition → closeOut in-flight → save →
9
+ * pending:unregister → onRunDone」终态序列的唯一定义点(D5-② 单点化,收敛原
10
+ * 8 处逐字复制;OR-8 收口步骤与 OR-4/B-4 双围栏内化于本函数)
5
11
  *
6
12
  * 4 个导出函数(domain-models.md §失败处理矩阵):
7
- * - handleWorkerMessage(run, raw, deps, handlers) — 路由 agent_call/return/error
13
+ * - handleWorkerMessage(run, raw, deps, handlers) — 路由 agent_call/return/error/log
8
14
  * - handleWorkerError(run, err, deps, handlers) — worker uncaught error
9
15
  * - handleWorkerExit(run, code, handle, deps, handlers) — worker exit
10
16
  * - handleScriptError(run, msg, deps, handlers) — type:"error" from worker
@@ -13,6 +19,11 @@
13
19
  * - worker error/exit(非零)→ 3 次重试 + 指数退避 1s/2s/4s;超限 failed
14
20
  * - script error → 3 次重试 + 指数退避;超限 failed
15
21
  * - 重试前 rebuildRuntime(G3-001:整个 RunRuntime 重建:worker+controller)
22
+ * - [OR-2] 重建动作本身失败(workerHost.start 抛错)回灌本矩阵:计入
23
+ * workerErrorCount,未超限再走退避+重建,超限收敛 done,failed(见
24
+ * scheduleRebuild / handleRebuildStartFailure)——恢复机制不得在它自己的
25
+ * 恢复路径上开口(旧实现裸调 rebuildRuntime → run 永久 running +
26
+ * rejection 经 void 变 unhandledRejection)
16
27
  *
17
28
  * 关键不变式:
18
29
  * - 重试前必须 rebuildRuntime(worker+controller 整体重建,避免孤儿资源)。
@@ -28,7 +39,7 @@
28
39
 
29
40
  import { getLogger } from "../core/logger.ts";
30
41
 
31
- import { SLUG_MAX_LENGTH } from "../execution/execute-options-mapper.ts";
42
+ import { SLUG_MAX_LENGTH } from "./models/types.ts";
32
43
  import { createRecord, updateFromEvent } from "../execution/execution-record.ts";
33
44
  import { SubagentStream } from "../execution/stream-sink.ts";
34
45
  import type { AgentEvent } from "../shared/agent-event.ts";
@@ -38,9 +49,10 @@ import { AgentCall } from "./models/agent-call.ts";
38
49
  import type { LifecycleDeps, WorkerHandlers } from "./models/ports.ts";
39
50
  import { RunRuntime } from "./models/run-runtime.ts";
40
51
  import type { WorkerLogEntry } from "./models/types.ts";
41
- import type { AgentCallOpts, AgentResult, ExecutionTraceNode } from "./models/types.ts";
52
+ import type { AgentCallOpts, AgentResult, DoneReason, ExecutionTraceNode } from "./models/types.ts";
42
53
  import type { WorkflowRun } from "./models/workflow-run.ts";
43
54
  import type { WorkerHandle } from "./worker-handle.ts";
55
+ import { toErrorMessage } from "../core/error-message.ts";
44
56
 
45
57
  const logger = getLogger("subagents");
46
58
 
@@ -66,6 +78,22 @@ const MAX_ERROR_LOGS = 500;
66
78
  /** malformed agent-call 日志中 opts JSON 的预览截断长度(字符)。 */
67
79
  const MALFORMED_MSG_LOG_PREVIEW_CHARS = 200;
68
80
 
81
+ /**
82
+ * [OR-8] run 到达 done 终态时残留 in-flight call 的收口文案。
83
+ *
84
+ * trace/call 状态枚举封闭(无 "cancelled" 态),以 failed + 本固定文案表达
85
+ * 「run 终态前被收口」——GUI/快照侧不再出现 done run 含 running 节点的不一致。
86
+ */
87
+ const IN_FLIGHT_CALL_CANCELLED_MSG =
88
+ "Cancelled: run reached terminal state while this call was in flight";
89
+
90
+ /**
91
+ * [P-SD/S-D] 测试钩子 env(设计 §7.3 P-SD):设为正整数 N 时 rebuildRuntime
92
+ * 第 N 次及以后的每次调用抛错,供 S-D「worker 崩溃后重建失败」验收注入。
93
+ * 安全约束:仅显式设置时激活 + 激活即 warn 留痕(见 resolveRebuildFailureInjectionThreshold)。
94
+ */
95
+ const REBUILD_FAILURE_INJECT_ENV = "XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE";
96
+
69
97
  /**
70
98
  * [F1] worker 交付前退出(无终态消息)的归因文案。
71
99
  *
@@ -114,7 +142,14 @@ interface WorkflowCallMsg {
114
142
  args: Record<string, unknown>;
115
143
  }
116
144
 
117
- type WorkerMsg = AgentCallMsg | WorkflowCallMsg | ReturnMsg | ErrorMsg;
145
+ /** 脚本 log() 全局发出的独立诊断消息(协议见 worker-script-builder 头注释,OR-6)。 */
146
+ interface LogMsg {
147
+ type: "log";
148
+ phase?: string;
149
+ message: string;
150
+ }
151
+
152
+ type WorkerMsg = AgentCallMsg | WorkflowCallMsg | ReturnMsg | ErrorMsg | LogMsg;
118
153
 
119
154
  // ── 内部 helper ──────────────────────────────────────────────
120
155
 
@@ -163,7 +198,7 @@ async function saveRunBestEffort(
163
198
  try {
164
199
  await deps.store.save(run);
165
200
  } catch (err) {
166
- const m = err instanceof Error ? err.message : String(err);
201
+ const m = toErrorMessage(err);
167
202
  logger.error(
168
203
  `[workflow] store.save failed (${context}, runId=${run.runId}): ${m}. ` +
169
204
  "Continuing state-machine finalization (in-memory state already terminal).",
@@ -171,6 +206,101 @@ async function saveRunBestEffort(
171
206
  }
172
207
  }
173
208
 
209
+ // ── finalizeRun(D5-② 终态 coda 单写点) ──────────────────────
210
+
211
+ /** finalizeRun 的可调项。 */
212
+ export interface FinalizeRunOptions {
213
+ /** store.save 失败日志的上下文标记(OB3 排障定位,如 "handleReturn (done,completed)")。 */
214
+ context: string;
215
+ /**
216
+ * 是否调 deps.onRunDone(Interface 层完成通知)。缺省 true。
217
+ * terminateRunningRuns 传 false——session 切换/关闭语境下主 agent 已离开本
218
+ * session,注入完成通知只会把消息发给已离开的 session(对齐 session_start
219
+ * 恢复先例:只发 unregister、不发 onRunDone)。
220
+ */
221
+ notifyDone?: boolean;
222
+ }
223
+
224
+ /**
225
+ * Run 终态五步 coda 的唯一定义点(D5-② + OR-8 收口内化):
226
+ * transition(done) → closeOutInFlightCalls → save(best-effort)→
227
+ * pending:unregister → onRunDone。
228
+ *
229
+ * 收敛前 8 处逐字复制(本文件 6 处 + lifecycle 2 处)已全部改走本函数。原各副本
230
+ * 的三处微差统一为规范形态(收敛裁决,非行为回归):
231
+ * - transition 失败(M12):一律吞掉并中止后续步骤——并发 abort/terminate 抢先
232
+ * 终态化时 illegal-transition 是预期事件,本路径的 unregister/onRunDone 语义已
233
+ * 由抢先方兑现,重复执行只会造成重复注销/重复通知。
234
+ * - save 失败(SW-DATA-3):一律 best-effort——save 抛错若向上抛,handle* 的
235
+ * 调用方(worker-host 绑定处 `void handlers.onXxx(...)`)无人接 →
236
+ * unhandledRejection + pending:unregister / onRunDone 不执行 → pending 通知
237
+ * 幽灵注销(列表残留永不清理的 running 条目)。
238
+ * - unregister reason:`run.state.reason ?? doneReason`——transition 成功后
239
+ * reason 恒有值(不变式 I2),兜底仅防御异常形态,取本路径的 doneReason 语义
240
+ * 最贴近(原各副本 `?? "completed"` / 固定 "failed" / `?? "time_limited"` 三种
241
+ * 死兜底等价收敛)。
242
+ *
243
+ * [OR-8] transition 成功后、save 之前 closeOutInFlightCalls——终态收口残留
244
+ * in-flight call(fire-and-forget agent() 未 await / worker 死亡时已 dispatch 的
245
+ * call),先收口再落盘,内存态与持久化快照同一时点收敛(快照不再含 running 节点)。
246
+ *
247
+ * [OR-4][B-4] unregister 与 onRunDone 各自独立 try 围栏(不共用一个 try——emit
248
+ * 抛错会跳过 onRunDone):这两个是真实副作用,任一同步抛错不得经 worker-host 的
249
+ * `void handlers.onXxx(...)` 变 unhandledRejection 崩宿主;通知总线故障也不得吞掉
250
+ * Interface 层完成回调(runAndWait 轮询依赖 onRunDone 语义收口,被跳过即悬挂)。
251
+ *
252
+ * @returns 是否成功 transition(false = 转移前已被并发终态化,后续步骤未执行)
253
+ */
254
+ export async function finalizeRun(
255
+ run: WorkflowRun,
256
+ deps: LifecycleDeps,
257
+ doneReason: DoneReason,
258
+ options: FinalizeRunOptions,
259
+ ): Promise<boolean> {
260
+ try {
261
+ run.transition("done", doneReason);
262
+ } catch (te: unknown) {
263
+ // M12:并发 abort/terminate 导致 illegal-transition 是预期的,可忽略——
264
+ // 抢先方已兑现 unregister/onRunDone,本路径让位(debug 日志留痕)。
265
+ void te;
266
+ deps.log?.("debug", "workflow:worker-message-pump", "finalize skipped: run already terminal", {
267
+ runId: run.runId,
268
+ doneReason,
269
+ context: options.context,
270
+ });
271
+ return false;
272
+ }
273
+ // [OR-8] 终态收口残留 in-flight call(先收口再落盘——快照不再含 running 节点)
274
+ closeOutInFlightCalls(run);
275
+ await saveRunBestEffort(run, deps, options.context);
276
+ deps.log?.("debug", "workflow:worker-message-pump", "run finalized", {
277
+ runId: run.runId,
278
+ reason: run.state.reason,
279
+ context: options.context,
280
+ });
281
+ // [OR-4] emit pending:unregister 独立围栏(M12 同款)——listener 同步抛错 error
282
+ // 留痕后继续,不崩宿主、不跳过 onRunDone
283
+ try {
284
+ deps.eventBus?.emit("pending:unregister", {
285
+ id: run.runId,
286
+ reason: run.state.reason ?? doneReason,
287
+ });
288
+ } catch (err) {
289
+ const m = toErrorMessage(err);
290
+ logger.error(`[workflow] pending:unregister emit failed (${options.context}): ${m}`);
291
+ }
292
+ // [OR-4][B-4] onRunDone 独立围栏(与 emit 拆分——emit 抛错不得吞掉完成回调)
293
+ if (options.notifyDone !== false) {
294
+ try {
295
+ deps.onRunDone?.(run);
296
+ } catch (err) {
297
+ const m = toErrorMessage(err);
298
+ logger.error(`[workflow] onRunDone failed (${options.context}): ${m}`);
299
+ }
300
+ }
301
+ return true;
302
+ }
303
+
174
304
  function delay(ms: number): Promise<void> {
175
305
  return new Promise((resolve) => {
176
306
  const timer = setTimeout(resolve, ms);
@@ -180,6 +310,59 @@ function delay(ms: number): Promise<void> {
180
310
 
181
311
  // ── rebuildRuntime(G3-001 整重建) ─────────────────────────
182
312
 
313
+ /** [P-SD] rebuildRuntime 进程级调用计数(注入阈值「第 N 次」的判定基准)。 */
314
+ let rebuildRuntimeInvocationCount = 0;
315
+ /** [P-SD] 钩子激活/非法值 warn 是否已发(多轮 rebuild 只留痕一次,防刷屏)。 */
316
+ let rebuildFailureHookWarned = false;
317
+
318
+ /**
319
+ * [P-SD/S-D] 读取重建失败注入阈值:env XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE=<N>
320
+ * 使 rebuildRuntime 第 N 次及以后的每次调用抛错(配合脚本内 process.exit 制造
321
+ * 「worker 崩溃后重建失败」的 S-D 验收场景)。
322
+ *
323
+ * 安全约束(设计 §7.3 P-SD,对齐 T7① 可见性原则):
324
+ * - 仅显式设置时激活;未设置/空串 = 钩子完全不激活(零行为差);
325
+ * - 首次读取到该 env(无论合法非法)即 logger.warn 留痕一次,杜绝静默生效;
326
+ * - 非法值(非正整数)不激活且 warn 指明原值,杜绝「以为注入了、实际没有」的
327
+ * 静默失效(LC-7 同族教训)。
328
+ *
329
+ * 语义取「第 N 次及以后每次」而非「仅第 N 次」:S-D 验收要求 run 收敛 done,failed——
330
+ * 仅注入一次会在重试预算(MAX_WORKER_RETRIES)耗尽前放行后续重建,run 可能正常完成,
331
+ * 验收不可证伪。连续注入让重试矩阵确定性走完:耗尽后经 handleRebuildStartFailure
332
+ * 收敛 done,failed。
333
+ *
334
+ * @returns 注入阈值(调用序数 >= 阈值的 rebuild 抛错);undefined = 未激活
335
+ */
336
+ function resolveRebuildFailureInjectionThreshold(): number | undefined {
337
+ const raw = process.env[REBUILD_FAILURE_INJECT_ENV];
338
+ if (raw === undefined || raw === "") return undefined;
339
+ const parsed = Number(raw);
340
+ if (!Number.isInteger(parsed) || parsed <= 0) {
341
+ if (!rebuildFailureHookWarned) {
342
+ rebuildFailureHookWarned = true;
343
+ logger.warn(
344
+ `[workflow] ${REBUILD_FAILURE_INJECT_ENV}="${raw}" is not a positive integer — ` +
345
+ "test hook INACTIVE, no rebuild failure will be injected",
346
+ );
347
+ }
348
+ return undefined;
349
+ }
350
+ if (!rebuildFailureHookWarned) {
351
+ rebuildFailureHookWarned = true;
352
+ logger.warn(
353
+ `[workflow] ${REBUILD_FAILURE_INJECT_ENV}=${raw} ACTIVE — rebuildRuntime invocations ` +
354
+ `#${parsed} and later will throw (S-D test hook; NEVER set in production)`,
355
+ );
356
+ }
357
+ return parsed;
358
+ }
359
+
360
+ /** 测试辅助:重置注入计数与 warn 状态(仅 __tests__ 导入,生产勿用)。 */
361
+ export function resetRebuildFailureInjectionForTest(): void {
362
+ rebuildRuntimeInvocationCount = 0;
363
+ rebuildFailureHookWarned = false;
364
+ }
365
+
183
366
  /**
184
367
  * 移除 run 中未真正完成的在飞 call(status !== "done")及其 trace 节点。
185
368
  *
@@ -203,6 +386,50 @@ function discardInFlightCalls(run: WorkflowRun): number[] {
203
386
  return inFlight.sort((a, b) => a - b);
204
387
  }
205
388
 
389
+ /**
390
+ * [OR-8] run 到达 done 终态时,把 calls Map 残留的 in-flight call(status !== "done")
391
+ * 收口为取消终态(不删除条目——保留调用痕迹,快照/GUI 不再出现 done run 含
392
+ * running 节点的不一致)。
393
+ *
394
+ * 场景:脚本 fire-and-forget agent()(不 await)后 return;或 worker 死亡/abort 时
395
+ * 已 dispatch 未完成的 call。旧实现 trace 节点永久 "running" 并原样落盘
396
+ * (run-snapshot 序列化不做状态修正)。
397
+ *
398
+ * 收口语义(trace/call 状态枚举封闭,无 "cancelled" 态):
399
+ * - AgentCall 补齐 pending→running→done 状态机(markDone 要求 running 前置),
400
+ * result 以 IN_FLIGHT_CALL_CANCELLED_MSG 承载取消原因;
401
+ * - trace 节点置 failed + 固定取消文案 + completedAt(「failed + Cancelled 文案」
402
+ * 即取消的既有表达形态,不新增状态枚举);
403
+ * - node.live 清除(终态 run 无 TUI 轮询,防 ExecutionRecord 滞留)。
404
+ *
405
+ * 调用点约定:每个 transition("done") 成功后、store.save 之前——先收口再落盘,
406
+ * 内存态与持久化快照在同一时点收敛(「run-snapshot 落盘前」的实现形态)。
407
+ * 返回被收口的 callId 数组(升序)供调用方记日志。
408
+ */
409
+ export function closeOutInFlightCalls(run: WorkflowRun): number[] {
410
+ const inFlight: number[] = [];
411
+ for (const [callId, call] of run.state.calls) {
412
+ if (call.status !== "done") inFlight.push(callId);
413
+ }
414
+ const completedAt = new Date().toISOString();
415
+ for (const callId of inFlight) {
416
+ const call = run.state.calls.get(callId);
417
+ if (!call) continue; // 防御:迭代后被删(正常路径不可达)
418
+ if (call.status === "pending") call.markRunning();
419
+ if (call.status === "running") {
420
+ call.markDone({ content: "", error: IN_FLIGHT_CALL_CANCELLED_MSG });
421
+ }
422
+ call.traceNode.live = undefined;
423
+ run.state.trace.update(callId, {
424
+ status: "failed",
425
+ result: { content: "", error: IN_FLIGHT_CALL_CANCELLED_MSG },
426
+ error: IN_FLIGHT_CALL_CANCELLED_MSG,
427
+ completedAt,
428
+ });
429
+ }
430
+ return inFlight.sort((a, b) => a - b);
431
+ }
432
+
206
433
  /**
207
434
  * 计算 run 的剩余时间预算(ms)[race-F3]。
208
435
  *
@@ -226,30 +453,15 @@ function remainingTimeBudgetMs(run: WorkflowRun): number | undefined {
226
453
  * 重试前发现时间预算已耗尽的收尾:不 rebuild,直接 done,time_limited 终态。
227
454
  *
228
455
  * 副作用与 handleWorkerError 超限路径对齐:transition + 持久化 + 注销
229
- * pending-notification + onRunDone。transition 单独 try(M12)——并发 abort 导致
230
- * illegal-transition 是预期的,可忽略。
456
+ * pending-notification + onRunDone(D5-② 收敛为 finalizeRun 单写点)。
231
457
  */
232
458
  async function finalizeTimeBudgetExhausted(run: WorkflowRun, deps: LifecycleDeps): Promise<void> {
233
- deps.log?.("debug", "workflow:error-recovery", "time budget exhausted on rebuild, transition done", {
459
+ deps.log?.("debug", "workflow:worker-message-pump", "time budget exhausted on rebuild, transition done", {
234
460
  runId: run.runId,
235
461
  budgetTimeMs: run.spec.budgetTimeMs,
236
462
  });
237
463
  run.state.error = run.state.error ?? `Time budget exhausted (${run.spec.budgetTimeMs} ms wall clock) before retry rebuild`;
238
- let transitioned = false;
239
- try {
240
- run.transition("done", "time_limited");
241
- transitioned = true;
242
- } catch (te: unknown) {
243
- // run 可能在检查后、transition 前被并发 abort——预期,不记错
244
- void te;
245
- }
246
- if (!transitioned) return;
247
- await deps.store.save(run).catch((e: unknown) => {
248
- const m = e instanceof Error ? e.message : String(e);
249
- logger.error(`[workflow] store.save failed (time budget exhausted): ${m}`);
250
- });
251
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "time_limited" });
252
- deps.onRunDone?.(run);
464
+ await finalizeRun(run, deps, "time_limited", { context: "time budget exhausted on rebuild" });
253
465
  }
254
466
 
255
467
  /**
@@ -275,58 +487,68 @@ export function rebuildRuntime(
275
487
  deps: LifecycleDeps,
276
488
  handlers: WorkerHandlers,
277
489
  ): void {
278
- // OB3(可观察性):rebuild 关键节点 debug 日志——此前函数体 0 处 deps.log,
279
- // 崩溃自愈只能靠行为证据诊断(L1 入口 / L2 重排 / L3 discard / L4 完成)。
280
- deps.log?.("debug", "workflow:error-recovery", "runtime rebuild start", {
490
+ // [P-SD] 测试钩子注入点:先于任何副作用(模拟 workerHost.start 抛错)。抛错由
491
+ // scheduleRebuild catch 接住回灌重试矩阵([OR-2]),不再裸抛。
492
+ rebuildRuntimeInvocationCount += 1;
493
+ const injectThreshold = resolveRebuildFailureInjectionThreshold();
494
+ if (injectThreshold !== undefined && rebuildRuntimeInvocationCount >= injectThreshold) {
495
+ throw new Error(
496
+ `[S-D test hook] injected rebuildRuntime failure ` +
497
+ `(invocation #${rebuildRuntimeInvocationCount}, ${REBUILD_FAILURE_INJECT_ENV}>=${injectThreshold})`,
498
+ );
499
+ }
500
+ // OB3(可观察性):rebuild 关键节点 debug 日志——此前函数体 0 处 deps.log,
501
+ // 崩溃自愈只能靠行为证据诊断(L1 入口 / L2 重排 / L3 discard / L4 完成)。
502
+ deps.log?.("debug", "workflow:worker-message-pump", "runtime rebuild start", {
281
503
  runId: run.runId,
282
504
  budgetTimeMs: run.spec.budgetTimeMs,
283
505
  });
284
506
  const controller = new AbortController();
285
507
  const worker = deps.workerHost.start(run.spec, run.spec.args, handlers);
286
- // D-12 regression fix (round-2 #2):重新调度 run 级墙钟预算计时器。
287
- // replaceRuntime 释放旧 runtime 时 clearTimeout 了旧计时器(run-runtime.release),
288
- // 新 runtime 必须重排,否则带 budgetTimeMs 的 run 命中一次 worker/script 错误重试后
289
- // 时间预算静默失效(直到 rebuildRuntime 才重排——本函数即唯一重排点)。
290
- // deps.scheduleTimeBudget 由 Interface 层注入;未注入时(旧测试)跳过重排(兼容,
291
- // 不影响无时间预算的 run)。
292
- // 重排分支改为 if——语义与原三元一致(同一条件调 scheduleTimeBudget),仅为在
293
- // 分支内记 L2 日志,控制流/异常语义零变化。
294
- // [race-F3] 重排值改为剩余墙钟(remainingTimeBudgetMs)而非满额——重试不重置预算;
295
- // L2 日志 payload 同步报实际重排值(排障时与 setTimeout 对得上)。remaining > 0
296
- // 由调用方 scheduleRebuild 保证(耗尽在那里转 time_limited,不进本函数);本处
297
- // remaining <= 0 时不挂 timer(防御直调,宁可不挂也不能挂出 0ms 立即触发)。
508
+ // D-12 regression fix (round-2 #2):重新调度 run 级墙钟预算计时器。
509
+ // replaceRuntime 释放旧 runtime 时 clearTimeout 了旧计时器(run-runtime.release),
510
+ // 新 runtime 必须重排,否则带 budgetTimeMs 的 run 命中一次 worker/script 错误重试后
511
+ // 时间预算静默失效(直到 rebuildRuntime 才重排——本函数即唯一重排点)。
512
+ // deps.scheduleTimeBudget 由 Interface 层注入;未注入时(旧测试)跳过重排(兼容,
513
+ // 不影响无时间预算的 run)。
514
+ // 重排分支改为 if——语义与原三元一致(同一条件调 scheduleTimeBudget),仅为在
515
+ // 分支内记 L2 日志,控制流/异常语义零变化。
516
+ // [race-F3] 重排值改为剩余墙钟(remainingTimeBudgetMs)而非满额——重试不重置预算;
517
+ // L2 日志 payload 同步报实际重排值(排障时与 setTimeout 对得上)。remaining > 0
518
+ // 由调用方 scheduleRebuild 保证(耗尽在那里转 time_limited,不进本函数);本处
519
+ // remaining <= 0 时不挂 timer(防御直调,宁可不挂也不能挂出 0ms 立即触发)。
298
520
  let timeBudgetTimer: ReturnType<typeof setTimeout> | undefined;
299
521
  const remainingBudgetMs = remainingTimeBudgetMs(run);
300
522
  if (remainingBudgetMs !== undefined && remainingBudgetMs > 0 && deps.scheduleTimeBudget) {
301
523
  timeBudgetTimer = deps.scheduleTimeBudget(run.runId, remainingBudgetMs);
302
- deps.log?.("debug", "workflow:error-recovery", "time budget rescheduled", {
524
+ deps.log?.("debug", "workflow:worker-message-pump", "time budget rescheduled", {
303
525
  runId: run.runId,
304
526
  budgetTimeMs: remainingBudgetMs,
305
527
  });
306
528
  }
307
529
  run.replaceRuntime(new RunRuntime(worker, controller, timeBudgetTimer));
308
- // 清除被旧 runtime abort 的在飞 call——必须在 replaceRuntime 之后同步执行(无
309
- // await 间隔):replaceRuntime 同步 abort 旧 controller + terminate 旧 worker,
310
- // 在飞 executeAgentCall 的 finalize 发生在 `await runner.run` resolve 后的
311
- // microtask,此刻在飞 call 仍为 "running"/"pending"(status !== "done")可精确
312
- // 清理;genuinely-done 的 call 保留(重跑 replay)。放 delay 退避之前会误删退避
313
- // 期间自然完成的真结果(重跑重复耗 token);放任何 await 之后,假失败已 finalize
314
- // 为 "done" 挡不住——重跑 replay 会把 abort 错误当真结果回放,静默污染输出。
315
- // 注意:discard 只清 Map/trace 条目,旧 executeAgentCall 的 promise 链仍会醒来
316
- // finalize。markDone 在孤儿实例上无害,但后续投递并非 no-op——postAgentResult
317
- // 会投给 run.runtime(已是新 worker)的同 callId pending,劫持重跑调用(实测
318
- // S7-second 竞态:旧失败结果被 worker 侧 resolve 为空串 → 脚本假成功);
319
- // finalizeCall 的 trace.update 在重跑已 append 同 stepIndex 新节点时命中新节点
320
- // (瞬时污染,由重跑完成时的 update 覆盖)。该投递由 dispatchAgentCall 的
321
- // 孤儿守卫(isOrphanedCall)拦截,trace.update 的瞬时污染由 executeAgentCall
322
- // 的 isOrphaned 谓词(OB2)拦截,此处不重复设防。
530
+ // 清除被旧 runtime abort 的在飞 call——必须在 replaceRuntime 之后同步执行(无
531
+ // await 间隔):replaceRuntime 同步 abort 旧 controller + terminate 旧 worker,
532
+ // 在飞 executeAgentCall 的 finalize 发生在 `await runner.run` resolve 后的
533
+ // microtask,此刻在飞 call 仍为 "running"/"pending"(status !== "done")可精确
534
+ // 清理;genuinely-done 的 call 保留(重跑 replay)。放 delay 退避之前会误删退避
535
+ // 期间自然完成的真结果(重跑重复耗 token);放任何 await 之后,假失败已 finalize
536
+ // 为 "done" 挡不住——重跑 replay 会把 abort 错误当真结果回放,静默污染输出。
537
+ // 注意:discard 只清 Map/trace 条目,旧 executeAgentCall 的 promise 链仍会醒来
538
+ // finalize。markDone 在孤儿实例上无害,但后续投递并非 no-op——postAgentResult
539
+ // 会投给 run.runtime(已是新 worker)的同 callId pending,劫持重跑调用(实测
540
+ // S7-second 竞态:旧失败结果被 worker 侧 resolve 为空串 → 脚本假成功);
541
+ // finalizeCall 的 trace.update 在重跑已 append 同 stepIndex 新节点时命中新节点
542
+ // (瞬时污染,由重跑完成时的 update 覆盖)。该投递由 dispatchAgentCall 的
543
+ // 孤儿守卫(isOrphanedCall)拦截,trace.update 的瞬时污染由 executeAgentCall
544
+ // 的 isOrphaned 谓词(OB2)拦截,此处不重复设防。
323
545
  const discardedCallIds = discardInFlightCalls(run);
324
- deps.log?.("debug", "workflow:error-recovery", "in-flight calls discarded", {
546
+ deps.log?.("debug", "workflow:worker-message-pump", "in-flight calls discarded", {
325
547
  runId: run.runId,
326
548
  callIds: discardedCallIds,
327
549
  count: discardedCallIds.length,
328
550
  });
329
- deps.log?.("debug", "workflow:error-recovery", "runtime rebuild complete", {
551
+ deps.log?.("debug", "workflow:worker-message-pump", "runtime rebuild complete", {
330
552
  runId: run.runId,
331
553
  });
332
554
  }
@@ -339,6 +561,11 @@ export function rebuildRuntime(
339
561
  * agent_call → 派发 executeAgentCall(异步,不 await——立即返回让 worker 继续发消息)
340
562
  * return → transition done,completed(脚本正常返回)
341
563
  * error → handleScriptError(脚本主动抛错)
564
+ * log → 计入 run.state.errorLogs + debug 留痕([OR-6/T7④] 主线程半边——worker 侧
565
+ * log() 双通路的独立消息面消费点;u-m0a 已接 workerLogs 随 return/error 带回,
566
+ * 本 case 消费独立 {type:"log"} 消息,与 worker-script-builder 协议注释对齐)
567
+ * default → warn 留痕后丢弃([OR-6/T7④] 协议漂移防线——协议文档与实现漂移零
568
+ * 可观测的反面;未知类型静默丢弃会让协议单方面演化不可发现)
342
569
  *
343
570
  * 终态(done)下的 stale 消息丢弃(P0-1)。
344
571
  */
@@ -348,10 +575,10 @@ export async function handleWorkerMessage(
348
575
  deps: LifecycleDeps,
349
576
  handlers: WorkerHandlers,
350
577
  ): Promise<void> {
351
- // 终态(done)丢弃 stale 消息(P0-1)
578
+ // 终态(done)丢弃 stale 消息(P0-1)
352
579
  if (isTerminal(run)) return;
353
580
 
354
- // M7: 形状校验——防畸形 IPC 消息(worker 崩溃/发非对象)导致下游 TypeError
581
+ // M7: 形状校验——防畸形 IPC 消息(worker 崩溃/发非对象)导致下游 TypeError
355
582
  if (typeof raw !== "object" || raw === null) return;
356
583
  const msg = raw as WorkerMsg;
357
584
  switch (msg.type) {
@@ -369,7 +596,7 @@ export async function handleWorkerMessage(
369
596
  await handleReturn(run, msg, deps);
370
597
  return;
371
598
  case "error":
372
- // M1: 传 handlers(rebuildRuntime 需要)
599
+ // M1: 传 handlers(rebuildRuntime 需要)
373
600
  // [F1] 同 return——error 也是终态消息,标记本代际已交付(同上防误判)。
374
601
  if (run.runtime) run.runtime.receivedTerminalMessage = true;
375
602
  await handleScriptError(
@@ -380,9 +607,46 @@ export async function handleWorkerMessage(
380
607
  handlers,
381
608
  );
382
609
  return;
610
+ case "log":
611
+ handleWorkerLog(run, msg, deps);
612
+ return;
613
+ default:
614
+ // [OR-6/T7④] 协议漂移防线:未知消息类型 warn 留痕后丢弃。畸形消息(M7 形状
615
+ // 校验之上、已知类型之外的 type)此前静默穿过 switch——协议注释新增消息类型
616
+ // 而主线程未接线时,这里提供可观测信号(而非零痕迹丢弃)。
617
+ logger.warn(
618
+ `[workflow] unknown worker message type dropped (runId=${run.runId}): ` +
619
+ `${JSON.stringify((msg as { type?: unknown }).type)}`,
620
+ );
621
+ deps.log?.("warn", "workflow:worker-message-pump", "unknown worker message type", {
622
+ runId: run.runId,
623
+ type: (msg as { type?: unknown }).type,
624
+ });
625
+ return;
383
626
  }
384
627
  }
385
628
 
629
+ /**
630
+ * 消费 worker 的独立 log 消息([OR-6/T7④] 主线程半边)。
631
+ *
632
+ * 计入 run.state.errorLogs(与 workerLogs 通路的 L9 追加/上限语义一致——该容器
633
+ * 本就承载全级别 worker 日志,"log" 级条目已在其中)+ deps.log debug 留痕
634
+ * (含 phase,协议字段不落 WorkerLogEntry 但排查时可见)。
635
+ * 终态守卫(isTerminal)已由 handleWorkerMessage 前置——此处只管写入。
636
+ */
637
+ function handleWorkerLog(run: WorkflowRun, msg: LogMsg, deps: LifecycleDeps): void {
638
+ const message = typeof msg.message === "string" ? msg.message : String(msg.message);
639
+ run.state.errorLogs.push({ level: "log", message });
640
+ if (run.state.errorLogs.length > MAX_ERROR_LOGS) {
641
+ run.state.errorLogs = run.state.errorLogs.slice(-MAX_ERROR_LOGS);
642
+ }
643
+ deps.log?.("debug", "workflow:worker-message-pump", "worker log", {
644
+ runId: run.runId,
645
+ phase: msg.phase,
646
+ message,
647
+ });
648
+ }
649
+
386
650
  /**
387
651
  * 派发 agent 调用:构建 AgentCall + trace 节点,异步触发 executeAgentCall。
388
652
  *
@@ -403,6 +667,16 @@ export async function handleWorkerMessage(
403
667
  * completion 不投递——rebuild 不改 status,第 1 层拦不住跨 runtime 代际的迟到
404
668
  * 结果(S7-second 竞态:旧失败结果投给新 worker 劫持重跑 pending → 假成功)。
405
669
  */
670
+ /**
671
+ * M4: agent-call 消息 IPC 字段校验谓词——畸形(opts 非对象/缺失、callId 非数字、
672
+ * prompt 缺失)= true。提取为谓词保持 dispatchAgentCall 主流程可读(圈复杂度门禁)。
673
+ */
674
+ function isMalformedAgentCallMsg(msg: AgentCallMsg): boolean {
675
+ return typeof msg.callId !== "number" || !Number.isFinite(msg.callId) ||
676
+ typeof msg.opts !== "object" || msg.opts === null ||
677
+ typeof msg.opts.prompt !== "string";
678
+ }
679
+
406
680
  function dispatchAgentCall(
407
681
  run: WorkflowRun,
408
682
  msg: AgentCallMsg,
@@ -411,14 +685,12 @@ function dispatchAgentCall(
411
685
  // M4: IPC 字段校验——畸形 agent-call 消息(opts 非对象/缺失、callId 非数字、prompt 缺失)
412
686
  // 不写 trace / 不 postAgentResult——这类消息通常意味着 worker 模块版本不匹配或内存损坏,
413
687
  // 回发结果给 worker 也没意义(worker 可能已崩)。仅记日志,让 worker timeout/exit 路径接管。
414
- if (typeof msg.callId !== "number" || !Number.isFinite(msg.callId) ||
415
- typeof msg.opts !== "object" || msg.opts === null ||
416
- typeof msg.opts.prompt !== "string") {
688
+ if (isMalformedAgentCallMsg(msg)) {
417
689
  logger.error(`[workflow] malformed agent-call message: callId=${JSON.stringify(msg.callId)}, opts=${JSON.stringify(msg.opts)?.slice(0, MALFORMED_MSG_LOG_PREVIEW_CHARS)}`);
418
690
  return;
419
691
  }
420
692
 
421
- // 已缓存的调用直接 replay(跨 rebuild——崩溃重建后重跑脚本,已完成调用按 callId 命中缓存)
693
+ // 已缓存的调用直接 replay(跨 rebuild——崩溃重建后重跑脚本,已完成调用按 callId 命中缓存)
422
694
  const cached = run.state.calls.get(msg.callId);
423
695
  if (cached && cached.status === "done") {
424
696
  postAgentResult(run, msg.callId, cached.result!, true);
@@ -430,12 +702,14 @@ function dispatchAgentCall(
430
702
  // slug 复用 agentName(超长截断),live record 的 slug 仅用于 TUI 展示。
431
703
  const liveSlug = agentName.length > SLUG_MAX_LENGTH ? agentName.slice(0, SLUG_MAX_LENGTH) : agentName;
432
704
  const now = new Date().toISOString();
705
+ // 未显式指定 model 的展示口径(live record 与 trace node 共用,两处一致)。
706
+ const model = msg.opts.model ?? "default";
433
707
  // live record:收口 agent 执行过程中的 text/thinking/toolCalls/usage,
434
708
  // 供 TUI 在 agent 运行期间显示进度(getEventLog/getCurrentActivity)。
435
709
  // 完成时由下方 .then 清除(终态由 node.result 承载)。
436
710
  const liveRecord = createRecord(String(msg.callId), {
437
711
  agent: agentName,
438
- model: msg.opts.model ?? "default",
712
+ model,
439
713
  mode: "background",
440
714
  task: msg.opts.prompt,
441
715
  slug: liveSlug,
@@ -445,7 +719,7 @@ function dispatchAgentCall(
445
719
  stepIndex: msg.callId,
446
720
  agent: agentName,
447
721
  task: msg.opts.prompt,
448
- model: msg.opts.model ?? "default",
722
+ model,
449
723
  status: "running" as const,
450
724
  phase: msg.phase,
451
725
  startedAt: now,
@@ -453,8 +727,8 @@ function dispatchAgentCall(
453
727
  };
454
728
  run.state.trace.append(node);
455
729
 
456
- // 构建 AgentCall(opts 形状对齐 AgentCallOpts;schema: unknown → Record)
457
- // 跨进程 IPC 边界的 schema 为 unknown,窄化前加 typeof guard 兜底。
730
+ // 构建 AgentCall(opts 形状对齐 AgentCallOpts;schema: unknown → Record)
731
+ // 跨进程 IPC 边界的 schema 为 unknown,窄化前加 typeof guard 兜底。
458
732
  const rawSchema = msg.opts.schema;
459
733
  const opts: AgentCallOpts = {
460
734
  ...msg.opts,
@@ -464,11 +738,11 @@ function dispatchAgentCall(
464
738
  : undefined,
465
739
  };
466
740
 
467
- // BL-1:解析 skill/schema → skillPath / schemaEnv / appendSystemPrompt。
468
- // M2 修正后 resolveAgentOpts 单参数,只处理 schema SO 指令(内容直传)+ skill。
469
- // agent ref 处理(systemPrompt/model/thinkingLevel)交 resolveIdentity(经
470
- // getAgentConfig + resolveModel 完整覆盖),消除双重注入与 model 层级混乱。
471
- // 解析失败(skill 未找到)走 error 路径,不发 slot、不 spawn。
741
+ // BL-1:解析 skill/schema → skillPath / schemaEnv / appendSystemPrompt。
742
+ // M2 修正后 resolveAgentOpts 单参数,只处理 schema SO 指令(内容直传)+ skill。
743
+ // agent ref 处理(systemPrompt/model/thinkingLevel)交 resolveIdentity(经
744
+ // getAgentConfig + resolveModel 完整覆盖),消除双重注入与 model 层级混乱。
745
+ // 解析失败(skill 未找到)走 error 路径,不发 slot、不 spawn。
472
746
  const resolved = resolveAgentOpts(opts);
473
747
  if (resolved.error) {
474
748
  const call = new AgentCall(msg.callId, opts, node);
@@ -485,7 +759,7 @@ function dispatchAgentCall(
485
759
  });
486
760
  postAgentResult(run, msg.callId, errorResult, false);
487
761
  deps.store.save(run).catch((e: unknown) => {
488
- logger.error(`[workflow] store.save failed (resolveAgentOpts): ${e instanceof Error ? e.message : String(e)}`);
762
+ logger.error(`[workflow] store.save failed (resolveAgentOpts): ${toErrorMessage(e)}`);
489
763
  });
490
764
  return;
491
765
  }
@@ -493,14 +767,14 @@ function dispatchAgentCall(
493
767
  const call = new AgentCall(msg.callId, resolved.opts, node);
494
768
  run.state.calls.set(msg.callId, call);
495
769
 
496
- // C-3:agent call 执行入口。
497
- // (原经 gate.withSlot 包装,并发门闩 gate 已删——no-op 抽象,实际并发由
498
- // SubagentService ConcurrencyPool 管理;仅保留其 pre-abort 检查语义,见下方
499
- // dispatchCall 内 signal.aborted 分支。)executeAgentCall 管 retry/budget/stale-context;
500
- // runner(runner.run)管 spawn pi 子进程。
501
- // assignRuntime/replaceRuntime 保证 status==="running" ⟺ runtime defined,
502
- // 故 run.runtime 在此必存在(dispatchAgentCall 仅从 handleWorkerMessage 调用,
503
- // 后者已守 terminal(isTerminal)早期 return)。fallback new AbortController 已移除。
770
+ // C-3:agent call 执行入口。
771
+ // (原经 gate.withSlot 包装,并发门闩 gate 已删——no-op 抽象,实际并发由
772
+ // SubagentService ConcurrencyPool 管理;仅保留其 pre-abort 检查语义,见下方
773
+ // dispatchCall 内 signal.aborted 分支。)executeAgentCall 管 retry/budget/stale-context;
774
+ // runner(runner.run)管 spawn pi 子进程。
775
+ // assignRuntime/replaceRuntime 保证 status==="running" ⟺ runtime defined,
776
+ // 故 run.runtime 在此必存在(dispatchAgentCall 仅从 handleWorkerMessage 调用,
777
+ // 后者已守 terminal(isTerminal)早期 return)。fallback new AbortController 已移除。
504
778
  const runtime = run.runtime!;
505
779
  const signal = runtime.controller.signal;
506
780
  // D-005: onEvent 签名升级——executeAndAwait 直接出 AgentEvent(强类型,
@@ -516,8 +790,8 @@ function dispatchAgentCall(
516
790
  const stream = deps.streamSink
517
791
  ? new SubagentStream(`${run.runId}-${msg.callId}`, deps.streamSink)
518
792
  : undefined;
519
- // 原 gate.withSlot(fn, signal) 语义内联:pre-aborted 时 reject AbortError(
520
- // 下方 .catch 依赖此约定不记错),否则直接执行——并发调度归 ConcurrencyPool。
793
+ // 原 gate.withSlot(fn, signal) 语义内联:pre-aborted 时 reject AbortError(
794
+ // 下方 .catch 依赖此约定不记错),否则直接执行——并发调度归 ConcurrencyPool。
521
795
  const dispatchCall = async (): Promise<void> => {
522
796
  if (signal.aborted) {
523
797
  const abortErr = new Error("Operation aborted before start");
@@ -525,9 +799,9 @@ function dispatchAgentCall(
525
799
  throw abortErr;
526
800
  }
527
801
  try {
528
- // OB2(S7 残留):isOrphaned 谓词注入——旧代际 finalize 在 trace.update 前被
529
- // 拦截(判定语义与下方 .then/.catch 守卫同一 isOrphanedCall,详见
530
- // execute-agent-call.ts finalizeCall 文档注释)。
802
+ // OB2(S7 残留):isOrphaned 谓词注入——旧代际 finalize 在 trace.update 前被
803
+ // 拦截(判定语义与下方 .then/.catch 守卫同一 isOrphanedCall,详见
804
+ // execute-agent-call.ts finalizeCall 文档注释)。
531
805
  await executeAgentCall(call, deps.runner, run.state.budget, signal, run.state.trace, onEvent, stream, () => isOrphanedCall(run, msg.callId, call));
532
806
  } finally {
533
807
  stream?.dispose();
@@ -535,97 +809,72 @@ function dispatchAgentCall(
535
809
  };
536
810
  void dispatchCall()
537
811
  .then(() => {
538
- // 清除 live record:终态已由 executeAgentCall → finalizeCall 写入 node.result,
539
- // live 不再需要(且含可变状态,不保留)。无论 stale 与否都清,避免内存泄漏。
540
- // M4: 必须在 stale guard 之前清,否则跨 rebuild 的迟到 completion 会累积 live record。
812
+ // 清除 live record:终态已由 executeAgentCall → finalizeCall 写入 node.result,
813
+ // live 不再需要(且含可变状态,不保留)。无论 stale 与否都清,避免内存泄漏。
814
+ // M4: 必须在 stale guard 之前清,否则跨 rebuild 的迟到 completion 会累积 live record。
541
815
  node.live = undefined;
542
- // run 终止(终态)后到达的 stale completion 不写 state
816
+ // run 终止(终态)后到达的 stale completion 不写 state
543
817
  if (run.state.status !== "running") return;
544
- // 孤儿 call 守卫(S7-second 竞态):rebuild 的 discardInFlightCalls 已移除本
545
- // call、或重跑 dispatch 已用新实例替换同 callId 条目时,本 completion 属于旧
546
- // runtime 代际。postAgentResult 的投递目标是 run.runtime(已是新 worker),
547
- // 迟到结果会劫持新 worker 内重跑 agent() 的 pending Promise——跳过投递 /
548
- // budget 同步 / 持久化,仅留日志。executeAgentCall 内 finalizeCall 的
549
- // trace.update 若已命中重跑新节点(瞬时污染),由重跑完成时的 update 覆盖。
818
+ // 孤儿 call 守卫(S7-second 竞态):rebuild 的 discardInFlightCalls 已移除本
819
+ // call、或重跑 dispatch 已用新实例替换同 callId 条目时,本 completion 属于旧
820
+ // runtime 代际。postAgentResult 的投递目标是 run.runtime(已是新 worker),
821
+ // 迟到结果会劫持新 worker 内重跑 agent() 的 pending Promise——跳过投递 /
822
+ // budget 同步 / 持久化,仅留日志。executeAgentCall 内 finalizeCall 的
823
+ // trace.update 若已命中重跑新节点(瞬时污染),由重跑完成时的 update 覆盖。
550
824
  if (isOrphanedCall(run, msg.callId, call)) {
551
- deps.log?.("debug", "workflow:error-recovery", "orphan agent call completion dropped", { runId: run.runId, callId: msg.callId });
825
+ deps.log?.("debug", "workflow:worker-message-pump", "orphan agent call completion dropped", { runId: run.runId, callId: msg.callId });
552
826
  return;
553
827
  }
554
828
  if (call.result) postAgentResult(run, msg.callId, call.result, false);
555
- // D-12 regression fix (round-2 #1):executeAgentCall 内 consume/incrementCallCount
556
- // 后同步 worker $BUDGET(否则 $BUDGET.spent()/remaining() 恒为 0)
829
+ // D-12 regression fix (round-2 #1):executeAgentCall 内 consume/incrementCallCount
830
+ // 后同步 worker $BUDGET(否则 $BUDGET.spent()/remaining() 恒为 0)
557
831
  postBudgetUpdate(run);
558
832
  deps.store.save(run).catch((e: unknown) => {
559
- const m = e instanceof Error ? e.message : String(e);
833
+ const m = toErrorMessage(e);
560
834
  logger.error(`[workflow] store.save failed (agent call ${msg.callId}): ${m}`);
561
835
  });
562
836
 
563
- // C-2:budget 超限 → 终止整个 run(避免继续 spawn 烧预算)
564
- // 内联 terminate(不调 lifecycle.abortRun 避免 engine 内循环依赖):
565
- // 若 run 仍非终态,transition done,budget_limited + 持久化。
566
- // 上方 status !== "running" 已保证此处非 done(且 transition 内含 done no-op 守卫)。
837
+ // C-2:budget 超限 → 终止整个 run(避免继续 spawn 烧预算)
838
+ // 内联 terminate(不调 lifecycle.abortRun 避免 engine 内循环依赖):
839
+ // 若 run 仍非终态,transition done,budget_limited + 持久化。
840
+ // 上方 status !== "running" 已保证此处非 done(且 finalizeRun 内含 done 让位守卫)。
567
841
  if (run.state.budget.isExceeded()) {
568
842
  run.state.error = run.state.error ?? "Budget exceeded";
569
- deps.log?.("debug", "workflow:error-recovery", "budget exceeded, transition done", { runId: run.runId });
570
- // M12: transition 单独 try——并发 abort 导致 illegal-transition 是预期的,可忽略
571
- let transitioned = false;
572
- try {
573
- run.transition("done", "budget_limited");
574
- transitioned = true;
575
- } catch (te: unknown) {
576
- // run 可能在 budget 检查后、transition 前被并发 abort——预期,不记错
577
- void te;
578
- }
579
- if (transitioned) {
580
- deps.store.save(run).catch((e: unknown) => {
581
- const m = e instanceof Error ? e.message : String(e);
582
- logger.error(`[workflow] store.save failed (budget done): ${m}`);
583
- });
584
- deps.log?.("debug", "workflow:error-recovery", "run saved after budget done", { runId: run.runId, reason: run.state.reason });
585
- // M12: onRunDone/emit 单独 try——这些是真实副作用,错误不应被静默吞掉
586
- try {
587
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
588
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
589
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
590
- deps.onRunDone?.(run);
591
- } catch (err) {
592
- const m = err instanceof Error ? err.message : String(err);
593
- logger.error(`[workflow] onRunDone/emit failed (budget done): ${m}`);
594
- }
595
- }
843
+ deps.log?.("debug", "workflow:worker-message-pump", "budget exceeded, transition done", { runId: run.runId });
844
+ void finalizeRun(run, deps, "budget_limited", { context: "agent call budget done" });
596
845
  }
597
846
  })
598
847
  .catch((err: unknown) => {
599
- // pre-abort 检查(原 gate.withSlot 语义)在 dispatchCall 入口 reject AbortError——预期,不记错。
848
+ // pre-abort 检查(原 gate.withSlot 语义)在 dispatchCall 入口 reject AbortError——预期,不记错。
600
849
  if (err instanceof Error && err.name === "AbortError") return;
601
- const message = err instanceof Error ? err.message : String(err);
850
+ const message = toErrorMessage(err);
602
851
  logger.error(`[workflow] agent call ${msg.callId} failed: ${message}`);
603
- // 兜底回发:executeAgentCall 抛非 Abort 异常时(如 runner undefined 的 TypeError、
604
- // dispatchCall 内部 bug)原 catch 仅 console.error,worker 内对 callId 的 pending
605
- // Promise 永不 resolve → agent() 永久 await → worker 脚本挂死。构造 failed AgentResult
606
- //(与 resolveAgentOpts 失败路径 L262-275 一致的模式)postAgentResult 回 worker,
607
- // 让 pending Promise resolve(结果为 error),脚本可继续或失败退出。
608
- // 孤儿 call 守卫(与 .then 对称,S7-second 竞态):rebuild 后本 call 已被 discard
609
- // 移除/替换——markDone 虽在孤儿实例上无害,但 trace.update 会污染重跑新建的同
610
- // stepIndex 节点、postAgentResult 会劫持新 worker 的同 callId pending。孤儿时只
611
- // 留日志,全部跳过。node.live 无条件先清(旧节点已脱离 trace,防御性统一)。
852
+ // 兜底回发:executeAgentCall 抛非 Abort 异常时(如 runner undefined 的 TypeError、
853
+ // dispatchCall 内部 bug)原 catch 仅 console.error,worker 内对 callId 的 pending
854
+ // Promise 永不 resolve → agent() 永久 await → worker 脚本挂死。构造 failed AgentResult
855
+ //(与 resolveAgentOpts 失败路径 L262-275 一致的模式)postAgentResult 回 worker,
856
+ // 让 pending Promise resolve(结果为 error),脚本可继续或失败退出。
857
+ // 孤儿 call 守卫(与 .then 对称,S7-second 竞态):rebuild 后本 call 已被 discard
858
+ // 移除/替换——markDone 虽在孤儿实例上无害,但 trace.update 会污染重跑新建的同
859
+ // stepIndex 节点、postAgentResult 会劫持新 worker 的同 callId pending。孤儿时只
860
+ // 留日志,全部跳过。node.live 无条件先清(旧节点已脱离 trace,防御性统一)。
612
861
  node.live = undefined;
613
862
  if (isOrphanedCall(run, msg.callId, call)) {
614
- deps.log?.("debug", "workflow:error-recovery", "orphan agent call failure dropped", { runId: run.runId, callId: msg.callId });
863
+ deps.log?.("debug", "workflow:worker-message-pump", "orphan agent call failure dropped", { runId: run.runId, callId: msg.callId });
615
864
  return;
616
865
  }
617
866
  const errorResult: AgentResult = { content: "", error: message };
618
- // call 已 done(executeAgentCall 内 finalizeCall 已 markDone)时跳过,避免重复 markDone。
619
- // status 理论上必为 running(executeAgentCall L130 markRunning 先于 reject),pending
620
- // 分支为防御性保护。非 running/done 意外态:跳过 markDone(markDone 要求 running)。
867
+ // call 已 done(executeAgentCall 内 finalizeCall 已 markDone)时跳过,避免重复 markDone。
868
+ // status 理论上必为 running(executeAgentCall L130 markRunning 先于 reject),pending
869
+ // 分支为防御性保护。非 running/done 意外态:跳过 markDone(markDone 要求 running)。
621
870
  if (call.status !== "done") {
622
871
  if (call.status === "pending") call.markRunning();
623
872
  call.markDone(errorResult);
624
873
  }
625
- // state 一致性三件套(与 resolveAgentOpts 失败 L268-276 / .then L319-325 对等):
626
- // trace 标 failed + 清 live record(防泄漏)+ 持久化(catch 恰是最需留证的场景)。
627
- // stale 终态(run 已 done)时 run.runtime 为 undefined,postAgentResult 用
628
- // optional chaining 跳过 worker 回发;trace/state 写入仍执行(无害,终态快照已存)。
874
+ // state 一致性三件套(与 resolveAgentOpts 失败 L268-276 / .then L319-325 对等):
875
+ // trace 标 failed + 清 live record(防泄漏)+ 持久化(catch 恰是最需留证的场景)。
876
+ // stale 终态(run 已 done)时 run.runtime 为 undefined,postAgentResult 用
877
+ // optional chaining 跳过 worker 回发;trace/state 写入仍执行(无害,终态快照已存)。
629
878
  run.state.trace.update(msg.callId, {
630
879
  status: "failed",
631
880
  result: errorResult,
@@ -635,7 +884,7 @@ function dispatchAgentCall(
635
884
  // S2: 与 .then 对称——catch 路径也同步 worker $BUDGET(幂等)
636
885
  postBudgetUpdate(run);
637
886
  deps.store.save(run).catch((e: unknown) => {
638
- logger.error(`[workflow] store.save failed (catch fallback): ${e instanceof Error ? e.message : String(e)}`);
887
+ logger.error(`[workflow] store.save failed (catch fallback): ${toErrorMessage(e)}`);
639
888
  });
640
889
  });
641
890
  }
@@ -646,7 +895,7 @@ function dispatchAgentCall(
646
895
  * postResult(workflow-call)与 postAgentResult(agent-call)各自前缀不同,故 prefix 参数化,
647
896
  * 共享返回类型与构造逻辑,避免字面量重复导致形状漂移。
648
897
  *
649
- * W2 防御关键纯函数——export 供独立单测(error-recovery-serialize-failed-result.test.ts)验证
898
+ * W2 防御关键纯函数——export 供独立单测(worker-message-pump-serialize-failed-result.test.ts)验证
650
899
  * 返回 shape `{content:"", error:"<prefix>: <errMsg>"}`,确保两条 fallback 路径(workflow-call /
651
900
  * agent-call)共享同一构造逻辑不漂移。
652
901
  */
@@ -690,7 +939,7 @@ function dispatchWorkflowCall(
690
939
  result,
691
940
  });
692
941
  } catch (err) {
693
- const errMsg = err instanceof Error ? err.message : String(err);
942
+ const errMsg = toErrorMessage(err);
694
943
  logger.error(`[workflow] postResult (workflow-call callId=${msg.callId}) failed: ${errMsg}. Sending error fallback.`);
695
944
  // 回发纯字符串 fallback result(必可克隆),让 worker pending resolve
696
945
  try {
@@ -720,7 +969,7 @@ function dispatchWorkflowCall(
720
969
  .catch((err: unknown) => {
721
970
  postResult({
722
971
  content: "",
723
- error: err instanceof Error ? err.message : String(err),
972
+ error: toErrorMessage(err),
724
973
  });
725
974
  });
726
975
  }
@@ -743,7 +992,7 @@ function postAgentResult(
743
992
  try {
744
993
  run.runtime?.worker.postMessage({ type: "agent-result", callId, result, cached });
745
994
  } catch (err) {
746
- const msg = err instanceof Error ? err.message : String(err);
995
+ const msg = toErrorMessage(err);
747
996
  logger.error(`[workflow] postAgentResult failed (callId=${callId}): ${msg}. Result likely contains non-cloneable value.`);
748
997
  // 回发纯字符串 fallback result(必可克隆),让 worker pending resolve(避免永久挂起)
749
998
  try {
@@ -766,7 +1015,7 @@ function postAgentResult(
766
1015
  * 更新 spent()/remaining())。每次 agent 调用消费 usage 后发送,保持 worker 内 $BUDGET
767
1016
  * 与主线程 Budget 值对象同步。
768
1017
  *
769
- * D-12 regression fix (round-2 #1):重建 budget-update 发送方。被 error-recovery 主路径调用
1018
+ * D-12 regression fix (round-2 #1):重建 budget-update 发送方。被 worker-message-pump 主路径调用
770
1019
  * (dispatch 后同步 worker $BUDGET)——单一实现,避免消息形状漂移。
771
1020
  */
772
1021
  export function postBudgetUpdate(run: WorkflowRun): void {
@@ -779,7 +1028,7 @@ export function postBudgetUpdate(run: WorkflowRun): void {
779
1028
  },
780
1029
  });
781
1030
  } catch (err) {
782
- const msg = err instanceof Error ? err.message : String(err);
1031
+ const msg = toErrorMessage(err);
783
1032
  // budget 是纯 number 不太可能失败,但防御性兜底——budget 同步非关键(worker 仍可
784
1033
  // 基于 $BUDGET.spent() 自行累计),失败仅记日志,不中断调用方流程。
785
1034
  logger.error(`[workflow] postBudgetUpdate failed: ${msg}. Budget sync to worker skipped (non-critical).`);
@@ -794,8 +1043,8 @@ async function handleReturn(
794
1043
  msg: ReturnMsg,
795
1044
  deps: LifecycleDeps,
796
1045
  ): Promise<void> {
797
- deps.log?.("debug", "workflow:error-recovery", "handleReturn", { runId: run.runId, status: run.state.status });
798
- // 捕获 worker 诊断日志(P2-2)
1046
+ deps.log?.("debug", "workflow:worker-message-pump", "handleReturn", { runId: run.runId, status: run.state.status });
1047
+ // 捕获 worker 诊断日志(P2-2)
799
1048
  // L9: 追加而非覆盖——保留重试历史的诊断日志(各 worker 实例的 console 输出)
800
1049
  if (msg.workerLogs && msg.workerLogs.length > 0) {
801
1050
  run.state.errorLogs.push(...msg.workerLogs);
@@ -804,15 +1053,9 @@ async function handleReturn(
804
1053
  }
805
1054
  }
806
1055
  run.state.scriptResult = msg.result;
807
- run.transition("done", "completed");
808
- // [SW-DATA-3] save 失败不阻断终态推进(原 await 裸抛 → unhandledRejection + 幽灵注销)
809
- await saveRunBestEffort(run, deps, "handleReturn (done,completed)");
810
- deps.log?.("debug", "workflow:error-recovery", "run saved after return", { runId: run.runId, reason: run.state.reason });
811
- // C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
812
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
813
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
814
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
815
- deps.onRunDone?.(run);
1056
+ // C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
1057
+ // (D5-② 四步 coda 收敛为 finalizeRun 单写点,含 SW-DATA-3 save 兜底)
1058
+ await finalizeRun(run, deps, "completed", { context: "handleReturn (done,completed)" });
816
1059
  }
817
1060
 
818
1061
  // ── handleWorkerError ────────────────────────────────────────
@@ -835,18 +1078,18 @@ export async function handleWorkerError(
835
1078
  deps: LifecycleDeps,
836
1079
  handlers: WorkerHandlers,
837
1080
  ): Promise<void> {
838
- // 与 handleWorkerMessage 对称——终态(done)丢弃 stale error。
839
- // 否则终态后到达的 worker error 仍会 workerErrorCount++(污染跨 runtime 计数)。
1081
+ // 与 handleWorkerMessage 对称——终态(done)丢弃 stale error。
1082
+ // 否则终态后到达的 worker error 仍会 workerErrorCount++(污染跨 runtime 计数)。
840
1083
  if (isTerminal(run)) return;
841
1084
 
842
- // [R4-F1] 同代际幂等守卫:worker 崩溃时 error + exit(1) 双事件各派发一次
843
- // handleWorkerError(onError 先到,exit 非 0 经 handleWorkerExit 委托二次到达)——
844
- // 旧实现单次崩溃 workerErrorCount +2、两个 scheduleRebuild 并行交错(双 rebuild
845
- // 各自 new Worker,旧 handle 的 terminate/exit 事件与新 handle 的生命周期互相踩踏)。
846
- // 复用 R4 的 receivedTerminalMessage 代际标志(RunRuntime 字段,rebuild 自然重置):
847
- // 进入处理前置 true 标记「本代际已有 error/terminal 处理」,第二个事件(无论
848
- // onError 直达还是 exit(1) 委托)命中标志直接跳过。新代际的 handleWorkerError
849
- // 不受影响(新 RunRuntime 的标志为 false)。
1085
+ // [R4-F1] 同代际幂等守卫:worker 崩溃时 error + exit(1) 双事件各派发一次
1086
+ // handleWorkerError(onError 先到,exit 非 0 经 handleWorkerExit 委托二次到达)——
1087
+ // 旧实现单次崩溃 workerErrorCount +2、两个 scheduleRebuild 并行交错(双 rebuild
1088
+ // 各自 new Worker,旧 handle 的 terminate/exit 事件与新 handle 的生命周期互相踩踏)。
1089
+ // 复用 R4 的 receivedTerminalMessage 代际标志(RunRuntime 字段,rebuild 自然重置):
1090
+ // 进入处理前置 true 标记「本代际已有 error/terminal 处理」,第二个事件(无论
1091
+ // onError 直达还是 exit(1) 委托)命中标志直接跳过。新代际的 handleWorkerError
1092
+ // 不受影响(新 RunRuntime 的标志为 false)。
850
1093
  if (run.runtime?.receivedTerminalMessage) return;
851
1094
  if (run.runtime) run.runtime.receivedTerminalMessage = true;
852
1095
 
@@ -858,18 +1101,12 @@ export async function handleWorkerError(
858
1101
  return;
859
1102
  }
860
1103
 
861
- // 超限 → failed
1104
+ // 超限 → failed
862
1105
  run.state.error = err.message;
863
- deps.log?.("debug", "workflow:error-recovery", "handleWorkerError retries exceeded, transition done", { runId: run.runId, count });
864
- run.transition("done", "failed");
865
- // [SW-DATA-3] save 失败不阻断终态推进
866
- await saveRunBestEffort(run, deps, "handleWorkerError (done,failed)");
867
- deps.log?.("debug", "workflow:error-recovery", "run saved after worker error", { runId: run.runId, reason: run.state.reason });
868
- // C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
869
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
870
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
871
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
872
- deps.onRunDone?.(run);
1106
+ deps.log?.("debug", "workflow:worker-message-pump", "handleWorkerError retries exceeded, transition done", { runId: run.runId, count });
1107
+ // C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
1108
+ // (D5-② 四步 coda 收敛为 finalizeRun 单写点)
1109
+ await finalizeRun(run, deps, "failed", { context: "handleWorkerError (done,failed)" });
873
1110
  }
874
1111
 
875
1112
  // ── handleWorkerExit ─────────────────────────────────────────
@@ -896,7 +1133,7 @@ export async function handleWorkerExit(
896
1133
  deps: LifecycleDeps,
897
1134
  handlers: WorkerHandlers,
898
1135
  ): Promise<void> {
899
- // G-025: stale exit 事件丢弃(handle 已不是当前 runtime 的 worker)
1136
+ // G-025: stale exit 事件丢弃(handle 已不是当前 runtime 的 worker)
900
1137
  if (!handle.isCurrent) return;
901
1138
  if (isTerminal(run)) return;
902
1139
 
@@ -907,20 +1144,15 @@ export async function handleWorkerExit(
907
1144
  // [F1] 无终态消息的 exit(0) = worker 静默退出(不可克隆 return 被吞 / 脚本直调
908
1145
  // process.exit(0) 等)。置 failed 保证 runAndWait 必有终态。不重试:rebuild 重跑
909
1146
  // 脚本对确定性根因(不可克隆 return)无意义,且 belt 路径优先给用户明确归因。
910
- deps.log?.("debug", "workflow:error-recovery", "worker exited without terminal message, transition done", { runId: run.runId });
1147
+ deps.log?.("debug", "workflow:worker-message-pump", "worker exited without terminal message, transition done", { runId: run.runId });
911
1148
  run.state.error = WORKER_EXITED_WITHOUT_RESULT_MSG;
912
- run.transition("done", "failed");
913
- await saveRunBestEffort(run, deps, "handleWorkerExit (done,failed, no terminal message)");
914
- deps.log?.("debug", "workflow:error-recovery", "run saved after exit without result", { runId: run.runId, reason: run.state.reason });
915
1149
  // C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
916
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
917
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
918
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
919
- deps.onRunDone?.(run);
1150
+ // (D5-② 四步 coda 收敛为 finalizeRun 单写点)
1151
+ await finalizeRun(run, deps, "failed", { context: "handleWorkerExit (done,failed, no terminal message)" });
920
1152
  return;
921
1153
  }
922
1154
 
923
- // 非零 exit → 委托 handleWorkerError(C.3: onExit 传 handle 用于竞态防护)
1155
+ // 非零 exit → 委托 handleWorkerError(C.3: onExit 传 handle 用于竞态防护)
924
1156
  await handleWorkerError(
925
1157
  run,
926
1158
  new Error(`Worker exited with code ${code}`),
@@ -947,10 +1179,10 @@ export async function handleScriptError(
947
1179
  deps: LifecycleDeps,
948
1180
  handlers: WorkerHandlers,
949
1181
  ): Promise<void> {
950
- // 与 handleWorkerMessage/handleWorkerError 对称——终态守卫前置。
1182
+ // 与 handleWorkerMessage/handleWorkerError 对称——终态守卫前置。
951
1183
  if (isTerminal(run)) return;
952
1184
 
953
- // P2-2: 捕获 worker 诊断日志
1185
+ // P2-2: 捕获 worker 诊断日志
954
1186
  // L9: 追加而非覆盖
955
1187
  if (workerLogs.length > 0) {
956
1188
  run.state.errorLogs.push(...workerLogs);
@@ -967,18 +1199,12 @@ export async function handleScriptError(
967
1199
  return;
968
1200
  }
969
1201
 
970
- // 超限 → failed
1202
+ // 超限 → failed
971
1203
  run.state.error = `Workflow failed after ${MAX_WORKER_RETRIES} retries: ${errorMsg}`;
972
- deps.log?.("debug", "workflow:error-recovery", "handleScriptError retries exceeded, transition done", { runId: run.runId, count });
973
- run.transition("done", "failed");
974
- // [SW-DATA-3] save 失败不阻断终态推进
975
- await saveRunBestEffort(run, deps, "handleScriptError (done,failed)");
976
- deps.log?.("debug", "workflow:error-recovery", "run saved after script error", { runId: run.runId, reason: run.state.reason });
977
- // C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
978
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
979
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
980
- deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
981
- deps.onRunDone?.(run);
1204
+ deps.log?.("debug", "workflow:worker-message-pump", "handleScriptError retries exceeded, transition done", { runId: run.runId, count });
1205
+ // C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
1206
+ // (D5-② 四步 coda 收敛为 finalizeRun 单写点)
1207
+ await finalizeRun(run, deps, "failed", { context: "handleScriptError (done,failed)" });
982
1208
  }
983
1209
 
984
1210
  // ── scheduleRebuild(退避 + 重建) ──────────────────────────
@@ -988,31 +1214,82 @@ export async function handleScriptError(
988
1214
  *
989
1215
  * 退避期间 run 可能被 abort(转终态 done)——rebuildRuntime 前重检状态,终态时
990
1216
  * 跳过重建(避免给已终止的 run 启新 worker)。
1217
+ *
1218
+ * [OR-2] rebuildRuntime 抛错(workerHost.start 失败:线程/内存耗尽、eval 编译失败等)
1219
+ * 不再裸抛——裸抛会沿 handleWorkerError/handleScriptError 的 await 链冒泡到
1220
+ * worker-host 的 `void handlers.onXxx(...)` 变 unhandledRejection,且 run 卡 running
1221
+ * (旧 worker 已死、新 worker 未建,再无任何事件可达)。本函数 catch 后回灌重试矩阵
1222
+ * (handleRebuildStartFailure):计入 workerErrorCount,未超限再退避重建,超限收敛
1223
+ * done,failed——恢复机制在它自己的恢复路径上不再开口。
991
1224
  */
992
1225
  async function scheduleRebuild(
993
1226
  run: WorkflowRun,
994
1227
  deps: LifecycleDeps,
995
1228
  handlers: WorkerHandlers,
996
1229
  ): Promise<void> {
997
- // 用当前重试计数算退避(workerErrorCount 或 scriptErrorCount 已递增)
1230
+ // 用当前重试计数算退避(workerErrorCount 或 scriptErrorCount 已递增)
998
1231
  const retryIndex = Math.max(
999
1232
  run.meta.workerErrorCount ?? 0,
1000
1233
  run.meta.scriptErrorCount ?? 0,
1001
1234
  );
1002
1235
  await delay(backoffDelay(retryIndex));
1003
1236
 
1004
- // 退避期间状态可能变化——重检
1237
+ // 退避期间状态可能变化——重检
1005
1238
  if (isTerminal(run)) return;
1006
1239
 
1007
- // [race-F3] 时间预算折算后已耗尽 → 不再 rebuild 重试,直接 time_limited 终态。
1008
- // 必须在退避 delay 之后、rebuildRuntime 之前检查:检查前移会在「退避期间耗尽」的
1009
- // 窗口漏判(rebuild 挂不出 timer,run 预算静默失效);检查点与 rebuildRuntime 的
1010
- // 计时器挂载之间无 await,remaining > 0 判定不会失效。
1240
+ // [race-F3] 时间预算折算后已耗尽 → 不再 rebuild 重试,直接 time_limited 终态。
1241
+ // 必须在退避 delay 之后、rebuildRuntime 之前检查:检查前移会在「退避期间耗尽」的
1242
+ // 窗口漏判(rebuild 挂不出 timer,run 预算静默失效);检查点与 rebuildRuntime 的
1243
+ // 计时器挂载之间无 await,remaining > 0 判定不会失效。
1011
1244
  const remainingMs = remainingTimeBudgetMs(run);
1012
1245
  if (remainingMs !== undefined && remainingMs <= 0) {
1013
1246
  await finalizeTimeBudgetExhausted(run, deps);
1014
1247
  return;
1015
1248
  }
1016
1249
 
1017
- rebuildRuntime(run, deps, handlers);
1250
+ try {
1251
+ rebuildRuntime(run, deps, handlers);
1252
+ } catch (err) {
1253
+ await handleRebuildStartFailure(run, err, deps, handlers);
1254
+ }
1255
+ }
1256
+
1257
+ /**
1258
+ * [OR-2] rebuildRuntime 抛错回灌重试矩阵。
1259
+ *
1260
+ * 重建动作本身失败按 worker 家族计数(重建的就是 worker)——计入 run.meta.workerErrorCount
1261
+ * (跨 runtime 存活的重试计数载体),与既有 handleWorkerError 共用同一上限
1262
+ * MAX_WORKER_RETRIES 与退避序列:
1263
+ * - count <= MAX → 递归 scheduleRebuild(天然复用退避 / isTerminal 重检 / 预算折算守卫;
1264
+ * 每轮计数 +1,递归深度有界 ≤ MAX_WORKER_RETRIES);
1265
+ * - count > MAX → 收敛 done,failed(transition + 收口 in-flight + 持久化 + 围栏副作用),
1266
+ * run 不再卡 running。
1267
+ *
1268
+ * 终态路径顺序与其余 handle* 对齐:transition → closeOut → save → 围栏 emit/onRunDone。
1269
+ */
1270
+ async function handleRebuildStartFailure(
1271
+ run: WorkflowRun,
1272
+ err: unknown,
1273
+ deps: LifecycleDeps,
1274
+ handlers: WorkerHandlers,
1275
+ ): Promise<void> {
1276
+ if (isTerminal(run)) return;
1277
+ const message = toErrorMessage(err);
1278
+ const count = (run.meta.workerErrorCount ?? 0) + 1;
1279
+ run.meta.workerErrorCount = count;
1280
+ logger.error(
1281
+ `[workflow] rebuildRuntime failed (runId=${run.runId}, attempt ${count}/${MAX_WORKER_RETRIES}): ${message}`,
1282
+ );
1283
+
1284
+ if (count <= MAX_WORKER_RETRIES) {
1285
+ await scheduleRebuild(run, deps, handlers);
1286
+ return;
1287
+ }
1288
+
1289
+ // 耗尽 → 收敛 done,failed(不卡 running)
1290
+ run.state.error = `Runtime rebuild failed after ${MAX_WORKER_RETRIES} retries: ${message}`;
1291
+ deps.log?.("debug", "workflow:worker-message-pump", "rebuild retries exhausted, transition done", { runId: run.runId, count });
1292
+ // 终态序列与其余 handle* 对齐:transition → closeOut → save → 围栏 emit/onRunDone
1293
+ // (D5-② 收敛为 finalizeRun 单写点)
1294
+ await finalizeRun(run, deps, "failed", { context: "handleRebuildStartFailure (done,failed)" });
1018
1295
  }