@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
@@ -0,0 +1,384 @@
1
+ /**
2
+ * [OR-4] 终态收尾 emit/onRunDone M12 同款围栏 + [OR-6] handleWorkerMessage log case /
3
+ * default 留痕 + [OR-8] run done 时 in-flight call 收口 cancelled 测试。
4
+ *
5
+ * 覆盖(unbounded-wait-audit §4.1 OR-4 / §4.3 OR-6、OR-8 / §7.2 T3④⑤、T7④):
6
+ * - OR-4:eventBus.emit 或 onRunDone 同步抛错时,五个终态路径(return / worker error
7
+ * 超限 / script error 超限 / exit 无终态消息 / time_limited)promise 均 resolve——
8
+ * 错误被围栏捕获 error 留痕,不再经 worker-host 的 `void handlers.onXxx(...)`
9
+ * 变 unhandledRejection 崩宿主
10
+ * - OR-6 主线程半边:{type:"log"} 计入 errorLogs + deps.log debug 留痕(含上限裁剪);
11
+ * 未知 type warn 留痕(协议漂移防线)
12
+ * - OR-8:脚本 return 时残留 fire-and-forget in-flight call 收口为取消终态
13
+ * (call done + trace failed + Cancelled 文案 + completedAt + live 清除),
14
+ * 先收口再落盘——done 快照不含 running 节点
15
+ */
16
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
17
+
18
+ import { getLogger } from "../../core/logger.ts";
19
+ import {
20
+ closeOutInFlightCalls,
21
+ handleScriptError,
22
+ handleWorkerError,
23
+ handleWorkerExit,
24
+ handleWorkerMessage,
25
+ } from "../worker-message-pump.ts";
26
+ import { Budget } from "../models/budget.ts";
27
+ import { RunRuntime } from "../models/run-runtime.ts";
28
+ import { toRunSnapshot } from "../run-snapshot.ts";
29
+ import { Trace } from "../models/trace.ts";
30
+ import type { AgentResult, WorkerLogEntry } from "../models/types.ts";
31
+ import { WorkflowRun } from "../models/workflow-run.ts";
32
+ import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
33
+ import type { WorkerHandle } from "../worker-handle.ts";
34
+ import { flushMicrotasks } from "./helpers/flush-microtasks.ts";
35
+
36
+ // ── helpers ──────────────────────────────────────────────────
37
+
38
+ const MAX_ERROR_LOGS = 500;
39
+
40
+ function makeRealRun(runId: string, opts: { budgetTimeMs?: number } = {}): WorkflowRun {
41
+ const run = new WorkflowRun(
42
+ runId,
43
+ {
44
+ scriptName: "test-wf",
45
+ scriptSource: "agent('hi')",
46
+ args: {},
47
+ scriptPath: "/tmp/test-wf.js",
48
+ budgetTimeMs: opts.budgetTimeMs,
49
+ },
50
+ {
51
+ status: "running",
52
+ budget: new Budget(),
53
+ calls: new Map(),
54
+ trace: new Trace(),
55
+ errorLogs: [],
56
+ },
57
+ { startedAt: new Date().toISOString() },
58
+ );
59
+ const initialWorker = {
60
+ postMessage: vi.fn(),
61
+ terminate: vi.fn(async () => {}),
62
+ } as unknown as WorkerHandle;
63
+ run.assignRuntime(new RunRuntime(initialWorker, new AbortController()));
64
+ return run;
65
+ }
66
+
67
+ function makeDeps(opts: {
68
+ /** eventBus.emit 同步抛错(模拟 listener 异常)。 */
69
+ emitThrows?: boolean;
70
+ /** onRunDone 同步抛错(模拟 evictDoneRunsBeyondCap 等收尾异常)。 */
71
+ onRunDoneThrows?: boolean;
72
+ } = {}): Omit<LifecycleDeps, "store" | "workerHost" | "runner" | "eventBus" | "onRunDone" | "log"> & {
73
+ // 真实类型整体保留(接口成员完整),仅 mock 方法交叉 vi.fn 能力
74
+ store: LifecycleDeps["store"] & { save: LifecycleDeps["store"]["save"] & ReturnType<typeof vi.fn> };
75
+ workerHost: LifecycleDeps["workerHost"] & { start: LifecycleDeps["workerHost"]["start"] & ReturnType<typeof vi.fn> };
76
+ runner: LifecycleDeps["runner"] & { run: LifecycleDeps["runner"]["run"] & ReturnType<typeof vi.fn> };
77
+ eventBus: NonNullable<LifecycleDeps["eventBus"]> & { emit: NonNullable<LifecycleDeps["eventBus"]>["emit"] & ReturnType<typeof vi.fn> };
78
+ onRunDone: LifecycleDeps["onRunDone"] & ReturnType<typeof vi.fn>;
79
+ log: LifecycleDeps["log"] & ReturnType<typeof vi.fn>;
80
+ } {
81
+ return {
82
+ store: { save: vi.fn(async () => {}) },
83
+ workerHost: {
84
+ start: vi.fn(
85
+ () => ({ postMessage: vi.fn(), terminate: vi.fn(async () => {}) }) as unknown as WorkerHandle,
86
+ ),
87
+ },
88
+ runner: { run: vi.fn(async () => ({}) as AgentResult) },
89
+ runs: new Map(),
90
+ eventBus: {
91
+ emit: opts.emitThrows
92
+ ? vi.fn(() => {
93
+ throw new Error("listener exploded");
94
+ })
95
+ : vi.fn(),
96
+ },
97
+ onRunDone: opts.onRunDoneThrows
98
+ ? vi.fn(() => {
99
+ throw new Error("onRunDone exploded");
100
+ })
101
+ : vi.fn(),
102
+ log: vi.fn(),
103
+ } as unknown as ReturnType<typeof makeDeps>;
104
+ }
105
+
106
+ function makeHandlers(): WorkerHandlers {
107
+ return {
108
+ onMessage: vi.fn(async () => {}),
109
+ onError: vi.fn(async () => {}),
110
+ onExit: vi.fn(async () => {}),
111
+ } as unknown as WorkerHandlers;
112
+ }
113
+
114
+ function makeAgentCallMsg(callId: number): unknown {
115
+ return {
116
+ type: "agent-call",
117
+ callId,
118
+ opts: { prompt: "test task", agent: "worker", description: "test-slug" },
119
+ };
120
+ }
121
+
122
+ beforeEach(() => {
123
+ vi.useFakeTimers();
124
+ });
125
+
126
+ afterEach(() => {
127
+ vi.useRealTimers();
128
+ vi.restoreAllMocks();
129
+ });
130
+
131
+ // ── [OR-4] 终态收尾围栏 ──────────────────────────────────────
132
+
133
+ describe("[OR-4] 终态收尾 emit/onRunDone 围栏(不产 unhandledRejection)", () => {
134
+ it("handleReturn:eventBus.emit 同步抛错 → promise resolve + error 留痕(save 已发生)", async () => {
135
+ const run = makeRealRun("wf-fence-1");
136
+ const deps = makeDeps({ emitThrows: true });
137
+
138
+ await expect(
139
+ handleWorkerMessage(run, { type: "return", result: { ok: true } }, deps, makeHandlers()),
140
+ ).resolves.toBeUndefined();
141
+
142
+ expect(run.state.status).toBe("done");
143
+ expect(deps.store.save).toHaveBeenCalledTimes(1);
144
+ // [B-4] 独立围栏:emit 故障不再跳过 onRunDone(旧实现同一 try 会跳过)
145
+ expect(deps.onRunDone).toHaveBeenCalledTimes(1);
146
+ });
147
+
148
+ it("handleReturn:onRunDone 同步抛错 → promise resolve + error 留痕", async () => {
149
+ const run = makeRealRun("wf-fence-2");
150
+ const deps = makeDeps({ onRunDoneThrows: true });
151
+
152
+ await expect(
153
+ handleWorkerMessage(run, { type: "return", result: { ok: true } }, deps, makeHandlers()),
154
+ ).resolves.toBeUndefined();
155
+
156
+ expect(run.state.status).toBe("done");
157
+ expect(deps.store.save).toHaveBeenCalledTimes(1);
158
+ expect(deps.eventBus.emit).toHaveBeenCalledTimes(1); // emit 正常发出后才抛
159
+ });
160
+
161
+ it("handleWorkerError 超限:emit 抛错 → resolve(旧实现裸调 → unhandledRejection)", async () => {
162
+ const run = makeRealRun("wf-fence-3");
163
+ (run.meta as { workerErrorCount?: number }).workerErrorCount = 3; // count=4 > MAX
164
+ const deps = makeDeps({ emitThrows: true });
165
+
166
+ await expect(handleWorkerError(run, new Error("boom"), deps, makeHandlers())).resolves.toBeUndefined();
167
+ expect(run.state.reason).toBe("failed");
168
+ expect(deps.store.save).toHaveBeenCalledTimes(1);
169
+ });
170
+
171
+ it("handleScriptError 超限:onRunDone 抛错 → resolve", async () => {
172
+ const run = makeRealRun("wf-fence-4");
173
+ (run.meta as { scriptErrorCount?: number }).scriptErrorCount = 3;
174
+ const deps = makeDeps({ onRunDoneThrows: true });
175
+
176
+ await expect(handleScriptError(run, "boom", [], deps, makeHandlers())).resolves.toBeUndefined();
177
+ expect(run.state.reason).toBe("failed");
178
+ expect(deps.store.save).toHaveBeenCalledTimes(1);
179
+ });
180
+
181
+ it("handleWorkerExit 无终态消息路径:emit 抛错 → resolve", async () => {
182
+ const run = makeRealRun("wf-fence-5");
183
+ const deps = makeDeps({ emitThrows: true });
184
+ const handle = { isCurrent: true } as unknown as WorkerHandle;
185
+
186
+ await expect(handleWorkerExit(run, 0, handle, deps, makeHandlers())).resolves.toBeUndefined();
187
+ expect(run.state.reason).toBe("failed");
188
+ expect(deps.store.save).toHaveBeenCalledTimes(1);
189
+ });
190
+
191
+ it("time_limited 路径(预算耗尽 + 超限重试):emit 抛错 → resolve", async () => {
192
+ const run = makeRealRun("wf-fence-6", { budgetTimeMs: 5000 });
193
+ // 已耗 6000ms > 预算 5000ms(fake timers 冻结 Date)
194
+ run.meta.startedAt = new Date(Date.now() - 6000).toISOString();
195
+ const deps = makeDeps({ emitThrows: true });
196
+
197
+ const p = handleScriptError(run, "boom", [], deps, makeHandlers());
198
+ await vi.advanceTimersByTimeAsync(1000); // 退避
199
+ await expect(p).resolves.toBeUndefined();
200
+
201
+ expect(run.state.reason).toBe("time_limited");
202
+ expect(deps.store.save).toHaveBeenCalledTimes(1);
203
+ // [B-4] 独立围栏:emit 故障不再跳过 onRunDone(旧实现同一 try 会跳过)
204
+ expect(deps.onRunDone).toHaveBeenCalledTimes(1);
205
+ });
206
+
207
+ it("围栏捕获后 error 留痕(shared logger)", async () => {
208
+ const errorSpy = vi.spyOn(getLogger("subagents"), "error");
209
+ const run = makeRealRun("wf-fence-7");
210
+ const deps = makeDeps({ onRunDoneThrows: true });
211
+
212
+ await handleWorkerMessage(run, { type: "return", result: 1 }, deps, makeHandlers());
213
+
214
+ const errLogs = errorSpy.mock.calls.map((c) => String(c[0]));
215
+ expect(errLogs.some((m) => m.includes("onRunDone failed") && m.includes("onRunDone exploded"))).toBe(true);
216
+ });
217
+
218
+ it("[B-4] emit 抛错 → onRunDone 仍执行(独立围栏,完成回调不被通知总线故障吞掉)", async () => {
219
+ const errorSpy = vi.spyOn(getLogger("subagents"), "error");
220
+ const run = makeRealRun("wf-fence-8");
221
+ const deps = makeDeps({ emitThrows: true });
222
+
223
+ await handleWorkerMessage(run, { type: "return", result: 1 }, deps, makeHandlers());
224
+
225
+ expect(run.state.reason).toBe("completed");
226
+ expect(deps.onRunDone).toHaveBeenCalledTimes(1); // 旧实现同一 try:emit 抛错会跳过
227
+ const errLogs = errorSpy.mock.calls.map((c) => String(c[0]));
228
+ expect(errLogs.some((m) => m.includes("pending:unregister emit failed") && m.includes("listener exploded"))).toBe(true);
229
+ });
230
+ });
231
+
232
+ // ── [OR-6] handleWorkerMessage log case + default 留痕 ───────
233
+
234
+ describe("[OR-6] log 消息消费 + 未知类型 default 留痕", () => {
235
+ it("{type:\"log\"} 计入 run.state.errorLogs + deps.log debug 留痕(不触发终态)", async () => {
236
+ const run = makeRealRun("wf-log-1");
237
+ const deps = makeDeps();
238
+
239
+ await handleWorkerMessage(run, { type: "log", phase: "build", message: "step 1 done" }, deps, makeHandlers());
240
+
241
+ expect(run.state.errorLogs).toEqual([{ level: "log", message: "step 1 done" }]);
242
+ expect(run.state.status).toBe("running"); // 不触发终态
243
+ expect(deps.store.save).not.toHaveBeenCalled();
244
+ expect(deps.log).toHaveBeenCalledWith("debug", "workflow:worker-message-pump", "worker log", {
245
+ runId: "wf-log-1",
246
+ phase: "build",
247
+ message: "step 1 done",
248
+ });
249
+ });
250
+
251
+ it("log 超上限(MAX_ERROR_LOGS)时裁剪保留尾部(与 workerLogs 通路同语义)", async () => {
252
+ const run = makeRealRun("wf-log-2");
253
+ const deps = makeDeps();
254
+ const prefill: WorkerLogEntry[] = Array.from({ length: MAX_ERROR_LOGS }, (_, i) => ({
255
+ level: "log",
256
+ message: `pre-${i}`,
257
+ }));
258
+ run.state.errorLogs.push(...prefill);
259
+
260
+ await handleWorkerMessage(run, { type: "log", message: "the-latest" }, deps, makeHandlers());
261
+
262
+ expect(run.state.errorLogs).toHaveLength(MAX_ERROR_LOGS);
263
+ expect(run.state.errorLogs.at(-1)).toEqual({ level: "log", message: "the-latest" });
264
+ expect(run.state.errorLogs[0]).toEqual({ level: "log", message: "pre-1" }); // 头部被裁
265
+ });
266
+
267
+ it("[B-3] log 单通路:log 消息即时入账 + return workerLogs(仅 console.* 条目)→ 同一日志恰一份", async () => {
268
+ // worker 侧 [B-3] 后的产出形态(worker-script-builder-runtime.test.ts 锁定):
269
+ // log() 只发独立 {type:"log"} 消息,return 的 workerLogs 只含 console.* 捕获条目。
270
+ // 主线程两条消息各走各的入账 → 同一日志在 errorLogs 恰一份(旧双通路为 2 份)。
271
+ const run = makeRealRun("wf-log-dup");
272
+ const deps = makeDeps();
273
+
274
+ await handleWorkerMessage(run, { type: "log", phase: "build", message: "step-1" }, deps, makeHandlers());
275
+ await handleWorkerMessage(
276
+ run,
277
+ { type: "return", result: { ok: true }, workerLogs: [{ level: "error", message: "console-entry" }] },
278
+ deps,
279
+ makeHandlers(),
280
+ );
281
+
282
+ expect(run.state.errorLogs.filter((l) => l.message === "step-1")).toHaveLength(1);
283
+ expect(run.state.errorLogs.filter((l) => l.message === "console-entry")).toHaveLength(1);
284
+ });
285
+
286
+ it("未知消息类型:warn 留痕后丢弃(协议漂移防线),不写状态不终态", async () => {
287
+ const warnSpy = vi.spyOn(getLogger("subagents"), "warn");
288
+ const run = makeRealRun("wf-log-3");
289
+ const deps = makeDeps();
290
+
291
+ await handleWorkerMessage(run, { type: "future-unknown-type", payload: 1 }, deps, makeHandlers());
292
+
293
+ expect(warnSpy.mock.calls.map((c) => String(c[0])).some((m) => m.includes("unknown worker message type") && m.includes("future-unknown-type"))).toBe(true);
294
+ expect(deps.log).toHaveBeenCalledWith("warn", "workflow:worker-message-pump", "unknown worker message type", {
295
+ runId: "wf-log-3",
296
+ type: "future-unknown-type",
297
+ });
298
+ expect(run.state.status).toBe("running");
299
+ expect(deps.store.save).not.toHaveBeenCalled();
300
+ });
301
+ });
302
+
303
+ // ── [OR-8] run done 时 in-flight call 收口 cancelled ─────────
304
+
305
+ describe("[OR-8] run done 时残留 in-flight call 收口 cancelled", () => {
306
+ it("fire-and-forget agent() 后 return:call 收口 done + trace failed(Cancelled 文案),快照无 running 节点", async () => {
307
+ const run = makeRealRun("wf-or8-1");
308
+ const deps = makeDeps();
309
+ // fire-and-forget dispatch:runner 永不 settle(脚本不 await,模拟在飞子进程)
310
+ deps.runner.run.mockImplementation(() => new Promise<AgentResult>(() => {}));
311
+ await handleWorkerMessage(run, makeAgentCallMsg(1), deps, makeHandlers());
312
+ await flushMicrotasks();
313
+ expect(run.state.calls.get(1)?.status).toBe("running");
314
+
315
+ // 脚本 return(fire-and-forget call 仍在飞)
316
+ await handleWorkerMessage(run, { type: "return", result: "done early" }, deps, makeHandlers());
317
+
318
+ expect(run.state.status).toBe("done");
319
+ const call = run.state.calls.get(1);
320
+ // call 收口为 done + 取消文案(不删除条目——保留调用痕迹)
321
+ expect(call?.status).toBe("done");
322
+ expect(call?.result?.error).toContain("Cancelled");
323
+ // trace 节点 failed + completedAt + live 清除(快照/GUI 不再显示 running 步骤)
324
+ const node = run.state.trace.find(1);
325
+ expect(node?.status).toBe("failed");
326
+ expect(node?.error).toContain("Cancelled");
327
+ expect(node?.completedAt).toBeDefined();
328
+ expect(node?.live).toBeUndefined();
329
+ // 持久化快照(收口先于 save)不含 running 形态
330
+ const snap = toRunSnapshot(run);
331
+ expect(snap.state.calls.map((c) => c.status)).toEqual(["done"]);
332
+ expect(snap.state.trace.map((n) => n.status)).toEqual(["failed"]);
333
+ });
334
+
335
+ it("handleWorkerError 超限路径同样收口(failed 快照一致)", async () => {
336
+ const run = makeRealRun("wf-or8-2");
337
+ const deps = makeDeps();
338
+ deps.runner.run.mockImplementation(() => new Promise<AgentResult>(() => {}));
339
+ await handleWorkerMessage(run, makeAgentCallMsg(2), deps, makeHandlers());
340
+ await flushMicrotasks();
341
+
342
+ // 预置计数已达上限:count=4 > MAX → 无退避直接 failed(in-flight call 一并收口)
343
+ (run.meta as { workerErrorCount?: number }).workerErrorCount = 3;
344
+ await handleWorkerError(run, new Error("final boom"), deps, makeHandlers());
345
+
346
+ expect(run.state.reason).toBe("failed");
347
+ expect(run.state.calls.get(2)?.status).toBe("done");
348
+ expect(run.state.calls.get(2)?.result?.error).toContain("Cancelled");
349
+ expect(run.state.trace.find(2)?.status).toBe("failed");
350
+ });
351
+
352
+ it("closeOutInFlightCalls 对 pending 状态 call 补齐状态机(markRunning→markDone)", () => {
353
+ const run = makeRealRun("wf-or8-3");
354
+ const deps = makeDeps();
355
+ deps.runner.run.mockImplementation(() => new Promise<AgentResult>(() => {}));
356
+ void handleWorkerMessage(run, makeAgentCallMsg(3), deps, makeHandlers());
357
+ // 直接构造 pending 形态(防御 pending 分支可达性)
358
+ const call = run.state.calls.get(3)!;
359
+ (call as { status: string }).status = "pending";
360
+
361
+ const closed = closeOutInFlightCalls(run);
362
+
363
+ expect(closed).toEqual([3]);
364
+ expect(call.status).toBe("done");
365
+ expect(call.result?.error).toContain("Cancelled");
366
+ });
367
+
368
+ it("无 in-flight call 时收口为 no-op(正常完成的 call 不受影响)", async () => {
369
+ const run = makeRealRun("wf-or8-4");
370
+ const deps = makeDeps();
371
+ deps.runner.run.mockImplementation(
372
+ async () => ({ content: "ok", durationMs: 1, error: undefined, toolCalls: [] }) as AgentResult,
373
+ );
374
+ await handleWorkerMessage(run, makeAgentCallMsg(4), deps, makeHandlers());
375
+ await flushMicrotasks(20);
376
+ expect(run.state.calls.get(4)?.status).toBe("done");
377
+
378
+ await handleWorkerMessage(run, { type: "return", result: "ok" }, deps, makeHandlers());
379
+
380
+ // 已完成 call 的结果原样保留(不被取消文案覆盖)
381
+ expect(run.state.calls.get(4)?.result?.content).toBe("ok");
382
+ expect(run.state.trace.find(4)?.status).toBe("completed");
383
+ });
384
+ });
@@ -6,16 +6,17 @@
6
6
  import { describe, expect, it, vi } from "vitest";
