@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,387 @@
1
+ // src/execution/__tests__/run-and-finalize-anchoring.test.ts
2
+ //
3
+ // runAndFinalize 特征锚定基线(第二批复杂度债务 U04:锚定测试先行)。
4
+ //
5
+ // 既有 run-and-finalize-chatmode.test.ts 覆盖 settle 阶段的 chatMode × closeAfterRound
6
+ // 分流;本文件补直接分支缺口,重构前后行为逐字节对照(错误文案 / 终态写盘 / 事件顺序):
7
+ // G1 pooled acquire 成功 → acquire → runSpawn → release 调用次序 + 轮次回退事件(running)
8
+ // G2 排队 acquire 被中止(signal.aborted)→ finalizeAborted("cancelled by user")
9
+ // G3 acquire 非中止失败 → finalizeFailed 合成 "aborted" 文案(原始错误被吞——现状怪癖锚定)
10
+ // G4 非 chatMode + runSpawn 抛错 → finalizeFailed:closed/gc + archive + manifest 落盘
11
+ // G5 chatMode + runSpawn 抛错 → MF-6 回退:record.result="round did not complete: <msg>"
12
+ // G6 chatMode + 抛错 + CAS 输(record 已 closed)→ 不回退、不 archive、原样返回合成 result
13
+ // G7 chatMode + idle timer armed → early return:settle 不执行(round/closeAfterRound 不动)
14
+ // G8 settle 阶段 CAS 输(record 已 closed)→ 跳过收尾、closeAfterRound 不消费
15
+ // G9 非 chatMode + 失败 result → else 分支:closed/gc + archive + manifest 落盘 + endedAt
16
+ // G10 运行中 abort → closedReason="cancelled" 终态化,result 原样返回(不改写为 cancelled 形态)
17
+ //
18
+ // harness 与 run-and-finalize-chatmode.test.ts 同形态:mock session-runner.runSpawn,
19
+ // 走 SubagentService 真实 runAndFinalize 分流逻辑,断言「最终可观察状态」。
20
+
21
+ import * as fs from "node:fs";
22
+ import * as os from "node:os";
23
+ import * as path from "node:path";
24
+
25
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
26
+
27
+ // mock logger(doFinalizeRecord 的 manifest 写入降级路径用 logger.error)
28
+ const { loggerMock } = vi.hoisted(() => ({
29
+ loggerMock: { debug: vi.fn(), error: vi.fn(), warn: vi.fn() },
30
+ }));
31
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
32
+
33
+ // mock session-runner:runSpawn 受控返回 result / 抛错,killAllSpawnedChildren 空实现。
34
+ // 注意:必须在 import SubagentService 之前 mock(vi.mock 提升到顶部)。
35
+ vi.mock("../engine/engines/pi/session-runner.ts", () => ({
36
+ runSpawn: vi.fn(),
37
+ killAllSpawnedChildren: vi.fn(),
38
+ getChildByRecord: vi.fn(() => undefined),
39
+ registerSpawnedChildForRecord: vi.fn(),
40
+ killRecordChildWithEscalation: vi.fn(),
41
+ spawnedChildren: new Map(),
42
+ }));
43
+
44
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
45
+ import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
46
+ import { armIdleTimer, disarmIdleTimer } from "../lifecycle-manager.ts";
47
+ import { createRecord } from "../execution-record.ts";
48
+ import { ModelConfigService } from "../model-config-service.ts";
49
+ import type { ModelInfo } from "../model-resolver.ts";
50
+ import { getSubagentRecordsDir } from "../path-encoding.ts";
51
+ import { RecordStore } from "../record-store.ts";
52
+ import type { ConcurrencyPool } from "../concurrency-pool.ts";
53
+ import { SubagentService } from "../subagent-service.ts";
54
+ import type { PiLike } from "../subagent-service.ts";
55
+ import type {
56
+ AgentResult,
57
+ ExecutionRecord,
58
+ ExecuteOptions,
59
+ } from "../types.ts";
60
+
61
+ const mockRunSpawn = vi.mocked(runSpawn);
62
+
63
+ // ── 最小合法 ModelInfo(resolveModel 未注入 registry 会抛错,故 stub;runSpawn 被 mock 不消费)──
64
+ const STUB_MODEL: ModelInfo = {
65
+ id: "test-model",
66
+ name: "Test",
67
+ provider: "test",
68
+ reasoning: false,
69
+ };
70
+
71
+ function makeTmpAgentDir(): string {
72
+ return fs.mkdtempSync(path.join(os.tmpdir(), "raf-anchor-test-"));
73
+ }
74
+
75
+ function makePi(): PiLike & {
76
+ appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
77
+ events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
78
+ sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
79
+ } {
80
+ return {
81
+ appendEntry: vi.fn((customType: string, data?: unknown) => {}),
82
+ events: { emit: vi.fn((channel: string, data: unknown) => {}) },
83
+ sendMessage: vi.fn(() => {}),
84
+ };
85
+ }
86
+
87
+ function makeResult(success: boolean): AgentResult {
88
+ return {
89
+ text: success ? "done" : "err",
90
+ turns: 1,
91
+ durationMs: 100,
92
+ success,
93
+ error: success ? undefined : "boom",
94
+ sessionId: "sess-1",
95
+ toolCalls: [],
96
+ };
97
+ }
98
+
99
+ function makeRecord(chatMode: boolean, id: string): ExecutionRecord {
100
+ return createRecord(id, {
101
+ agent: "general-purpose",
102
+ model: "test-model",
103
+ mode: "background",
104
+ task: "do something",
105
+ slug: "test",
106
+ startedAt: 1000,
107
+ rootSessionId: "root-session",
108
+ chatMode,
109
+ });
110
+ }
111
+
112
+ /** 暴露 runAndFinalize + store + pool 的私有访问接口(测试专用 cast)。 */
113
+ interface ServiceInternals {
114
+ store: RecordStore;
115
+ pool: ConcurrencyPool;
116
+ runAndFinalize: (
117
+ record: ExecutionRecord,
118
+ opts: ExecuteOptions,
119
+ ctx: SessionRunnerContext,
120
+ identity: {
121
+ agent: string;
122
+ agentConfig: unknown;
123
+ resolved: { model: ModelInfo; thinkingLevel: string | undefined };
124
+ },
125
+ signal: AbortSignal | undefined,
126
+ priority: number,
127
+ ) => Promise<AgentResult>;
128
+ }
129
+
130
+ describe("runAndFinalize 特征锚定基线(U04 重构前后行为对照)", () => {
131
+ let agentDir: string;
132
+ let modelService: ModelConfigService;
133
+ let service: SubagentService;
134
+ let internals: ServiceInternals;
135
+ let pi: ReturnType<typeof makePi>;
136
+ let armedTimerRecordIds: string[];
137
+
138
+ beforeEach(() => {
139
+ // rootCwd 推导基线:PI_SUBAGENT_ROOT_CWD 若被宿主 env 污染会改写 recordsDir 编码段,
140
+ // manifest 落盘断言(G4/G9)依赖「rootCwd = init.cwd」的未设基线。
141
+ delete process.env.PI_SUBAGENT_ROOT_CWD;
142
+ agentDir = makeTmpAgentDir();
143
+ modelService = new ModelConfigService({ agentDir, cwd: agentDir });
144
+ service = new SubagentService({ cwd: agentDir, modelService });
145
+ pi = makePi();
146
+ service.initSession({ pi, sessionId: "root-session" });
147
+ internals = service as unknown as ServiceInternals;
148
+ armedTimerRecordIds = [];
149
+ mockRunSpawn.mockReset();
150
+ });
151
+
152
+ afterEach(() => {
153
+ for (const id of armedTimerRecordIds) disarmIdleTimer(id);
154
+ service.dispose();
155
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
156
+ });
157
+
158
+ /** 直接调私有 runAndFinalize(mock runSpawn 后走完整分流逻辑)。 */
159
+ async function callRunAndFinalize(
160
+ record: ExecutionRecord,
161
+ mockImpl: () => void,
162
+ signal?: AbortSignal,
163
+ ): Promise<AgentResult> {
164
+ mockImpl();
165
+ const opts: ExecuteOptions = { task: "do something", slug: "test" };
166
+ const ctx: SessionRunnerContext = {
167
+ cwd: agentDir,
168
+ agentDir,
169
+ skillDirs: [],
170
+ mainCwd: agentDir,
171
+ sessionRootId: "s-root",
172
+ rootCwd: agentDir,
173
+ };
174
+ const identity = {
175
+ agent: "general-purpose",
176
+ agentConfig: undefined,
177
+ resolved: { model: STUB_MODEL, thinkingLevel: undefined },
178
+ };
179
+ return internals.runAndFinalize(record, opts, ctx, identity, signal, 0);
180
+ }
181
+
182
+ function manifestPath(recordId: string): string {
183
+ return path.join(getSubagentRecordsDir(agentDir, agentDir), `${recordId}.json`);
184
+ }
185
+
186
+ it("G1: pooled acquire 成功 → acquire → runSpawn → release 次序锚定 + 轮次回退事件(running)", async () => {
187
+ const record = makeRecord(false, "sa-anchor-g1");
188
+ internals.store.register(record);
189
+ const pool = internals.pool;
190
+ const acquireSpy = vi.spyOn(pool, "acquire");
191
+ const releaseSpy = vi.spyOn(pool, "release");
192
+
193
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
194
+
195
+ expect(result.text).toBe("done");
196
+ // 事件顺序:acquire 先于 runSpawn,release(finally 收口)最后。
197
+ expect(acquireSpy).toHaveBeenCalledTimes(1);
198
+ expect(mockRunSpawn).toHaveBeenCalledTimes(1);
199
+ expect(releaseSpy).toHaveBeenCalledTimes(1);
200
+ const order = acquireSpy.mock.invocationCallOrder[0];
201
+ const spawnOrder = mockRunSpawn.mock.invocationCallOrder[0];
202
+ const releaseOrder = releaseSpy.mock.invocationCallOrder[0];
203
+ expect(order).toBeLessThan(spawnOrder);
204
+ expect(spawnOrder).toBeLessThan(releaseOrder);
205
+ // one-shot 成功无 closeAfterRound → finalizeRoundToIdle:留内存 + 进程死注销事件(running)。
206
+ expect(record.status).toBe("running");
207
+ expect(internals.store.getMutable(record.id)).toBe(record);
208
+ expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "running" });
209
+ });
210
+
211
+ it("G2: 排队 acquire 被中止(signal.aborted)→ finalizeAborted:'cancelled by user' + closed/cancelled + archive", async () => {
212
+ const record = makeRecord(false, "sa-anchor-g2");
213
+ internals.store.register(record);
214
+ const releaseSpy = vi.spyOn(internals.pool, "release");
215
+ const controller = new AbortController();
216
+ controller.abort(); // pre-aborted:signal.aborted = true
217
+ // 真实 ConcurrencyPool.acquire 对 pre-aborted signal 并不保证 reject——
218
+ // 显式 mock 排队中止(acquire reject + signal.aborted)以触发 catch 的 S1 分支。
219
+ vi.spyOn(internals.pool, "acquire").mockRejectedValueOnce(new Error("AbortError: queued acquire aborted"));
220
+
221
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)), controller.signal);
222
+
223
+ // 错误路径文案逐字节:S1 对齐已运行被 abort 的 cancel 语义。
224
+ expect(result.error).toBe("cancelled by user");
225
+ expect(result.success).toBe(false);
226
+ // 终态:closed + cancelled + archive(写盘收尾走完),runSpawn 未触达。
227
+ expect(record.status).toBe("closed");
228
+ expect(record.closedReason).toBe("cancelled");
229
+ expect(record.endedAt).toBeDefined();
230
+ expect(internals.store.getMutable(record.id)).toBeUndefined();
231
+ expect(mockRunSpawn).not.toHaveBeenCalled();
232
+ // acquire 失败早退(try/finally 之前)→ 池槽不归还(acquired=false 语义)。
233
+ expect(releaseSpy).not.toHaveBeenCalled();
234
+ expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
235
+ });
236
+
237
+ it("G3: acquire 非中止失败 → finalizeFailed 合成 'aborted' 文案(原始错误被吞——现状怪癖锚定)", async () => {
238
+ const record = makeRecord(false, "sa-anchor-g3");
239
+ internals.store.register(record);
240
+ const releaseSpy = vi.spyOn(internals.pool, "release");
241
+ vi.spyOn(internals.pool, "acquire").mockRejectedValueOnce(new Error("pool exploded"));
242
+
243
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
244
+
245
+ // 现状:catch 吞掉原始错误,统一合成 new Error("aborted") → toErrorMessage = "aborted"。
246
+ // "pool exploded" 不透传——重构必须保持该文案(行为逐字节不变)。
247
+ expect(result.error).toBe("aborted");
248
+ expect(result.success).toBe(false);
249
+ expect(record.status).toBe("closed");
250
+ expect(record.closedReason).toBe("gc");
251
+ expect(internals.store.getMutable(record.id)).toBeUndefined();
252
+ expect(mockRunSpawn).not.toHaveBeenCalled();
253
+ expect(releaseSpy).not.toHaveBeenCalled();
254
+ });
255
+
256
+ it("G4: 非 chatMode + runSpawn 抛错 → finalizeFailed:error 透传 + closed/gc + archive + manifest 落盘", async () => {
257
+ const record = makeRecord(false, "sa-anchor-g4");
258
+ internals.store.register(record);
259
+
260
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockRejectedValueOnce(new Error("spawn exploded")));
261
+
262
+ // 错误文案:toErrorMessage(Error) → .message 逐字节透传。
263
+ expect(result.error).toBe("spawn exploded");
264
+ expect(result.success).toBe(false);
265
+ // 终态写盘:closed + gc + archive + manifest json 落盘。
266
+ expect(record.status).toBe("closed");
267
+ expect(record.closedReason).toBe("gc");
268
+ expect(record.endedAt).toBeDefined();
269
+ expect(internals.store.getMutable(record.id)).toBeUndefined();
270
+ expect(fs.existsSync(manifestPath(record.id))).toBe(true);
271
+ expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
272
+ });
273
+
274
+ it("G5: chatMode + runSpawn 抛错 → MF-6 回退:'round did not complete: <msg>' + running + round+1 + 留内存", async () => {
275
+ const record = makeRecord(true, "sa-anchor-g5");
276
+ internals.store.register(record);
277
+
278
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockRejectedValueOnce(new Error("spawn creation failed")));
279
+
280
+ // 合成 failed result 错误文案透传(swallow,不 re-throw)。
281
+ expect(result.error).toBe("spawn creation failed");
282
+ // MF-6:不销毁对话——finalizeRoundToIdle 回退 running-resumable(留内存可重试 message/close)。
283
+ expect(record.status).toBe("running");
284
+ expect(record.closedReason).toBeUndefined(); // [S10] 不残留 gc
285
+ expect(record.round).toBe(1);
286
+ expect(internals.store.getMutable(record.id)).toBe(record);
287
+ // MF-2 写点:失败轮 error 优先 → record.result 固定前缀文案(notify 正文逐字节)。
288
+ expect(record.result).toBe("round did not complete: spawn creation failed");
289
+ // 事件:进程死注销 reason=running(非 closed——record 留内存非终态)。
290
+ expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "running" });
291
+ });
292
+
293
+ it("G6: chatMode + 抛错 + CAS 输(record 已 closed)→ 不回退不 archive,原样返回合成 result", async () => {
294
+ const record = makeRecord(true, "sa-anchor-g6");
295
+ record.status = "closed"; // cancel/收尾抢先:tryTransition(closed, gc) 必败
296
+ internals.store.register(record);
297
+
298
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockRejectedValueOnce(new Error("spawn creation failed")));
299
+
300
+ expect(result.error).toBe("spawn creation failed");
301
+ // CAS 输:无任何收尾副作用——状态/轮次不动、留内存、无注销事件。
302
+ expect(record.status).toBe("closed");
303
+ expect(record.round).toBe(0);
304
+ expect(internals.store.getMutable(record.id)).toBe(record);
305
+ expect(pi.events.emit).not.toHaveBeenCalled();
306
+ });
307
+
308
+ it("G7: chatMode + idle timer armed → early return:settle 不执行(round/closeAfterRound 不动)", async () => {
309
+ const record = makeRecord(true, "sa-anchor-g7");
310
+ record.status = "running";
311
+ record.round = 1;
312
+ internals.store.register(record);
313
+ // V2 决策 2/3 形态:onRoundSettled 已 arm idle timer(agent_settled 提前闭环)。
314
+ armIdleTimer(record.id, () => {}, 60_000);
315
+ armedTimerRecordIds.push(record.id);
316
+
317
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
318
+
319
+ // early return:原样返回 runSpawn result,settle 阶段未执行。
320
+ expect(result.text).toBe("done");
321
+ expect(record.status).toBe("running");
322
+ expect(record.round).toBe(1); // 未 +1(round+1 归 onRoundSettled,非本函数)
323
+ expect(record.closeAfterRound).toBeUndefined();
324
+ expect(internals.store.getMutable(record.id)).toBe(record);
325
+ expect(pi.events.emit).not.toHaveBeenCalled();
326
+ });
327
+
328
+ it("G8: settle 阶段 CAS 输(record 已 closed)→ 跳过收尾、closeAfterRound 不消费", async () => {
329
+ const record = makeRecord(false, "sa-anchor-g8");
330
+ record.status = "closed"; // cancel 抢先(A2-1 形态)
331
+ record.closeAfterRound = true;
332
+ internals.store.register(record);
333
+
334
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
335
+
336
+ // 原样返回(不改写为 cancelled 形态);收尾全跳过。
337
+ expect(result.text).toBe("done");
338
+ expect(result.success).toBe(true);
339
+ expect(record.status).toBe("closed");
340
+ expect(record.closeAfterRound).toBe(true); // 标志不被本路径消费
341
+ expect(record.endedAt).toBeUndefined();
342
+ expect(internals.store.getMutable(record.id)).toBe(record);
343
+ expect(pi.events.emit).not.toHaveBeenCalled();
344
+ });
345
+
346
+ it("G9: 非 chatMode + 失败 result → else 分支:closed/gc + archive + manifest 落盘 + endedAt", async () => {
347
+ const record = makeRecord(false, "sa-anchor-g9");
348
+ internals.store.register(record);
349
+
350
+ const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(false)));
351
+
352
+ // 失败 result 原样返回(错误文案不经本函数改写)。
353
+ expect(result.success).toBe(false);
354
+ expect(result.error).toBe("boom");
355
+ // 非 chatMode 失败 = 一次性销毁(archive + manifest 落盘)。
356
+ expect(record.status).toBe("closed");
357
+ expect(record.closedReason).toBe("gc");
358
+ expect(record.endedAt).toBeDefined();
359
+ expect(internals.store.getMutable(record.id)).toBeUndefined();
360
+ expect(fs.existsSync(manifestPath(record.id))).toBe(true);
361
+ expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
362
+ });
363
+
364
+ it("G10: 运行中 abort → closedReason='cancelled' 终态化,result 原样返回(不改写)", async () => {
365
+ const record = makeRecord(false, "sa-anchor-g10");
366
+ internals.store.register(record);
367
+ const controller = new AbortController();
368
+ // acquire(signal 未中止 → 放行)后、runSpawn resolve 前中止。
369
+ mockRunSpawn.mockImplementationOnce(async () => {
370
+ await new Promise((r) => { setTimeout(r, 15); });
371
+ return makeResult(true);
372
+ });
373
+ setTimeout(() => controller.abort(), 5);
374
+
375
+ const result = await callRunAndFinalize(record, () => {}, controller.signal);
376
+
377
+ // result 原样返回(success true 不被改写为 cancelled 形态——现状锚定)。
378
+ expect(result.success).toBe(true);
379
+ expect(result.text).toBe("done");
380
+ // 终态:closed + cancelled(abort 折入 closed)+ archive。
381
+ expect(record.status).toBe("closed");
382
+ expect(record.closedReason).toBe("cancelled");
383
+ expect(record.endedAt).toBeDefined();
384
+ expect(internals.store.getMutable(record.id)).toBeUndefined();
385
+ expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
386
+ });
387
+ });
@@ -25,7 +25,7 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
25
25
 
