@zhushanwen/subagent-core 0.2.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-43ONBFZX.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-V3VHZ2VX.js +59 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
  18. package/dist/execution/engine/engines/zcode/constants.js +39 -7
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +18234 -1277
  24. package/dist/index.d.cts +5646 -673
  25. package/dist/index.d.ts +5646 -673
  26. package/dist/index.js +17828 -1277
  27. package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
  28. package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
  29. package/package.json +7 -4
  30. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  31. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  32. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  33. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  34. package/src/__tests__/manifest-store.test.ts +10 -9
  35. package/src/__tests__/record-store-cache.test.ts +0 -2
  36. package/src/__tests__/record-store-index.test.ts +1 -2
  37. package/src/__tests__/review-fix-loop-script.test.ts +93 -3
  38. package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
  39. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  40. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  41. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  42. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  43. package/src/__tests__/session-runner.test.ts +1 -1
  44. package/src/__tests__/smoke.test.ts +9 -2
  45. package/src/core/__tests__/host-services.test.ts +2 -2
  46. package/src/core/__tests__/logger.test.ts +1 -1
  47. package/src/core/error-message.ts +9 -0
  48. package/src/core/host-services.ts +21 -5
  49. package/src/core/notify-ports.ts +18 -5
  50. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  51. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  52. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  53. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  54. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  55. package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
  56. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
  57. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  58. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  59. package/src/execution/__tests__/delivery-methods.test.ts +77 -62
  60. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  61. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  62. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  63. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  64. package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
  65. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  66. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  67. package/src/execution/__tests__/execution-record.test.ts +217 -60
  68. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  69. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  70. package/src/execution/__tests__/finalize-record.test.ts +278 -16
  71. package/src/execution/__tests__/gc-timer.test.ts +57 -2
  72. package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
  73. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  74. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  75. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  76. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  77. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  78. package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
  79. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  80. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  83. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  84. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  85. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  86. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  87. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  88. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  89. package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
  90. package/src/execution/__tests__/output-collector.test.ts +117 -6
  91. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  92. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  93. package/src/execution/__tests__/record-store.test.ts +89 -5
  94. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  95. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  96. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  97. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  98. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  105. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
  106. package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
  107. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  108. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  109. package/src/execution/__tests__/session-pending.test.ts +471 -0
  110. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  111. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  112. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  113. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  114. package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
  115. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
  116. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  117. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  118. package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
  119. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  120. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  121. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  122. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  123. package/src/execution/__tests__/spawned-children.test.ts +155 -11
  124. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  125. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  126. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  127. package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
  128. package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
  129. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  130. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
  131. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
  132. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
  133. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  134. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  135. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  136. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  137. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  138. package/src/execution/__tests__/timeout-integration.test.ts +13 -12
  139. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  140. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  141. package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
  142. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  143. package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
  144. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  145. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  146. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  147. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  148. package/src/execution/agent-registry.ts +298 -30
  149. package/src/execution/agent-result-mapper.ts +3 -0
  150. package/src/execution/agents-assembly.ts +88 -0
  151. package/src/execution/cold-resurrect.ts +199 -0
  152. package/src/execution/concurrency-pool.ts +71 -9
  153. package/src/execution/dialog-queue.ts +101 -3
  154. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  155. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  156. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  158. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  159. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  160. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  161. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  162. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
  163. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +94 -3
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engine-discovery.ts +12 -16
  181. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  182. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  183. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  184. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
  185. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  186. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  187. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  188. package/src/execution/engine/engines/pi/reader.ts +11 -0
  189. package/src/execution/engine/engines/pi/registration.ts +4 -2
  190. package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
  191. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  192. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  193. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  194. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  195. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  197. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  198. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
  199. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  200. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  202. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  203. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  209. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  210. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
  211. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  212. package/src/execution/engine/engines/zcode/connection.ts +598 -0
  213. package/src/execution/engine/engines/zcode/constants.ts +147 -14
  214. package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
  215. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  216. package/src/execution/engine/engines/zcode/preparer.ts +22 -150
  217. package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
  218. package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
  219. package/src/execution/engine/host-task-spec.ts +32 -36
  220. package/src/execution/engine/model-validation.ts +177 -0
  221. package/src/execution/engine/port.ts +65 -7
  222. package/src/execution/engine/registry.ts +53 -0
  223. package/src/execution/engine/routing.ts +75 -1
  224. package/src/execution/engine/types.ts +30 -103
  225. package/src/execution/execution-record.ts +222 -144
  226. package/src/execution/finalize-record.ts +181 -85
  227. package/src/execution/idle-gc.ts +28 -0
  228. package/src/execution/lifecycle-manager.ts +27 -3
  229. package/src/execution/lifecycle-predicates.ts +1 -1
  230. package/src/execution/manifest-store.ts +53 -62
  231. package/src/execution/notifier.ts +10 -10
  232. package/src/execution/notify-host.ts +212 -0
  233. package/src/execution/notify-ledger.ts +117 -16
  234. package/src/execution/record-entry.ts +8 -2
  235. package/src/execution/record-store.ts +148 -88
  236. package/src/execution/round-settlement.ts +93 -0
  237. package/src/execution/session-file-gc.ts +96 -62
  238. package/src/execution/session-pending.ts +213 -62
  239. package/src/execution/session-reconstructor.ts +256 -143
  240. package/src/execution/sessions-index.ts +127 -102
  241. package/src/execution/settled-watchdog.ts +311 -0
  242. package/src/execution/subagent-actions-core.ts +686 -0
  243. package/src/execution/subagent-service.ts +1106 -907
  244. package/src/execution/subprocess-agent-runner.ts +144 -97
  245. package/src/execution/types.ts +9 -0
  246. package/src/execution/ui-request-handler-factory.ts +126 -75
  247. package/src/execution/ui-request-queue.ts +50 -19
  248. package/src/execution/worktree-git-ops.ts +397 -0
  249. package/src/execution/worktree-manager.ts +72 -10
  250. package/src/execution/worktree-registry.ts +6 -12
  251. package/src/index.ts +542 -9
  252. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  253. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
  254. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  255. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  257. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  258. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
  259. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  260. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  261. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  262. package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
  263. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  264. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  265. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  266. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  267. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  268. package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
  269. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  270. package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
  271. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  272. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  273. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  274. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  275. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  276. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
  277. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  278. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  279. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  280. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  281. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  282. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  283. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  284. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  285. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  286. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  287. package/src/orchestration/agent-opts-resolver.ts +8 -8
  288. package/src/orchestration/args-meta.ts +198 -0
  289. package/src/orchestration/args-validator.ts +53 -31
  290. package/src/orchestration/execute-agent-call.ts +25 -79
  291. package/src/orchestration/file-run-store.ts +327 -0
  292. package/src/orchestration/launcher.ts +160 -104
  293. package/src/orchestration/lifecycle.ts +371 -97
  294. package/src/orchestration/models/agent-call.ts +7 -7
  295. package/src/orchestration/models/budget.ts +5 -5
  296. package/src/orchestration/models/ports.ts +4 -4
  297. package/src/orchestration/models/run-runtime.ts +15 -15
  298. package/src/orchestration/models/trace.ts +9 -9
  299. package/src/orchestration/models/types.ts +111 -22
  300. package/src/orchestration/models/workflow-run.ts +28 -28
  301. package/src/orchestration/models/workflow-script.ts +4 -4
  302. package/src/orchestration/run-snapshot.ts +266 -0
  303. package/src/orchestration/script-generate.ts +154 -0
  304. package/src/orchestration/script-lint.ts +123 -90
  305. package/src/orchestration/worker-handle.ts +10 -10
  306. package/src/orchestration/worker-host.ts +10 -10
  307. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
  308. package/src/orchestration/worker-script-builder.ts +401 -346
  309. package/src/orchestration/workflow-files.ts +37 -11
  310. package/src/orchestration/workflow-run-summary.ts +69 -0
  311. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  312. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  313. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  314. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  315. package/src/shared/__tests__/injection-render.test.ts +518 -0
  316. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  317. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  318. package/src/shared/__tests__/resource-discovery.test.ts +261 -217
  319. package/src/shared/agent-ref.ts +143 -2
  320. package/src/shared/atomic-write.ts +323 -0
  321. package/src/shared/bounded-serialize.ts +154 -0
  322. package/src/shared/injection-render.ts +279 -0
  323. package/src/shared/meta-parser.ts +182 -68
  324. package/src/shared/model-ref.ts +15 -2
  325. package/src/shared/resource-discovery.ts +97 -204
  326. package/src/shared/resource-meta.ts +14 -0
  327. package/src/shared/schema-jsonify.ts +1 -1
  328. package/src/shared/xml-injection.ts +9 -9
  329. package/workflows/README.md +9 -9
  330. package/workflows/chain.js +4 -2
  331. package/workflows/map-reduce.js +5 -3
  332. package/workflows/parallel.js +5 -3
  333. package/workflows/review-fix-loop-utils.cjs +165 -92
  334. package/workflows/review-fix-loop-utils.d.cts +287 -0
  335. package/workflows/review-fix-loop.js +18 -7
  336. package/workflows/scatter-gather.js +4 -2
  337. package/dist/chunk-3VOERJPJ.js +0 -22
  338. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  339. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  340. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  341. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  342. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  343. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  344. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  345. package/src/execution/execute-options-mapper.ts +0 -115
  346. package/src/execution/session-runner.ts +0 -1789
  347. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -26,14 +26,25 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
