@zhushanwen/subagent-core 0.4.0 → 0.6.0

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 (309) 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 +17100 -14082
  11. package/dist/index.d.cts +5336 -4033
  12. package/dist/index.d.ts +5336 -4033
  13. package/dist/index.js +17020 -14055
  14. package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
  16. package/dist.bundle/index.cjs +34933 -0
  17. package/package.json +3 -3
  18. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  19. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  20. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  21. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  22. package/src/__tests__/manifest-store.test.ts +10 -9
  23. package/src/__tests__/record-store-cache.test.ts +0 -2
  24. package/src/__tests__/record-store-index.test.ts +1 -2
  25. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  26. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  27. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  28. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  29. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  30. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  31. package/src/__tests__/session-runner.test.ts +1 -1
  32. package/src/core/__tests__/host-services.test.ts +2 -2
  33. package/src/core/__tests__/logger.test.ts +1 -1
  34. package/src/core/error-message.ts +9 -0
  35. package/src/core/host-services.ts +21 -5
  36. package/src/core/notify-ports.ts +18 -5
  37. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  38. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  39. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  40. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  41. package/src/execution/__tests__/alive-store.test.ts +1 -1
  42. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  43. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  44. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  45. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  46. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  47. package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
  48. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  49. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
  50. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  51. package/src/execution/__tests__/config.test.ts +1 -1
  52. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  53. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  54. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  55. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  56. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  57. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  58. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  59. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  60. package/src/execution/__tests__/execution-record.test.ts +217 -60
  61. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  62. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
  63. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  64. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  65. package/src/execution/__tests__/gc-timer.test.ts +56 -3
  66. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  67. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  68. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  69. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  70. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  71. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  72. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  73. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  74. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  75. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  76. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  77. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  78. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  79. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  80. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  81. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  82. package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
  83. package/src/execution/__tests__/output-collector.test.ts +117 -6
  84. package/src/execution/__tests__/pi-invocation.test.ts +36 -4
  85. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  86. package/src/execution/__tests__/record-store.test.ts +10 -9
  87. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  88. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  89. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  90. package/src/execution/__tests__/relay-env.test.ts +12 -1
  91. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  92. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  93. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  94. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  95. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  96. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  97. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  98. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  99. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  100. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  101. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  102. package/src/execution/__tests__/session-file-gc.test.ts +36 -1
  103. package/src/execution/__tests__/session-pending.test.ts +257 -14
  104. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  105. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  106. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  107. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  108. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  109. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  110. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  112. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  113. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +3 -3
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  117. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  118. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  119. package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
  120. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  122. package/src/execution/__tests__/subagent-actions-core.test.ts +48 -22
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  124. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
  125. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  126. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  127. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  128. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  129. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  130. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  131. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  132. package/src/execution/__tests__/sync-collect-recovery.test.ts +1117 -0
  133. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  134. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  135. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  136. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  137. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  138. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  139. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  140. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  141. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  142. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  143. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  144. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  145. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  146. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  147. package/src/execution/agent-registry.ts +74 -30
  148. package/src/execution/agent-result-mapper.ts +3 -0
  149. package/src/execution/cold-resurrect.ts +199 -0
  150. package/src/execution/collect-coordinator.ts +200 -0
  151. package/src/execution/config.ts +77 -5
  152. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  153. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  154. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  155. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  156. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  157. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  158. package/src/execution/engine/__tests__/common/pool-manager.test.ts +213 -53
  159. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  160. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  161. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  162. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  163. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  181. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  182. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  183. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  184. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  185. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  186. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  187. package/src/execution/engine/engines/pi/reader.ts +11 -0
  188. package/src/execution/engine/engines/pi/registration.ts +4 -2
  189. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  190. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  191. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  192. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  193. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  194. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  195. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  196. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  197. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  198. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  199. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  200. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  209. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  210. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  211. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  212. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  213. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  214. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  215. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  216. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  217. package/src/execution/engine/host-task-spec.ts +32 -36
  218. package/src/execution/engine/model-validation.ts +177 -0
  219. package/src/execution/engine/port.ts +24 -6
  220. package/src/execution/engine/routing.ts +75 -1
  221. package/src/execution/engine/types.ts +30 -103
  222. package/src/execution/execution-record.ts +229 -144
  223. package/src/execution/finalize-record.ts +141 -99
  224. package/src/execution/idle-gc.ts +28 -0
  225. package/src/execution/lifecycle-predicates.ts +1 -1
  226. package/src/execution/notifier.ts +234 -25
  227. package/src/execution/notify-host.ts +232 -0
  228. package/src/execution/record-entry.ts +16 -0
  229. package/src/execution/record-store.ts +280 -103
  230. package/src/execution/round-settlement.ts +93 -0
  231. package/src/execution/session-file-gc.ts +96 -62
  232. package/src/execution/session-reconstructor.ts +256 -143
  233. package/src/execution/sessions-index.ts +117 -47
  234. package/src/execution/settled-watchdog.ts +264 -52
  235. package/src/execution/subagent-actions-core.ts +84 -28
  236. package/src/execution/subagent-service.ts +1225 -964
  237. package/src/execution/subprocess-agent-runner.ts +144 -97
  238. package/src/execution/sync-rebuild.ts +87 -0
  239. package/src/execution/types.ts +72 -0
  240. package/src/execution/ui-request-handler-factory.ts +102 -74
  241. package/src/execution/ui-request-queue.ts +50 -19
  242. package/src/index.ts +442 -316
  243. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  244. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  245. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  246. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  247. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  248. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  249. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  250. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  251. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  252. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  253. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  254. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  255. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  256. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  257. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  258. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  259. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  260. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  261. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  262. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  263. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  264. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  265. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  266. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  267. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  268. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  269. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  270. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  271. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  272. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  273. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  274. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  275. package/src/orchestration/agent-opts-resolver.ts +1 -1
  276. package/src/orchestration/args-validator.ts +53 -31
  277. package/src/orchestration/execute-agent-call.ts +19 -73
  278. package/src/orchestration/launcher.ts +128 -81
  279. package/src/orchestration/lifecycle.ts +43 -45
  280. package/src/orchestration/models/ports.ts +4 -4
  281. package/src/orchestration/models/run-runtime.ts +2 -2
  282. package/src/orchestration/models/trace.ts +1 -1
  283. package/src/orchestration/models/types.ts +111 -22
  284. package/src/orchestration/models/workflow-run.ts +1 -1
  285. package/src/orchestration/script-lint.ts +91 -58
  286. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  287. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  288. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  289. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  290. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  291. package/src/shared/atomic-write.ts +12 -9
  292. package/src/shared/meta-parser.ts +158 -84
  293. package/src/shared/model-ref.ts +15 -2
  294. package/src/shared/resource-discovery.ts +9 -183
  295. package/src/shared/schema-jsonify.ts +1 -1
  296. package/workflows/review-fix-loop-utils.cjs +151 -89
  297. package/workflows/review-fix-loop-utils.d.cts +287 -0
  298. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  299. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  300. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  301. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  302. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  303. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  305. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  306. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  307. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  308. package/src/execution/execute-options-mapper.ts +0 -115
  309. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,120 @@