26
26
  // mock session-runner:runSpawn 受控返回 result,killAllSpawnedChildren 空实现。
27
27
  // 注意:必须在 import SubagentService 之前 mock(vi.mock 提升到顶部)。
28
- vi.mock("../session-runner.ts", () => ({
28
+ vi.mock("../engine/engines/pi/session-runner.ts", () => ({
29
29
  runSpawn: vi.fn(),
30
30
  killAllSpawnedChildren: vi.fn(),
31
31
  getChildByRecord: vi.fn(() => undefined),
@@ -34,8 +34,8 @@ vi.mock("../session-runner.ts", () => ({
34
34
  spawnedChildren: new Map(),
35
35
  }));
36
36
 
37
- import { runSpawn } from "../session-runner.ts";
38
- import type { SessionRunnerContext } from "../session-runner.ts";
37
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
38
+ import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
39
39
  import { createRecord } from "../execution-record.ts";
40
40
  import { ModelConfigService } from "../model-config-service.ts";
41
41
  import type { ModelInfo } from "../model-resolver.ts";
@@ -63,11 +63,15 @@ function makeTmpAgentDir(): string {
63
63
  }
64
64
 
65
65
  function makePi(): PiLike & {
66
- appendEntry: ReturnType<typeof vi.fn>;
67
- events: { emit: ReturnType<typeof vi.fn> };
68
- sendMessage: ReturnType<typeof vi.fn>;
66
+ appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
67
+ events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
68
+ sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
69
69
  } {
70
- return { appendEntry: vi.fn(), events: { emit: vi.fn() }, sendMessage: vi.fn() };
70
+ return {
71
+ appendEntry: vi.fn((customType: string, data?: unknown) => {}),
72
+ events: { emit: vi.fn((channel: string, data: unknown) => {}) },
73
+ sendMessage: vi.fn(() => {}),
74
+ };
71
75
  }
72
76
 
73
77
  function makeResult(success: boolean): AgentResult {
@@ -120,7 +124,7 @@ describe("runAndFinalize chatMode idle 分流 (M2-A)", () => {
120
124
 
121
125
  beforeEach(() => {
122
126
  agentDir = makeTmpAgentDir();
123
- modelService = new ModelConfigService({ agentDir });
127
+ modelService = new ModelConfigService({ agentDir, cwd: agentDir });
124
128
  service = new SubagentService({ cwd: agentDir, modelService });
125
129
  service.initSession({ pi: makePi(), sessionId: "root-session" });
126
130
  internals = service as unknown as ServiceInternals;
@@ -129,7 +133,7 @@ describe("runAndFinalize chatMode idle 分流 (M2-A)", () => {
129
133
 
130
134
  afterEach(() => {
131
135
  service.dispose();
132
- fs.rmSync(agentDir, { recursive: true, force: true });
136
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
133
137
  });
134
138
 
135
139
  /** 直接调私有 runAndFinalize(mock runSpawn 后走完整分流逻辑)。 */
@@ -141,6 +145,8 @@ describe("runAndFinalize chatMode idle 分流 (M2-A)", () => {
141
145
  agentDir,
142
146
  skillDirs: [],
143
147
  mainCwd: agentDir,
148
+ sessionRootId: "s-root",
149
+ rootCwd: agentDir,
144
150
  };
145
151
  const identity = {
146
152
  agent: "general-purpose",
@@ -63,7 +63,7 @@ vi.mock("../session-pending.ts", () => ({
63
63
  prunePendingCursor: vi.fn(),
64
64
  }));
65
65
 
66
- vi.mock("../temp-prompt.ts", () => ({
66
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
67
67
  writePromptToTempFile: vi.fn(async (agent: string) => {
68
68
  const safeName = agent.replace(/[^\w.-]+/g, "_");
69
69
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -71,8 +71,8 @@ vi.mock("../temp-prompt.ts", () => ({
71
71
  cleanupTempPrompt: vi.fn(async () => {}),
72
72
  }));
73
73
 
74
- import { runSpawn } from "../session-runner.ts";
75
- import type { SessionRunnerContext } from "../session-runner.ts";
74
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
75
+ import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
76
76
  import {
77
77
  DEFAULT_IDLE_TIMEOUT_MS,
78
78
  hasIdleTimer,
@@ -101,7 +101,7 @@ function makeChatModeRecord(id = "sa-chat"): ExecutionRecord {
101
101
  return createRecord(id, {
102
102
  agent: "general-purpose",
103
103
  model: "test-model",
104
- mode: "sync",
104
+ mode: "background",
105
105
  task: "chat task",
106
106
  slug: "chat",
107
107
  startedAt: 1_000_000,
@@ -78,7 +78,7 @@ vi.mock("../session-pending.ts", () => ({
78
78
  prunePendingCursor: vi.fn(),
79
79
  }));
80
80
 
81
- vi.mock("../temp-prompt.ts", () => ({
81
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
82
82
  writePromptToTempFile: vi.fn(async (agent: string) => {
83
83
  const safeName = agent.replace(/[^\w.-]+/g, "_");
84
84
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -86,7 +86,7 @@ vi.mock("../temp-prompt.ts", () => ({
86
86
  cleanupTempPrompt: vi.fn(async () => {}),
87
87
  }));
88
88
 
89
- import { KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS, killAllSpawnedChildren, runSpawn, spawnedChildren, WAKEUP_GRACE_MS, maxTurnsToWatchdogMs, SPAWN_WATCHDOG_ENV } from "../session-runner.ts";
89
+ import { KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS, killAllSpawnedChildren, runSpawn, spawnedChildren, WAKEUP_GRACE_MS, maxTurnsToWatchdogMs, SPAWN_WATCHDOG_ENV } from "../engine/engines/pi/session-runner.ts";
90
90
  import { writeAliveMarker } from "../alive-store.ts";
91
91
  import { getSubagentSessionDir } from "../path-encoding.ts";
92
92
  import { readActivePendingFromSessionFile } from "../session-pending.ts";
@@ -566,7 +566,7 @@ describe("runSpawn", () => {
566
566
  it("MF-4: agent_end(count>0)→ 等待超时 = maxTurnsToWatchdogMs(maxTurns)(动态,非固定 2h)", async () => {
567
567
  const maxTurns = 20;
568
568
  const expected = maxTurnsToWatchdogMs(maxTurns);
569
- mockPending.mockReturnValue({ count: 2 });
569
+ mockPending.mockReturnValue({ count: 2, recentUnregister: false });
570
570
  const record = makeRecord();
571
571
  const promise = runSpawn(record, "Task: slow-desc", makeOpts({ maxTurns }), makeCtx());
572
572
 
@@ -610,7 +610,7 @@ describe("runSpawn", () => {
610
610
  // hermetic:确保兑底 env 未设(若外层 shell 误设会让「裸缺省」断言失效)
611
611
  const prevWatchdogEnv = process.env[SPAWN_WATCHDOG_ENV];
612
612
  delete process.env[SPAWN_WATCHDOG_ENV];
613
- mockPending.mockReturnValue({ count: 2 });
613
+ mockPending.mockReturnValue({ count: 2, recentUnregister: false });
614
614
  const record = makeRecord();
615
615
  const promise = runSpawn(record, "Task: slow-desc-no-turns", makeOpts(), makeCtx());
616
616
 
@@ -670,7 +670,7 @@ describe("runSpawn", () => {
670
670
  it("S-9: agent_end(count=0 + error)→ 保守不 kill + watchdog re-arm 到动态超时", async () => {
671
671
  const maxTurns = 20;
672
672
  const expected = maxTurnsToWatchdogMs(maxTurns);
673
- mockPending.mockReturnValue({ count: 0, error: "session file unreadable: EACCES" });
673
+ mockPending.mockReturnValue({ count: 0, recentUnregister: false, error: "session file unreadable: EACCES" });
674
674
  const record = makeRecord();
675
675
  const promise = runSpawn(record, "Task: unreadable", makeOpts({ maxTurns }), makeCtx());
676
676
 
@@ -706,7 +706,7 @@ describe("runSpawn", () => {
706
706
  });
707
707
 
708
708
  it("agent_end(willRetry=false,无活跃后代)→ child.kill(SIGTERM) 被调用,close 后 success=true", async () => {
709
- mockPending.mockReturnValue({ count: 0 });
709
+ mockPending.mockReturnValue({ count: 0, recentUnregister: false });
710
710
  const record = makeRecord();
711
711
  const promise = runSpawn(record, "Task: done", makeOpts(), makeCtx());
712
712
 
@@ -731,9 +731,9 @@ describe("runSpawn", () => {
731
731
 
732
732
  it("agent_end(willRetry=false,有活跃后代)→ 不 kill,进程保持 idle 等 steer 唤醒;后代完成后 kill", async () => {
733
733
  // 第一次 agent_end:有活跃后代(count=1)→ 不 kill
734
- mockPending.mockReturnValueOnce({ count: 1 });
734
+ mockPending.mockReturnValueOnce({ count: 1, recentUnregister: false });
735
735
  // 第二次 agent_end:后代已 unregister(count=0)→ kill
736
- mockPending.mockReturnValueOnce({ count: 0 });
736
+ mockPending.mockReturnValueOnce({ count: 0, recentUnregister: false });
737
737
  const record = makeRecord();
738
738
  const promise = runSpawn(record, "Task: orchestrate", makeOpts(), makeCtx());
739
739
 
@@ -760,7 +760,7 @@ describe("runSpawn", () => {
760
760
  });
761
761
 
762
762
  it("agent_end(willRetry=true)→ child.kill 不被调用(agent 会重试,等下一个 agent_end)", async () => {
763
- mockPending.mockReturnValue({ count: 0 });
763
+ mockPending.mockReturnValue({ count: 0, recentUnregister: false });
764
764
  const record = makeRecord();
765
765
  const promise = runSpawn(record, "Task: retry", makeOpts(), makeCtx());
766
766
 
@@ -789,7 +789,7 @@ describe("runSpawn", () => {
789
789
  });
790
790
 
791
791
  it("agent_end 后的后续 event 仍被 handleSdkEvent 处理(kill 不阻塞 event pump)", async () => {
792
- mockPending.mockReturnValue({ count: 0 });
792
+ mockPending.mockReturnValue({ count: 0, recentUnregister: false });
793
793
  const record = makeRecord();
794
794
  const promise = runSpawn(record, "Task: flush", makeOpts(), makeCtx());
795
795
 
@@ -829,7 +829,9 @@ describe("runSpawn", () => {
829
829
  describe("[T1] agent_end 惰性回补 + LC-4 收尾反查", () => {
830
830
  const mockPending = vi.mocked(readActivePendingFromSessionFile);
831
831
  const mockWriteAliveMarker = vi.mocked(writeAliveMarker);
832
- const mockReaddirSync = vi.mocked(fs.readdirSync);
832
+ // 被测链路(finalize-record 兜底反查)调用无 options 的 string[] 重载;
833
+ // vi.mocked 直接包 fs.readdirSync 会推导到 withFileTypes 重载(Dirent[]),显式绑定
834
+ const mockReaddirSync = vi.mocked(fs.readdirSync as (path: fs.PathLike) => string[]);
833
835
 
834
836
  /** 统计 stdin 收到的 get_state 请求数(握手 1 次 + agent_end 惰性回补 1 次)。 */
835
837
  function countGetStateRequests(child: FakeChild): { seen: number } {
@@ -892,7 +894,7 @@ describe("runSpawn", () => {
892
894
  // ① 握手失败(sessionFile 空)→ agent_end 惰性 get_state 回填 → 正常三分支(final kill)
893
895
  it("① 握手失败 sessionFile 缺失 → agent_end 惰性 get_state 回填 → 无后代走 final kill(非保守不杀)", async () => {
894
896
  // 回补成功后判定:无活跃后代 → final kill 分支(若仍走保守分支则不会 kill,断言失败)
895
- mockPending.mockReturnValue({ count: 0 });
897
+ mockPending.mockReturnValue({ count: 0, recentUnregister: false });
896
898
  const record = makeRecord();
897
899
  const promise = runSpawn(record, "Task: lazy-backfill", makeOpts(), makeCtx());
898
900
 
@@ -935,7 +937,7 @@ describe("runSpawn", () => {
935
937
  // ② 回补超时/失败 → 仍走保守分支(行为不劣化)
936
938
  it("② 回补无响应超时 → record.sessionFile 仍缺失 → 保守不杀(不劣化为 final kill)", async () => {
937
939
  // mock 判定:sessionFile 缺失形态返回 error(真实现语义,见 session-pending.ts:85)
938
- mockPending.mockReturnValue({ count: 0, error: "no sessionFile (handshake not settled)" });
940
+ mockPending.mockReturnValue({ count: 0, recentUnregister: false, error: "no sessionFile (handshake not settled)" });
939
941
  const record = makeRecord();
940
942
  const promise = runSpawn(record, "Task: lazy-timeout", makeOpts(), makeCtx());
941
943
 
@@ -982,7 +984,7 @@ describe("runSpawn", () => {
982
984
  // 且顺带覆盖惰性回补的 sessionId 补入 handshakeResult 分支(close 收尾 lookupId 源)。
983
985
  it("③ LC-4: 惰性回补只回 sessionId(sessionFile 仍无)→ close 收尾 sessionDir 反查修正 record.sessionFile", async () => {
984
986
  // sessionFile 缺失 → 决策点惰性回补;回补判定保守(error → 不杀)
985
- mockPending.mockReturnValue({ count: 0, error: "no sessionFile (handshake not settled)" });
987
+ mockPending.mockReturnValue({ count: 0, recentUnregister: false, error: "no sessionFile (handshake not settled)" });
986
988
  const record = makeRecord();
987
989
  const promise = runSpawn(record, "Task: lc4-lookup", makeOpts(), makeCtx());
988
990
 
@@ -1031,7 +1033,7 @@ describe("runSpawn", () => {
1031
1033
  it("④ A1-3: 回补等待窗口内 kill → close → continuation 不触达三分支(不 touch marker / 不挂 timer)", async () => {
1032
1034
  // 若守卫缺失(回归),continuation 会以 count=1 走 keep-alive 分支:
1033
1035
  // touchAliveMarkerForHeartbeat 写 marker + 裸缺省 arm 无进展 timer(30min 后 kill)
1034
- mockPending.mockReturnValue({ count: 1 });
1036
+ mockPending.mockReturnValue({ count: 1, recentUnregister: false });
1035
1037
  const record = makeRecord();
1036
1038
  const promise = runSpawn(record, "Task: lazy-race-close", makeOpts(), makeCtx());
1037
1039
 
@@ -73,7 +73,7 @@ vi.mock("../alive-store.ts", () => ({
73
73
  // temp-prompt:mock 掉真实 fs.promises I/O(mkdtemp/writeFile/rm)。
74
74
  // 原先保留真实实现导致 fake-timers 测试偶发 flaky——writePromptToTempFile 的真实异步
75
75
  // I/O 在 CI 慢机器上无法在 advanceTimersByTimeAsync 的有限步数内 resolve,spawn 永不触发。
76
- vi.mock("../temp-prompt.ts", () => ({
76
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
77
77
  writePromptToTempFile: vi.fn(async (agent: string) => {
78
78
  const safeName = agent.replace(/[^\w.-]+/g, "_");
79
79
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -81,7 +81,7 @@ vi.mock("../temp-prompt.ts", () => ({
81
81
  cleanupTempPrompt: vi.fn(async () => {}),
82
82
  }));
83
83
 
84
- import { runSpawn } from "../session-runner.ts";
84
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
85
85
  import {
86
86
  emitStdoutLine,
87
87
  type FakeChild,
@@ -53,7 +53,7 @@ vi.mock("../alive-store.ts", () => ({
53
53
  writeAliveMarker: vi.fn(),
54
54
  }));
55
55
 
56
- vi.mock("../temp-prompt.ts", () => ({
56
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
57
57
  writePromptToTempFile: vi.fn(async (agent: string) => {
58
58
  const safeName = agent.replace(/[^\w.-]+/g, "_");
59
59
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -61,7 +61,7 @@ vi.mock("../temp-prompt.ts", () => ({
61
61
  cleanupTempPrompt: vi.fn(async () => {}),
62
62
  }));
63
63
 
64
- import { getChildByRecord, runSpawn, spawnedChildren } from "../session-runner.ts";
64
+ import { getChildByRecord, runSpawn, spawnedChildren } from "../engine/engines/pi/session-runner.ts";
65
65
  import {
66
66
  emitStdoutLine,
67
67
  type FakeChild,
@@ -53,7 +53,7 @@ vi.mock("../alive-store.ts", () => ({
53
53
  writeAliveMarker: vi.fn(),
54
54
  }));
55
55
 
56
- vi.mock("../temp-prompt.ts", () => ({
56
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
57
57
  writePromptToTempFile: vi.fn(async (agent: string) => {
58
58
  const safeName = agent.replace(/[^\w.-]+/g, "_");
59
59
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -61,7 +61,7 @@ vi.mock("../temp-prompt.ts", () => ({
61
61
  cleanupTempPrompt: vi.fn(async () => {}),
62
62
  }));
63
63
 
64
- import { runSpawn } from "../session-runner.ts";
64
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
65
65
  import {
66
66
  emitStdoutLine,
67
67
  type FakeChild,