26
26
 
27
27
  // mock session-runner:runSpawn/killAllSpawnedChildren 占位(import 链需要),
28
28
  // getChildByRecord 默认返回 undefined(无活进程 → isResumable=true),用例内按需覆盖。
29
- vi.mock("../session-runner.ts", () => ({
29
+ vi.mock("../engine/engines/pi/session-runner.ts", () => ({
30
30
  runSpawn: vi.fn(),
31
31
  killAllSpawnedChildren: vi.fn(),
32
32
  getChildByRecord: vi.fn(() => undefined),
33
+ registerSpawnedChildForRecord: vi.fn(),
34
+ killRecordChildWithEscalation: vi.fn(),
33
35
  spawnedChildren: new Map(),
34
36
  }));
35
37
 
36
- import { getChildByRecord } from "../session-runner.ts";
38
+ // [D8] idle-gc 归档时释放引擎池引用(releasePoolRef getEngineDataDir 解析
39
+ // dataDir)——测试钉到模块级 holder(vi.mock factory 闭包只能引用模块级变量),
40
+ // beforeEach 刷新为当前 tmp agentDir,防触碰真实数据目录。
41
+ let gcDataDirHolder = "";
42
+ vi.mock("../engine/common/data-dir.ts", () => ({
43
+ getEngineDataDir: () => gcDataDirHolder,
44
+ }));
45
+
46
+ import { acquirePool } from "../engine/common/pool-manager.ts";
47
+ import { getChildByRecord } from "../engine/engines/pi/session-runner.ts";
37
48
  import { createRecord } from "../execution-record.ts";
38
49
  import { ModelConfigService } from "../model-config-service.ts";
39
50
  import { RecordStore } from "../record-store.ts";
@@ -95,6 +106,7 @@ describe("[M8] idle record GC 定时器(startGcTimer)", () => {
95
106
  beforeEach(() => {
96
107
  vi.useFakeTimers();
97
108
  agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "gc-timer-"));
109
+ gcDataDirHolder = agentDir;
98
110
  const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
99
111
  service = new SubagentService({ cwd: agentDir, modelService });
100
112
  service.initSession({ pi: makePi(), sessionId: "root-session" });
@@ -181,4 +193,47 @@ describe("[M8] idle record GC 定时器(startGcTimer)", () => {
181
193
 
182
194
  expect(store.getMutable("sa-after-dispose")).toBeDefined();
183
195
  });
196
+
197
+ it("[D8] 归档超 TTL record 时释放引擎池引用:journal 跟随删除、refs 移除、归零删池原生状态", () => {
198
+ // 建池(engine 缺省投影 'pi' + poolKey 'shared'——与 runEngineTask 回填形态一致)
199
+ const poolDir = acquirePool(agentDir, "pi", "shared", "sa-pool-1");
200
+ fs.mkdirSync(path.join(poolDir, "native-state"), { recursive: true });
201
+ fs.writeFileSync(path.join(poolDir, "native-state", "db.sqlite"), "x");
202
+ fs.writeFileSync(path.join(poolDir, "journal-sa-pool-1.jsonl"), "{}\n");
203
+ fs.writeFileSync(path.join(poolDir, "journal-sa-pool-2.jsonl"), "{}\n"); // 他人 journal(refs 无条目)
204
+
205
+ const fireAt = Date.now() + GC_INTERVAL_MS;
206
+ const record = makeIdleRecord("sa-pool-1", fireAt - IDLE_TTL_MS - 1);
207
+ record.engineHandle = { sessionRef: {}, poolKey: "shared" };
208
+ store.register(record);
209
+
210
+ service.startGcTimer();
211
+ vi.advanceTimersByTime(GC_INTERVAL_MS);
212
+
213
+ expect(store.getMutable("sa-pool-1")).toBeUndefined(); // 已归档
214
+ // release 语义:自己的 journal 删、他人 journal 保留、归零删原生状态、目录保留(剩 journal)
215
+ expect(fs.existsSync(path.join(poolDir, "journal-sa-pool-1.jsonl"))).toBe(false);
216
+ expect(fs.existsSync(path.join(poolDir, "journal-sa-pool-2.jsonl"))).toBe(true);
217
+ expect(fs.existsSync(path.join(poolDir, "native-state"))).toBe(false);
218
+ expect(fs.existsSync(poolDir)).toBe(true);
219
+ // refs 归零后随池清理(无残留幻影引用)
220
+ const refsPath = path.join(poolDir, "refs.json");
221
+ if (fs.existsSync(refsPath)) {
222
+ expect(Object.keys((JSON.parse(fs.readFileSync(refsPath, "utf8")) as { refs: object }).refs)).toEqual([]);
223
+ }
224
+ });
225
+
226
+ it("[D8] 无 engineHandle 的 record 归档不触碰引擎池(存量 record 零影响)", () => {
227
+ const poolDir = acquirePool(agentDir, "pi", "shared", "sa-plain");
228
+ fs.writeFileSync(path.join(poolDir, "journal-sa-plain.jsonl"), "{}\n");
229
+
230
+ const fireAt = Date.now() + GC_INTERVAL_MS;
231
+ store.register(makeIdleRecord("sa-plain", fireAt - IDLE_TTL_MS - 1)); // 无 engineHandle
232
+
233
+ service.startGcTimer();
234
+ vi.advanceTimersByTime(GC_INTERVAL_MS);
235
+
236
+ expect(store.getMutable("sa-plain")).toBeUndefined(); // 正常归档
237
+ expect(fs.existsSync(path.join(poolDir, "journal-sa-plain.jsonl"))).toBe(true); // 池/journal 不动
238
+ });
184
239
  });
@@ -27,10 +27,12 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
27
27
 
28
28
  // mock session-runner:import 链需要(getRecordForAction 本身不调 spawn,仅守卫读
29
29
  // hasLiveProcessHandle——默认无活进程,与跨重启场景一致)。
30
- vi.mock("../session-runner.ts", () => ({
30
+ vi.mock("../engine/engines/pi/session-runner.ts", () => ({
31
31
  runSpawn: vi.fn(),
32
32
  killAllSpawnedChildren: vi.fn(),
33
33
  getChildByRecord: vi.fn(() => undefined),
34
+ registerSpawnedChildForRecord: vi.fn(),
35
+ killRecordChildWithEscalation: vi.fn(),
34
36
  spawnedChildren: new Map(),
35
37
  }));
36
38
 
@@ -167,7 +169,7 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
167
169
  const file = writeSessionJsonl(sessionsDir, { id: "sa-restart-1", rootSessionId: "root-session" });
168
170
  expect(store.getMutable("sa-restart-1")).toBeUndefined(); // 前置:内存确无
169
171
 
170
- const record = service.getRecordForAction("sa-restart-1");
172
+ const record = service.chatActions.getRecordForAction("sa-restart-1");
171
173
 
172
174
  // [v4 A-3] 无条件 chatMode=true(跨重启恢复入口)
173
175
  expect(record.chatMode).toBe(true);
@@ -184,7 +186,7 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
184
186
  expect(record.status).toBe("running");
185
187
  // register 生效:进内存,二次调用走内存命中(同一引用)
186
188
  expect(store.getMutable("sa-restart-1")).toBe(record);
187
- expect(service.getRecordForAction("sa-restart-1")).toBe(record);
189
+ expect(service.chatActions.getRecordForAction("sa-restart-1")).toBe(record);
188
190
  });
189
191
 
190
192
  it("rootSessionId 校验仍生效:异树 record(other-session)→ throw not found or not owned", () => {
@@ -192,7 +194,7 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
192
194
  // 但后置校验 record.rootSessionId !== this.sessionRootId 必须拦截
193
195
  writeSessionJsonl(sessionsDir, { id: "sa-foreign", rootSessionId: "other-session" });
194
196
 
195
- expect(() => service.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
197
+ expect(() => service.chatActions.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
196
198
  });
197
199
 
198
200
  it("直接父校验仍生效:孙级 record(parentRecordId=sa-parent)→ 主进程 throw direct parent", () => {
@@ -204,14 +206,14 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
204
206
  });
205
207
 
206
208
  // 重建后 parentRecordId=sa-parent ≠ 主进程 baseline(undefined) → cross-layer 守卫拦截
207
- expect(() => service.getRecordForAction("sa-grand")).toThrow(/direct parent/);
209
+ expect(() => service.chatActions.getRecordForAction("sa-grand")).toThrow(/direct parent/);
208
210
  });
209
211
 
210
212
  it(".finalized sidecar(closed 终态)不重建 → throw not found or not owned", () => {
211
213
  const file = writeSessionJsonl(sessionsDir, { id: "sa-fin", rootSessionId: "root-session" });
212
214
  writeFinalized(file); // sidecar 矩阵分支 2 → status=closed → find(status==="running") miss
213
215
 
214
- expect(() => service.getRecordForAction("sa-fin")).toThrow(/not found or not owned/);
216
+ expect(() => service.chatActions.getRecordForAction("sa-fin")).toThrow(/not found or not owned/);
215
217
  expect(store.getMutable("sa-fin")).toBeUndefined(); // 未重建注册
216
218
  });
217
219
 
@@ -228,14 +230,14 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
228
230
  worktree: true,
229
231
  });
230
232
 
231
- const record = service.getRecordForAction("sa-wt");
233
+ const record = service.chatActions.getRecordForAction("sa-wt");
232
234
  // hadWorktree 从磁盘 identity entry 的 worktree 标志恢复
233
235
  expect(record.hadWorktree).toBe(true);
234
236
  // handle 无法恢复(不可序列化)
235
237
  expect(record.worktreeHandle).toBeUndefined();
236
238
 
237
- // 冷路径续聊(无活进程)→ resumeRound 守卫拒绝,不 spawn 回落主 repo
238
- await expect(service.deliverMessage(record, "resume after restart", false)).rejects.toThrow(
239
+ // 冷路径续聊(无活进程)→ 续轮守卫拒绝,不 spawn 回落主 repo
240
+ await expect(service.chatActions.deliverChatMessage(record, "resume after restart", false)).rejects.toThrow(
239
241
  /worktree isolation.*lost when the parent process restarted/,
240
242
  );
241
243
  });
@@ -244,11 +246,11 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
244
246
  // identity entry 无 worktree 字段(旧文件)→ found.worktree undefined → hadWorktree false
245
247
  writeSessionJsonl(sessionsDir, { id: "sa-nowt", rootSessionId: "root-session" });
246
248
 
247
- const record = service.getRecordForAction("sa-nowt");
249
+ const record = service.chatActions.getRecordForAction("sa-nowt");
248
250
  expect(record.hadWorktree).toBe(false);
249
251
 
250
- // deliverMessage 冷路径不被 worktree 守卫拦截(resume 正常发起;后续 spawn 编排
252
+ // 冷路径续聊不被 worktree 守卫拦截(resume 正常发起;后续 spawn 编排
251
253
  // 超出本用例关注点——runSpawn 在本文件是 no-op mock)
252
- await expect(service.deliverMessage(record, "resume normal", false)).resolves.toBeUndefined();
254
+ await expect(service.chatActions.deliverChatMessage(record, "resume normal", false)).resolves.toBeUndefined();
253
255
  });
