@zhushanwen/subagent-core 0.4.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 (272) hide show
  1. package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
  2. package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
  3. package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
  4. package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
  5. package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
  6. package/dist/execution/engine/engines/zcode/constants.js +17 -27
  7. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  8. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  9. package/dist/execution/engine/engines/zcode/reader.js +2 -2
  10. package/dist/index.cjs +16418 -14070
  11. package/dist/index.d.cts +5136 -4016
  12. package/dist/index.d.ts +5136 -4016
  13. package/dist/index.js +16343 -14048
  14. package/dist/{types-DpUO16pj.d.cts → types-Dv4QhSJ_.d.cts} +215 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-Dv4QhSJ_.d.ts} +215 -112
  16. package/package.json +3 -3
  17. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  18. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  19. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  20. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  21. package/src/__tests__/manifest-store.test.ts +10 -9
  22. package/src/__tests__/record-store-cache.test.ts +0 -2
  23. package/src/__tests__/record-store-index.test.ts +1 -2
  24. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  25. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  26. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  27. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  28. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  29. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  30. package/src/__tests__/session-runner.test.ts +1 -1
  31. package/src/core/__tests__/host-services.test.ts +2 -2
  32. package/src/core/__tests__/logger.test.ts +1 -1
  33. package/src/core/error-message.ts +9 -0
  34. package/src/core/host-services.ts +21 -5
  35. package/src/core/notify-ports.ts +18 -5
  36. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  37. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  38. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  39. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  40. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  41. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  42. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  43. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  44. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  45. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  46. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  47. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  48. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  49. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  50. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  51. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  52. package/src/execution/__tests__/execution-record.test.ts +217 -60
  53. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  54. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  55. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  56. package/src/execution/__tests__/gc-timer.test.ts +55 -2
  57. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  58. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  59. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  60. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  61. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  62. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  63. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  64. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  65. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  66. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  67. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  68. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  69. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  70. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  71. package/src/execution/__tests__/notify-ledger.test.ts +973 -18
  72. package/src/execution/__tests__/output-collector.test.ts +117 -6
  73. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  74. package/src/execution/__tests__/record-store.test.ts +10 -9
  75. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  76. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  77. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  78. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  79. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  80. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  81. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  82. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  83. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  84. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  85. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  86. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  87. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  88. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  89. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  90. package/src/execution/__tests__/session-pending.test.ts +257 -14
  91. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  92. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  93. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  94. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  95. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  96. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  97. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  98. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  99. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  100. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  101. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  102. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  103. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  104. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  105. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  106. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  107. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  108. package/src/execution/__tests__/subagent-actions-core.test.ts +42 -22
  109. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  110. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +8 -8
  111. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  112. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  113. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  114. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  115. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  116. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  117. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  118. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  119. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  120. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  121. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  122. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  123. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  124. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  125. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  126. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  127. package/src/execution/agent-registry.ts +74 -30
  128. package/src/execution/agent-result-mapper.ts +3 -0
  129. package/src/execution/cold-resurrect.ts +199 -0
  130. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  131. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  132. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  133. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  134. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  135. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  136. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  137. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  138. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  139. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  140. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  141. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  142. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  143. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  144. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  145. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  146. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  147. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  148. package/src/execution/engine/common/capability-gate.ts +92 -0
  149. package/src/execution/engine/common/journal-wiring.ts +97 -0
  150. package/src/execution/engine/common/kill-chain.ts +42 -10
  151. package/src/execution/engine/common/nesting-guard.ts +66 -0
  152. package/src/execution/engine/common/persona-router.ts +18 -8
  153. package/src/execution/engine/common/pool-manager.ts +332 -78
  154. package/src/execution/engine/common/session-view-service.ts +506 -0
  155. package/src/execution/engine/common/session-view-types.ts +125 -0
  156. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  157. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  158. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  159. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  160. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  161. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  162. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  163. package/src/execution/engine/engines/pi/reader.ts +11 -0
  164. package/src/execution/engine/engines/pi/registration.ts +4 -2
  165. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  166. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  167. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  168. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  169. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  170. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  171. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  172. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  173. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  174. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  175. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  176. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  177. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  178. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  179. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +25 -319
  180. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  181. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  182. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  183. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  184. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  185. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  186. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  187. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  188. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  189. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  190. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  191. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  192. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  193. package/src/execution/engine/host-task-spec.ts +32 -36
  194. package/src/execution/engine/model-validation.ts +177 -0
  195. package/src/execution/engine/port.ts +24 -6
  196. package/src/execution/engine/routing.ts +75 -1
  197. package/src/execution/engine/types.ts +30 -103
  198. package/src/execution/execution-record.ts +222 -144
  199. package/src/execution/finalize-record.ts +131 -93
  200. package/src/execution/idle-gc.ts +28 -0
  201. package/src/execution/lifecycle-predicates.ts +1 -1
  202. package/src/execution/notify-host.ts +212 -0
  203. package/src/execution/record-store.ts +119 -73
  204. package/src/execution/round-settlement.ts +93 -0
  205. package/src/execution/session-file-gc.ts +96 -62
  206. package/src/execution/session-reconstructor.ts +256 -143
  207. package/src/execution/sessions-index.ts +117 -47
  208. package/src/execution/settled-watchdog.ts +264 -52
  209. package/src/execution/subagent-actions-core.ts +22 -22
  210. package/src/execution/subagent-service.ts +815 -963
  211. package/src/execution/subprocess-agent-runner.ts +144 -97
  212. package/src/execution/types.ts +9 -0
  213. package/src/execution/ui-request-handler-factory.ts +102 -74
  214. package/src/execution/ui-request-queue.ts +50 -19
  215. package/src/index.ts +442 -316
  216. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  217. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  218. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  219. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  220. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  221. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  222. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  223. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  224. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  225. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  226. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  227. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  228. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  229. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  230. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  231. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  232. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  233. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  234. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  235. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  236. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  237. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  238. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  239. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  240. package/src/orchestration/agent-opts-resolver.ts +1 -1
  241. package/src/orchestration/args-validator.ts +53 -31
  242. package/src/orchestration/execute-agent-call.ts +19 -73
  243. package/src/orchestration/launcher.ts +128 -81
  244. package/src/orchestration/lifecycle.ts +43 -45
  245. package/src/orchestration/models/ports.ts +4 -4
  246. package/src/orchestration/models/run-runtime.ts +2 -2
  247. package/src/orchestration/models/trace.ts +1 -1
  248. package/src/orchestration/models/types.ts +111 -22
  249. package/src/orchestration/models/workflow-run.ts +1 -1
  250. package/src/orchestration/script-lint.ts +91 -58
  251. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  252. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  253. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  254. package/src/shared/atomic-write.ts +12 -9
  255. package/src/shared/meta-parser.ts +158 -84
  256. package/src/shared/model-ref.ts +15 -2
  257. package/src/shared/resource-discovery.ts +9 -183
  258. package/src/shared/schema-jsonify.ts +1 -1
  259. package/workflows/review-fix-loop-utils.cjs +151 -89
  260. package/workflows/review-fix-loop-utils.d.cts +287 -0
  261. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  262. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  263. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  264. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  265. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  266. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  267. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  268. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  269. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  270. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  271. package/src/execution/execute-options-mapper.ts +0 -115
  272. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,229 @@