1
+ // src/execution/__tests__/config-collect-sync.test.ts
2
+ //
3
+ // collectSync 节 sanitize(E5)测试(subagent-sync-collect U1 foundation)。
4
+ // E5(设计 §3.1.5):config 坏值(负数/非枚举/坏类型)逐字段回默认,不炸启动,
5
+ // 与 maxConcurrent sanitize 同判(正整数/枚举成员校验)。
6
+ //
7
+ // 纯函数测试:无文件 IO、无 timer 依赖——不触真实数据目录(红线)。
8
+
9
+ import { describe, expect, it, vi, beforeEach } from "vitest";
10
+
11
+ // logger mock(D6 #8 坏值 warn 断言;config.test.ts 同款拦 getLogger)
12
+ const loggerMock = vi.hoisted(() => ({ warn: vi.fn(), debug: vi.fn(), info: vi.fn(), error: vi.fn() }));
13
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
14
+
15
+ import { DEFAULT_COLLECT_SYNC, DEFAULT_CONFIG, sanitizeCollectSync } from "../config.ts";
16
+
17
+ beforeEach(() => {
18
+ loggerMock.warn.mockClear();
19
+ });
20
+
21
+ // ============================================================
22
+ // 权威默认值
23
+ // ============================================================
24
+
25
+ describe("DEFAULT_COLLECT_SYNC", () => {
26
+ it("matches the design defaults (async / 4000 / 24000)", () => {
27
+ expect(DEFAULT_COLLECT_SYNC).toEqual({
28
+ default: "async",
29
+ perItemChars: 4000,
30
+ totalChars: 24000,
31
+ });
32
+ });
33
+
34
+ it("DEFAULT_CONFIG omits collectSync (缺省键缺失:startupConfig 声明守护保持绿,消费方 ?? 兜底)", () => {
35
+ expect(DEFAULT_CONFIG.collectSync).toBeUndefined();
36
+ });
37
+ });
38
+
39
+ // ============================================================
40
+ // sanitizeCollectSync(E5)
41
+ // ============================================================
42
+
43
+ describe("sanitizeCollectSync (E5)", () => {
44
+ it("passes a fully valid node through", () => {
45
+ expect(
46
+ sanitizeCollectSync({ default: "sync", perItemChars: 8000, totalChars: 48000 }),
47
+ ).toEqual({ default: "sync", perItemChars: 8000, totalChars: 48000 });
48
+ });
49
+
50
+ it("fills per-field defaults for a partial node (部分覆盖合法)", () => {
51
+ expect(sanitizeCollectSync({ default: "sync" })).toEqual({
52
+ default: "sync",
53
+ perItemChars: 4000,
54
+ totalChars: 24000,
55
+ });
56
+ expect(sanitizeCollectSync({ totalChars: 12000 })).toEqual({
57
+ default: "async",
58
+ perItemChars: 4000,
59
+ totalChars: 12000,
60
+ });
61
+ });
62
+
63
+ it("returns undefined for a missing node (消费方读 DEFAULT_COLLECT_SYNC 兜底)", () => {
64
+ expect(sanitizeCollectSync(undefined)).toBeUndefined();
65
+ });
66
+
67
+ it("falls back to default for non-enum collectSync.default values (E5 非枚举)", () => {
68
+ for (const bad of ["immediate", "SYNC", "batch", 1, 0, true, null]) {
69
+ expect(sanitizeCollectSync({ default: bad })?.default).toBe("async");
70
+ }
71
+ });
72
+
73
+ it("accepts both enum members", () => {
74
+ expect(sanitizeCollectSync({ default: "sync" })?.default).toBe("sync");
75
+ expect(sanitizeCollectSync({ default: "async" })?.default).toBe("async");
76
+ });
77
+
78
+ it("falls back to default for negative/zero/fraction/non-numeric budgets (E5 负数)", () => {
79
+ for (const bad of [-1, 0, -4000, 1.5, "4000", null]) {
80
+ expect(sanitizeCollectSync({ perItemChars: bad })?.perItemChars).toBe(4000);
81
+ expect(sanitizeCollectSync({ totalChars: bad })?.totalChars).toBe(24000);
82
+ }
83
+ expect(sanitizeCollectSync({ perItemChars: Number.NaN })?.perItemChars).toBe(4000);
84
+ expect(sanitizeCollectSync({ perItemChars: Number.POSITIVE_INFINITY })?.perItemChars).toBe(4000);
85
+ });
86
+
87
+ it("returns undefined for non-object scalar nodes (E5 坏类型)", () => {
88
+ for (const bad of [null, 42, "sync", true]) {
89
+ expect(sanitizeCollectSync(bad)).toBeUndefined();
90
+ }
91
+ });
92
+
93
+ it("treats an array node as an object with all-bad fields (whole-defaults)", () => {
94
+ expect(sanitizeCollectSync([])).toEqual(DEFAULT_COLLECT_SYNC);
95
+ });
96
+
97
+ it("never throws on arbitrary garbage (E5 不炸启动)", () => {
98
+ expect(() =>
99
+ sanitizeCollectSync({ default: Symbol("x"), perItemChars: {}, totalChars: NaN }),
100
+ ).not.toThrow();
101
+ });
102
+
103
+ it("D6 #8:显式坏值字段回默认时 warn 一条(含字段名与原值);缺省字段不 warn", () => {
104
+ // 全好字段(含合法部分覆盖)→ 零 warn
105
+ sanitizeCollectSync({ default: "sync", perItemChars: 8000 });
106
+ expect(loggerMock.warn).not.toHaveBeenCalled();
107
+
108
+ // 显式坏值(default 非枚举 + totalChars 负数)→ 一条 warn 列出两个坏字段
109
+ sanitizeCollectSync({ default: "immediate", perItemChars: 8000, totalChars: -5 });
110
+ expect(loggerMock.warn).toHaveBeenCalledTimes(1);
111
+ expect(loggerMock.warn).toHaveBeenCalledWith(
112
+ '[subagents] config collectSync invalid field(s) reverted to defaults: default="immediate", totalChars=-5',
113
+ );
114
+
115
+ // 缺省字段(部分覆盖)不算坏值——warn 计数不增
116
+ loggerMock.warn.mockClear();
117
+ sanitizeCollectSync({ perItemChars: 4000 });
118
+ expect(loggerMock.warn).not.toHaveBeenCalled();
119
+ });
120
+ });
@@ -34,7 +34,7 @@ beforeEach(() => {
34
34
  });
