@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
@@ -10,6 +10,9 @@
10
10
  * - evictDoneRunsBeyondCap(runs, keepDone) → number(done run 内存淘汰)
11
11
  * - scheduleTimeBudget(runId, deps, budgetTimeMs) → timer(C.7 时间预算)
12
12
  *
13
+ * 第 6 个导出:recoverCrashedRuns(store, runs, reason, hooks?) —— 崩溃恢复四步
14
+ * 装配(loadAll→failed→save→evict,D8/B1),宿主专属事件经 hooks 外置。
15
+ *
13
16
  * 私有 makeHandlers(run, deps) → WorkerHandlers:
14
17
  * - onMessage → handleWorkerMessage(run, raw, deps, handlers)
15
18
  * - onError → handleWorkerError(run, err, deps, handlers) + workerErrorCount++
@@ -21,7 +24,7 @@
21
24
  * 「releaseRuntime → 改 status」原子顺序。
22
25
  *
23
26
  * **G3-001**(run 一次性生命周期):AbortController 一次性无法复用,runtime 释放后
24
- * 只有两类重建——rebuildRuntime(error-recovery,崩溃重试路径,run 保持 running、
27
+ * 只有两类重建——rebuildRuntime(worker-message-pump,崩溃重试路径,run 保持 running、
25
28
  * replaceRuntime 原子换新)与 abort/terminate 的终态释放(transition("done") 内
26
29
  * releaseRuntime,run 不再恢复)。
27
30
  * (旧并发门闩 gate 抽象已删——no-op 无生产语义,实际并发由 SubagentService
@@ -38,18 +41,21 @@ import { getLogger } from "../core/logger.ts";
38
41
  import { assertSafeTimerDelay } from "../shared/timer-delay.ts";
39
42
  import { validateRunArgs } from "./args-validator.ts";
40
43
  import {
44
+ closeOutInFlightCalls,
45
+ finalizeRun,
41
46
  handleWorkerError,
42
47
  handleWorkerExit,
43
48
  handleWorkerMessage,
44
- } from "./error-recovery.ts";
49
+ } from "./worker-message-pump.ts";
45
50
  import { Budget } from "./models/budget.ts";
46
- import type { LifecycleDeps, WorkerHandlers } from "./models/ports.ts";
51
+ import type { LifecycleDeps, RunStore, WorkerHandlers } from "./models/ports.ts";
47
52
  import { RunRuntime } from "./models/run-runtime.ts";
48
53
  import type { RunSpec } from "./models/run-spec.ts";
49
54
  import { Trace } from "./models/trace.ts";
50
55
  import type { DoneReason } from "./models/types.ts";
51
56
  import { WorkflowRun } from "./models/workflow-run.ts";
52
57
  import type { WorkerHandle } from "./worker-handle.ts";
58
+ import { toErrorMessage } from "../core/error-message.ts";
53
59
 
54
60
  const logger = getLogger("subagents");
55
61
 
@@ -73,17 +79,63 @@ function generateRunId(): string {
73
79
  return `wf-${Date.now()}-${Math.random().toString(RUNID_RADIX).slice(RUNID_SLICE_START, RUNID_SLICE_END)}`;
74
80
  }
75
81
 
76
- // ── makeHandlers(路由 worker 事件到 error-recovery handle* 函数) ──────
82
+ // ── OR-7:signal abort listener 终态移除 ─────────────────────
83
+
84
+ /**
85
+ * run → signal abort listener 收口句柄注册表。
86
+ *
87
+ * [OR-7] 旧实现 `{ once: true }` 只在 abort 触发时自清——run 经 return / error /
88
+ * abortRun / terminate 等任何终态路径收敛后,listener 仍挂在(可能长生命周期的)
89
+ * 外部 signal 上,同 signal 连续跑多 run 时每次泄漏一个(超 11 个触发
90
+ * MaxListeners 告警;tool-workflow 的 run action 传的是 pi 会话级 signal,泄漏
91
+ * 真实可达)。对齐 launcher.ts L-2 修复形态:命名 handler + run 终态
92
+ * removeEventListener。
93
+ *
94
+ * WeakMap:run 被 GC 时条目随之消亡——即使未来新增终态路径漏调 dispose,
95
+ * 注册表本身也不成为新的无界泄漏面。
96
+ */
97
+ const signalAbortDisposers = new WeakMap<WorkflowRun, () => void>();
98
+
99
+ /** 移除 run 关联的 signal abort listener(幂等;未注册 run 为 no-op)。 */
100
+ function disposeSignalAbortListener(run: WorkflowRun): void {
101
+ const dispose = signalAbortDisposers.get(run);
102
+ if (!dispose) return;
103
+ signalAbortDisposers.delete(run);
104
+ dispose();
105
+ }
106
+
107
+ /**
108
+ * [OR-3] 向 worker 广播 {type:"abort"}——终止 run 时先于 transition(= releaseRuntime
109
+ * → worker.terminate)通知 worker 优雅解阻:worker 侧 pending Map 全部 reject
110
+ * (WorkflowAbortedError,worker-script-builder 既有 abort 分支),脚本 catch 后自然
111
+ * 收尾退出,不再依赖 terminate 硬杀。
112
+ *
113
+ * P-T3 语义:广播与 worker 自然退出交错不产 unhandledRejection——postMessage 同步抛错
114
+ * (worker 已退出/通道关闭)只 debug 留痕不中断 abort 主流程(pendings 随 worker 死亡
115
+ * 清理;WorkerHandle.postMessage 对已 terminate handle 本身 no-op);transition 必须继续。
116
+ */
117
+ function broadcastAbortToWorker(run: WorkflowRun, reason: string): void {
118
+ try {
119
+ run.runtime?.worker.postMessage({ type: "abort", reason });
120
+ } catch (err) {
121
+ const msg = toErrorMessage(err);
122
+ logger.debug(
123
+ `[workflow] abort broadcast to worker failed (runId=${run.runId}, worker likely already exited): ${msg}`,
124
+ );
125
+ }
126
+ }
127
+
128
+ // ── makeHandlers(路由 worker 事件到 worker-message-pump handle* 函数) ──────
77
129
 
78
130
  /**
79
131
  * 构造 WorkerHandlers——将 worker 的 onMessage/onError/onExit 事件路由到
80
- * error-recovery 的 handleWorker* 函数。
132
+ * worker-message-pump 的 handleWorker* 函数。
81
133
  *
82
134
  * 闭包捕获 run + deps。runtime 重建(replaceRuntime)后 run 实例不变、deps 不变,
83
- * 故 handlers 对新 worker 仍有效(lifecycle 与 error-recovery 共用 handlers)。
135
+ * 故 handlers 对新 worker 仍有效(lifecycle 与 worker-message-pump 共用 handlers)。
84
136
  *
85
137
  * **onExit G-025**:handleWorkerExit 内部检查 handle.isCurrent(stale exit 丢弃)。
86
- * 本函数不在 onExit 里重复检查——error-recovery.handleWorkerExit 是单一守卫点。
138
+ * 本函数不在 onExit 里重复检查——worker-message-pump.handleWorkerExit 是单一守卫点。
87
139
  *
88
140
  * **workerErrorCount**:onError 触发时递增(C.5 跨 runtime 存活的重试计数载体)。
89
141
  * 注意 handleWorkerError 内部也会递增——这里 onError 递增是 worker 事件层面的
@@ -91,20 +143,32 @@ function generateRunId(): string {
91
143
  * 实际 handleWorkerError 会做最终计数(含重试上限判断),onError 不重复递增。
92
144
  */
93
145
  function makeHandlers(run: WorkflowRun, deps: LifecycleDeps): WorkerHandlers {
94
- // 自引用——error-recovery rebuildRuntime 需要 handlers 参数(handlers 引用自身)
146
+ // [OR-7] per-run deps 视图:包装 onRunDone,把「run 终态」转译为「移除 signal abort
147
+ // listener」。worker-message-pump 全部终态路径(handleReturn / handleWorkerError /
148
+ // handleScriptError / handleWorkerExit / 预算终止 / time_limited / [OR-2] rebuild
149
+ // 失败收敛——均经 finalizeRun)都经 handlers 调用链消费本视图——消息面终态在此
150
+ // 统一收口;abortRun / terminateRunningRuns 两个 lifecycle 自有终态路径另行显式 dispose。
151
+ const depsWithTerminalCleanup: LifecycleDeps = {
152
+ ...deps,
153
+ onRunDone: (doneRun: WorkflowRun): void => {
154
+ disposeSignalAbortListener(run);
155
+ deps.onRunDone?.(doneRun);
156
+ },
157
+ };
158
+ // 自引用——worker-message-pump rebuildRuntime 需要 handlers 参数(handlers 引用自身)
95
159
  const handlers: WorkerHandlers = {
96
160
  async onMessage(raw: unknown): Promise<void> {
97
- await handleWorkerMessage(run, raw, deps, handlers);
161
+ await handleWorkerMessage(run, raw, depsWithTerminalCleanup, handlers);
98
162
  },
99
163
  async onError(err: Error): Promise<void> {
100
- await handleWorkerError(run, err, deps, handlers);
164
+ await handleWorkerError(run, err, depsWithTerminalCleanup, handlers);
101
165
  },
102
166
  async onExit(code: number, handle: WorkerHandle): Promise<void> {
103
- // H-2:用 worker-host 传入的 handle(即真正触发 exit 的那个 handle),而非
104
- // run.runtime?.worker——重试竞态下 runtime.worker 可能已被 replaceRuntime 替换
105
- // 为新 handle,导致 handleWorkerExit 内的 isCurrent 检查误判(漏判 stale exit 或
106
- // 误杀新 worker)。G-025 检查仍在 handleWorkerExit 内(handle.isCurrent)。
107
- await handleWorkerExit(run, code, handle, deps, handlers);
167
+ // H-2:用 worker-host 传入的 handle(即真正触发 exit 的那个 handle),而非
168
+ // run.runtime?.worker——重试竞态下 runtime.worker 可能已被 replaceRuntime 替换
169
+ // 为新 handle,导致 handleWorkerExit 内的 isCurrent 检查误判(漏判 stale exit 或
170
+ // 误杀新 worker)。G-025 检查仍在 handleWorkerExit 内(handle.isCurrent)。
171
+ await handleWorkerExit(run, code, handle, depsWithTerminalCleanup, handlers);
108
172
  },
109
173
  };
110
174
  return handlers;
@@ -134,18 +198,132 @@ export function scheduleTimeBudget(
134
198
  const timer = setTimeout(() => {
135
199
  void abortRun(runId, deps, "Time budget exceeded", "time_limited").catch(
136
200
  (err: unknown) => {
137
- const msg = err instanceof Error ? err.message : String(err);
201
+ const msg = toErrorMessage(err);
138
202
  logger.error(`[workflow] time budget abort failed: ${msg}`);
139
203
  },
140
204
  );
141
205
  }, budgetTimeMs);
142
- // unref:不阻止 Node 退出(workflow 是后台任务,不应因计时器持有事件循环)。
206
+ // unref:不阻止 Node 退出(workflow 是后台任务,不应因计时器持有事件循环)。
143
207
  timer.unref();
144
208
  return timer;
145
209
  }
146
210
 
147
211
  // ── runWorkflow ──────────────────────────────────────────────
148
212
 
213
+ /**
214
+ * rfl 仪表(tier-1 §7.1):向 spec.args 原地注入稳定 _runId。runAndWait 与
215
+ * executeNestedWorkflow 两个 args 入口都经 runWorkflow 这一 choke point;
216
+ * rebuildRuntime 复用 run.spec.args 同一对象(worker-message-pump.ts),worker rebuild
217
+ * 后脚本侧 $ARGS._runId 不漂移——修复「rebuild 回退 run-<Date.now()> 导致同一
218
+ * 逻辑 run 碎裂到多个 state 目录」。注入在 validateRunArgs 之后,不参与脚本
219
+ * 参数 schema 校验(引擎内部字段)。
220
+ */
221
+ function injectRunId(spec: RunSpec, runId: string): void {
222
+ if (spec.args && typeof spec.args === "object") {
223
+ spec.args._runId = runId;
224
+ }
225
+ }
226
+
227
+ /** P1-2: pre-aborted signal → fail fast(run 创建前抛,zero side effects)。 */
228
+ function assertSignalNotAborted(signal: AbortSignal | undefined): void {
229
+ if (signal?.aborted) {
230
+ throw new Error("Workflow run aborted before start");
231
+ }
232
+ }
233
+
234
+ /** 创建 running 态 WorkflowRun(budget 共享引用优先,否则按 spec 上限新建)。 */
235
+ function createRunningRun(runId: string, spec: RunSpec): WorkflowRun {
236
+ return new WorkflowRun(
237
+ runId,
238
+ spec,
239
+ {
240
+ status: "running",
241
+ budget: spec.budgetRef ?? new Budget({
242
+ maxTokens: spec.budgetTokens,
243
+ maxTimeMs: spec.budgetTimeMs,
244
+ }),
245
+ calls: new Map(),
246
+ trace: new Trace(),
247
+ errorLogs: [],
248
+ },
249
+ { startedAt: new Date().toISOString() },
250
+ );
251
+ }
252
+
253
+ /**
254
+ * C.7:run 级时间预算计时器(spec.budgetTimeMs > 0 时启用,到期 abortRun
255
+ * time_limited)。OR-1 顺序修复(unbounded-wait-audit §4.1):调度先于
256
+ * workerHost.start——budgetTimeMs 越界(>2^31-1)的 fail-fast throw
257
+ * (assertSafeTimerDelay)发生在任何副作用(worker 启动、runs 注册)之前,
258
+ * 杜绝「worker 已跑、run 永不可达」的孤儿窗口(此前 runs.set 在最末,
259
+ * fail-fast 后连 abortRun 都抛 not found)。
260
+ */
261
+ function armTimeBudgetTimer(
262
+ runId: string,
263
+ deps: LifecycleDeps,
264
+ spec: RunSpec,
265
+ ): ReturnType<typeof setTimeout> | undefined {
266
+ return spec.budgetTimeMs && spec.budgetTimeMs > 0
267
+ ? scheduleTimeBudget(runId, deps, spec.budgetTimeMs)
268
+ : undefined;
269
+ }
270
+
271
+ /**
272
+ * 启动 worker;失败时清理已挂的 timeBudget timer——前移的代价:start 抛错时
273
+ * 已挂的 timer 会残留,到期对从未注册的 runId 触发 abortRun(必抛 not found)
274
+ * ——该路径 runs.set 未到,须就地清理。
275
+ */
276
+ function startWorkerGuarded(
277
+ spec: RunSpec,
278
+ deps: LifecycleDeps,
279
+ handlers: WorkerHandlers,
280
+ timeBudgetTimer: ReturnType<typeof setTimeout> | undefined,
281
+ ): WorkerHandle {
282
+ try {
283
+ return deps.workerHost.start(spec, spec.args, handlers);
284
+ } catch (err) {
285
+ if (timeBudgetTimer) clearTimeout(timeBudgetTimer);
286
+ throw err;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * signal abort → abortRun([OR-7] 命名 listener + run 终态移除,注册表
292
+ * signalAbortDisposers;触发时自移除 = 显式 once 语义)。
293
+ * [B-2] 注册点后移到全部 fail-fast throw(assertSafeTimerDelay / workerHost.start)
294
+ * 成功之后——旧实现先注册 listener,scheduleTimeBudget/start 两条 throw 路径 run
295
+ * 永不到终态,disposeSignalAbortListener 永不被调,同 signal 重试每次泄漏一个
296
+ * listener(MaxListeners 告警面)。移后 throw 路径天然未注册,零泄漏;首个真正
297
+ * 副作用前完成校验的语义保持(addEventListener 本身也是设计意义上的副作用)。
298
+ * 注册点到 store.save 之间无 await(同步段),signal 在此窗口 abort 不可能丢失。
299
+ */
300
+ function registerSignalAbortListener(
301
+ run: WorkflowRun,
302
+ runId: string,
303
+ deps: LifecycleDeps,
304
+ signal: AbortSignal | undefined,
305
+ ): void {
306
+ if (!signal) return;
307
+ const onAbort = (): void => {
308
+ disposeSignalAbortListener(run);
309
+ void abortRun(runId, deps, "External signal aborted").catch((err: unknown) => {
310
+ const msg = toErrorMessage(err);
311
+ logger.error(`[workflow] abortRun on signal failed: ${msg}`);
312
+ });
313
+ };
314
+ signal.addEventListener("abort", onAbort);
315
+ signalAbortDisposers.set(run, () => signal.removeEventListener("abort", onAbort));
316
+ }
317
+
318
+ /** pending-notifications: run 启动 → 注册(所有 workflow 启动路径的单一汇聚点)。 */
319
+ function emitPendingRegister(runId: string, deps: LifecycleDeps, spec: RunSpec): void {
320
+ deps.eventBus?.emit("pending:register", {
321
+ id: runId,
322
+ type: "workflow",
323
+ name: spec.slug || spec.scriptName || runId,
324
+ });
325
+ }
326
+
149
327
  /**
150
328
  * 启动一个 workflow run。
151
329
  *
@@ -172,81 +350,37 @@ export async function runWorkflow(
172
350
  validateRunArgs(spec);
173
351
 
174
352
  const runId = generateRunId();
175
- // rfl 仪表(tier-1 §7.1):注入稳定 _runId。runAndWait 与 executeNestedWorkflow
176
- // 两个 args 入口都经本 choke point;rebuildRuntime 复用 run.spec.args 同一对象
177
- // (error-recovery.ts),worker rebuild 后脚本侧 $ARGS._runId 不漂移——修复
178
- // 「rebuild 回退 run-<Date.now()> 导致同一逻辑 run 碎裂到多个 state 目录」。
179
- // 注入在 validateRunArgs 之后,不参与脚本参数 schema 校验(引擎内部字段)。
180
- if (spec.args && typeof spec.args === "object") {
181
- spec.args._runId = runId;
182
- }
353
+ injectRunId(spec, runId);
183
354
  deps.log?.("debug", "workflow:lifecycle", "runWorkflow start", { runId, scriptName: spec.scriptName });
184
355
 
185
- // P1-2: pre-aborted signal → fail fast
186
- if (signal?.aborted) {
187
- throw new Error("Workflow run aborted before start");
188
- }
356
+ // P1-2: pre-aborted signal → fail fast
357
+ assertSignalNotAborted(signal);
189
358
 
190
- const run = new WorkflowRun(
191
- runId,
192
- spec,
193
- {
194
- status: "running",
195
- budget: spec.budgetRef ?? new Budget({
196
- maxTokens: spec.budgetTokens,
197
- maxTimeMs: spec.budgetTimeMs,
198
- }),
199
- calls: new Map(),
200
- trace: new Trace(),
201
- errorLogs: [],
202
- },
203
- { startedAt: new Date().toISOString() },
204
- );
205
-
206
- // signal abort → abortRun(一次性监听)
207
- if (signal) {
208
- signal.addEventListener(
209
- "abort",
210
- () => {
211
- void abortRun(runId, deps, "External signal aborted").catch((err: unknown) => {
212
- const msg = err instanceof Error ? err.message : String(err);
213
- logger.error(`[workflow] abortRun on signal failed: ${msg}`);
214
- });
215
- },
216
- { once: true },
217
- );
218
- }
359
+ const run = createRunningRun(runId, spec);
219
360
 
220
- // 构造 handlers + runtime(worker + controller)
361
+ // 构造 handlers + runtime(worker + controller)
221
362
  const handlers = makeHandlers(run, deps);
222
363
  const controller = new AbortController();
223
- const worker = deps.workerHost.start(spec, spec.args, handlers);
224
- // C.7:run 级时间预算计时器(spec.budgetTimeMs > 0 时启用,到期 abortRun time_limited)。
225
- const timeBudgetTimer =
226
- spec.budgetTimeMs && spec.budgetTimeMs > 0
227
- ? scheduleTimeBudget(runId, deps, spec.budgetTimeMs)
228
- : undefined;
364
+ const timeBudgetTimer = armTimeBudgetTimer(runId, deps, spec);
365
+ const worker = startWorkerGuarded(spec, deps, handlers, timeBudgetTimer);
229
366
  const runtime = new RunRuntime(worker, controller, timeBudgetTimer);
230
367
 
231
- // assignRuntime(注入 runtime,恢复 I1:running ⟺ runtime!==undefined)
368
+ // assignRuntime(注入 runtime,恢复 I1:running ⟺ runtime!==undefined)
232
369
  run.assignRuntime(runtime);
233
370
 
234
- // 注册到 deps.runs(assignRuntime 之后——构造到 assignRuntime 之间 run 处于
235
- // I1 跳过窗口(running 而 runtime undefined),后移保证窗口对外不可见;
236
- // worker.start 抛错时 run 未注册,无孤儿 run 残留)
371
+ // 注册到 deps.runs(assignRuntime 之后——构造到 assignRuntime 之间 run 处于
372
+ // I1 跳过窗口(running 而 runtime undefined),后移保证窗口对外不可见;
373
+ // scheduleTimeBudget/worker.start 抛错时 run 未注册,无孤儿 run 残留)
237
374
  deps.runs.set(runId, run);
238
375
 
376
+ registerSignalAbortListener(run, runId, deps, signal);
377
+
239
378
  await deps.store.save(run);
240
379
  deps.log?.("debug", "workflow:lifecycle", "run saved", { runId, status: run.state.status });
241
380
 
242
- // pending-notifications: run 启动 → 注册(所有 workflow 启动路径的单一汇聚点:
243
- // runAndWait / actionRun / 未来入口全覆盖)
381
+ // pending-notifications: run 启动 → 注册(runAndWait / actionRun / 未来入口全覆盖)
244
382
  deps.log?.("debug", "workflow:lifecycle", "emit pending:register", { runId });
245
- deps.eventBus?.emit("pending:register", {
246
- id: runId,
247
- type: "workflow",
248
- name: spec.slug || spec.scriptName || runId,
249
- });
383
+ emitPendingRegister(runId, deps, spec);
250
384
  deps.log?.("debug", "workflow:lifecycle", "emit pending:register done", { runId });
251
385
 
252
386
  return runId;
@@ -279,25 +413,29 @@ export async function abortRun(
279
413
 
280
414
  deps.log?.("debug", "workflow:lifecycle", "abortRun", { runId, status: run.state.status, reason, doneReason });
281
415
 
282
- // done 状态 no-op
416
+ // done 状态 no-op
283
417
  if (run.state.status === "done") {
284
418
  deps.log?.("debug", "workflow:lifecycle", "abortRun no-op: already done", { runId });
285
419
  return;
286
420
  }
287
421
 
288
- // 记录中止原因
422
+ // 记录中止原因
289
423
  if (reason) {
290
424
  run.state.error = reason;
291
425
  }
292
- // A4: transition 内部 releaseRuntimecleanup before mutate)
293
- run.transition("done", doneReason);
294
- await deps.store.save(run);
295
- deps.log?.("debug", "workflow:lifecycle", "abortRun transition done", { runId, reason: run.state.reason });
296
- // C-4: run 到达 done 终态 注销 pending-notification + 通知 Interface 层
297
- deps.log?.("debug", "workflow:lifecycle", "emit pending:unregister", { runId, reason: run.state.reason });
298
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
299
- deps.log?.("debug", "workflow:lifecycle", "emit pending:unregister done", { runId });
300
- deps.onRunDone?.(run);
426
+ // [OR-3] 先广播 abort 再 transitiontransition releaseRuntime→terminate,
427
+ // terminate 之后广播发不进去)——worker 侧 pending 优雅解阻
428
+ broadcastAbortToWorker(run, reason ?? `Workflow aborted (${doneReason})`);
429
+ // [OR-7] run 终态:移除 signal abort listener(幂等;abort signal 触发时
430
+ // listener 已在 onAbort 内自移除,此处为其余 abort 入口的收口;即使 finalizeRun
431
+ // 因并发终态化让位,WeakMap 条目也应清除,不依赖 transition 成败)
432
+ disposeSignalAbortListener(run);
433
+ // A4 + C-4: transition 内部 releaseRuntime(cleanup before mutate);到达 done 终态
434
+ // → 注销 pending-notification + 通知 Interface 层(D5-② 终态 coda 收敛为
435
+ // worker-message-pump 的 finalizeRun 单写点——OR-8 in-flight 收口 + save best-effort
436
+ // (SW-DATA-3)不再向上抛,内存态已终态,调用方无需因落盘失败收到 abort 异常;
437
+ // OR-4/B-4 unregister 与 onRunDone 各自独立围栏)
438
+ await finalizeRun(run, deps, doneReason, { context: `abortRun (${doneReason})` });
301
439
  }
302
440
 
303
441
  // ── terminateRunningRuns(session 切换/关闭:终止全部 running run) ────────
@@ -309,11 +447,12 @@ export async function abortRun(
309
447
  * done,failed 持久化落盘——重启后 kill-9 恢复不误判,也不再存在「挂起待恢复」
310
448
  * 的中间态。
311
449
  *
312
- * per-run 行为:`state.error = reason` → `transition("done","failed")`(内部先
313
- * releaseRuntime,A4)→ `await store.save(run)` `eventBus.emit("pending:unregister",
314
- * {reason:"failed"})`。
450
+ * per-run 行为:`state.error = reason` → `finalizeRun(run, deps, "failed",
451
+ * {notifyDone:false})`(transition("done","failed") 内部先 releaseRuntime,A4 →
452
+ * save best-effort → `eventBus.emit("pending:unregister", {reason:"failed"})`)。
315
453
  *
316
- * **不调 deps.onRunDone**:对齐 session_start 恢复先例(index.ts kill-9 恢复只发
454
+ * **不调 deps.onRunDone**(经 finalizeRun notifyDone:false 承载,D5-②):
455
+ * 对齐 session_start 恢复先例(index.ts kill-9 恢复只发
317
456
  * unregister、不发 onRunDone)——session 切换/关闭语境下主 agent 已离开本 session,
318
457
  * 注入完成通知只会把消息发给已离开的 session。
319
458
  *
@@ -336,14 +475,24 @@ export async function terminateRunningRuns(
336
475
  try {
337
476
  deps.log?.("debug", "workflow:lifecycle", "terminateRunningRuns", { runId: run.runId, reason });
338
477
  run.state.error = reason;
339
- // A4: transition 内部先 releaseRuntimecleanup before mutate)
340
- run.transition("done", "failed");
341
- await deps.store.save(run);
342
- // C-4: run 到达 done 终态 → 注销 pending-notification(reason 固定 "failed")
343
- deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: "failed" });
478
+ // [OR-3] abortRun:先广播 abort 再 transitionworker pending 优雅解阻)
479
+ broadcastAbortToWorker(run, reason);
480
+ // [OR-7] run 终态:移除 signal abort listener(terminateRunningRuns 不走
481
+ // onRunDone——本路径显式收口,不依赖 finalizeRun notifyDone 分支)
482
+ disposeSignalAbortListener(run);
483
+ // A4 + C-4: transition 内部 releaseRuntime(cleanup before mutate);done 终态 →
484
+ // 注销 pending-notification(D5-② 终态 coda 收敛为 finalizeRun 单写点)。
485
+ // notifyDone: false——**不调 deps.onRunDone**(真差异经参数承载):对齐
486
+ // session_start 恢复先例(index.ts kill-9 恢复只发 unregister、不发
487
+ // onRunDone)——session 切换/关闭语境下主 agent 已离开本 session,注入完成
488
+ // 通知只会把消息发给已离开的 session。OR-8 in-flight 收口由 finalizeRun 承载。
489
+ await finalizeRun(run, deps, "failed", {
490
+ context: "terminateRunningRuns",
491
+ notifyDone: false,
492
+ });
344
493
  deps.log?.("debug", "workflow:lifecycle", "run terminated", { runId: run.runId, reason: run.state.reason });
345
494
  } catch (err) {
346
- const msg = err instanceof Error ? err.message : String(err);
495
+ const msg = toErrorMessage(err);
347
496
  logger.error(
348
497
  `[workflow] terminateRunningRuns failed for run ${run.runId}: ${msg} (reason: ${reason})`,
349
498
  );
@@ -397,3 +546,128 @@ export function evictDoneRunsBeyondCap(
397
546
  }
398
547
  return excess;
399
548
  }
549
+
550
+ // ── recoverCrashedRuns(kill-9/崩溃恢复四步装配,D8/B1) ────────────────
551
+
552
+ /**
553
+ * 崩溃恢复循环的宿主事件外置 hooks(core 平台中立,宿主注入专属行为)。
554
+ *
555
+ * pi 宿主在 onRunRecovered 中发射 `pending:unregister` 事件(pending-notifications
556
+ * 扩展的注销信号灯);zsw/第三宿主可接自己的通知通道。不注入 = 无宿主事件,
557
+ * 恢复语义(failed 转换 + 落盘 + 淘汰)不受影响。
558
+ */
559
+ export interface RecoverCrashedRunsHooks {
560
+ /**
561
+ * 每个 running → done,failed 转换的 run 恰好调用一次。
562
+ *
563
+ * payload 形状对齐 pi `pending:unregister` 事件:`{ id: runId, reason: "failed" }`
564
+ * ——宿主可直接把 payload 转发到自己的事件总线。
565
+ *
566
+ * 错误围栏:回调同步 throw 经 core logger facade warn 留痕后被吞掉,恢复循环
567
+ * 继续其余 run(转换/落盘不受影响)——与 save 步骤「单 run 失败不中断其余」
568
+ * 容错口径对称。
569
+ */
570
+ onRunRecovered?: (payload: { id: string; reason: string }) => void;
571
+ }
572
+
573
+ /**
574
+ * recoverCrashedRuns 的计数结果(宿主启动日志/健康面用)。
575
+ *
576
+ * `recovered` 只计 running 遗留被转换为 done,failed 的条数;`loaded` 是 loadAll
577
+ * 重水合的全量数(含 done 历史快照)——两者分开口径,避免宿主把「重水合 N 条」
578
+ * 误报为「恢复 N 条」。
579
+ */
580
+ export interface RecoverCrashedRunsResult {
581
+ /** loadAll 重水合的 run 总数(含 done 历史快照)。 */
582
+ loaded: number;
583
+ /** running 遗留被转换为 done,failed 的条数(0 = 无崩溃遗留)。 */
584
+ recovered: number;
585
+ }
586
+
587
+ /**
588
+ * 崩溃恢复四步装配(设计 D8/B1):loadAll → failed → save → evict。
589
+ *
590
+ * 平移自 pi 壳 session_start 恢复循环(subagent-workflow index.ts:578-627)与
591
+ * zsw orchestration-host recoverOrphans(逐行同构,pending:unregister 为唯一
592
+ * 宿主差异点——经 {@link RecoverCrashedRunsHooks} 外置)。
593
+ *
594
+ * 步骤语义:
595
+ * 1. **loadAll**:从 store 全量重水合。失败向上抛——fail-fast 策略(pi 的
596
+ * storeHealthy=false 停初始化)是宿主职责,调用方 catch 后自行决定;
597
+ * 2. **failed**:残留 running run(进程被杀,worker 必死)逐个
598
+ * `state.error = reason` → `transition("done","failed")`(A4:transition 内部
599
+ * 先 releaseRuntime),并发宿主事件(hooks);done run 原样保留;
600
+ * 3. **save**:转换后的 run 落盘(恢复终态必须持久化,不 save 则下次启动重水合
601
+ * 仍见 running,侧栏永久卡 running)。单 run save 失败仅记日志不中断其余
602
+ * run——恢复天然幂等,下次启动重开重试;
603
+ * 4. **evict**:全量重水合后立即 `evictDoneRunsBeyondCap(runs,
604
+ * MAX_RETAINED_DONE_RUNS)`(K=20)——done run 内存有界性;只 delete 内存
605
+ * Map 条目,磁盘 state 文件不动(对齐 pi,历史审计保留)。
606
+ *
607
+ * 所有 loaded run(含 done)都注册进 runs Map(runId → WorkflowRun)——与 pi
608
+ * 一致,done run 也入 Map 供列表/查询消费。
609
+ *
610
+ * @param store RunStore port(loadAll/save)
611
+ * @param runs per-session 的 run 注册表(原地写入 + 淘汰)
612
+ * @param reason 恢复原因(写入 running run 的 state.error,如
613
+ * "Process killed (kill-9 or crash recovery)")
614
+ * @param hooks 宿主事件外置(可选)
615
+ * @returns 计数 `{ loaded, recovered }`——recovered 只计 running→failed 转换条数
616
+ * @throws store.loadAll 失败时原样抛出(步骤 1)
617
+ */
618
+ export async function recoverCrashedRuns(
619
+ store: RunStore,
620
+ runs: Map<string, WorkflowRun>,
621
+ reason: string,
622
+ hooks?: RecoverCrashedRunsHooks,
623
+ ): Promise<RecoverCrashedRunsResult> {
624
+ // 步骤 1:loadAll(失败上抛,宿主决定 fail-fast 策略)
625
+ const loaded = await store.loadAll();
626
+ let recovered = 0;
627
+
628
+ for (const run of loaded) {
629
+ if (run.state.status === "running") {
630
+ // 步骤 2:running → done,failed(顺序对齐 pi:set error → transition → 宿主事件)
631
+ run.state.error = reason;
632
+ run.transition("done", "failed");
633
+ // [OR-8] 重水合 running 快照可携带 in-flight call(崩溃瞬间的 running 节点)——
634
+ // 终态收口为 cancelled,先收口再落盘
635
+ closeOutInFlightCalls(run);
636
+ recovered += 1;
637
+ // 宿主事件外置点:pi 发 pending:unregister,位置在 transition 后、save 前
638
+ // (对齐 pi emit 位置——先解除挂起通知再落盘,事件观察者不依赖落盘完成)。
639
+ // 错误围栏:宿主回调同步 throw 只 warn 不中断循环——与步骤 3 save 的
640
+ // 「单 run 失败不中断其余」容错口径对称(通知通道故障不等价于恢复失败;
641
+ // 恢复天然幂等,未送达的通知随下次启动重试,见步骤 3 注释)。
642
+ try {
643
+ hooks?.onRunRecovered?.({ id: run.runId, reason: "failed" });
644
+ } catch (err) {
645
+ const msg = toErrorMessage(err);
646
+ logger.warn(
647
+ `[workflow] recoverCrashedRuns onRunRecovered hook failed for run ${run.runId} (recovery continues): ${msg}`,
648
+ );
649
+ }
650
+ // 步骤 3:恢复终态落盘(单 run 失败不中断其余 run——幂等恢复,下次重试)
651
+ try {
652
+ await store.save(run);
653
+ } catch (err) {
654
+ const msg = toErrorMessage(err);
655
+ logger.error(
656
+ `[workflow] recoverCrashedRuns store.save failed for run ${run.runId}: ${msg} (reason: ${reason})`,
657
+ );
658
+ }
659
+ }
660
+ runs.set(run.runId, run);
661
+ }
662
+
663
+ // 步骤 4:done run 内存有界性(K=20)。kill-9 恢复转换出的 failed run
664
+ // completedAt 为 transition 时刻(全局最新)必在保留端;多条恢复 run 同 ms
665
+ // completedAt → tie 稳定排序(evictDoneRunsBeyondCap 契约)。
666
+ const evicted = evictDoneRunsBeyondCap(runs, MAX_RETAINED_DONE_RUNS);
667
+ if (evicted > 0) {
668
+ logger.debug(
669
+ `[workflow] recoverCrashedRuns evicted ${evicted} done runs beyond cap (keep=${MAX_RETAINED_DONE_RUNS})`,
670
+ );
671
+ }
672
+ return { loaded: loaded.length, recovered };
673
+ }
@@ -34,11 +34,11 @@ export class AgentCall {
34
34
  status: AgentCallStatus = "pending";
35
35
  attempts = 0;
36
36
  result?: AgentResult;
37
- /** Pi subprocess session ID(uuidv7,G-017 归此)。 */
37
+ /** Pi subprocess session ID(uuidv7,G-017 归此)。 */
38
38
  sessionId?: string;
39
- /** Session JSONL 绝对路径(finalizeCall 后从 result.sessionFile 填入,对齐 sessionId 模式)。 */
39
+ /** Session JSONL 绝对路径(finalizeCall 后从 result.sessionFile 填入,对齐 sessionId 模式)。 */
40
40
  sessionFile?: string;
41
- /** 与 Trace 共享的节点引用(D-10 单源)。AgentCall 不直接改其字段。 */
41
+ /** 与 Trace 共享的节点引用(D-10 单源)。AgentCall 不直接改其字段。 */
42
42
  readonly traceNode: ExecutionTraceNode;
43
43
 
44
44
  constructor(id: number, opts: AgentCallOpts, traceNode: ExecutionTraceNode) {
@@ -47,7 +47,7 @@ export class AgentCall {
47
47
  this.traceNode = traceNode;
48
48
  }
49
49
 
50
- /**
50
+ /**
51
51
  * 标记进入 running 状态(dispatch 前)。attempts++(含首次)。
52
52
  * @throws 若已 done(不可重启)
53
53
  */
@@ -59,7 +59,7 @@ export class AgentCall {
59
59
  this.attempts += 1;
60
60
  }
61
61
 
62
- /**
62
+ /**
63
63
  * 标记完成(成功或失败均调用——result.error 区分)。
64
64
  * @throws 若当前非 running(pending 不能直接跳 done,必须先 markRunning)
65
65
  */
@@ -71,12 +71,12 @@ export class AgentCall {
71
71
  this.status = "done";
72
72
  }
73
73
 
74
- /** 记录 pi subprocess session ID(dispatch 成功后)。 */
74
+ /** 记录 pi subprocess session ID(dispatch 成功后)。 */
75
75
  setSessionId(sessionId: string): void {
76
76
  this.sessionId = sessionId;
77
77
  }
78
78
 
79
- /** 记录 session JSONL 绝对路径(finalizeCall 后,对齐 setSessionId 模式)。 */
79
+ /** 记录 session JSONL 绝对路径(finalizeCall 后,对齐 setSessionId 模式)。 */
80
80
  setSessionFile(sessionFile: string): void {
81
81
  this.sessionFile = sessionFile;
82
82
  }