@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
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { describe, expect, it, vi } from "vitest";
14
14
 
15
- import { handleWorkerMessage } from "../error-recovery.ts";
15
+ import { handleWorkerMessage } from "../worker-message-pump.ts";
16
16
  import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
17
17
  import { Trace } from "../models/trace.ts";
18
18
  import type { WorkflowRun } from "../models/workflow-run.ts";
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { describe, expect, it, vi } from "vitest";
10
10
 
11
- import { handleWorkerMessage } from "../error-recovery.ts";
11
+ import { handleWorkerMessage } from "../worker-message-pump.ts";
12
12
  import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
13
13
  import { Trace } from "../models/trace.ts";
14
14
  import type { WorkflowRun } from "../models/workflow-run.ts";
@@ -122,7 +122,7 @@ describe("U5: widgetKey 格式", () => {
122
122
  expect(setWidget.mock.calls.length).toBeGreaterThanOrEqual(1);
123
123
  });
124
124
 
125
- const widgetKey = setWidget.mock.calls[0][0] as string;
125
+ const widgetKey = setWidget.mock.calls[0]![0] as string;
126
126
  expect(widgetKey).toBe("subagent-stream-wf-test-123-2");
127
127
  });
128
128
  });
@@ -123,6 +123,20 @@ describe("validateRunArgs — 校验语义", () => {
123
123
  }
124
124
  });
125
125
 