35
35
 
36
36
  afterEach(() => {
37
- fs.rmSync(tmpDir, { recursive: true, force: true });
37
+ fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
38
38
  });
39
39
 
40
40
  /** 写入 config.json(自动创建 subagents/ 子目录)。 */
@@ -1,14 +1,14 @@
1
1
  // src/execution/__tests__/delivery-methods.test.ts
2
2
  //
3
- // resumeRound + deliverMessage 单元测试(M2-B1 投递基础设施)。
3
+ // 冷路径续轮 + chatMode 统一投递单元测试(M2-B1 投递基础设施;D2 单轨后口径)。
4
4
  //
5
5
  // mock session-runner(runSpawn 受控 + killAllSpawnedChildren 空实现 + getChildByRecord/spawnedChildren
6
- // 真实 Map 语义),走 SubagentService 真实的 resumeRound/deliverMessage 逻辑:
7
- // - resumeRound:idle→running→detached kickOff runSpawn 收到 resume 参数;chatMode+done 回 idle/round+1;
8
- // idle / 无 sessionFile / 无 controller throw
9
- // - deliverMessage:按进程死活分流(热路径 prompt+streamingBehavior / 冷路径 resume
6
+ // 真实 Map 语义),走 SubagentService.deliverChatMessage PiEngine.deliverPrompt 真实逻辑:
7
+ // - 冷路径(进程死):续轮 resume spawn(runSpawn 收到 resume 参数);chatMode+done 回 running/round+1;
8
+ // 终态 / 无 sessionFile / 无 controller throw 行动语言
9
+ // - 热路径(进程活):prompt + streamingBehavior(interrupt → steer/followUp
10
10
  //