7
7
 
8
8
  import {
9
+ classifyFailureKind,
10
+ describeMissingParsedOutput,
9
11
  DETERMINISTIC_SCHEMA_FAILURE_PREFIX,
10
- executeAgentCall,
11
12
  isDeterministicSchemaFailureMsg,
12
13
  isStaleContextErrorMsg,
13
14
  STALE_CONTEXT_PATTERNS,
14
- } from "../execute-agent-call.ts";
15
+ } from "../../execution/engine/engines/pi/output-collector.ts";
16
+ import { executeAgentCall } from "../execute-agent-call.ts";
15
17
  import { AgentCall } from "../models/agent-call.ts";
16
18
  import { Budget } from "../models/budget.ts";
17
19
  import type { AgentRunner } from "../models/ports.ts";
18
- import { describeMissingParsedOutput } from "../../execution/output-collector.ts";
19
20
  import type { ToolCall } from "../../execution/types.ts";
20
21
  import { Trace } from "../models/trace.ts";
21
22
  import type { ExecutionTraceNode } from "../models/types.ts";
@@ -67,6 +68,15 @@ function createMockRunner(impl?: ReturnType<typeof vi.fn>): AgentRunner & { run:
67
68
  return { run } as unknown as AgentRunner & { run: ReturnType<typeof vi.fn> };
68
69
  }