1
+ /**
2
+ * finalizeRun — D5-② 终态 coda 单写点测试。
3
+ *
4
+ * 「transition → save → pending:unregister → onRunDone」四步终态序列的唯一
5
+ * 定义点(worker-message-pump.ts)。收敛前 8 处逐字复制(本文件 6 处 + lifecycle
6
+ * 2 处)——本文件锁定:
7
+ * 1. 四步恰好一次且有序(transition 先于 save 先于 unregister 先于 onRunDone)
8
+ * 2. notifyDone:false 真差异承载(terminateRunningRuns 不发 onRunDone、unregister 仍发)
9
+ * 3. transition 让位(并发终态化)→ 后三步全不执行
10
+ * 4. save best-effort(SW-DATA-3)→ unregister/onRunDone 不被落盘失败短路
11
+ *
12
+ * 其余终态路径的「恰好一次四步」行为断言(save×1 + unregister + onRunDone×1)
13
+ * 已由既有测试覆盖:handleWorkerError/handleScriptError/handleWorkerExit 超限
14
+ * (worker-message-pump-handlers / worker-exit-without-result)、time_limited
15
+ * (worker-message-pump-handlers race-F3 describe)、abortRun(lifecycle.test)、
16
+ * handleReturn(worker-exit-without-result SW-DATA-3 describe)。本文件补齐
17
+ * budget_limited 路径(dispatchAgentCall coda)与 notifyDone:false 路径。
18
+ */
19
+ import { describe, expect, it, vi } from "vitest";
20
+
21
+ import {
22
+ finalizeRun,
23
+ handleWorkerMessage,
24
+ } from "../worker-message-pump.ts";
25
+ import { Budget } from "../models/budget.ts";
26
+ import { RunRuntime } from "../models/run-runtime.ts";
27
+ import { Trace } from "../models/trace.ts";
28
+ import type { AgentResult } from "../models/types.ts";
29
+ import { WorkflowRun } from "../models/workflow-run.ts";
30
+ import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
31
+ import type { WorkerHandle } from "../worker-handle.ts";
32
+
33
+ // ── helpers ──────────────────────────────────────────────────
34
+
35
+ /** 构造真实 WorkflowRun(真实状态机 transition)+ 初始 runtime。 */
36
+ function makeRealRun(runId: string): WorkflowRun {
37
+ const run = new WorkflowRun(
38
+ runId,
39
+ {
40
+ scriptName: "test-wf",
41
+ scriptSource: "agent('hi')",
42
+ args: {},
43
+ scriptPath: "/tmp/test-wf.js",
44
+ },
45
+ {
46
+ status: "running",
47
+ budget: new Budget(),
48
+ calls: new Map(),
49
+ trace: new Trace(),
50
+ errorLogs: [],
51
+ },
52
+ { startedAt: new Date().toISOString() },
53
+ );
54
+ const worker = {
55
+ postMessage: vi.fn(),
56
+ terminate: vi.fn(async () => {}),
57
+ } as unknown as WorkerHandle;
58
+ run.assignRuntime(new RunRuntime(worker, new AbortController()));
59
+ return run;
60
+ }
61
+
62
+ /** deps mock:副作用打点进单一顺序数组(断言四步顺序)。 */
63
+ function makeTracingDeps(): LifecycleDeps & {
64
+ order: string[];
65
+ store: { save: ReturnType<typeof vi.fn> };
66
+ } {
67
+ const order: string[] = [];
68
+ return {
69
+ order,
70
+ store: {
71
+ save: vi.fn(async () => {
72
+ order.push("save");
73
+ }),
74
+ },
75
+ workerHost: { start: vi.fn(() => ({ postMessage: vi.fn() })) },
76
+ runner: { run: vi.fn(async () => ({}) as AgentResult) },
77
+ runs: new Map(),
78
+ eventBus: {
79
+ emit: vi.fn((event: string) => {
80
+ order.push(`emit:${event}`);
81
+ }),
82
+ },
83
+ onRunDone: vi.fn(() => {
84
+ order.push("onRunDone");
85
+ }),
86
+ log: vi.fn(),
87
+ } as unknown as ReturnType<typeof makeTracingDeps>;
88
+ }
89
+
90
+ /** flush 微任务队列(void finalizeRun 的 async 链推进到稳定态)。 */
91
+ async function flushMicrotasks(ticks = 20): Promise<void> {
92
+ for (let i = 0; i < ticks; i++) {
93
+ // eslint-disable-next-line no-await-in-loop -- 排空微任务队列的固定 tick 循环,非逐项等待
94
+ await Promise.resolve();
95
+ }
96
+ }
97
+
98
+ // ── finalizeRun 直测 ─────────────────────────────────────────
99
+
100
+ describe("finalizeRun(D5-② 单写点直测)", () => {
101
+ it("四步恰好一次且有序:transition → save → pending:unregister → onRunDone", async () => {
102
+ const run = makeRealRun("wf-fin-1");
103
+ const deps = makeTracingDeps();
104
+
105
+ const transitionSpy = vi.spyOn(run, "transition");
106
+ const ok = await finalizeRun(run, deps, "completed", { context: "test" });
107
+
108
+ expect(ok).toBe(true);
109
+ expect(run.state.status).toBe("done");
110
+ expect(run.state.reason).toBe("completed");
111
+ // 四步各恰好一次
112
+ expect(transitionSpy).toHaveBeenCalledTimes(1);
113
+ expect(deps.store.save).toHaveBeenCalledTimes(1);
114
+ expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
115
+ expect(deps.onRunDone).toHaveBeenCalledTimes(1);
116
+ // 顺序:save → unregister → onRunDone(transition 已同步先行)
117
+ expect(deps.order).toEqual(["save", "emit:pending:unregister", "onRunDone"]);
118
+ // unregister reason = transition 后的 state.reason
119
+ expect(deps.eventBus!.emit).toHaveBeenCalledWith("pending:unregister", {
120
+ id: "wf-fin-1",
121
+ reason: "completed",
122
+ });
123
+ });
124
+
125
+ it("notifyDone:false → onRunDone 不调、unregister 仍发(terminateRunningRuns 真差异经参数承载)", async () => {
126
+ const run = makeRealRun("wf-fin-2");
127
+ const deps = makeTracingDeps();
128
+
129
+ const ok = await finalizeRun(run, deps, "failed", {
130
+ context: "terminateRunningRuns",
131
+ notifyDone: false,
132
+ });
133
+
134
+ expect(ok).toBe(true);
135
+ expect(run.state.reason).toBe("failed");
136
+ expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
137
+ expect(deps.eventBus!.emit).toHaveBeenCalledWith("pending:unregister", {
138
+ id: "wf-fin-2",
139
+ reason: "failed",
140
+ });
141
+ expect(deps.onRunDone).not.toHaveBeenCalled();
142
+ expect(deps.order).toEqual(["save", "emit:pending:unregister"]);
143
+ });
144
+
145
+ it("transition 抛错(并发 abort 抢先终态化)→ 返回 false,后三步全不执行", async () => {
146
+ const run = makeRealRun("wf-fin-3");
147
+ // 抢先终态化——此后 running→done 转移抛 illegal-transition
148
+ run.transition("done", "aborted");
149
+ const deps = makeTracingDeps();
150
+
151
+ const ok = await finalizeRun(run, deps, "failed", { context: "test" });
152
+
153
+ expect(ok).toBe(false);
154
+ // 抢先方已兑现 unregister/onRunDone——本路径让位,不重复执行
155
+ expect(deps.store.save).not.toHaveBeenCalled();
156
+ expect(deps.eventBus!.emit).not.toHaveBeenCalled();
157
+ expect(deps.onRunDone).not.toHaveBeenCalled();
158
+ expect(run.state.reason).toBe("aborted");
159
+ });
160
+
161
+ it("save 抛错(ENOSPC)→ best-effort:unregister + onRunDone 照常(SW-DATA-3 统一)", async () => {
162
+ const run = makeRealRun("wf-fin-4");
163
+ const deps = makeTracingDeps();
164
+ deps.store.save = vi.fn(async () => {
165
+ throw new Error("ENOSPC: no space left on device");
166
+ });
167
+
168
+ const ok = await finalizeRun(run, deps, "failed", { context: "test" });
169
+
170
+ expect(ok).toBe(true);
171
+ expect(run.state.status).toBe("done");
172
+ expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
173
+ expect(deps.onRunDone).toHaveBeenCalledTimes(1);
174
+ });
175
+
176
+ it("onRunDone 抛错 → 被捕获记日志(M12:真实副作用错误不静默吞、不上抛)", async () => {
177
+ const run = makeRealRun("wf-fin-5");
178
+ const deps = makeTracingDeps();
179
+ deps.onRunDone = vi.fn(() => {
180
+ throw new Error("interface notify blew up");
181
+ });
182
+
183
+ await expect(
184
+ finalizeRun(run, deps, "completed", { context: "test" }),
185
+ ).resolves.toBe(true);
186
+ expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
187
+ expect(run.state.status).toBe("done");
188
+ });
189
+ });
190
+
191
+ // ── budget_limited 路径(dispatchAgentCall coda)四步恰好一次 ────────────
192
+
193
+ describe("budget_limited 终态路径(dispatchAgentCall → finalizeRun)", () => {
194
+ it("agent call 后预算超限 → 四步恰好一次(unregister reason=budget_limited + onRunDone)", async () => {
195
+ const run = makeRealRun("wf-budget-1");
196
+ const deps = makeTracingDeps();
197
+ // budget.isExceeded 恒 true——runner.run 成功返回后命中 C-2 coda
198
+ (run.state.budget as unknown as { isExceeded: () => boolean }).isExceeded =
199
+ () => true;
200
+ deps.runner.run = vi.fn(async () =>
201
+ ({ content: "ok", durationMs: 1, error: undefined, toolCalls: [] }) as AgentResult,
202
+ );
203
+ const handlers: WorkerHandlers = {
204
+ onMessage: vi.fn(async () => {}),
205
+ onError: vi.fn(async () => {}),
206
+ onExit: vi.fn(async () => {}),
207
+ };
208
+
209
+ await handleWorkerMessage(
210
+ run,
211
+ { type: "agent-call", callId: 1, opts: { prompt: "p", description: "d" } },
212
+ deps,
213
+ handlers,
214
+ );
215
+ await flushMicrotasks();
216
+
217
+ expect(run.state.status).toBe("done");
218
+ expect(run.state.reason).toBe("budget_limited");
219
+ // save 2 次 = call 完成快照(dispatchAgentCall .then 的常规持久化)+ budget
220
+ // 终态快照(finalizeRun 内)——两次语义不同,收敛前后一致
221
+ expect(deps.store.save).toHaveBeenCalledTimes(2);
222
+ expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
223
+ expect(deps.eventBus!.emit).toHaveBeenCalledWith("pending:unregister", {
224
+ id: "wf-budget-1",
225
+ reason: "budget_limited",
226
+ });
227
+ expect(deps.onRunDone).toHaveBeenCalledTimes(1);
228
+ });
229
+ });
@@ -1,7 +1,7 @@
1
1
  /**
2
- * error-recovery handlers — handleWorkerExit/Error/ScriptError + postBudgetUpdate 测试。
2
+ * worker-message-pump handlers — handleWorkerExit/Error/ScriptError + postBudgetUpdate 测试。
3
3
  *
4
- * 参考 error-recovery-workflow-call.test.ts 的 mock 构建。通过 vi.useFakeTimers() 跳过
4
+ * 参考 worker-message-pump-workflow-call.test.ts 的 mock 构建。通过 vi.useFakeTimers() 跳过
5
5
  * scheduleRebuild 的指数退避(1s/2s/4s)。
6
6
  *
7
7
  * 覆盖:
@@ -23,11 +23,11 @@ import {
23
23
  handleWorkerMessage,
24
24
  postBudgetUpdate,
25
25
  rebuildRuntime,
26
- } from "../error-recovery.ts";
26
+ } from "../worker-message-pump.ts";
27
27
  import { Budget } from "../models/budget.ts";
28
28
  import { RunRuntime } from "../models/run-runtime.ts";
29
29
  import { Trace } from "../models/trace.ts";
30
- import type { AgentResult } from "../models/types.ts";
30
+ import type { AgentResult, DoneReason, RunStatus } from "../models/types.ts";
31
31
  import { WorkflowRun } from "../models/workflow-run.ts";
32
32
  import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
33
33
  import type { WorkerHandle } from "../worker-handle.ts";
@@ -71,26 +71,34 @@ function makeRunningRun(opts: {
71
71
  receivedTerminalMessage: opts.receivedTerminalMessage,
72
72
  },
73
73
  // transition 副作用——run.state.status 由调用方通过 mock 控制后再次断言
74
- transition(target: string, reason?: string): void {
74
+ transition(this: WorkflowRun, target: RunStatus, reason?: DoneReason): void {
75
75
  this.state.status = target;
76
76
  if (target === "done") this.state.reason = reason;
77
77
  },
78
- replaceRuntime(rt: unknown): void {
78
+ replaceRuntime(this: WorkflowRun, rt: NonNullable<WorkflowRun["runtime"]>): void {
79
79
  this.runtime = rt;
80
80
  },
81
81
  } as unknown as WorkflowRun;
82
82
  }
83
83
 
84
- /** LifecycleDeps mock:store/workerHost/runner/eventBus/scheduleTimeBudget 可观察。 */
84
+ /** LifecycleDeps mock:store/workerHost/runner/eventBus/scheduleTimeBudget 可观察。
85
+ * mock 成员 = 真实签名 & vi.fn 能力(交叉纯 Mock 会丢真实签名,传回被测函数即报错)。 */
86
+ type MockLifecycleDeps = Omit<
87
+ LifecycleDeps,
88
+ "store" | "workerHost" | "runner" | "eventBus" | "onRunDone" | "log"
89
+ > & {
90
+ // 真实类型整体保留(RunStore/WorkerHost 等接口成员完整),仅 mock 方法交叉 vi.fn 能力
91
+ store: LifecycleDeps["store"] & { save: LifecycleDeps["store"]["save"] & ReturnType<typeof vi.fn> };
92
+ workerHost: LifecycleDeps["workerHost"] & { start: LifecycleDeps["workerHost"]["start"] & ReturnType<typeof vi.fn> };
93
+ runner: LifecycleDeps["runner"] & { run: LifecycleDeps["runner"]["run"] & ReturnType<typeof vi.fn> };
94
+ eventBus: NonNullable<LifecycleDeps["eventBus"]> & { emit: NonNullable<LifecycleDeps["eventBus"]>["emit"] & ReturnType<typeof vi.fn> };
95
+ onRunDone: LifecycleDeps["onRunDone"] & ReturnType<typeof vi.fn>;
96
+ log: LifecycleDeps["log"] & ReturnType<typeof vi.fn>;
97
+ };
98
+
85
99
  function makeDeps(opts: {
86
100
  scheduleTimeBudget?: LifecycleDeps["scheduleTimeBudget"];
87
- } = {}): LifecycleDeps & {
88
- store: { save: ReturnType<typeof vi.fn> };
89
- workerHost: { start: ReturnType<typeof vi.fn> };
90
- eventBus: { emit: ReturnType<typeof vi.fn> };
91
- onRunDone: ReturnType<typeof vi.fn>;
92
- log: ReturnType<typeof vi.fn>;
93
- } {
101
+ } = {}): MockLifecycleDeps {
94
102
  return {
95
103
  store: { save: vi.fn(async () => {}) },
96
104
  workerHost: { start: vi.fn(() => ({ postMessage: vi.fn() })) },
@@ -344,7 +352,7 @@ describe("rebuildRuntime", () => {
344
352
 
345
353
  it("带 budgetTimeMs 时重排 scheduleTimeBudget 计时器", () => {
346
354
  const run = makeRunningRun({ budgetTimeMs: 5000 });
347
- const scheduleTimeBudget = vi.fn(() => undefined);
355
+ const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
348
356
  const deps = makeDeps({ scheduleTimeBudget });
349
357
 
350
358
  rebuildRuntime(run, deps, makeHandlers());
@@ -413,7 +421,7 @@ describe("race-F3: rebuild 时间预算折算", () => {
413
421
  const run = makeRunningRun({ budgetTimeMs: 5000 });
414
422
  // 已耗 70%(3500ms)→ 剩余 1500ms;fake Date 冻结,elapsed 精确
415
423
  run.meta.startedAt = new Date(Date.now() - 3500).toISOString();
416
- const scheduleTimeBudget = vi.fn(() => undefined);
424
+ const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
417
425
  const deps = makeDeps({ scheduleTimeBudget });
418
426
 
419
427
  rebuildRuntime(run, deps, makeHandlers());
@@ -429,7 +437,7 @@ describe("race-F3: rebuild 时间预算折算", () => {
429
437
  const run = makeRunningRun({ budgetTimeMs: 5000 });
430
438
  // 已耗 6000ms > 预算 5000ms(退避 advance 1000ms 后已耗 7000ms,仍耗尽)
431
439
  run.meta.startedAt = new Date(Date.now() - 6000).toISOString();
432
- const scheduleTimeBudget = vi.fn(() => undefined);
440
+ const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
433
441
  const deps = makeDeps({ scheduleTimeBudget });
434
442
 
435
443
  const p = handleScriptError(run, "boom", [], deps, makeHandlers());
@@ -452,7 +460,7 @@ describe("race-F3: rebuild 时间预算折算", () => {
452
460
 
453
461
  it("预算未耗尽(fresh run)→ 照常 rebuild,不误转 time_limited(防误伤回归)", async () => {
454
462
  const run = makeRunningRun({ budgetTimeMs: 5000 }); // startedAt ≈ now,remaining 满
455
- const scheduleTimeBudget = vi.fn(() => undefined);
463
+ const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
456
464
  const deps = makeDeps({ scheduleTimeBudget });
457
465
 
458
466
  const p = handleScriptError(run, "boom", [], deps, makeHandlers());
@@ -745,7 +753,7 @@ describe("rebuildRuntime 可观察性(OB3 日志点)", () => {
745
753
 
746
754
  it("U7: 带 budgetTimeMs + scheduleTimeBudget 注入时含 L2(在 L1 之后、L3 之前,payload 含 budgetTimeMs)", () => {
747
755
  const run = makeRealRun("wf-rebuild-log-2", { budgetTimeMs: 5000 });
748
- const scheduleTimeBudget = vi.fn(() => undefined);
756
+ const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
749
757
  const deps = makeDeps({ scheduleTimeBudget });
750
758
 
751
759
  rebuildRuntime(run, deps, makeHandlers());
@@ -1,4 +1,4 @@
1
- // src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts
1
+ // src/orchestration/__tests__/worker-message-pump-postmessage-defense.test.ts
2
2
  //
3
3
  // W2: 主线程层 postMessage 防御测试。
4
4
  //
@@ -35,13 +35,13 @@ vi.mock("../../core/logger.ts", () => ({
35
35
  getLogger: () => loggerMock,
36
36
  }));
37
37
 
38
- import { handleWorkerMessage, postBudgetUpdate } from "../error-recovery.ts";
38
+ import { handleWorkerMessage, postBudgetUpdate } from "../worker-message-pump.ts";
39
39
  import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
40
40
  import type { WorkflowRun } from "../models/workflow-run.ts";
41
41
 
42
42
  const __dirname = dirname(fileURLToPath(import.meta.url));
43
- const ERROR_RECOVERY_SRC = readFileSync(
44
- join(__dirname, "../error-recovery.ts"),
43
+ const WORKER_MESSAGE_PUMP_SRC = readFileSync(
44
+ join(__dirname, "../worker-message-pump.ts"),
45
45
  "utf-8",
46
46
  );
47
47
 
@@ -273,7 +273,7 @@ describe("W2b: dispatchWorkflowCall postResult 防御 DataCloneError", () => {
273
273
  // 子进程)。利用这一点构造行为测试——往 cached.result 里塞不可克隆值(function),
274
274
  // mock postMessage 在首次发送 agent-result 时抛 DataCloneError,验证 fallback 路径。
275
275
  //
276
- // 另外补充:验证 error-recovery.ts 源码中三处 postMessage 调用点都有 try/catch 包裹,
276
+ // 另外补充:验证 worker-message-pump.ts 源码中三处 postMessage 调用点都有 try/catch 包裹,
277
277
  // 防止未来重构误删防御(类似 worker-script-builder.test.ts 的源码字符串断言模式)。
278
278
 
279
279
  /** 构造 status="running" 且 calls 已含一个 done 结果(含不可克隆成员)的 mock run。
@@ -376,7 +376,7 @@ describe("W2c: postAgentResult 防御(源码结构断言)", () => {
376
376
  }
377
377
 
378
378
  it("postAgentResult 包含 try/catch + fallback result", () => {
379
- const match = ERROR_RECOVERY_SRC.match(/function postAgentResult\([\s\S]*?\n\}/);
379
+ const match = WORKER_MESSAGE_PUMP_SRC.match(/function postAgentResult\([\s\S]*?\n\}/);
380
380
  expect(match, "postAgentResult 函数定义应存在").toBeTruthy();
381
381
  expect(hasTryCatchAroundPostMessage(match![0])).toBe(true);
382
382
  // fallback 通过共享工厂 makeSerializeFailedResult 构造纯字符串 result(必可克隆)
@@ -387,14 +387,14 @@ describe("W2c: postAgentResult 防御(源码结构断言)", () => {
387
387
  });
388
388
 
389
389
  it("postBudgetUpdate 包含 try/catch", () => {
390
- const match = ERROR_RECOVERY_SRC.match(/export function postBudgetUpdate\([\s\S]*?\n\}/);
390
+ const match = WORKER_MESSAGE_PUMP_SRC.match(/export function postBudgetUpdate\([\s\S]*?\n\}/);
391
391
  expect(match, "postBudgetUpdate 函数定义应存在").toBeTruthy();
392
392
  expect(hasTryCatchAroundPostMessage(match![0])).toBe(true);
393
393
  });
394
394
 
395
395
  it("dispatchWorkflowCall postResult 闭包包含 try/catch + fallback", () => {
396
396
  // postResult 是 const 闭包,匹配到下一个 }; 结尾
397
- const match = ERROR_RECOVERY_SRC.match(/const postResult = \(result[\s\S]*?\n \};/);
397
+ const match = WORKER_MESSAGE_PUMP_SRC.match(/const postResult = \(result[\s\S]*?\n \};/);
398
398
  expect(match, "postResult 闭包定义应存在").toBeTruthy();
399
399
  expect(hasTryCatchAroundPostMessage(match![0])).toBe(true);
400
400
  expect(match![0]).toContain("Workflow result serialization failed");
@@ -1,4 +1,4 @@
1
- // src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts
1
+ // src/orchestration/__tests__/worker-message-pump-serialize-failed-result.test.ts
2
2
  //
3
3
  // makeSerializeFailedResult 独立单测(W2 防御关键纯函数)。
4
4
  //
@@ -15,7 +15,7 @@
15
15
 
16
16
  import { describe, expect, it } from "vitest";
17
17
 
18
- import { makeSerializeFailedResult } from "../error-recovery.ts";
18
+ import { makeSerializeFailedResult } from "../worker-message-pump.ts";
19
19
 
20
20
  describe("makeSerializeFailedResult", () => {
21
21
  it("返回 {content:'', error:'<prefix>: <errMsg>'} 形状(必可克隆 fallback)", () => {
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { describe, expect, it, vi } from "vitest";
12
12
 
13
- import { handleWorkerMessage } from "../error-recovery.ts";
13
+ import { handleWorkerMessage } from "../worker-message-pump.ts";
14
14
  import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
15
15
  import type { WorkflowRun } from "../models/workflow-run.ts";
16
16
 
@@ -105,7 +105,7 @@ function runWorker(userScript: string, opts: RunOptions = {}): Promise<RunResult
105
105
  worker.on("message", (raw: unknown) => {
106
106
  if (isAgentCall(raw)) {
107
107
  result.agentCallCount += 1;
108
- opts.onAgentCall?.(raw as { callId: number; opts: Record<string, unknown> }, worker);
108
+ opts.onAgentCall?.(raw as unknown as { callId: number; opts: Record<string, unknown> }, worker);
109
109
  return;
110
110
  }
111
111
  if (isReturn(raw)) {
@@ -225,7 +225,7 @@ describe("buildWorkerScript — W2 agent() returnMeta mode", () => {
225
225
  // ── thinkingLevel 参数透传(agent() 三分支) ──
226
226
  // 验证 thinkingLevel 在 string / task-agent / object.prompt 三个分支均被透传至
227
227
  // agent-call 的 opts,且加入 _knownFields 白名单(object.prompt 分支整体透传,
228
- // 不被 unknown-fields warn 误报)。底层 AgentCallOpts→mapToExecuteOptions→
228
+ // 不被 unknown-fields warn 误报)。底层 AgentCallOpts(D6 合流后 EnginePort 直传,
229
229
  // buildSpawnArgs 拼 pi CLI --model provider/modelId:thinkingLevel 已打通,
230
230
  // 此处只验入口层 wiring。
231
231
 
@@ -24,7 +24,7 @@ vi.mock("../lifecycle.ts", () => ({
24
24
  scheduleTimeBudget: vi.fn(() => undefined),
25
25
  }));
26
26
 
27
- import { handleWorkerMessage } from "../error-recovery.ts";
27
+ import { handleWorkerMessage } from "../worker-message-pump.ts";
28
28
  import { executeNestedWorkflow, type LauncherDeps } from "../launcher.ts";
29
29
  import { runWorkflow } from "../lifecycle.ts";
30
30
  import { Budget } from "../models/budget.ts";
@@ -10,7 +10,7 @@
10
10
  * agent ref 处理(systemPrompt/model/thinkingLevel)已移交 resolveIdentity(execution 层,
11
11
  * 经 getAgentConfig + resolveModel 完整覆盖),消除双重注入与 model 层级混乱。
12
12
  *
13
- * 调用方:engine/error-recovery.ts dispatchAgentCall(每次 agent-call 消息)。
13
+ * 调用方:engine/worker-message-pump.ts dispatchAgentCall(每次 agent-call 消息)。
14
14
  * - skill → resolveSkillPath → skillPath(--skill)
15
15
  * - schema → 结构化输出指令内容直传 appendSystemPrompt(--append-system-prompt)+ schemaEnv(PI_WORKFLOW_SCHEMA)
16
16
  *
@@ -58,6 +58,57 @@ function formatMessage(name: string, errors: readonly unknown[]): string {
58
58
  );
59
59
  }
60
60
 
61
+ // ── validateRunArgs 阶段 helper(按 预处理/编译 提取,主函数只留编排) ────
62
+
63
+ /** schema.properties 提取(畸形/缺失 → {},null-scan 在空 properties 上安全退化)。 */
64
+ function extractSchemaProperties(schema: Record<string, unknown>): Record<string, unknown> {
65
+ return schema.properties !== null && typeof schema.properties === "object"
66
+ ? (schema.properties as Record<string, unknown>)
67
+ : {};
68
+ }
69
+
70
+ /** 单个 property 声明是否 nullable(type 含 "null" 或 type === "null")。 */
71
+ function isNullableProp(prop: unknown): boolean {
72
+ if (prop === null || typeof prop !== "object") return false;
73
+ const propType = (prop as Record<string, unknown>).type;
74
+ return Array.isArray(propType)
75
+ ? (propType as unknown[]).includes("null")
76
+ : propType === "null";
77
+ }
78
+
79
+ /**
80
+ * null-scan:null 值视为缺失(coerceTypes 会把 null→"" 放行 required,绕过 fail-fast)。
81
+ * 只删 schema 未声明 nullable 的键——type 含 "null"(如 ["string","null"])的合法 null
82
+ * 输入保留(m3 exec-review M1 探针实证:全键删除会拒掉 nullable required 的合法值)。
83
+ */
84
+ function deleteNonNullableNullArgs(
85
+ args: Record<string, unknown>,
86
+ schema: Record<string, unknown>,
87
+ ): void {
88
+ const properties = extractSchemaProperties(schema);
89
+ for (const key of Object.keys(args)) {
90
+ if (args[key] !== null) continue;
91
+ if (!isNullableProp(properties[key])) delete args[key];
92
+ }
93
+ }
94
+
95
+ /** ajv 编译(畸形 schema → 结构化 ArgsValidationError,不泄漏原始 throw)。 */
96
+ function compileSchema(
97
+ parameters: Record<string, unknown>,
98
+ scriptName: string,
99
+ ): ReturnType<Ajv["compile"]> {
100
+ try {
101
+ return ajv.compile(parameters);
102
+ } catch (err) {
103
+ // 真畸形 schema(如 type:'not-a-type')→ 结构化 ArgsValidationError,不泄漏原始 throw
104
+ const detail = err instanceof Error ? err.message : String(err);
105
+ throw new ArgsValidationError(
106
+ scriptName,
107
+ `Workflow '${scriptName}' has an invalid parameter schema: ${detail}. Read the workflow script file (location from <available_workflows>) to inspect it.`,
108
+ );
109
+ }
110
+ }
111
+
61
112
  /**
62
113
  * 校验 spec.parameters(JSON Schema draft-07)对 spec.args 的约束。
63
114
  *
@@ -79,38 +130,9 @@ export function validateRunArgs(spec: RunSpec): void {
79
130
  );
80
131
  }
81
132
 
82
- // null-scan:null 值视为缺失(coerceTypes 会把 null→"" 放行 required,绕过 fail-fast)。
83
- // 只删 schema 未声明 nullable 的键——type 含 "null"(如 ["string","null"])的合法 null
84
- // 输入保留(m3 exec-review M1 探针实证:全键删除会拒掉 nullable required 的合法值)。
85
- const schema = parameters as Record<string, unknown>;
86
- const properties =
87
- schema.properties !== null && typeof schema.properties === "object"
88
- ? (schema.properties as Record<string, unknown>)
89
- : {};
90
- for (const key of Object.keys(args)) {
91
- if (args[key] !== null) continue;
92
- const prop = properties[key];
93
- let isNullable = false;
94
- if (prop !== null && typeof prop === "object") {
95
- const propType = (prop as Record<string, unknown>).type;
96
- isNullable = Array.isArray(propType)
97
- ? (propType as unknown[]).includes("null")
98
- : propType === "null";
99
- }
100
- if (!isNullable) delete args[key];
101
- }
133
+ deleteNonNullableNullArgs(args, parameters);
102
134
 
103
- let validate: ReturnType<Ajv["compile"]>;
104
- try {
105
- validate = ajv.compile(parameters);
106
- } catch (err) {
107
- // 真畸形 schema(如 type:'not-a-type')→ 结构化 ArgsValidationError,不泄漏原始 throw
108
- const detail = err instanceof Error ? err.message : String(err);
109
- throw new ArgsValidationError(
110
- scriptName,
111
- `Workflow '${scriptName}' has an invalid parameter schema: ${detail}. Read the workflow script file (location from <available_workflows>) to inspect it.`,
112
- );
113
- }
135
+ const validate = compileSchema(parameters, scriptName);
114
136
 
115
137
  if (!validate(args)) {
116
138
  throw new ArgsValidationError(