126
+ it("TC6c: parameters 非 object(数组)→ ArgsValidationError「expected object」", () => {
127
+ // 锚定 schema 形状守卫分支:数组 parameters 在 null-scan/compile 之前 fail-fast
128
+ const spec = makeSpec([{ target: "string" }] as unknown as Record<string, unknown>, { a: 1 });
129
+ try {
130
+ validateRunArgs(spec);
131
+ expect.unreachable("should throw");
132
+ } catch (err) {
133
+ expect(err).toBeInstanceOf(ArgsValidationError);
134
+ expect((err as ArgsValidationError).message).toContain(
135
+ "invalid parameter schema (expected object)",
136
+ );
137
+ }
138
+ });
139
+
126
140
  it("TC6b: strictSchema:false 容忍自定义关键字与 format(design-review major-3 回归)", () => {
127
141
  const spec = makeSpec(
128
142
  { type: "object", properties: { target: { type: "string", format: "uri" } }, required: ["target"], "x-custom": true },
@@ -19,7 +19,7 @@ import {
19
19
  import {
20
20
  handleWorkerError,
21
21
  resetRebuildFailureInjectionForTest,
22
- } from "../error-recovery.ts";
22
+ } from "../worker-message-pump.ts";
23
23
  import { Budget } from "../models/budget.ts";
24
24
  import { RunRuntime } from "../models/run-runtime.ts";
25
25
  import { Trace } from "../models/trace.ts";
@@ -22,7 +22,7 @@ import {
22
22
  handleWorkerError,
23
23
  handleWorkerExit,
24
24
  handleWorkerMessage,
25
- } from "../error-recovery.ts";
25
+ } from "../worker-message-pump.ts";
26
26
  import { Budget } from "../models/budget.ts";
27
27
  import { RunRuntime } from "../models/run-runtime.ts";
28
28
  import { toRunSnapshot } from "../run-snapshot.ts";
@@ -69,12 +69,14 @@ function makeDeps(opts: {
69
69
  emitThrows?: boolean;
70
70
  /** onRunDone 同步抛错(模拟 evictDoneRunsBeyondCap 等收尾异常)。 */
71
71
  onRunDoneThrows?: boolean;
72
- } = {}): LifecycleDeps & {
73
- store: { save: ReturnType<typeof vi.fn> };
74
- workerHost: { start: ReturnType<typeof vi.fn> };
75
- eventBus: { emit: ReturnType<typeof vi.fn> };
76
- onRunDone: ReturnType<typeof vi.fn>;
77
- log: ReturnType<typeof vi.fn>;
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>;
78
80
  } {
79
81
  return {
80
82
  store: { save: vi.fn(async () => {}) },
@@ -239,7 +241,7 @@ describe("[OR-6] log 消息消费 + 未知类型 default 留痕", () => {
239
241
  expect(run.state.errorLogs).toEqual([{ level: "log", message: "step 1 done" }]);
240
242
  expect(run.state.status).toBe("running"); // 不触发终态
241
243
  expect(deps.store.save).not.toHaveBeenCalled();
242
- expect(deps.log).toHaveBeenCalledWith("debug", "workflow:error-recovery", "worker log", {
244
+ expect(deps.log).toHaveBeenCalledWith("debug", "workflow:worker-message-pump", "worker log", {
243
245
  runId: "wf-log-1",
244
246
  phase: "build",
245
247
  message: "step 1 done",
@@ -289,7 +291,7 @@ describe("[OR-6] log 消息消费 + 未知类型 default 留痕", () => {
289
291
  await handleWorkerMessage(run, { type: "future-unknown-type", payload: 1 }, deps, makeHandlers());
290
292
 
291
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);
292
- expect(deps.log).toHaveBeenCalledWith("warn", "workflow:error-recovery", "unknown worker message type", {
294
+ expect(deps.log).toHaveBeenCalledWith("warn", "workflow:worker-message-pump", "unknown worker message type", {
293
295
  runId: "wf-log-3",
294
296
  type: "future-unknown-type",
295
297
  });
@@ -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
+ });
@@ -24,13 +24,13 @@ import { WorkflowRun } from "../models/workflow-run.ts";
24
24
  import { FileRunStore } from "../file-run-store.ts";
25
25
 
26
26
  let dataRoot: string;
27
- let logSpy: ReturnType<typeof vi.fn>;
27
+ let logSpy: ReturnType<typeof vi.fn<(level: import("../../core/logger.ts").LogLevel, component: string, message: string, data?: unknown) => void>>;
28
28
  let store: FileRunStore;
29
29
 
30
30
  beforeEach(() => {
31
31
  resetCoreForTests();
32
32
  dataRoot = mkdtempSync(join(tmpdir(), "file-run-store-"));
33
- logSpy = vi.fn();
33
+ logSpy = vi.fn((_level: import("../../core/logger.ts").LogLevel, _component: string, _message: string, _data?: unknown) => {});
34
34
  const host: HostServices = {
35
35
  dataRoot: () => dataRoot,
36
36
  log: logSpy,
@@ -41,7 +41,7 @@ beforeEach(() => {
41
41
 
42
42
  afterEach(() => {
43
43
  resetCoreForTests();
44
- rmSync(dataRoot, { recursive: true, force: true });
44
+ rmSync(dataRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
45
45
  });
46
46
 
47
47
  /** 构造可持久化的 WorkflowRun(对齐 lifecycle.test.ts makeEvictableRun 模式)。 */
@@ -328,12 +328,13 @@ describe("FileRunStore — 版本衔接与 live-strip(U8 / ⛔5,D4 裁决)
328
328
 
329
329
  it("⛔5 live 字段 strip 后落盘(落盘行无 live 键,内存 run 不受影响)", async () => {
330
330
  const run = makeRun("wf-livestrip-1");
331
- const node = {
331
+ const node: import("../models/types.ts").ExecutionTraceNode = {
332
332
  stepIndex: 0,
333
333
  agent: "coder",
334
334
  task: "do work",
335
335
  model: "test-model",
336
- status: "running" as const,
336
+ status: "running",
337
+ live: undefined,
337
338
  };
338
339
  run.state.trace.append(node);
339
340
  const AgentCallMod = await import("../models/agent-call.ts");
@@ -378,7 +379,7 @@ describe("FileRunStore — stateFilePath / 端口语义", () => {
378
379
  join(altRoot, "workflow-state", "wf-x-2.jsonl"),
379
380
  );
380
381
  } finally {
381
- rmSync(altRoot, { recursive: true, force: true });
382
+ rmSync(altRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
382
383
  }
383
384
  });
384
385
 
@@ -618,7 +618,7 @@ describe("runAndWait model 透传", () => {
618
618
  );
619
619
  expect(result.reason).toBe("completed");
620
620
  expect(vi.mocked(runWorkflow)).toHaveBeenCalledTimes(1);
621
- const spec = vi.mocked(runWorkflow).mock.calls[0][0] as RunSpec;
621
+ const spec = vi.mocked(runWorkflow).mock.calls[0]![0] as RunSpec;
622
622
  expect(spec.model).toBe("zai-coding-cn/glm-5.3-flash");
623
623
  });
624
624
 
@@ -628,7 +628,7 @@ describe("runAndWait model 透传", () => {
628
628
  setupRunWorkflow(childRun);
629
629
 
630
630
  await runAndWait("child-wf", {}, deps);
631
- const spec = vi.mocked(runWorkflow).mock.calls[0][0] as RunSpec;
631
+ const spec = vi.mocked(runWorkflow).mock.calls[0]![0] as RunSpec;
632
632
  expect(spec.model).toBeUndefined();
633
633
  });
634
634
  });
@@ -324,7 +324,7 @@ describe("[OR-7] signal abort listener run 终态移除", () => {
324
324
  describe("[B-2] runWorkflow fail-fast throw 路径不泄漏 signal abort listener", () => {
325
325
  /** signal 上当前真实挂载的 abort listener 数(EventTarget 权威查询,非 spy 计数)。 */
326
326
  function liveListenerCount(signal: AbortSignal): number {
327
- return getEventListeners(signal).length;
327
+ return getEventListeners(signal, "abort").length;
328
328
  }
329
329
 
330
330
  it("budgetTimeMs 越界 fail-fast 重试 12 次 → 每次拒绝后 listener 计数归零(无 MaxListeners 累积)", async () => {
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { describe, expect, it, vi } from "vitest";
11
11
 
12
- import { rebuildRuntime } from "../error-recovery.ts";
12
+ import { rebuildRuntime } from "../worker-message-pump.ts";
13
13
  import { runWorkflow } from "../lifecycle.ts";
14
14
  import type { RunSpec } from "../models/run-spec.ts";
15
15
  import type { LifecycleDeps } from "../models/ports.ts";
@@ -73,11 +73,11 @@ describe("A4 rebuild 后 _runId 稳定(tier-1 §7.1)", () => {
73
73
  const runId = await runWorkflow(spec, deps);
74
74
  expect(deps.startCalls.length).toBe(1);
75
75
 
76
- const run = deps.runs.get(runId);
76
+ const run = deps.runs.get(runId)!;
77
77
  expect(run).toBeTruthy();
78
78
  expect(run.state.status).toBe("running");
79
79
 
80
- // 触发 rebuild(对齐 error-recovery handleWorkerError 的恢复路径)
80
+ // 触发 rebuild(对齐 worker-message-pump handleWorkerError 的恢复路径)
81
81
  rebuildRuntime(run, deps, {
82
82
  onMessage: vi.fn(),
83
83
  onError: vi.fn(),
@@ -87,7 +87,7 @@ function makeRunningRealRun(
87
87
  const terminate = vi.fn(async () => {});
88
88
  const controller = new AbortController();
89
89
  const abort = vi.spyOn(controller, "abort");
90
- const worker = { terminate, postMessage: vi.fn() } as unknown as Parameters<typeof RunRuntime.prototype.constructor>[0];
90
+ const worker = { terminate, postMessage: vi.fn() } as unknown as ConstructorParameters<typeof RunRuntime>[0];
91
91
  const runtime = new RunRuntime(
92
92
  worker as never,
93
93
  controller,
@@ -217,7 +217,7 @@ describe("runWorkflow", () => {
217
217
  const deps = makeDeps();
218
218
  // worker.start 被调时探测 runs 注册状态——证明 runs.set 在 assignRuntime 之后
219
219
  let runsSizeAtWorkerStart = -1;
220
- deps.workerHost.start = vi.fn(() => {
220
+ vi.spyOn(deps.workerHost, "start").mockImplementation(() => {
221
221
  runsSizeAtWorkerStart = deps.runs.size;
222
222
  return { postMessage: vi.fn(), terminate: vi.fn(async () => {}) };
223
223
  });
@@ -513,7 +513,7 @@ describe("terminateRunningRuns", () => {
513
513
  expect(terminateB).toHaveBeenCalledTimes(1);
514
514
  });
515
515
 
516
- it("单 run save 抛错不中断其余(其余 run 仍落盘 + unregister)", async () => {
516
+ it("单 run save 抛错不中断其余(save best-effort:unregister 仍发 + 其余 run 照常落盘)", async () => {
517
517
  const { run: bad } = makeRunningRealRun("wf-term-err");
518
518
  const { run: good } = makeRunningRealRun("wf-term-ok");
519
519
  const deps = makeDeps();
@@ -525,10 +525,12 @@ describe("terminateRunningRuns", () => {
525
525
 
526
526
  await terminateRunningRuns(deps, "Session shutdown: run terminated");
527
527
 
528
- // 抛错的 run:transition 先于 save,状态已转 done,但 unregister save 失败短路
528
+ // [D5-② finalizeRun 收敛] save 失败 best-effort(SW-DATA-3 统一):transition 先于
529
+ // save,状态已转 done;与收敛前不同,unregister 不再被 save 失败短路——否则
530
+ // pending 通知幽灵注销(列表残留永不清理的 running 条目)
529
531
  expect(bad.state.status).toBe("done");
530
532
  expect(bad.state.reason).toBe("failed");
531
- expect(deps.eventBus.emit).not.toHaveBeenCalledWith("pending:unregister", {
533
+ expect(deps.eventBus.emit).toHaveBeenCalledWith("pending:unregister", {
532
534
  id: "wf-term-err",
533
535
  reason: "failed",
534
536
  });
@@ -80,7 +80,7 @@ describe("[F1] 不可克隆 return → run failed(非悬挂)— e2e", () =>
80
80
  // worker 侧 fallback 消息经 handleScriptError 超限路径写入归因
81
81
  expect(run.state.error).toContain("structured-clone failed");
82
82
  // 终态副作用齐全(对比 SW-DATA-3/F1 前的幽灵悬挂:无 unregister / 无 onRunDone)
83
- expect(deps.eventBus.emit).toHaveBeenCalledWith(
83
+ expect(deps.eventBus!.emit).toHaveBeenCalledWith(
84
84
  "pending:unregister",
85
85
  expect.objectContaining({ id: runId, reason: "failed" }),
86
86
  );
@@ -14,6 +14,7 @@
14
14
  import { describe, expect, it } from "vitest";
15
15
 
16
16
  import type { ExecutionRecord } from "../../execution/types.ts";
17
+ import type { ExecutionTraceNode } from "../models/types.ts";
17
18
  import { AgentCall } from "../models/agent-call.ts";
18
19
  import { Budget } from "../models/budget.ts";
19
20
  import { Trace } from "../models/trace.ts";
@@ -54,7 +55,7 @@ function makeLiveRecord(id: string): ExecutionRecord {
54
55
  agent: "coder",
55
56
  model: "test-model",
56
57
  thinkingLevel: undefined,
57
- mode: "sync",
58
+ mode: "background",
58
59
  task: "do work",
59
60
  slug: "do-work",
60
61
  startedAt: 0,
@@ -206,12 +207,13 @@ describe("run-snapshot — toRunSnapshot/fromRunSnapshot 往返等值", () => {
206
207
  describe("run-snapshot — live-strip 防御内聚", () => {
207
208
  it("trace 节点与 calls[].traceNode 带 live → 序列化输出无 live 键", () => {
208
209
  const run = makeRun("wf-live-1");
209
- const node = {
210
+ const node: ExecutionTraceNode = {
210
211
  stepIndex: 0,
211
212
  agent: "coder",
212
213
  task: "do work",
213
214
  model: "test-model",
214
- status: "running" as const,
215
+ status: "running",
216
+ live: undefined,
215
217
  };
216
218
  run.state.trace.append(node);
217
219
  const call = new AgentCall(0, { prompt: "do work" }, node);
@@ -229,12 +231,13 @@ describe("run-snapshot — live-strip 防御内聚", () => {
229
231
 
230
232
  it("strip 产出新对象,不 mutate 内存 run(save 后 run 可继续跑)", () => {
231
233
  const run = makeRun("wf-live-2");
232
- const node = {
234
+ const node: ExecutionTraceNode = {
233
235
  stepIndex: 0,
234
236
  agent: "coder",
235
237
  task: "do work",
236
238
  model: "test-model",
237
- status: "running" as const,
239
+ status: "running",
240
+ live: undefined,
238
241
  };
239
242
  run.state.trace.append(node);
240
243
  node.live = makeLiveRecord("run-0");
@@ -16,6 +16,7 @@ import { readFileSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
 
18
18
  import { lintAgentMeta, lintScript } from "../script-lint.ts";
19
+ import type { LintFinding } from "../script-lint.ts";
19
20
  import { parseResourceMeta } from "../../shared/meta-parser.ts";
20
21
 
21
22
  const WORKFLOWS_DIR = join(__dirname, "../../../workflows");
@@ -491,6 +492,22 @@ describe("MF-4 回归:非字面量实参 agent(callVar) / agent(expr()) 跳过
491
492
  expect(descWarnings).toHaveLength(1);
492
493
  expect(descWarnings[0].line).toBe(2);
493
494
  });
495
+
496
+ it("`agent(` 换行 `{` 的多行字面量调用保留追踪(argTail 为空分支)", () => {
497
+ // agent( 与 { 分离两行:argTail 为空不跳过,范围跨三行闭合——缺 description
498
+ // 报 1 条且行号落在调用起点(锚定 matchAgentCallHead 的空 argTail 保留路径)
499
+ const src = [
500
+ `await agent(`,
501
+ ` { prompt: "x" }`,
502
+ `);`,
503
+ ``,
504
+ ].join("\n");
505
+ const result = lintScript(src);
506
+
507
+ const descWarnings = warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message));
508
+ expect(descWarnings).toHaveLength(1);
509
+ expect(descWarnings[0].line).toBe(1);
510
+ });
494
511
  });
495
512
 
496
513
  // ── MF-4 回归:checkAgentDescription 的两个跳过分支 ────────────────
@@ -68,6 +68,7 @@ export function mkCtx(
68
68
  ui: mkUi(),
69
69
  // pi 在 xyz-agent 下以 rpc 子进程模式运行;rpc 模式 hasUI 为 true(SDK 契约注释)
70
70
  mode: "rpc",
71
+ scopedModels: [],
71
72
  hasUI: true,
72
73
  cwd: "/tmp",
73
74
  sessionManager: {
@@ -168,6 +169,7 @@ export function mkPi(
168
169
  registerTool: vi.fn(),
169
170
  registerCommand: vi.fn(),
170
171
  registerShortcut: vi.fn(),
172
+ registerMarkdownTransformer: vi.fn(),
171
173
  registerFlag: vi.fn(),
172
174
  getFlag: vi.fn(() => undefined),
173
175
  registerMessageRenderer: vi.fn(),
@@ -18,7 +18,7 @@
18
18
  * `void handlers.onXxx(...)` 无人接 → unhandledRejection + pending:unregister / onRunDone
19
19
  * 不执行(pending 通知幽灵注销)。修复后 save 失败仅 logger.error,状态机继续推进。
20
20
  *
21
- * mock 构建参考 error-recovery-handlers.test.ts(plain-object WorkflowRun mock)。
21
+ * mock 构建参考 worker-message-pump-handlers.test.ts(plain-object WorkflowRun mock)。
22
22
  */
23
23
  import { describe, expect, it, vi } from "vitest";
24
24
 
@@ -27,16 +27,17 @@ import {
27
27
  handleWorkerMessage,
28
28
  handleScriptError,
29
29
  handleWorkerError,
30
- } from "../error-recovery.ts";
30
+ } from "../worker-message-pump.ts";
31
31
  import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
32
+ import type { DoneReason, RunStatus } from "../models/types.ts";
32
33
  import type { WorkflowRun } from "../models/workflow-run.ts";
33
34
  import type { WorkerHandle } from "../worker-handle.ts";
34
35
 
35
- /** [F1] 归因文案——与 error-recovery.ts 常量一致(不直接 import 常量以锚定对外文案)。 */
36
+ /** [F1] 归因文案——与 worker-message-pump.ts 常量一致(不直接 import 常量以锚定对外文案)。 */
36
37
  const EXITED_WITHOUT_RESULT_MSG =
37
38
  "worker exited before delivering a result (return value may not be structured-cloneable)";
38
39
 
39
- // ── helpers(对齐 error-recovery-handlers.test.ts)─────────────────
40
+ // ── helpers(对齐 worker-message-pump-handlers.test.ts)─────────────────
40
41
 
41
42
  interface RunMockOpts {
42
43
  workerErrorCount?: number;
@@ -70,11 +71,11 @@ function makeRunningRun(opts: RunMockOpts = {}): WorkflowRun {
70
71
  worker: { postMessage: vi.fn() },
71
72
  receivedTerminalMessage: opts.receivedTerminalMessage,
72
73
  },
73
- transition(target: string, reason?: string): void {
74
+ transition(this: WorkflowRun, target: RunStatus, reason?: DoneReason): void {
74
75
  this.state.status = target;
75
76
  if (target === "done") this.state.reason = reason;
76
77
  },
77
- replaceRuntime(rt: unknown): void {
78
+ replaceRuntime(this: WorkflowRun, rt: NonNullable<WorkflowRun["runtime"]>): void {
78
79
  this.runtime = rt;
79
80
  },
80
81
  } as unknown as WorkflowRun;