11
- // stdin-writer 不 mock(端到端验证 deliverMessage→sendPromptCommand→child.stdin 字节)。
11
+ // stdin-writer 不 mock(端到端验证 PiEngine.deliverPrompt→sendPromptCommand→child.stdin 字节)。
12
12
  // [review 修复] 已删除 deliverToRunning describe(busy follow_up/steer 投递)——随
13
13
  // deliverToRunning 方法一并移除(无生产调用方,pendingMessages 消费确认制死机制)。
14
14
 
@@ -29,7 +29,7 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
29
29
 
30
30
  // mock session-runner:runSpawn 受控 + killAllSpawnedChildren 空实现;
31
31
  // getChildByRecord/spawnedChildren 提供真实 Map 语义(deliverToRunning 注册 mock child 用)。
32
- vi.mock("../session-runner.ts", () => {
32
+ vi.mock("../engine/engines/pi/session-runner.ts", () => {
33
33
  const spawnedChildren = new Map<string, unknown>();
34
34
  return {
35
35
  runSpawn: vi.fn(),
@@ -41,7 +41,7 @@ vi.mock("../session-runner.ts", () => {
41
41
  };
42
42
  });
43
43
 
44
- import { runSpawn, spawnedChildren, type SessionRunnerContext } from "../session-runner.ts";
44
+ import { runSpawn, spawnedChildren, type SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
45
45
  import * as lifecycle from "../lifecycle-manager.ts";
46
46
  import { createRecord } from "../execution-record.ts";
47
47
  import { ModelConfigService } from "../model-config-service.ts";
@@ -59,11 +59,15 @@ function makeTmpAgentDir(): string {
59
59
  }
60
60
 
61
61
  function makePi(): PiLike & {
62
- appendEntry: ReturnType<typeof vi.fn>;
63
- events: { emit: ReturnType<typeof vi.fn> };
64
- sendMessage: ReturnType<typeof vi.fn>;
62
+ appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
63
+ events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
64
+ sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
65
65
  } {
66
- return { appendEntry: vi.fn(), events: { emit: vi.fn() }, sendMessage: vi.fn() };
66
+ return {
67
+ appendEntry: vi.fn((customType: string, data?: unknown) => {}),
68
+ events: { emit: vi.fn((channel: string, data: unknown) => {}) },
69
+ sendMessage: vi.fn(() => {}),
70
+ };
67
71
  }
68
72
 
69
73
  function makeResult(success: boolean): AgentResult {
@@ -100,7 +104,7 @@ function makeIdleRecord(id = "sa-chat"): ExecutionRecord {
100
104
 
101
105
  /** PassThrough child(可读出 stdin 字节验证 deliverToRunning 写入)。 */
102
106
  function makeStreamChild(): ChildProcess {
103
- // exitCode/signalCode:真 ChildProcess 未退出时均为 null(deliverMessage [race-F5]
107
+ // exitCode/signalCode:真 ChildProcess 未退出时均为 null(热路径投递 [race-F5]
104
108
  // 写后死进程检测读这两个字段,缺省 undefined 会被误判为已死触发 warn)。
105
109
  return { stdin: new PassThrough(), exitCode: null, signalCode: null } as unknown as ChildProcess;
106
110
  }
@@ -108,7 +112,7 @@ function makeStreamChild(): ChildProcess {
108
112
  function readStdinLines(child: ChildProcess): unknown[] {
109
113
  const stream = child.stdin as unknown as PassThrough;
110
114
  stream.pause();
111
- const text = stream.read()?.toString() ?? "";
115
+ const text: string = stream.read()?.toString() ?? "";
112
116
  return text
113
117
  .split("\n")
114
118
  .map((l) => l.trim())
@@ -116,14 +120,14 @@ function readStdinLines(child: ChildProcess): unknown[] {
116
120
  .map((l) => JSON.parse(l));
117
121
  }
118
122
 
119
- describe("resumeRound (M2-B1 idle 投递)", () => {
123
+ describe("冷路径续轮(M2-B1 idle 投递;D2 后经 deliverChatMessage 无活进程到达)", () => {
120
124
  let agentDir: string;
121
125
  let service: SubagentService;
122
126
  let record: ExecutionRecord;
123
127
 
124
128
  beforeEach(() => {
125
129
  agentDir = makeTmpAgentDir();
126
- const modelService = new ModelConfigService({ agentDir });
130
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
127
131
  service = new SubagentService({ cwd: agentDir, modelService });
128
132
  service.initSession({ pi: makePi(), sessionId: "root-session" });
129
133
  record = makeIdleRecord();
@@ -134,16 +138,16 @@ describe("resumeRound (M2-B1 idle 投递)", () => {
134
138
 
135
139
  afterEach(() => {
136
140
  service.dispose();
137
- fs.rmSync(agentDir, { recursive: true, force: true });
141
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
138
142
  });
139
143
 
140
- it("resumeRound(running) → kickOff runSpawn 收到 resume 参数;chatMode+done 回 running/round+1", async () => {
144
+ it("冷路径续轮(running) → kickOff runSpawn 收到 resume 参数;chatMode+done 回 running/round+1", async () => {
141
145
  mockRunSpawn.mockResolvedValueOnce(makeResult(true));
142
146
  const beforeRound = record.round;
143
147
 
144
- service.resumeRound(record, "next round msg");
148
+ await service.chatActions.deliverChatMessage(record, "next round msg", false);
145
149
 
146
- // 同步:status 已手动设回 running(M2-A 边界,绕过 tryTransition)
150
+ // 冷路径守卫通过后:status 已手动设回 running(M2-A 边界,绕过 tryTransition)
147
151
  expect(record.status).toBe("running");
148
152
 
149
153
  // detached:等 runSpawn 被调
@@ -163,42 +167,42 @@ describe("resumeRound (M2-B1 idle 投递)", () => {
163
167
  expect(record.round).toBe(beforeRound! + 1);
164
168
  });
165
169
 
166
- it("终态 closed record → throw 行动语言(MF-4,仅 running 可续聊),不触发 kickOff", () => {
170
+ it("终态 closed record → throw 行动语言(MF-4,仅 running 可续聊),不触发 kickOff", async () => {
167
171
  record.status = "closed";
168
172
  // MF-4:行动语言(spec §3.1),不暴露 resume/controller 内部词汇
169
- expect(() => service.resumeRound(record, "msg")).toThrow(/not ready for a new message/);
173
+ await expect(service.chatActions.deliverChatMessage(record, "msg", false)).rejects.toThrow(/not ready for a new message/);
170
174
  expect(mockRunSpawn).not.toHaveBeenCalled();
171
175
  });
172
176
 
173
- it("record 无 sessionFile → throw 行动语言(MF-4 canonical session unavailable),不触发 kickOff", () => {
177
+ it("record 无 sessionFile → throw 行动语言(MF-4 canonical session unavailable),不触发 kickOff", async () => {
174
178
  record.sessionFile = undefined;
175
- expect(() => service.resumeRound(record, "msg")).toThrow(/session unavailable/);
179
+ await expect(service.chatActions.deliverChatMessage(record, "msg", false)).rejects.toThrow(/session unavailable/);
176
180
  expect(mockRunSpawn).not.toHaveBeenCalled();
177
181
  });
178
182
 
179
- it("record 无 controller → throw 行动语言(MF-4),不触发 kickOff", () => {
183
+ it("record 无 controller → throw 行动语言(MF-4),不触发 kickOff", async () => {
180
184
  record.controller = undefined;
181
- expect(() => service.resumeRound(record, "msg")).toThrow(/not ready for a new message/);
185
+ await expect(service.chatActions.deliverChatMessage(record, "msg", false)).rejects.toThrow(/not ready for a new message/);
182
186
  expect(mockRunSpawn).not.toHaveBeenCalled();
183
187
  });
184
188
  });
185
189
 
186
190
  // ============================================================
187
- // deliverMessage(V2 决策 3 chatMode 统一投递:按进程死活分流)
191
+ // deliverChatMessage(V2 决策 3 chatMode 统一投递:按进程死活分流;D2 后经 engine.interactRecord)
188
192
  // ============================================================
189
193
 
190
- describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
194
+ describe("deliverChatMessage (V2 决策 3 chatMode 统一投递)", () => {
191
195
  let agentDir: string;
192
196
  let service: SubagentService;
193
197
  let record: ExecutionRecord;
194
198
 
195
199
  beforeEach(() => {
196
200
  agentDir = makeTmpAgentDir();
197
- const modelService = new ModelConfigService({ agentDir });
201
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
198
202
  service = new SubagentService({ cwd: agentDir, modelService });
199
203
  service.initSession({ pi: makePi(), sessionId: "root-session" });
200
204
  record = makeIdleRecord(); // chatMode:true, idle, round=1
201
- // sessionFile:冷路径 resumeRound 需要(热路径不用,设了无害)
205
+ // sessionFile:冷路径续轮需要(热路径不用,设了无害)
202
206
  record.sessionFile = path.join(agentDir, "fake-session.jsonl");
203
207
  spawnedChildren.clear();
204
208
  mockRunSpawn.mockReset();
@@ -209,15 +213,15 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
209
213
  service.dispose();
210
214
  spawnedChildren.clear();
211
215
  lifecycle._resetLifecycleState();
212
- fs.rmSync(agentDir, { recursive: true, force: true });
216
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
213
217
  });
214
218
 
215
- it("热路径 interrupt=false:进程活 → prompt streamingBehavior:followUp + status=running + pid 记录", () => {
219
+ it("热路径 interrupt=false:进程活 → prompt streamingBehavior:followUp + status=running + pid 记录", async () => {
216
220
  const child = makeStreamChild();
217
221
  Object.assign(child, { pid: 12345 });
218
222
  spawnedChildren.set(record.id, child);
219
223
 
220
- service.deliverMessage(record, "after you finish", false);
224
+ await service.chatActions.deliverChatMessage(record, "after you finish", false);
221
225
 
222
226
  const lines = readStdinLines(child);
223
227
  expect(lines).toHaveLength(1);
@@ -227,11 +231,11 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
227
231
  expect(record.pid).toBe(12345);
228
232
  });
229
233
 
230
- it("热路径 interrupt=true:进程活 → prompt streamingBehavior:steer", () => {
234
+ it("热路径 interrupt=true:进程活 → prompt streamingBehavior:steer", async () => {
231
235
  const child = makeStreamChild();
232
236
  spawnedChildren.set(record.id, child);
233
237
 
234
- service.deliverMessage(record, "stop now", true);
238
+ await service.chatActions.deliverChatMessage(record, "stop now", true);
235
239
 
236
240
  const lines = readStdinLines(child);
237
241
  expect(lines[0]).toMatchObject({ type: "prompt", message: "stop now", streamingBehavior: "steer" });
@@ -241,13 +245,13 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
241
245
  // [race-F5] 写后死进程检测:热路径 write 同步成功(数据进内核缓冲)但子进程在读取前
242
246
  // 已死(gate/idle kill 竞速)→ 消息将随缓冲静默丢弃。修复:写后检查 exitCode/signalCode,
243
247
  // 已死则 warn 留证(含 runId 与消息类型),不抛错不重试(终态已由 kill 路径保证)。
244
- it("[race-F5] 热路径写后子进程已死 → logger.warn 被记录(含 runId 与消息类型),不抛错", () => {
248
+ it("[race-F5] 热路径写后子进程已死 → logger.warn 被记录(含 runId 与消息类型),不抛错", async () => {
245
249
  const child = makeStreamChild();
246
250
  // 模拟 gate/idle kill 竞速:写 stdin 成功但子进程已死(SIGTERM 终止形态)
247
251
  Object.assign(child, { signalCode: "SIGTERM" });
248
252
  spawnedChildren.set(record.id, child);
249
253
 
250
- expect(() => service.deliverMessage(record, "lost msg", true)).not.toThrow();
254
+ await expect(service.chatActions.deliverChatMessage(record, "lost msg", true)).resolves.toBeUndefined();
251
255
 
252
256
  // 写入照常发生(热路径语义不变,不做二次分发)
253
257
  const lines = readStdinLines(child);
@@ -259,13 +263,13 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
259
263
  );
260
264
  });
261
265
 
262
- it("[race-F5] 活进程正常投递不触发死进程 warn(守卫不误报)", () => {
266
+ it("[race-F5] 活进程正常投递不触发死进程 warn(守卫不误报)", async () => {
263
267
  // loggerMock 是模块级共享 mock:清历史调用后再断言(防前一用例的 warn 干扰)
264
268
  loggerMock.warn.mockClear();
265
269
  const child = makeStreamChild();
266
270
  spawnedChildren.set(record.id, child);
267
271
 
268
- service.deliverMessage(record, "normal", false);
272
+ await service.chatActions.deliverChatMessage(record, "normal", false);
269
273
 
270
274
  expect(loggerMock.warn).not.toHaveBeenCalledWith(
271
275
  expect.stringContaining("died around stdin write"),
@@ -273,24 +277,24 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
273
277
  );
274
278
  });
275
279
 
276
- it("热路径 disarm idle timer:arm deliverMessage → timer 清除(防 turn 期间误杀)", () => {
280
+ it("热路径 disarm idle timer:arm 后投递 → timer 清除(防 turn 期间误杀)", async () => {
277
281
  const child = makeStreamChild();
278
282
  spawnedChildren.set(record.id, child);
279
283
  // 先 arm idle timer(模拟 Step 4a agent_settled 后 armed)
280
284
  lifecycle.armIdleTimer(record.id, () => {}, 10000);
281
285
  expect(lifecycle.hasIdleTimer(record.id)).toBe(true);
282
286
 
283
- service.deliverMessage(record, "msg", false);
287
+ await service.chatActions.deliverChatMessage(record, "msg", false);
284
288
 
285
289
  // disarmIdleTimer 被调 → timer 清除(新 turn 不被 idle timer 误杀)
286
290
  expect(lifecycle.hasIdleTimer(record.id)).toBe(false);
287
291
  });
288
292
 
289
- it("冷路径:进程死(无 child)→ resumeRound spawn(runSpawn 收到 resume 参数)", async () => {
293
+ it("冷路径:进程死(无 child)→ 续轮 resume spawn(runSpawn 收到 resume 参数)", async () => {
290
294
  mockRunSpawn.mockResolvedValueOnce(makeResult(true));
291
295
  // spawnedChildren 无该 record → getChildByRecord 返回 undefined → 冷路径
292
296
 
293
- service.deliverMessage(record, "resume msg", false);
297
+ await service.chatActions.deliverChatMessage(record, "resume msg", false);
294
298
 
295
299
  await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
296
300
  const call = mockRunSpawn.mock.calls[0]!;
@@ -308,7 +312,7 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
308
312
  Object.assign(child, { killed: true }); // 进程已 kill
309
313
  spawnedChildren.set(record.id, child);
310
314
 
311
- service.deliverMessage(record, "after kill", false);
315
+ await service.chatActions.deliverChatMessage(record, "after kill", false);
312
316
 
313
317
  await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
314
318
  });
@@ -316,7 +320,7 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
316
320
  it("续聊后 agent_settled → onRoundSettled 设 running + round+1(v4 B-1 idle 折入 running,复用 Step 4a 链路)", () => {
317
321
  // buildSessionRunnerContext 是 private,类型断言访问(测试专用)
318
322
  const ctx = (service as unknown as { buildSessionRunnerContext(): SessionRunnerContext }).buildSessionRunnerContext();
319
- record.status = "running"; // 模拟 deliverMessage 热路径设的新 turn 状态
323
+ record.status = "running"; // 模拟热路径投递设的新 turn 状态
320
324
  const beforeRound = record.round;
321
325
 
322
326
  // 模拟 session-runner 在 agent_settled 时调本回调(Step 4a 接入点)
@@ -331,21 +335,21 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
331
335
  // 冷路径并发守卫(review round2 MF1:同 turn 批量两条 message 双冷路径双 spawn)
332
336
  // ============================================================
333
337
  // 复现链(reviewer 探针实证):pi 对同一条 assistant message 的 tool calls 顺序执行
334
- // (subagent tool sequential),tool1 的 deliverMessage 在冷路径 resumeRound 返回即
338
+ // (subagent tool sequential),tool1 的投递在冷路径续轮返回即
335
339
  // resolve——早于 runSpawn 完成 spawn 注册(session-runner spawnedChildren.set 前有
336
340
  // pool.acquire await / writePromptToTempFile 等多个异步点);tool2 立即执行 →
337
- // getChildByRecord 仍 undefined → 再次冷路径。v4 两态收敛后 resumeRound 的
341
+ // getChildByRecord 仍 undefined → 再次冷路径。v4 两态收敛后续轮的
338
342
  // `status !== "running"` 守卫对 idle-resumable record 恒放行(idle 本来就是 running)、
339
343
  // `status = "running"` 是幂等写 → 两次 kickOff → runSpawn 被调 2 次 → 两个 pi 子进程
340
344
  // 以 --session 同一 JSONL 双写 + 第一个进程脱离 kill 记账成孤儿。
341
- describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
345
+ describe("deliverChatMessage 冷路径并发守卫(review round2 MF1)", () => {
342
346
  let agentDir: string;
343
347
  let service: SubagentService;
344
348
  let record: ExecutionRecord;
345
349
 
346
350
  beforeEach(() => {
347
351
  agentDir = makeTmpAgentDir();
348
- const modelService = new ModelConfigService({ agentDir });
352
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
349
353
  service = new SubagentService({ cwd: agentDir, modelService });
350
354
  service.initSession({ pi: makePi(), sessionId: "root-session" });
351
355
  record = makeIdleRecord();
@@ -359,7 +363,7 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
359
363
  service.dispose();
360
364
  spawnedChildren.clear();
361
365
  lifecycle._resetLifecycleState();
362
- fs.rmSync(agentDir, { recursive: true, force: true });
366
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
363
367
  });
364
368
 
365
369
  it("同 record 连续两条 message 冷路径 → 第二条 throw 行动语言,runSpawn 仅 1 次", async () => {
@@ -371,13 +375,13 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
371
375
  );
372
376
 
373
377
  // 第一条:正常冷路径 resume
374
- await expect(service.deliverMessage(record, "first msg", false)).resolves.toBeUndefined();
378
+ await expect(service.chatActions.deliverChatMessage(record, "first msg", false)).resolves.toBeUndefined();
375
379
  await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
376
380
 
377
381
  // 第二条:spawn 仍在途(getChildByRecord undefined)→ 再走冷路径。
378
- // 修复前:resumeRound 守卫恒放行 → 第二次 kickOff → runSpawn 2 次(双 spawn 双写 session)。
382
+ // 修复前:续轮守卫恒放行 → 第二次 kickOff → runSpawn 2 次(双 spawn 双写 session)。
379
383
  // 修复后:in-flight 守卫 throw 行动语言(MF-4)。
380
- await expect(service.deliverMessage(record, "second msg", false)).rejects.toThrow(
384
+ await expect(service.chatActions.deliverChatMessage(record, "second msg", false)).rejects.toThrow(
381
385
  /already starting a new round/,
382
386
  );
383
387
  expect(mockRunSpawn).toHaveBeenCalledTimes(1);
@@ -388,7 +392,7 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
388
392
  expect((service as unknown as { resumesInFlight: Set<string> }).resumesInFlight.has(record.id)).toBe(false),
389
393
  );
390
394
  mockRunSpawn.mockResolvedValueOnce(makeResult(true));
391
- await expect(service.deliverMessage(record, "third msg", false)).resolves.toBeUndefined();
395
+ await expect(service.chatActions.deliverChatMessage(record, "third msg", false)).resolves.toBeUndefined();
392
396
  await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(2));
393
397
  expect(mockRunSpawn.mock.calls[1]![1]).toBe("third msg");
394
398
  });
@@ -401,25 +405,34 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
401
405
  () => new Promise<AgentResult>((res) => { releaseA = res; }),
402
406
  );
403
407
 
404
- await service.deliverMessage(record, "A msg", false);
408
+ await service.chatActions.deliverChatMessage(record, "A msg", false);
405
409
  await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
406
410
 
407
411
  // B 的冷路径不受 A 在途影响
408
- await expect(service.deliverMessage(recordB, "B msg", false)).resolves.toBeUndefined();
412
+ await expect(service.chatActions.deliverChatMessage(recordB, "B msg", false)).resolves.toBeUndefined();
409
413
  await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(2));
410
414
  expect(mockRunSpawn.mock.calls[1]![0]).toBe(recordB);
411
415
 
412
416
  releaseA(makeResult(true));
413
417
  });
414
418
 
415
- it("resume 在途时直接调 resumeRound(不经 deliverMessage)同样被守卫拦截", () => {
416
- // promise 同步创建:release 立即可用(runSpawn 在 pool.acquire 微任务后才被调)
419
+ it("冷路径续轮在途时 EPIPE 兜底重入同样被守卫拦截(不持锁路径,review round2 MF1)", async () => {
420
+ // D2 后续轮本体是编排层私有,不持锁的重入形态只剩 EPIPE 兜底(PiEngine.deliverPrompt
421
+ // catch 分支直调续轮)——此处经热路径 EPIPE 复现:活 child 的 stdin 已销毁 → 写入
422
+ // EPIPE → 兜底转冷路径。首条消息已占在途守卫时,第二条的 EPIPE 兜底被拒。
417
423
  let release!: (r: AgentResult) => void;
418
- const gate = new Promise<AgentResult>((res) => { release = res; });
419
- mockRunSpawn.mockImplementationOnce(() => gate);
424
+ mockRunSpawn.mockImplementationOnce(() => new Promise<AgentResult>((res) => { release = res; }));
425
+ // 第一条:冷路径(无 child)→ 续轮在途
426
+ await expect(service.chatActions.deliverChatMessage(record, "first", false)).resolves.toBeUndefined();
427
+ await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
420
428
 
421
- service.resumeRound(record, "first");
422
- expect(() => service.resumeRound(record, "second")).toThrow(/already starting a new round/);
429
+ // 第二条:EPIPE 兜底(child 活但 stdin 断)→ 兜底转冷路径 → 在途守卫 throw 行动语言
430
+ const epipe = new Error("write EPIPE") as NodeJS.ErrnoException;
431
+ epipe.code = "EPIPE"; // writeStdinLine 的 R3 判据:err.code === 'EPIPE' 才转 throw
432
+ const child = { stdin: { destroyed: false, write: () => { throw epipe; } }, exitCode: null, signalCode: null, killed: false } as unknown as ChildProcess;
433
+ spawnedChildren.set(record.id, child);
434
+ await expect(service.chatActions.deliverChatMessage(record, "second", false)).rejects.toThrow(/already starting a new round/);
435
+ expect(mockRunSpawn).toHaveBeenCalledTimes(1);
423
436
 
424
437
  release(makeResult(true));
425
438
  });