254
256
  });
@@ -0,0 +1,127 @@
1
+ // src/execution/__tests__/get-state-handshake.test.ts
2
+ //
3
+ // [T1/RC-1] requestGetStateOnce 单测:agent_end 决策点惰性回补用的单次 get_state 请求辅助。
4
+ //
5
+ // 契约锚点(与 performGetStateHandshake 共用消息构造 sendGetStateCommand + 字段提取
6
+ // extractGetStateFields,但不做重试循环):
7
+ // 1. response 到达 → resolve 提取后的 GetStateResult(无条件 finish,单次语义无重试);
8
+ // 2. 超时 → resolve 空对象(调用方走保守分支);
9
+ // 3. stdin 同步写失败(EPIPE 形态)→ resolve 空对象、永不 reject(fire-and-forget 契约);
10
+ // 4. 自清理:finish 后从监听表注销本请求 resolver(消费注册器返回的注销函数);
11
+ // 注册器不返回注销函数时退化为 no-op(与握手同形态)。
12
+ //
13
+ // performGetStateHandshake 既有行为由 run-spawn-rpc-mode.test.ts 集成覆盖,此处不重复。
14
+
15
+ import { describe, expect, it, vi } from "vitest";
16
+
17
+ import { requestGetStateOnce } from "../engine/engines/pi/get-state-handshake.ts";
18
+ import type { ChildProcess } from "node:child_process";
19
+
20
+ /** 最小 FakeChild:只需 stdin.write 行为(成功 / 可注入同步 throw)。 */
21
+ function makeFakeStdin(behavior?: { throwOnWrite?: Error }): { child: ChildProcess; writes: string[] } {
22
+ const writes: string[] = [];
23
+ const child = {
24
+ stdin: {
25
+ write(line: string): boolean {
26
+ if (behavior?.throwOnWrite) throw behavior.throwOnWrite;
27
+ writes.push(line);
28
+ return true;
29
+ },
30
+ },
31
+ } as unknown as ChildProcess;
32
+ return { child, writes };
33
+ }
34
+
35
+ /** 可控的监听表:模拟 stdout pump 的 get_stateListeners(注册返回注销函数)。 */
36
+ function makeListenerRegistry() {
37
+ const resolvers = new Map<string, (data: unknown) => void>();
38
+ const removed: string[] = [];
39
+ const add = (id: string, resolver: (data: unknown) => void) => {
40
+ resolvers.set(id, resolver);
41
+ return () => {
42
+ if (resolvers.get(id) === resolver) resolvers.delete(id);
43
+ removed.push(id);
44
+ };
45
+ };
46
+ return { resolvers, removed, add };
47
+ }
48
+
49
+ describe("requestGetStateOnce([T1/RC-1] 惰性回补单次请求)", () => {
50
+ it("response 到达 → resolve 提取的 sessionFile/sessionId,并从监听表注销", async () => {
51
+ const { child, writes } = makeFakeStdin();
52
+ const reg = makeListenerRegistry();
53
+
54
+ const promise = requestGetStateOnce(child, reg.add, 1000);
55
+ // 消息构造:单行 JSON {id, type:"get_state"}(与 performGetStateHandshake 同一 sendGetStateCommand)
56
+ expect(writes).toHaveLength(1);
57
+ const sent = JSON.parse(writes[0]!) as { id: string; type: string };
58
+ expect(sent.type).toBe("get_state");
59
+ expect(reg.resolvers.has(sent.id)).toBe(true);
60
+
61
+ // response 到达(含空串 sessionFile 应被过滤的字段形态覆盖提取规则)
62
+ reg.resolvers.get(sent.id)?.({
63
+ sessionFile: "/tmp/sessions/abc.jsonl",
64
+ sessionId: "sess-1",
65
+ });
66
+
67
+ await expect(promise).resolves.toEqual({
68
+ sessionFile: "/tmp/sessions/abc.jsonl",
69
+ sessionId: "sess-1",
70
+ });
71
+ // 自清理:finish 后从监听表移除本请求 resolver
72
+ expect(reg.resolvers.has(sent.id)).toBe(false);
73
+ expect(reg.removed).toEqual([sent.id]);
74
+ });
75
+
76
+ it("response 只含部分字段(仅 sessionId)→ resolve 已提取部分(单次语义:到达即 finish,不等重试)", async () => {
77
+ const { child } = makeFakeStdin();
78
+ const reg = makeListenerRegistry();
79
+
80
+ const promise = requestGetStateOnce(child, reg.add, 1000);
81
+ const id = reg.resolvers.keys().next().value as string;
82
+ reg.resolvers.get(id)?.({ sessionId: "only-id" });
83
+
84
+ await expect(promise).resolves.toEqual({ sessionId: "only-id" });
85
+ });
86
+
87
+ it("超时无 response → resolve 空对象(不 reject,不调注销前的 resolver)", async () => {
88
+ vi.useFakeTimers();
89
+ try {
90
+ const { child } = makeFakeStdin();
91
+ const reg = makeListenerRegistry();
92
+
93
+ const promise = requestGetStateOnce(child, reg.add, 1000);
94
+ await vi.advanceTimersByTimeAsync(1000);
95
+
96
+ await expect(promise).resolves.toEqual({});
97
+ } finally {
98
+ vi.useRealTimers();
99
+ }
100
+ });
101
+
102
+ it("stdin 同步写失败(EPIPE code,writeStdinLine rethrow 路径)→ 立即 resolve 空对象、永不 reject", async () => {
103
+ // writeStdinLine 只对 code 为 EPIPE / ERR_STREAM_DESTROYED 的错误 rethrow([R3]),
104
+ // requestGetStateOnce 的 catch 捕获后按「回补失败」resolve 空对象(同超时语义)。
105
+ const epipeErr = Object.assign(new Error("write after end"), { code: "EPIPE" });
106
+ const { child } = makeFakeStdin({ throwOnWrite: epipeErr });
107
+ const reg = makeListenerRegistry();
108
+
109
+ await expect(requestGetStateOnce(child, reg.add, 1000)).resolves.toEqual({});
110
+ // 写失败路径不注册监听(未发出请求)
111
+ expect(reg.resolvers.size).toBe(0);
112
+ });
113
+
114
+ it("注册器不返回注销函数(void 形态,对齐旧握手调用方)→ 正常 resolve 不抛", async () => {
115
+ const { child } = makeFakeStdin();
116
+ const resolvers = new Map<string, (data: unknown) => void>();
117
+ const addVoid = (id: string, resolver: (data: unknown) => void): void => {
118
+ resolvers.set(id, resolver);
119
+ };
120
+
121
+ const promise = requestGetStateOnce(child, addVoid, 1000);
122
+ const id = resolvers.keys().next().value as string;
123
+ resolvers.get(id)?.({ sessionFile: "/tmp/x.jsonl" });
124
+
125
+ await expect(promise).resolves.toEqual({ sessionFile: "/tmp/x.jsonl" });
126
+ });
127
+ });
@@ -21,7 +21,7 @@ export function mockExtensionApi(
21
21
  const noop = (): void => { /* test mock: method not invoked by this test */ };
22
22
  // Proxy<T> 泛型参数决定返回类型——直接声明为 ExtensionAPI,
23
23
  // TS 接受(Proxy handler 对 target 的类型不约束 T)。
24
- return new Proxy<ExtensionAPI>(overrides as ExtensionAPI, {
24
+ return new Proxy<ExtensionAPI>(overrides as unknown as ExtensionAPI, {
25
25
  get(target, prop: string | symbol): unknown {
26
26
  if (prop in target) return target[prop as keyof ExtensionAPI];
27
27
  return noop;
@@ -0,0 +1,113 @@
1
+ // src/execution/__tests__/helpers/session-runner-mocks.ts
2
+ //
3
+ // session-runner 系测试(keep-alive-no-progress / recursive-visibility-env 等)的
4
+ // 测试文件侧装配:vi.mocked 取回 + spawn/keep-alive 前奏 + 子进程收尾。
5
+ //
6
+ // 与 spawn-mock.ts 的分工(依赖约束,勿混淆):
7
+ // - spawn-mock.ts 会被各 vi.mock 工厂 `await import()`(求值时机 = 被 mock 模块首次
8
+ // 请求),故它禁止值依赖 session-runner / alive-store / session-pending——否则形成
9
+ // 「mock 工厂 → helper → session-runner → node:child_process(mock 求值中)」循环。
10
+ // mock 模块工厂 + FakeChild + 纯 fixture 都在 spawn-mock.ts。
11
+ // - 本文件只被测试文件顶层静态 import(vi.mock 已 hoist 注册完毕、mock 工厂已可安全
12
+ // 执行),故可值依赖 session-runner / alive-store / session-pending 做 vi.mocked 取回
13
+ // 与 runSpawn 前奏封装。
14
+
15
+ import { spawn } from "node:child_process";
16
+ import * as fs from "node:fs";
17
+
18
+ import { afterEach, beforeEach, vi } from "vitest";
19
+
20
+ import { runSpawn, SPAWN_WATCHDOG_ENV } from "../../engine/engines/pi/session-runner.ts";
21
+ import {
22
+ listActivePendingFromSessionFile,
23
+ readActivePendingFromSessionFile,
24
+ } from "../../session-pending.ts";
25
+ import { isProcessAlive, readAliveMarker, writeAliveMarker } from "../../alive-store.ts";
26
+ import {
27
+ emitStdoutLine,
28
+ lastSpawnedChild,
29
+ makeCtx,
30
+ makeOpts,
31
+ makeRecord,
32
+ waitForSpawn,
33
+ type FakeChild,
34
+ } from "./spawn-mock.ts";
35
+
36
+ /** keep-alive 用例的 session header 行(stdout 首行)。 */
37
+ function sessionHeaderLine(): Record<string, unknown> {
38
+ return { type: "session", id: "sess-ka", timestamp: "2026-09-01T00:00:00.000Z", cwd: "/tmp/test" };
39
+ }
40
+
41
+ /** vi.mocked 取回超集(各测试文件按需解构;与各 vi.mock 工厂返回同一 mock 实例)。 */
42
+ export function takeSessionRunnerMocks() {
43
+ return {
44
+ mockSpawn: vi.mocked(spawn),
45
+ mockExistsSync: vi.mocked(fs.existsSync),
46
+ mockReaddirSync: vi.mocked(fs.readdirSync as (path: fs.PathLike) => string[]),
47
+ mockPending: vi.mocked(readActivePendingFromSessionFile),
48
+ mockListPending: vi.mocked(listActivePendingFromSessionFile),
49
+ mockReadAliveMarker: vi.mocked(readAliveMarker),
50
+ mockWriteAliveMarker: vi.mocked(writeAliveMarker),
51
+ mockIsProcessAlive: vi.mocked(isProcessAlive),
52
+ };
53
+ }
54
+
55
+ /**
56
+ * 非 chatMode 层主 + fake timers + agent_end keep-alive 落位的公共前奏。
57
+ *
58
+ * 返回 promise(runSpawn 原始 promise,供手动 exit/close 收尾的用例)与 finish
59
+ * (标准 close 收尾)两种取用形态。
60
+ */
61
+ export async function spawnAndReachKeepAlive(
62
+ opts = makeOpts(),
63
+ task = "Task: keep-alive no-progress",
64
+ ): Promise<{
65
+ child: FakeChild;
66
+ promise: Promise<Awaited<ReturnType<typeof runSpawn>>>;
67
+ finish: (code?: number) => Promise<Awaited<ReturnType<typeof runSpawn>>>;
68
+ }> {
69
+ const record = makeRecord();
70
+ const promise = runSpawn(record, task, opts, makeCtx());
71
+ await waitForSpawn(vi.mocked(spawn));
72
+ const child = lastSpawnedChild(vi.mocked(spawn));
73
+ // fake timers 必须在 emit agent_end 之前启用(keep-alive timer 新建于 agent_end
74
+ // 处理器内;不 fake setImmediate——stream flush 靠真实事件循环交付,见 MF-3 先例)。
75
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
76
+ emitStdoutLine(child, sessionHeaderLine());
77
+ emitStdoutLine(child, { type: "agent_end", messages: [], willRetry: false });
78
+ await new Promise((r) => setImmediate(r));
79
+ return {
80
+ child,
81
+ promise,
82
+ finish: (code = 143) => {
83
+ child.stdout.end();
84
+ child.stderr.end();
85
+ child.emit("close", code);
86
+ return promise as Promise<Awaited<ReturnType<typeof runSpawn>>>;
87
+ },
88
+ };
89
+ }
90
+
91
+ /** 让最近 spawn 的 FakeChild 发 close(0) 并等 runSpawn 收尾(env 注入用例的标准收尾装配)。 */
92
+ export async function closeLastChildAndAwait<T>(promise: Promise<T>): Promise<T> {
93
+ const child = lastSpawnedChild(vi.mocked(spawn));
94
+ child.emit("close", 0);
95
+ return promise;
96
+ }
97
+
98
+ /** keep-alive 系测试的统一 hooks:清 mock + stub 空 watchdog env + 统一恢复(在 describe 内调用)。 */
99
+ export function keepAliveTestHooks(
100
+ mockPending: ReturnType<typeof takeSessionRunnerMocks>["mockPending"],
101
+ ): void {
102
+ beforeEach(() => {
103
+ vi.clearAllMocks();
104
+ vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
105
+ mockPending.mockReturnValue({ count: 1, recentUnregister: false });
106
+ });
107
+
108
+ afterEach(() => {
109
+ vi.restoreAllMocks();
110
+ vi.unstubAllEnvs();
111
+ vi.useRealTimers();
112
+ });
113
+ }
@@ -47,7 +47,7 @@
47
47
  // };
48
48
  // });
49
49
  // vi.mock("../alive-store.ts", () => ({ writeAliveMarker: vi.fn() }));
50
- // vi.mock("../temp-prompt.ts", () => ({
50
+ // vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
51
51
  // writePromptToTempFile: vi.fn(async (agent: string) => {
52
52
  // const safeName = agent.replace(/[^\w.-]+/g, "_");
53
53
  // return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -60,8 +60,10 @@ import type { ChildProcess } from "node:child_process";
60
60
  import { EventEmitter } from "node:events";
61
61
  import { PassThrough } from "node:stream";
62
62
 
63
+ import { vi } from "vitest";
64
+
63
65
  import { createRecord } from "../../execution-record.ts";
64
- import type { RunOptions, SessionRunnerContext } from "../../session-runner.ts";
66
+ import type { RunOptions, SessionRunnerContext } from "../../engine/engines/pi/session-runner.ts";
65
67
 
66
68
  /** FakeChild 的假 pid(满足 ChildProcess.pid 形状,无真实进程语义)。 */
67
69
  const FAKE_PID = 12345;
@@ -194,8 +196,9 @@ export function makeRecord(id = "run-1") {
194
196
  return createRecord(id, {
195
197
  agent: "general-purpose",
196
198
  model: "test-model",
197
- mode: "sync",
199
+ mode: "background",
198
200
  task: "do something",
201
+ slug: "spawn-mock",
199
202
  startedAt: 1_000_000,
200
203
  rootSessionId: "root-session",
201
204
  parentRecordId: undefined,
@@ -240,3 +243,147 @@ export function makeCtx(overrides: Partial<SessionRunnerContext> = {}): SessionR
240
243
  ...overrides,
241
244
  };
242
245
  }
246
+
247
+ // ── mock 模块工厂(session-runner 系测试共享,从 keep-alive-no-progress /
248
+ // recursive-visibility-env 的逐字重复 mock 块收敛为单源)──
249
+ //
250
+ // 使用模式(vi.mock 工厂内 await import 本文件,免疫 hoist 时序):
251
+ // vi.mock("node:child_process", async () =>
252
+ // (await import("./helpers/spawn-mock.ts")).childProcessModule());
253
+ //
254
+ // 本段保持「无 session-runner / alive-store / session-pending 值依赖」——本文件会被
255
+ // 各 vi.mock 工厂 await import(求值时机 = 被 mock 模块首次请求),若再值依赖
256
+ // session-runner 会构成「mock 工厂 → 本文件 → session-runner → node:child_process
257
+ // (mock 求值中)」循环。需要值依赖这些模块的测试文件侧装配放
258
+ // session-runner-mocks.ts(只被测试文件顶层静态 import)。
259
+
260
+ /** 共享 logger mock 单例(复核失败 warn 留痕断言用;vi.clearAllMocks 统一重置)。 */
261
+ export const loggerMock = { debug: vi.fn(), warn: vi.fn(), error: vi.fn() };
262
+
263
+ /** "../../core/logger.ts" mock 工厂。 */
264
+ export function coreLoggerModule() {
265
+ return { getLogger: () => loggerMock };
266
+ }
267
+
268
+ /** "node:child_process" mock 工厂(spawn 返回 FakeChild + execFile err-first 兜底)。 */
269
+ export function childProcessModule() {
270
+ return {
271
+ spawn: vi.fn(() => new FakeChild()),
272
+ // buildEnvBlock 用 execFile 异步取 git branch:默认 err-first 兜底(catch → branch=""),
273
+ // 形态同 worktree-manager.test.ts 的 setupExecFile
274
+ execFile: vi.fn(
275
+ (
276
+ _cmd: string,
277
+ _args: readonly string[],
278
+ _opts: unknown,
279
+ cb: (err: Error | null, stdout?: string, stderr?: string) => void,
280
+ ) => cb(new Error("execFile not configured in this test")),
281
+ ),
282
+ };
283
+ }
284
+
285
+ /** "node:fs" mock 工厂(fs 侧写方法 vi.fn 化,promises 保留 actual)。 */
286
+ export async function fsModule(): Promise<Record<string, unknown>> {
287
+ const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
288
+ return {
289
+ default: {
290
+ ...actual,
291
+ mkdirSync: vi.fn(),
292
+ existsSync: vi.fn(() => false),
293
+ appendFileSync: vi.fn(),
294
+ writeFileSync: vi.fn(),
295
+ readdirSync: vi.fn(() => []),
296
+ },
297
+ mkdirSync: vi.fn(),
298
+ existsSync: vi.fn(() => false),
299
+ appendFileSync: vi.fn(),
300
+ writeFileSync: vi.fn(),
301
+ readdirSync: vi.fn(() => []),
302
+ promises: actual.promises,
303
+ };
304
+ }
305
+
306
+ /** "../alive-store.ts" mock 工厂(keep-alive 系超集:marker 读写 + pid 存活判定兜底)。 */
307
+ export function aliveStoreModule() {
308
+ return {
309
+ writeAliveMarker: vi.fn(),
310
+ // [T5②] keep-alive 心跳刷新读取现有 marker id(缺失兜底 record.id)
311
+ readAliveMarker: vi.fn(() => undefined),
312
+ isProcessAlive: vi.fn(() => false),
313
+ };
314
+ }
315
+
316
+ /** "../session-pending.ts" mock 工厂(keep-alive 判定统一 count>0:有活跃后代 → keep-alive 分支)。 */
317
+ export function sessionPendingModule() {
318
+ return {
319
+ readActivePendingFromSessionFile: vi.fn(() => ({ count: 1, recentUnregister: false })),
320
+ prunePendingCursor: vi.fn(),
321
+ listActivePendingFromSessionFile: vi.fn(() => ({ items: [] })),
322
+ };
323
+ }
324
+
325
+ /** "../engine/engines/pi/temp-prompt.ts" mock 工厂(prompt 落 /tmp/fake-<agent>,收尾 no-op)。 */
326
+ export function tempPromptModule() {
327
+ return {
328
+ writePromptToTempFile: vi.fn(async (agent: string) => {
329
+ const safeName = agent.replace(/[^\w.-]+/g, "_");
330
+ return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
331
+ }),
332
+ cleanupTempPrompt: vi.fn(async () => {}),
333
+ };
334
+ }
335
+
336
+ // ── recursive-visibility / schema-env 族 fixture(env 断言族专用,与上方 makeRecord 系
337
+ // 默认值不同:/fake/* 路径 + test/model 形态参与 PI_SUBAGENT_* 断言,逐字保留原值)──
338
+
339
+ /** 构造 env 注入断言族的 ExecutionRecord(可 override id/depth)。 */
340
+ export function makeVisibilityRecord(overrides: { id?: string; depth?: number } = {}) {
341
+ return createRecord(overrides.id ?? "sa-test-record", {
342
+ agent: "general-purpose",
343
+ model: "test/model",
344
+ mode: "background",
345
+ slug: "t",
346
+ task: "test task",
347
+ startedAt: Date.now(),
348
+ rootSessionId: "should-be-overridden-by-sessionRootId-source",
349
+ parentRecordId: undefined,
350
+ depth: overrides.depth ?? 0,
351
+ });
352
+ }
353
+
354
+ /** 构造 env 注入断言族的 RunOptions(可 override 关键字段)。 */
355
+ export function makeRunOpts(overrides: Partial<RunOptions> = {}): RunOptions {
356
+ return {
357
+ resolved: { model: { provider: "test", id: "model", name: "Model", reasoning: false }, thinkingLevel: undefined },
358
+ agentConfig: undefined,
359
+ appendSystemPrompt: undefined,
360
+ skillPath: undefined,
361
+ schema: undefined,
362
+ maxTurns: undefined,
363
+ graceTurns: undefined,
364
+ signal: undefined,
365
+ onEvent: undefined,
366
+ ...overrides,
367
+ };
368
+ }
369
+
370
+ /** 构造 env 注入断言族的 SessionRunnerContext(可 override 关键字段)。 */
371
+ export function makeVisibilityCtx(overrides: Partial<SessionRunnerContext> = {}): SessionRunnerContext {
372
+ return {
373
+ cwd: "/fake/cwd",
374
+ agentDir: "/fake/agent",
375
+ skillDirs: [],
376
+ mainCwd: "/fake/cwd",
377
+ sessionRootId: "root-main-session",
378
+ rootCwd: "/fake/cwd",
379
+ ...overrides,
380
+ };
381
+ }
382
+
383
+ /** 取最近一次 spawn 调用传入的 env(跨进程身份注入断言用)。 */
384
+ export function getLastSpawnEnv<T extends { mock: { calls: unknown[][] } }>(
385
+ mockSpawn: T,
386
+ ): Record<string, string | undefined> {
387
+ const opts = mockSpawn.mock.calls.at(-1)?.[2] as { env?: Record<string, string | undefined> } | undefined;
388
+ return opts?.env ?? {};
389
+ }