@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
@@ -0,0 +1,227 @@
1
+ // src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts
2
+ //
3
+ // [u-svc / T5② + T4②] session-runner 侧两措施(真实模块):
4
+ // - T5②/PS-7a:keep-alive 期 agent_end 心跳——覆盖写 .alive marker 刷新软超时基准
5
+ // (P-T5 探针裁决写盘开销可忽略,主路径落地);
6
+ // - T4②/PS-4:agent_settled 的 armIdleTimer fail-fast 降级语义修正——「挂
7
+ // DEFAULT_IDLE_TIMEOUT_MS + warn」而非旧「不挂」(保住通知放行门 + 进程回收两个
8
+ // 不变量,兜底可见)。
9
+ //
10
+ // mock 结构与 keep-alive-no-progress.test.ts / run-spawn-chatmode-settled.test.ts 一致
11
+ //(FakeChild + 受控 session-pending;lifecycle-manager partial mock:armIdleTimer 可注入
12
+ // fail-fast,其余导出(hasIdleTimer/_resetLifecycleState/DEFAULT)为真实实现)。
13
+
14
+ import { spawn } from "node:child_process";
15
+ import * as fs from "node:fs";
16
+
17
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
18
+
19
+ const { loggerMock } = vi.hoisted(() => ({
20
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
21
+ }));
22
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
23
+
24
+ const { writeAliveMarkerMock, readAliveMarkerMock, armIdleTimerMock } = vi.hoisted(() => ({
25
+ writeAliveMarkerMock: vi.fn(),
26
+ readAliveMarkerMock: vi.fn<() => undefined>(() => undefined),
27
+ armIdleTimerMock: vi.fn(),
28
+ }));
29
+
30
+ vi.mock("node:child_process", async () => {
31
+ const { FakeChild } = await import("./helpers/spawn-mock.ts");
32
+ return {
33
+ spawn: vi.fn(() => new FakeChild()),
34
+ execFile: vi.fn(
35
+ (
36
+ _cmd: string,
37
+ _args: readonly string[],
38
+ _opts: unknown,
39
+ cb: (err: Error | null, stdout?: string, stderr?: string) => void,
40
+ ) => cb(new Error("execFile not configured in this test")),
41
+ ),
42
+ };
43
+ });
44
+
45
+ vi.mock("node:fs", async () => {
46
+ const actual = await import("node:fs");
47
+ return {
48
+ default: {
49
+ ...actual,
50
+ mkdirSync: vi.fn(),
51
+ existsSync: vi.fn(() => false),
52
+ appendFileSync: vi.fn(),
53
+ writeFileSync: vi.fn(),
54
+ readdirSync: vi.fn(() => []),
55
+ },
56
+ mkdirSync: vi.fn(),
57
+ existsSync: vi.fn(() => false),
58
+ appendFileSync: vi.fn(),
59
+ writeFileSync: vi.fn(),
60
+ readdirSync: vi.fn(() => []),
61
+ promises: actual.promises,
62
+ };
63
+ });
64
+
65
+ vi.mock("../alive-store.ts", () => ({
66
+ writeAliveMarker: writeAliveMarkerMock,
67
+ readAliveMarker: readAliveMarkerMock,
68
+ isProcessAlive: vi.fn(() => false),
69
+ ALIVE_SOFT_TIMEOUT_MS: 3_600_000,
70
+ }));
71
+
72
+ // keep-alive 心跳用例:count=1(有活跃后代 → keep-alive 分支)。
73
+ vi.mock("../session-pending.ts", () => ({
74
+ readActivePendingFromSessionFile: vi.fn(() => ({ count: 1, recentUnregister: false })),
75
+ prunePendingCursor: vi.fn(),
76
+ listActivePendingFromSessionFile: vi.fn(() => ({ items: [] })),
77
+ }));
78
+
79
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
80
+ writePromptToTempFile: vi.fn(async (agent: string) => {
81
+ const safeName = agent.replace(/[^\w.-]+/g, "_");
82
+ return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
83
+ }),
84
+ cleanupTempPrompt: vi.fn(async () => {}),
85
+ }));
86
+
87
+ vi.mock("../lifecycle-manager.ts", async (importOriginal) => {
88
+ const actual = await importOriginal<typeof import("../lifecycle-manager.ts")>();
89
+ return { ...actual, armIdleTimer: armIdleTimerMock };
90
+ });
91
+
92
+ import { DEFAULT_IDLE_TIMEOUT_MS, hasIdleTimer, _resetLifecycleState } from "../lifecycle-manager.ts";
93
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
94
+ import {
95
+ emitStdoutLine,
96
+ type FakeChild,
97
+ lastSpawnedChild as lastSpawnedChildOf,
98
+ makeCtx,
99
+ makeOpts,
100
+ makeRecord,
101
+ waitForSpawn as waitForSpawnOf,
102
+ } from "./helpers/spawn-mock.ts";
103
+
104
+ const mockSpawn = vi.mocked(spawn);
105
+
106
+ const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
107
+ const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
108
+
109
+ const flushPump = async (): Promise<void> => {
110
+ await new Promise((r) => setTimeout(r, 20));
111
+ };
112
+
113
+ describe("T5② alive marker heartbeat on keep-alive agent_end", () => {
114
+ beforeEach(() => {
115
+ vi.clearAllMocks();
116
+ writeAliveMarkerMock.mockClear();
117
+ readAliveMarkerMock.mockReturnValue(undefined);
118
+ _resetLifecycleState();
119
+ });
120
+
121
+ afterEach(() => {
122
+ vi.restoreAllMocks();
123
+ vi.useRealTimers();
124
+ _resetLifecycleState();
125
+ });
126
+
127
+ it("rewrites the alive marker each keep-alive agent_end (soft-timeout base refreshed)", async () => {
128
+ const record = makeRecord("sa-heartbeat");
129
+ // 预设 sessionFile(跳过 lazy get_state 回补;不 emit header——RPC mode 无 header,
130
+ // emit 会经 deriveSessionFilePath 覆盖预设路径)
131
+ record.sessionFile = "/tmp/fake-heartbeat-session.jsonl";
132
+ const promise = runSpawn(record, "Task: heartbeat", makeOpts(), makeCtx());
133
+ await waitForSpawn();
134
+ const child = lastSpawnedChild();
135
+
136
+ // 第一次 keep-alive agent_end → 心跳(保 pid/id、刷新 startedAt)
137
+ emitStdoutLine(child, { type: "agent_end", willRetry: false });
138
+ await flushPump();
139
+ expect(writeAliveMarkerMock).toHaveBeenCalledTimes(1);
140
+ expect(writeAliveMarkerMock).toHaveBeenCalledWith(
141
+ "/tmp/fake-heartbeat-session.jsonl",
142
+ expect.objectContaining({ pid: child.pid, id: record.id, startedAt: expect.any(Number) }),
143
+ );
144
+
145
+ // 第二次 agent_end(后代仍在)→ 再次心跳(软超时基准持续推新)
146
+ emitStdoutLine(child, { type: "agent_end", willRetry: false });
147
+ await flushPump();
148
+ expect(writeAliveMarkerMock).toHaveBeenCalledTimes(2);
149
+
150
+ child.stdout.end();
151
+ child.stderr.end();
152
+ child.emit("close", 143);
153
+ await promise;
154
+ });
155
+
156
+ it("keeps the existing marker id when refreshing (readAliveMarker hit)", async () => {
157
+ readAliveMarkerMock.mockReturnValue({ pid: 1, id: "sess-orig-id", startedAt: 1 } as never);
158
+ const record = makeRecord("sa-heartbeat-id");
159
+ record.sessionFile = "/tmp/fake-heartbeat-session-2.jsonl";
160
+ const promise = runSpawn(record, "Task: heartbeat id", makeOpts(), makeCtx());
161
+ await waitForSpawn();
162
+ const child = lastSpawnedChild();
163
+
164
+ emitStdoutLine(child, { type: "agent_end", willRetry: false });
165
+ await flushPump();
166
+ expect(writeAliveMarkerMock).toHaveBeenLastCalledWith(
167
+ "/tmp/fake-heartbeat-session-2.jsonl",
168
+ expect.objectContaining({ id: "sess-orig-id" }),
169
+ );
170
+
171
+ child.stdout.end();
172
+ child.stderr.end();
173
+ child.emit("close", 143);
174
+ await promise;
175
+ });
176
+ });
177
+
178
+ describe("T4② armIdleTimer fail-fast falls back to DEFAULT + warn", () => {
179
+ beforeEach(() => {
180
+ vi.clearAllMocks();
181
+ _resetLifecycleState();
182
+ });
183
+
184
+ afterEach(() => {
185
+ vi.restoreAllMocks();
186
+ vi.useRealTimers();
187
+ _resetLifecycleState();
188
+ });
189
+
190
+ it("agent_settled with failing armIdleTimer still arms the timer at DEFAULT duration with a warning", async () => {
191
+ // 首次调用(record.idleTimeoutMs 通道)注入 fail-fast,后续转发真实实现
192
+ const actual = await vi.importActual<typeof import("../lifecycle-manager.ts")>("../lifecycle-manager.ts");
193
+ armIdleTimerMock.mockImplementationOnce(() => {
194
+ throw new Error("idleTimeoutMs = 3000000000 exceeds the Node setTimeout limit");
195
+ });
196
+ armIdleTimerMock.mockImplementation(
197
+ (recordId: string, onTimeout: () => void, timeoutMs?: number) =>
198
+ actual.armIdleTimer(recordId, onTimeout, timeoutMs),
199
+ );
200
+
201
+ const record = makeRecord("sa-settled-fallback");
202
+ const mutable = record as { chatMode: boolean; idleTimeoutMs: number };
203
+ mutable.chatMode = true;
204
+ mutable.idleTimeoutMs = 600_000;
205
+ const promise = runSpawn(record, "Task: settled fallback", makeOpts(), makeCtx());
206
+ await waitForSpawn();
207
+ const child = lastSpawnedChild();
208
+
209
+ emitStdoutLine(child, { type: "agent_settled" });
210
+ await flushPump();
211
+
212
+ // 两次 arm:第一次(非法/失败通道)+ 降级(DEFAULT)
213
+ expect(armIdleTimerMock).toHaveBeenCalledTimes(2);
214
+ expect(armIdleTimerMock).toHaveBeenLastCalledWith(record.id, expect.any(Function), DEFAULT_IDLE_TIMEOUT_MS);
215
+ // 不变量保住:timer 确实 armed(通知放行门 + 进程回收有效)
216
+ expect(hasIdleTimer(record.id)).toBe(true);
217
+ // 降级可见
218
+ expect(loggerMock.warn).toHaveBeenCalledWith(
219
+ expect.stringContaining("fell back to DEFAULT_IDLE_TIMEOUT_MS"),
220
+ );
221
+
222
+ child.stdout.end();
223
+ child.stderr.end();
224
+ child.emit("close", 143);
225
+ await promise;
226
+ });
227
+ });
@@ -0,0 +1,290 @@
1
+ // src/execution/__tests__/session-runner-lifecycle-helpers.test.ts
2
+ //
3
+ // [S-17b / runSpawn 阶段拆分] 重构提取辅助函数的可观察行为锁定(生命周期/kill 决策域)。
4
+ //
5
+ // 背景:session-runner.ts 按执行阶段拆分后,以下新提取辅助函数残留分支无断言锁定:
6
+ // - hasLiveActiveDescendant(keep-alive no-progress fire 惰性复核)的两个 continue
7
+ // 盲区分支:pending 项无 sessionId / sessionId 反查不到 session 文件——两者均
8
+ // 「不计入存活」→ 真静默处置(与 A1-2② 同向,但走的是不同 continue 边);
9
+ // - sweepDescendantsOnChildClose 的整体失败 catch:sweep 抛错不掩盖层主自身的
10
+ // 收尾结果(best-effort 可见 + runSpawn 照常 resolve);
11
+ // - registerSpawnedChildForRecord 的 close/error 双通道按句移除(M4 竞态守卫);
12
+ // - killRecordChildWithEscalation 同 record 换新 child(resume spawn 覆盖注册)时
13
+ // 先清旧升级 timer 防叠加——与 service-kill-escalation.test.ts 的「同 child
14
+ // 重复调用 no-op」互补,覆盖的是 1396-1397 的 clearTimeout 真路径。
15
+ //
16
+ // mock 布局与 keep-alive-no-progress.test.ts 一致(FakeChild + fs/alive-store/
17
+ // session-pending/temp-prompt mock + 共享 logger mock)。
18
+
19
+ import { spawn } from "node:child_process";
20
+ import type { ChildProcess } from "node:child_process";
21
+
22
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
+
24
+ const { loggerMock } = vi.hoisted(() => ({
25
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
26
+ }));
27
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
28
+
29
+ vi.mock("node:child_process", async () => {
30
+ const { FakeChild } = await import("./helpers/spawn-mock.ts");
31
+ return {
32
+ spawn: vi.fn(() => new FakeChild()),
33
+ execFile: vi.fn(
34
+ (
35
+ _cmd: string,
36
+ _args: readonly string[],
37
+ _opts: unknown,
38
+ cb: (err: Error | null, stdout?: string, stderr?: string) => void,
39
+ ) => cb(new Error("execFile not configured in this test")),
40
+ ),
41
+ };
42
+ });
43
+
44
+ vi.mock("node:fs", async () => {
45
+ const actual = await import("node:fs");
46
+ return {
47
+ default: {
48
+ ...actual,
49
+ mkdirSync: vi.fn(),
50
+ existsSync: vi.fn(() => false),
51
+ appendFileSync: vi.fn(),
52
+ writeFileSync: vi.fn(),
53
+ readdirSync: vi.fn(() => []),
54
+ },
55
+ mkdirSync: vi.fn(),
56
+ existsSync: vi.fn(() => false),
57
+ appendFileSync: vi.fn(),
58
+ writeFileSync: vi.fn(),
59
+ readdirSync: vi.fn(() => []),
60
+ promises: actual.promises,
61
+ };
62
+ });
63
+
64
+ vi.mock("../alive-store.ts", () => ({
65
+ writeAliveMarker: vi.fn(),
66
+ readAliveMarker: vi.fn(() => undefined),
67
+ isProcessAlive: vi.fn(() => false),
68
+ }));
69
+
70
+ // keep-alive 判定:本文件统一 count>0(有活跃后代 → keep-alive 分支)。
71
+ vi.mock("../session-pending.ts", () => ({
72
+ readActivePendingFromSessionFile: vi.fn(() => ({ count: 1, recentUnregister: false })),
73
+ prunePendingCursor: vi.fn(),
74
+ listActivePendingFromSessionFile: vi.fn(() => ({ items: [] })),
75
+ }));
76
+
77
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
78
+ writePromptToTempFile: vi.fn(async (agent: string) => {
79
+ const safeName = agent.replace(/[^\w.-]+/g, "_");
80
+ return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
81
+ }),
82
+ cleanupTempPrompt: vi.fn(async () => {}),
83
+ }));
84
+
85
+ import {
86
+ KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS,
87
+ killRecordChildWithEscalation,
88
+ registerSpawnedChildForRecord,
89
+ runSpawn,
90
+ SPAWN_WATCHDOG_ENV,
91
+ spawnedChildren,
92
+ _resetServiceKillStateForTest,
93
+ getChildByRecord,
94
+ } from "../engine/engines/pi/session-runner.ts";
95
+ import { listActivePendingFromSessionFile, readActivePendingFromSessionFile } from "../session-pending.ts";
96
+ import { _resetLifecycleState } from "../lifecycle-manager.ts";
97
+ import {
98
+ emitStdoutLine,
99
+ FakeChild,
100
+ lastSpawnedChild as lastSpawnedChildOf,
101
+ makeCtx,
102
+ makeOpts,
103
+ makeRecord,
104
+ waitForSpawn as waitForSpawnOf,
105
+ } from "./helpers/spawn-mock.ts";
106
+
107
+ const mockSpawn = vi.mocked(spawn);
108
+ const mockPending = vi.mocked(readActivePendingFromSessionFile);
109
+ const mockListPending = vi.mocked(listActivePendingFromSessionFile);
110
+ const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
111
+ const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
112
+
113
+ /** keep-alive 30min 无进展阈值(与源码常量一致,测试可读性用)。 */
114
+ const SIGKILL_ESCALATION_MS = 30_000;
115
+
116
+ /** FakeChild → 注册 API 的 ChildProcess 形状收窄(结构缺私有字段;记账/kill 只消费 once/kill 形状)。 */
117
+ const asChildProcess = (c: FakeChild): ChildProcess => c as unknown as ChildProcess;
118
+
119
+ function sessionHeaderLine(): Record<string, unknown> {
120
+ return { type: "session", id: "sess-ka", timestamp: "2026-09-01T00:00:00.000Z", cwd: "/tmp/test" };
121
+ }
122
+
123
+ /** 非 chatMode 层主 + fake timers + agent_end keep-alive 落位的公共前奏。 */
124
+ async function spawnAndReachKeepAlive(): Promise<{
125
+ child: FakeChild;
126
+ finish: (code?: number) => Promise<Awaited<ReturnType<typeof runSpawn>>>;
127
+ }> {
128
+ const record = makeRecord();
129
+ const promise = runSpawn(record, "Task: keep-alive re-check", makeOpts(), makeCtx());
130
+ await waitForSpawn();
131
+ const child = lastSpawnedChild();
132
+ // fake timers 必须在 emit agent_end 之前启用(keep-alive timer 新建于 agent_end 处理器内)
133
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
134
+ emitStdoutLine(child, sessionHeaderLine());
135
+ emitStdoutLine(child, { type: "agent_end", messages: [], willRetry: false });
136
+ await new Promise((r) => setImmediate(r));
137
+ return {
138
+ child,
139
+ finish: (code = 143) => {
140
+ child.stdout.end();
141
+ child.stderr.end();
142
+ child.emit("close", code);
143
+ return promise as Promise<Awaited<ReturnType<typeof runSpawn>>>;
144
+ },
145
+ };
146
+ }
147
+
148
+ describe("[S-17b] hasLiveActiveDescendant:fire 复核的 T5 盲区 continue 边", () => {
149
+ beforeEach(() => {
150
+ vi.clearAllMocks();
151
+ vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
152
+ mockPending.mockReturnValue({ count: 1, recentUnregister: false });
153
+ });
154
+
155
+ afterEach(() => {
156
+ vi.restoreAllMocks();
157
+ vi.unstubAllEnvs();
158
+ vi.useRealTimers();
159
+ });
160
+
161
+ it("复核发现 pending 项缺 sessionId(marker 反查无门)→ 不计入存活,真静默处置", async () => {
162
+ mockListPending.mockReturnValue({
163
+ items: [{ id: "bg-nosess", sessionId: undefined, type: "session" }],
164
+ });
165
+
166
+ const { child, finish } = await spawnAndReachKeepAlive();
167
+ expect(child.killed).toBe(false);
168
+
169
+ // 连续静默 30min fire:盲区后代不豁免层主(归 T5 marker 机制兜底)
170
+ await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
171
+ expect(child.killed).toBe(true);
172
+ expect(child.killSignal).toBe("SIGTERM");
173
+
174
+ await finish();
175
+ });
176
+
177
+ it("复核发现 sessionId 反查不到 session 文件(未 flush / 非本树)→ 不计入存活,真静默处置", async () => {
178
+ mockListPending.mockReturnValue({
179
+ items: [{ id: "bg-lost", sessionId: "ka-missing-sess", type: "session" }],
180
+ });
181
+ // readdirSync 默认 mock 返回 [] → findSessionFileByHeaderId 反查失败
182
+
183
+ const { child, finish } = await spawnAndReachKeepAlive();
184
+ expect(child.killed).toBe(false);
185
+
186
+ await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
187
+ expect(child.killed).toBe(true);
188
+ expect(child.killSignal).toBe("SIGTERM");
189
+
190
+ await finish();
191
+ });
192
+ });
193
+
194
+ describe("[S-17b] sweepDescendantsOnChildClose:sweep 整体失败不掩盖层主收尾", () => {
195
+ beforeEach(() => {
196
+ vi.clearAllMocks();
197
+ vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
198
+ mockPending.mockReturnValue({ count: 1, recentUnregister: false });
199
+ });
200
+
201
+ afterEach(() => {
202
+ vi.restoreAllMocks();
203
+ vi.unstubAllEnvs();
204
+ vi.useRealTimers();
205
+ });
206
+
207
+ it("no-progress 处置后 close 收尾时 sweep 抛错 → best-effort error 留痕,runSpawn 照常 resolve 成功", async () => {
208
+ // 第一次 list 调用(fire 惰性复核):无存活后代 → 处置
209
+ // 第二次 list 调用(close 后 sweep 采集):整体抛错 → sweepDescendantsOnChildClose catch
210
+ mockListPending
211
+ .mockReturnValueOnce({ items: [] })
212
+ .mockImplementation(() => {
213
+ throw new Error("pending scan exploded");
214
+ });
215
+
216
+ const { child, finish } = await spawnAndReachKeepAlive();
217
+ await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
218
+ expect(child.killed).toBe(true);
219
+
220
+ const result = await finish();
221
+ // 层主自身收尾结果不被 sweep 失败掩盖(信号终止视为正常完成,既有语义)
222
+ expect(result.success).toBe(true);
223
+ expect(loggerMock.error).toHaveBeenCalledWith(
224
+ expect.stringContaining("descendant sweep (keep-alive watchdog) failed"),
225
+ expect.objectContaining({ detail: "pending scan exploded" }),
226
+ );
227
+ });
228
+ });
229
+
230
+ describe("[S-17b] registerSpawnedChildForRecord:close/error 双通道按句移除", () => {
231
+ beforeEach(() => {
232
+ vi.clearAllMocks();
233
+ spawnedChildren.clear();
234
+ });
235
+
236
+ afterEach(() => {
237
+ spawnedChildren.clear();
238
+ });
239
+
240
+ it("注册后 child 异步 'error'(spawn 失败)→ 移出 spawnedChildren(dispose 兜底不再 kill 它)", () => {
241
+ const child = new FakeChild();
242
+ registerSpawnedChildForRecord("rec-reg-error", asChildProcess(child));
243
+ expect(getChildByRecord("rec-reg-error")).toBe(child);
244
+
245
+ child.emit("error", new Error("spawn ENOENT"));
246
+ expect(getChildByRecord("rec-reg-error")).toBeUndefined();
247
+ });
248
+
249
+ it("注册后 child 'close'(正常退出)→ 同样移出(close/error 两监听同构)", () => {
250
+ const child = new FakeChild();
251
+ registerSpawnedChildForRecord("rec-reg-close", asChildProcess(child));
252
+ expect(getChildByRecord("rec-reg-close")).toBe(child);
253
+
254
+ child.emit("close", 0);
255
+ expect(getChildByRecord("rec-reg-close")).toBeUndefined();
256
+ });
257
+ });
258
+
259
+ describe("[S-17b] killRecordChildWithEscalation:同 record 换新 child 的升级 timer 防叠加", () => {
260
+ beforeEach(() => {
261
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
262
+ _resetServiceKillStateForTest();
263
+ spawnedChildren.clear();
264
+ });
265
+
266
+ afterEach(() => {
267
+ vi.useRealTimers();
268
+ _resetServiceKillStateForTest();
269
+ spawnedChildren.clear();
270
+ });
271
+
272
+ it("resume spawn 覆盖注册后重复 kill → 旧 child 的升级 timer 被清,30s 后仅新 child 收到 SIGKILL", async () => {
273
+ const oldChild = new FakeChild();
274
+ spawnedChildren.set("rec-resume-kill", asChildProcess(oldChild));
275
+ killRecordChildWithEscalation("rec-resume-kill", "closeChatIdle#1");
276
+ expect(oldChild.killSignal).toBe("SIGTERM");
277
+
278
+ // resume:同 record 换新 child(Map 条目被覆盖,旧 child 句柄仍在旧 timer 闭包里)
279
+ const newChild = new FakeChild();
280
+ spawnedChildren.set("rec-resume-kill", asChildProcess(newChild));
281
+ killRecordChildWithEscalation("rec-resume-kill", "closeChatIdle#2");
282
+ expect(newChild.killSignal).toBe("SIGTERM");
283
+
284
+ await vi.advanceTimersByTimeAsync(SIGKILL_ESCALATION_MS);
285
+
286
+ // 只有新 child 的升级 timer 存活:旧 timer 已被 clearTimeout,不产生第二次 SIGKILL
287
+ expect(newChild.killSignal).toBe("SIGKILL");
288
+ expect(oldChild.killSignal).toBe("SIGTERM");
289
+ });
290
+ });
@@ -77,7 +77,7 @@ vi.mock("../alive-store.ts", () => ({
77
77
  writeAliveMarker: vi.fn(),
78
78
  }));