69
70
 
71
+ /**
72
+ * [D5-③] 构造带真实分诊的失败 result——failureKind 经产出侧 classifyFailureKind
73
+ * 分类(复刻 collectResult → mapper 透传后的消费侧视角),避免手写字面量与
74
+ * 产出侧词表脱钩。
75
+ */
76
+ function makeFailedResult(error: string): AgentResult {
77
+ return makeMockResult({ error, failureKind: classifyFailureKind(error) });
78
+ }
79
+
70
80
  // ── U2: executeAgentCall 透传 stream 给 runner.run ──
71
81
 
72
82
  describe("U2: executeAgentCall 透传 stream", () => {
@@ -201,8 +211,9 @@ describe("isOrphaned 守卫", () => {
201
211
  it("U2: 谓词 true + stale-context 失败路径 → trace.update 0 次,markDone 保留(覆盖 stale finalize 调用点)", async () => {
202
212
  const { call, trace } = makeAgentCallAndTrace();
203
213
  const updateSpy = vi.spyOn(trace, "update");
214
+ // "context canceled" 经产出侧词表分类为 stale_context(D5-③ 后消费侧读字段)
204
215
  const runner = createMockRunner(
205
- vi.fn().mockResolvedValue(makeMockResult({ error: "context canceled" })),
216
+ vi.fn().mockResolvedValue(makeFailedResult("context canceled")),
206
217
  );
207
218
  const budget = new Budget();
208
219
 
@@ -297,9 +308,11 @@ describe("W4b: stale 分诊对齐 pi 真实文案", () => {
297
308
  });
298
309
 
299
310
  it("真实文案 → executeAgentCall 不重试(runner.run 恰 1 次)+ markDone failed", async () => {
311
+ // [D5-③] 全链路锁:产出侧 classifyFailureKind(真实文案 → stale_context)→
312
+ // 消费侧读 failureKind 字段分诊(不扫文案)
300
313
  const { call, trace } = makeAgentCallAndTrace();
301
314
  const runner = createMockRunner(
302
- vi.fn().mockResolvedValue(makeMockResult({ error: PI_REAL_STALE_MESSAGE })),
315
+ vi.fn().mockResolvedValue(makeFailedResult(PI_REAL_STALE_MESSAGE)),
303
316
  );
304
317
  const budget = new Budget();
305
318
 
@@ -351,7 +364,7 @@ describe("MF-1: 确定性 schema 失败不重试", () => {
351
364
 
352
365
  const { call, trace } = makeAgentCallAndTrace();
353
366
  const runner = createMockRunner(
354
- vi.fn().mockResolvedValue(makeMockResult({ error: attribution })),
367
+ vi.fn().mockResolvedValue(makeFailedResult(attribution)),
355
368
  );
356
369
  const budget = new Budget();
357
370
 
@@ -368,7 +381,7 @@ describe("MF-1: 确定性 schema 失败不重试", () => {
368
381
  const attribution = describeMissingParsedOutput([])!;
369
382
  const { call, trace } = makeAgentCallAndTrace();
370
383
  const runner = createMockRunner(
371
- vi.fn().mockResolvedValue(makeMockResult({ error: attribution })),
384
+ vi.fn().mockResolvedValue(makeFailedResult(attribution)),
372
385
  );
373
386
  const budget = new Budget();
374
387
 
@@ -384,7 +397,11 @@ describe("MF-1: 确定性 schema 失败不重试", () => {
384
397
  const { call, trace } = makeAgentCallAndTrace();
385
398
  const runner = createMockRunner(
386
399
  vi.fn().mockResolvedValue(
387
- makeMockResult({ error: attribution, usage: { input: 100, output: 50, cost: 0.01 } }),
400
+ makeMockResult({
401
+ error: attribution,
402
+ failureKind: "schema_deterministic",
403
+ usage: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, cost: 0.01, contextTokens: 0, turns: 0 },
404
+ }),
388
405
  ),
389
406
  );
390
407
  const budget = new Budget();
@@ -402,7 +419,7 @@ describe("MF-1: 确定性 schema 失败不重试", () => {
402
419
  const attribution = state2Attribution();
403
420
  const { call, trace } = makeAgentCallAndTrace();
404
421
  const runner = createMockRunner(
405
- vi.fn().mockResolvedValue(makeMockResult({ error: attribution })),
422
+ vi.fn().mockResolvedValue(makeFailedResult(attribution)),
406
423
  );
407
424
  const budget = new Budget();
408
425
 
@@ -414,7 +431,7 @@ describe("MF-1: 确定性 schema 失败不重试", () => {
414
431
  it("态③(no details,无标记)→ 照常重试(可重试语义保留)", async () => {
415
432
  vi.useFakeTimers();
416
433
  try {
417
- // 态③真实文案(不带确定性标记)——矩阵执行面锁定:无标记 = 可重试
434
+ // 态③真实文案(不带确定性标记)——矩阵执行面锁定:无标记 = unknown = 可重试
418
435
  const msg = describeMissingParsedOutput([
419
436
  { toolName: "structured-output", result: { content: [] } },
420
437
  ])!;
@@ -423,7 +440,7 @@ describe("MF-1: 确定性 schema 失败不重试", () => {
423
440
  const { call, trace } = makeAgentCallAndTrace();
424
441
  const runner = createMockRunner(
425
442
  vi.fn()
426
- .mockResolvedValueOnce(makeMockResult({ error: msg }))
443
+ .mockResolvedValueOnce(makeFailedResult(msg))
427
444
  .mockResolvedValueOnce(makeMockResult()),
428
445
  );
429
446
  const budget = new Budget();
@@ -449,3 +466,126 @@ describe("MF-1: 确定性 schema 失败不重试", () => {
449
466
  expect(isDeterministicSchemaFailureMsg(DETERMINISTIC_SCHEMA_FAILURE_PREFIX)).toBe(true);
450
467
  });
451
468
  });
469
+
470
+ // ── D5-③: failureKind 三态结构化分诊(V5③④ 验收的执行面锁定) ──
471
+ //
472
+ // 语义守恒(r1 MF4 钉正,最高优先约束):unknown(含字段缺省)= 可重试——保持
473
+ // 收敛前(子串分诊时代)的默认重试语义;仅 stale_context(不重试、换参重发由
474
+ // 上层编排)与 schema_deterministic 维持特判。词表识别已收敛到产出侧
475
+ // output-collector.classifyFailureKind(消费侧只读字段)。
476
+
477
+ describe("D5-③: failureKind 三态分诊", () => {
478
+ it("stale_context → 不退避不重试:runner.run 恰 1 次 + 终态 failed(V5③)", async () => {
479
+ const { call, trace } = makeAgentCallAndTrace();
480
+ const runner = createMockRunner(
481
+ vi.fn().mockResolvedValue(makeMockResult({
482
+ error: PI_REAL_STALE_MESSAGE,
483
+ failureKind: "stale_context",
484
+ })),
485
+ );
486
+ const budget = new Budget();
487
+
488
+ await executeAgentCall(call, runner, budget, new AbortController().signal, trace);
489
+
490
+ expect(runner.run).toHaveBeenCalledTimes(1);
491
+ expect(call.attempts).toBe(1);
492
+ expect(call.status).toBe("done");
493
+ expect(call.result?.error).toBe(PI_REAL_STALE_MESSAGE);
494
+ expect(trace.find(0)?.status).toBe("failed");
495
+ });
496
+
497
+ it("schema_deterministic → 不重试特判维持:runner.run 恰 1 次(V5③ 同族)", async () => {
498
+ const attribution = state2Attribution();
499
+ const { call, trace } = makeAgentCallAndTrace();
500
+ const runner = createMockRunner(
501
+ vi.fn().mockResolvedValue(makeMockResult({
502
+ error: attribution,
503
+ failureKind: "schema_deterministic",
504
+ })),
505
+ );
506
+ const budget = new Budget();
507
+
508
+ await executeAgentCall(call, runner, budget, new AbortController().signal, trace);
509
+
510
+ expect(runner.run).toHaveBeenCalledTimes(1);
511
+ expect(trace.find(0)?.status).toBe("failed");
512
+ });
513
+
514
+ it("unknown → 默认退避重试:瞬态错误(模拟 provider 5xx)退避后第二次成功(V5④ 正向)", async () => {
515
+ vi.useFakeTimers();
516
+ try {
517
+ const { call, trace } = makeAgentCallAndTrace();
518
+ const runner = createMockRunner(
519
+ vi.fn()
520
+ .mockResolvedValueOnce(makeMockResult({
521
+ error: "provider 503 service unavailable",
522
+ failureKind: "unknown",
523
+ }))
524
+ .mockResolvedValueOnce(makeMockResult()),
525
+ );
526
+ const budget = new Budget();
527
+
528
+ const promise = executeAgentCall(call, runner, budget, new AbortController().signal, trace);
529
+ await vi.advanceTimersByTimeAsync(2000);
530
+ await promise;
531
+
532
+ expect(runner.run).toHaveBeenCalledTimes(2);
533
+ expect(call.status).toBe("done");
534
+ expect(call.result?.error).toBeUndefined();
535
+ expect(trace.find(0)?.status).toBe("completed");
536
+ } finally {
537
+ vi.useRealTimers();
538
+ }
539
+ });
540
+
541
+ it("failureKind 缺省(旧链路/上游未写)→ 等同 unknown 默认退避重试(V5④ 语义守恒核心断言)", async () => {
542
+ vi.useFakeTimers();
543
+ try {
544
+ // 字段缺省是可重试而非「保守不重试」——反转会把一切未标注路径(瞬态 provider
545
+ // 错误、spawn 失败)静默丢进不重试(r1 MF4 击穿反例)
546
+ const { call, trace } = makeAgentCallAndTrace();
547
+ const runner = createMockRunner(
548
+ vi.fn()
549
+ .mockResolvedValueOnce(makeMockResult({ error: "spawn EAGAIN transient" }))
550
+ .mockResolvedValueOnce(makeMockResult()),
551
+ );
552
+ const budget = new Budget();
553
+
554
+ const promise = executeAgentCall(call, runner, budget, new AbortController().signal, trace);
555
+ await vi.advanceTimersByTimeAsync(2000);
556
+ await promise;
557
+
558
+ expect(runner.run).toHaveBeenCalledTimes(2);
559
+ expect(call.status).toBe("done");
560
+ } finally {
561
+ vi.useRealTimers();
562
+ }
563
+ });
564
+
565
+ it("词表漂移失效模式:未知错误文案 → classifyFailureKind=unknown → 走退避重试(安全默认)", async () => {
566
+ vi.useFakeTimers();
567
+ try {
568
+ // pi 升级改写 stale 文案后,旧词表对新文案零命中——分诊降级 unknown、
569
+ // 保守重试(可能多耗一次调用),而不是静默漏诊挂死在不重试
570
+ const futurePiError = "extension runtime was superseded by a newer orchestration epoch";
571
+ expect(classifyFailureKind(futurePiError)).toBe("unknown");
572
+
573
+ const { call, trace } = makeAgentCallAndTrace();
574
+ const runner = createMockRunner(
575
+ vi.fn()
576
+ .mockResolvedValueOnce(makeFailedResult(futurePiError))
577
+ .mockResolvedValueOnce(makeMockResult()),
578
+ );
579
+ const budget = new Budget();
580
+
581
+ const promise = executeAgentCall(call, runner, budget, new AbortController().signal, trace);
582
+ await vi.advanceTimersByTimeAsync(2000);
583
+ await promise;
584
+
585
+ expect(runner.run).toHaveBeenCalledTimes(2);
586
+ expect(call.status).toBe("done");
587
+ } finally {
588
+ vi.useRealTimers();
589
+ }
590
+ });
591
+ });