79
79
 
80
- vi.mock("../temp-prompt.ts", () => ({
80
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
81
81
  writePromptToTempFile: vi.fn(async (agent: string) => {
82
82
  const safeName = agent.replace(/[^\w.-]+/g, "_");
83
83
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -96,7 +96,7 @@ import {
96
96
  runSpawn,
97
97
  SCHEMA_ENV_MAX_BYTES,
98
98
  type SessionRunnerContext,
99
- } from "../session-runner.ts";
99
+ } from "../engine/engines/pi/session-runner.ts";
100
100
  import { schemaEnvByteLength } from "../../shared/schema-env.ts";
101
101
 
102
102
  const mockSpawn = vi.mocked(spawn);
@@ -128,8 +128,9 @@ function makeRecord() {
128
128
  return createRecord("test-1", {
129
129
  agent: "general-purpose",
130
130
  model: "test/model",
131
- mode: "sync",
131
+ mode: "background",
132
132
  task: "test task",
133
+ slug: "test",
133
134
  startedAt: Date.now(),
134
135
  rootSessionId: "s1",
135
136
  parentRecordId: undefined,
@@ -139,7 +140,7 @@ function makeRecord() {
139
140
 
140
141
  function makeRunOpts(overrides: Partial<RunOptions> = {}): RunOptions {
141
142
  return {
142
- resolved: { model: { provider: "test", id: "model" }, thinkingLevel: undefined },
143
+ resolved: { model: { provider: "test", id: "model", name: "Model", reasoning: false }, thinkingLevel: undefined },
143
144
  agentConfig: undefined,
144
145
  appendSystemPrompt: undefined,
145
146
  skillPath: undefined,
@@ -320,8 +321,9 @@ describe("runSpawn schemaEnv childEnv 注入 (T3.9/T3.11)", () => {
320
321
  const record = createRecord("test-fork-1", {
321
322
  agent: "general-purpose",
322
323
  model: "test/model",
323
- mode: "sync",
324
+ mode: "background",
324
325
  task: "test task",
326
+ slug: "test",
325
327
  startedAt: Date.now(),
326
328
  rootSessionId: "s1",
327
329
  parentRecordId: undefined,