@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
@@ -65,10 +65,10 @@ import { spawn } from "node:child_process";
65
65
  import type { EnginePort, RunContext } from "../engine/port.ts";
66
66
  import { resolveJournalPath } from "../engine/paths.ts";
67
67
  import { clearEngines, registerEngine } from "../engine/registry.ts";
68
+ import type { AgentCallOpts } from "../../orchestration/models/types.ts";
68
69
  import type {
69
70
  AgentEvent,
70
71
  AgentOutcome,
71
- AgentTaskSpec,
72
72
  EngineCapabilities,
73
73
  EngineHandle,
74
74
  ProbeReport,
@@ -76,7 +76,7 @@ import type {
76
76
  import { ModelConfigService } from "../model-config-service.ts";
77
77
  import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
78
78
  import { toSubagentRecordEntry } from "../record-entry.ts";
79
- import { getChildByRecord } from "../session-runner.ts";
79
+ import { getChildByRecord } from "../engine/engines/pi/session-runner.ts";
80
80
  import { SubagentService } from "../subagent-service.ts";
81
81
  import type { ExecuteOptions } from "../types.ts";
82
82
 
@@ -86,7 +86,7 @@ const mockSpawn = vi.mocked(spawn);
86
86
  // 假引擎(EnginePort 最小实现;run 行为由每个用例注入)
87
87
  // ============================================================
88
88
 
89
- /** zcode 形态 capabilities(conversation/steer unsupported、sandbox none)。 */
89
+ /** zcode 形态 capabilities(conversation/steer unsupported、sandbox none、maxTurns false)。 */
90
90
  const ZCODE_LIKE_CAPS: EngineCapabilities = {
91
91
  schemaEnforcement: "emulated",
92
92
  steer: "unsupported",
@@ -98,10 +98,11 @@ const ZCODE_LIKE_CAPS: EngineCapabilities = {
98
98
  resume: "cold",
99
99
  interrupt: "kill-only",
100
100
  permissionMode: "native",
101
+ maxTurns: false,
101
102
  };
102
103
 
103
104
  interface CapturedRun {
104
- task: AgentTaskSpec;
105
+ task: AgentCallOpts;
105
106
  ctx: RunContext;
106
107
  }
107
108
 
@@ -111,7 +112,7 @@ class FakeEngine implements EnginePort {
111
112
  /** U2:probe 结果注入(路由期 routeEngine 消费;缺省 ok——probe 通过的常态)。 */
112
113
  probeFailed = false;
113
114
  /** run 实现注入(缺省:挂起不 resolve——record 保持 running 便于内存断言)。 */
114
- runImpl: (task: AgentTaskSpec, ctx: RunContext) => Promise<{ handle: EngineHandle; outcome: AgentOutcome }>;
115
+ runImpl: (task: AgentCallOpts, ctx: RunContext) => Promise<{ handle: EngineHandle; outcome: AgentOutcome }>;
115
116
 
116
117
  constructor(id: string) {
117
118
  this.id = id;
@@ -132,7 +133,7 @@ class FakeEngine implements EnginePort {
132
133
  }
133
134
  : { ok: true, engineVersion: "fake", checks: [{ name: "bin", ok: true }] };
134
135
  }
135
- async run(task: AgentTaskSpec, ctx: RunContext): Promise<{ handle: EngineHandle; outcome: AgentOutcome }> {
136
+ async run(task: AgentCallOpts, ctx: RunContext): Promise<{ handle: EngineHandle; outcome: AgentOutcome }> {
136
137
  this.runs.push({ task, ctx });
137
138
  return this.runImpl(task, ctx);
138
139
  }
@@ -222,7 +223,7 @@ function baseOpts(agentDir: string, extra: Partial<ExecuteOptions> = {}): Execut
222
223
 
223
224
  /** 挂起 run 的引擎路径下取内存 running record 的 engine 盖章(collectRecords 内存源投影)。 */
224
225
  function runningEngineTag(service: SubagentService, id: string): string | undefined {
225
- const rec = service.collectRecords(10, "running").find((r) => r.id === id);
226
+ const rec = service.queries.collectRecords(10, "running").find((r) => r.id === id);
226
227
  return rec?.engine;
227
228
  }
228
229
 
@@ -242,7 +243,7 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
242
243
  // registry 是 globalThis 进程单例——必须清空,防假引擎泄漏进其他测试文件
243
244
  clearEngines();
244
245
  resetCoreForTests();
245
- fs.rmSync(agentDir, { recursive: true, force: true });
246
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
246
247
  vi.clearAllMocks();
247
248
  });
248
249
 
@@ -308,7 +309,7 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
308
309
  const handle = await service.execute(baseOpts(agentDir));
309
310
  await vi.waitFor(() => expect(mockSpawn).toHaveBeenCalled());
310
311
 
311
- const rec = service.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
312
+ const rec = service.queries.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
312
313
  expect(rec).toBeDefined();
313
314
  expect(rec?.engine).toBeUndefined();
314
315
  expect(JSON.stringify(toSubagentRecordEntry(rec!))).not.toContain("engine");
@@ -320,10 +321,10 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
320
321
  await vi.waitFor(() => expect(mockSpawn).toHaveBeenCalled());
321
322
 
322
323
  expect(zcode.runs.length).toBe(0);
323
- // piEngine 只是注册表占位——pi 缺省路径不查注册表(上一用例已验证零注册场景),
324
- // 此处断言 piEngine 也未被消费(pi 原路径不走 EnginePort)
324
+ // piEngine 只是注册表占位——D2 单轨后 pi chat 走 Service 内部 DI 的 PiEngine 实例
325
+ //(chatPiEngine,经 EnginePort 交接轮次),registry 占位实例仍未被 chat 域消费
325
326
  expect(piEngine.runs.length).toBe(0);
326
- const rec = service.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
327
+ const rec = service.queries.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
327
328
  expect(rec?.engine).toBeUndefined();
328
329
  });
329
330
 
@@ -342,7 +343,7 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
342
343
  expect((err as { recovery?: string }).recovery).toContain("engine: pi");
343
344
  expect((err as { recovery?: string }).recovery).toContain("不传该参数");
344
345
  expect(zcode.runs.length).toBe(0);
345
- expect(service.collectRecords(10, "all")).toHaveLength(0);
346
+ expect(service.queries.collectRecords(10, "all")).toHaveLength(0);
346
347
  });
347
348
 
348
349
  it("[预检] fork:true / worktree:true 同步拒绝", async () => {
@@ -354,7 +355,39 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
354
355
  /engine_capability_unsupported/,
355
356
  );
356
357
  expect(zcode.runs.length).toBe(0);
357
- expect(service.collectRecords(10, "all")).toHaveLength(0);
358
+ expect(service.queries.collectRecords(10, "all")).toHaveLength(0);
359
+ });
360
+
361
+ it("[D3-④] zcode + maxTurns → 同步拒绝(record 创建前,不产生孤儿 record)——旧形态的 engine.run 内异步拒绝废弃", async () => {
362
+ // 旧形态:Service 层预检不查 maxTurns → record 创建 + kickOffEngineRun →
363
+ // zcode run 内硬编码 shape 检查 throw → failed record(异步化)。
364
+ // D3-④ 检查点钉死后:capabilities.maxTurns 位驱动,record 创建前同步 throw。
365
+ const { service, zcode } = setup(agentDir);
366
+ const err = await service
367
+ .execute(baseOpts(agentDir, { engine: "zcode", maxTurns: 5 }))
368
+ .catch((e: unknown) => e);
369
+ expect(err).toBeInstanceOf(Error);
370
+ expect((err as Error).message).toContain("engine_capability_unsupported");
371
+ expect((err as Error).message).toContain("maxTurns");
372
+ expect((err as { recovery?: string }).recovery).toContain("engine: pi");
373
+ // 「不产生孤儿 record」断言落点(V4④):store 无新增条目、引擎未被触达
374
+ expect(zcode.runs.length).toBe(0);
375
+ expect(service.queries.collectRecords(10, "all")).toHaveLength(0);
376
+ });
377
+
378
+ it("[V4⑤ 反向] pi + maxTurns/fork/conversation/worktree → 零拦截(pi 既有合法能力无回归)", async () => {
379
+ const { service, zcode } = setup(agentDir);
380
+ // maxTurns=3(V4⑤ 场景原样):正常进入 pi 轮次(spawn 被调),无同步拒绝
381
+ const handle = await service.execute(baseOpts(agentDir, { maxTurns: 3 }));
382
+ await vi.waitFor(() => expect(mockSpawn).toHaveBeenCalled());
383
+ expect(zcode.runs.length).toBe(0);
384
+ expect(service.queries.collectRecords(10, "running").find((r) => r.id === handle.subagentId)).toBeDefined();
385
+ // fork / conversation 组合同样直通(第二次 spawn 被调,无 engine_capability_unsupported)
386
+ await service.execute(baseOpts(agentDir, { fork: true, conversation: true }));
387
+ await vi.waitFor(() => expect(mockSpawn.mock.calls.length).toBeGreaterThanOrEqual(2));
388
+ // pi + worktree:预检放行(caps.sandbox='emulated')——execute 不因能力被拒而
389
+ // reject(worktree 创建成败是另一维度,与拦截无关)
390
+ await expect(service.execute(baseOpts(agentDir, { worktree: true }))).resolves.toBeTypeOf("object");
358
391
  });
359
392
 
360
393
  // ============================================================
@@ -367,14 +400,14 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
367
400
  expect(err).toBeInstanceOf(Error);
368
401
  expect((err as Error).message).toContain("engine_not_found");
369
402
  expect((err as Error).message).toContain("zcode");
370
- expect(service.collectRecords(10, "all")).toHaveLength(0);
403
+ expect(service.queries.collectRecords(10, "all")).toHaveLength(0);
371
404
  });
372
405
 
373
406
  // ============================================================
374
407
  // 5. 引擎分支骨架(D10 终止链 + 终态迁移)
375
408
  // ============================================================
376
409
 
377
- it("[骨架] taskSpec 字段正确(task/cwd/model/effort/schema/persona)", async () => {
410
+ it("[骨架] 合流任务形状字段正确(prompt/cwd/model/thinkingLevel/schema/skillPath/appendSystemPrompt——D6 直传)", async () => {
378
411
  const { service, zcode } = setup(agentDir);
379
412
  await service.execute(
380
413
  baseOpts(agentDir, {
@@ -388,12 +421,14 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
388
421
  );
389
422
  await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
390
423
  const { task, ctx } = zcode.runs[0];
391
- expect(task.task).toBe("do work");
392
- expect(task.slug).toBe("routing-test");
424
+ // [D6 合流] task = AgentCallOpts(host-task-spec 从 ExecuteOptions 直译)
425
+ expect(task.prompt).toBe("do work");
426
+ expect(task.description).toBe("routing-test");
393
427
  expect(task.model).toBe("zcode/glm");
394
- expect(task.effort).toBe("high");
428
+ expect(task.thinkingLevel).toBe("high");
395
429
  expect(task.schema).toEqual({ type: "object" });
396
- expect(task.persona).toEqual({ skillPath: "/tmp/skill.md", appendSystemPrompt: ["extra"] });
430
+ expect(task.skillPath).toBe("/tmp/skill.md");
431
+ expect(task.appendSystemPrompt).toEqual(["extra"]);
397
432
  expect(ctx.poolKey).toBe("shared");
398
433
  });
399
434
 
@@ -403,7 +438,7 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
403
438
  const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
404
439
 
405
440
  // record 终态化即 archive(无 sessionFile 不可磁盘重建)→ 内存查询落空
406
- await vi.waitFor(() => expect(service.findRecord(handle.subagentId)).toBeUndefined());
441
+ await vi.waitFor(() => expect(service.queries.findRecord(handle.subagentId)).toBeUndefined());
407
442
  // 完成通知(chat 域宿主职责):notifier 立即 flush(无其他 running)→ result 进 sendMessage 正文
408
443
  await vi.waitFor(() => {
409
444
  const sent = pi.sendMessage.mock.calls.some((c) => String(c[0]?.content).includes("hello result"));
@@ -417,7 +452,7 @@ describe("chat 工具域引擎路由分叉(U0:D4/D5/D10)", () => {
417
452
  Promise.resolve({ handle: fakeHandle(), outcome: { ...doneOutcome(""), error: "engine_run_failed: boom", engineId: "zcode" } });
418
453
  const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
419
454
 
420
- await vi.waitFor(() => expect(service.findRecord(handle.subagentId)).toBeUndefined());
455
+ await vi.waitFor(() => expect(service.queries.findRecord(handle.subagentId)).toBeUndefined());
421
456
  await vi.waitFor(() => {
422
457
  const sent = pi.sendMessage.mock.calls.some((c) => String(c[0]?.content).includes("boom"));
423
458
  expect(sent).toBe(true);
@@ -468,7 +503,7 @@ describe("chat 引擎分支 U2:probe 兜底 / journal / engineHandle", () => {
468
503
  afterEach(() => {
469
504
  clearEngines();
470
505
  delete process.env.XYZ_AGENT_DATA_DIR;
471
- fs.rmSync(agentDir, { recursive: true, force: true });
506
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
472
507
  vi.clearAllMocks();
473
508
  });
474
509
 
@@ -482,7 +517,7 @@ describe("chat 引擎分支 U2:probe 兜底 / journal / engineHandle", () => {
482
517
  await vi.waitFor(() => expect(mockSpawn).toHaveBeenCalled());
483
518
  expect(piEngine.runs.length).toBe(0);
484
519
 
485
- const rec = service.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
520
+ const rec = service.queries.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
486
521
  expect(rec?.engine).toBe("pi");
487
522
  expect(rec?.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
488
523
  // entry(register 写点)含 engineFallback——兜底路径允许新增键(D5 只约束纯缺省路径)
@@ -498,7 +533,7 @@ describe("chat 引擎分支 U2:probe 兜底 / journal / engineHandle", () => {
498
533
  );
499
534
  expect(mockSpawn).not.toHaveBeenCalled();
500
535
  expect(zcode.runs.length).toBe(0);
501
- expect(service.collectRecords(10, "all")).toHaveLength(0);
536
+ expect(service.queries.collectRecords(10, "all")).toHaveLength(0);
502
537
  });
503
538
 
504
539
  it("[journal] taskId=record.id + onPoolResolved retarget 落 engines/zcode/<poolKey>/,journalPath 同源回填", async () => {
@@ -522,7 +557,7 @@ describe("chat 引擎分支 U2:probe 兜底 / journal / engineHandle", () => {
522
557
  });
523
558
  };
524
559
  const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
525
- await vi.waitFor(() => expect(service.findRecord(handle.subagentId)).toBeUndefined());
560
+ await vi.waitFor(() => expect(service.queries.findRecord(handle.subagentId)).toBeUndefined());
526
561
 
527
562
  // journal 文件名 = journal-<record.id>.jsonl,落在 engines/zcode/<poolKey>/ 下
528
563
  const journalPath = resolveJournalPath(agentDir, "zcode", POOL, handle.subagentId);
@@ -553,20 +588,91 @@ describe("chat 引擎分支 U2:probe 兜底 / journal / engineHandle", () => {
553
588
  });
554
589
  };
555
590
  const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
556
- await vi.waitFor(() => expect(service.findRecord(handle.subagentId)).toBeUndefined());
591
+ await vi.waitFor(() => expect(service.queries.findRecord(handle.subagentId)).toBeUndefined());
557
592
 
558
- const h = lastRecordEntry(pi)?.engineHandle;
593
+ const h = lastRecordEntry(pi)?.engineHandle as Record<string, unknown> | undefined;
559
594
  expect(h?.sessionRef).toEqual({ dbPath: "sessions.db" });
560
595
  expect(h?.poolKey).toBe(POOL);
561
596
  expect(h?.journalPath).toBe(resolveJournalPath(agentDir, "zcode", POOL, handle.subagentId));
562
597
  });
563
598
 
599
+ // ============================================================
600
+ // [R4 §3.4 不变量 3] onHandleReady 运行中回填:create 应答后立即落 entry——
601
+ // 运行中 GUI 经 entry 重建 record 即得 ①②级读取钥匙(不等 run resolve)
602
+ // ============================================================
603
+
604
+ it("[onHandleReady] create 应答后回调 → record.engineHandle 立即回填 + reportRecordTransition 落 entry(record 仍 running)", async () => {
605
+ process.env.XYZ_AGENT_DATA_DIR = agentDir;
606
+ const { service, zcode, pi } = setup(agentDir);
607
+ const POOL = "zcode-appserver-home";
608
+ let releaseRun!: (v: { handle: EngineHandle; outcome: AgentOutcome }) => void;
609
+ zcode.runImpl = (task, ctx) => {
610
+ ctx.onPoolResolved?.(POOL);
611
+ // create 应答后的回调时点(app-server 引擎在 session/create 应答后触发)
612
+ ctx.onHandleReady?.({
613
+ sessionRef: { dbPath: ".zcode/cli/db/db.sqlite", sessionId: "sess-live-1" },
614
+ poolKey: POOL,
615
+ });
616
+ return new Promise((resolve) => {
617
+ releaseRun = resolve; // 挂起 run——模拟运行中任务
618
+ });
619
+ };
620
+ const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
621
+ await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
622
+
623
+ // run 尚未 resolve(record 仍 running):engineHandle 已回填且 entry 已落盘
624
+ await vi.waitFor(() => {
625
+ const entries = pi.appendEntry.mock.calls.filter((c) => c[0] === "subagent-record");
626
+ const withHandle = entries.filter(
627
+ (c) => (c[1] as Record<string, unknown>).engineHandle !== undefined,
628
+ );
629
+ expect(withHandle.length).toBeGreaterThan(0);
630
+ });
631
+ const running = service["collectRecords"](10, "running").find((r) => r.id === handle.subagentId);
632
+ expect(running?.engineHandle).toEqual({
633
+ sessionRef: { dbPath: ".zcode/cli/db/db.sqlite", sessionId: "sess-live-1" },
634
+ poolKey: POOL,
635
+ journalPath: resolveJournalPath(agentDir, "zcode", POOL, handle.subagentId),
636
+ });
637
+ // 回填 entry 的 journalPath 与 onPoolResolved retarget 后的实际落盘路径一致(同源)
638
+ expect(fs.existsSync(resolveJournalPath(agentDir, "zcode", POOL, handle.subagentId))).toBe(false);
639
+
640
+ // 终态收口(防 dangling)
641
+ releaseRun({ handle: fakeHandle(), outcome: doneOutcome("ok") });
642
+ await vi.waitFor(() => expect(service["findRecord"](handle.subagentId)).toBeUndefined());
643
+ }, 10_000);
644
+
645
+ it("[onHandleReady] 引擎不回调(spawn 形态)时零回填——终态回填仍兜底(行为不变)", async () => {
646
+ process.env.XYZ_AGENT_DATA_DIR = agentDir;
647
+ const { service, zcode, pi } = setup(agentDir);
648
+ const POOL = "zcode-p-glm";
649
+ zcode.runImpl = (task, ctx) => {
650
+ ctx.onPoolResolved?.(POOL); // 只 onPoolResolved——spawn 引擎形态(无 onHandleReady)
651
+ return Promise.resolve({
652
+ handle: {
653
+ data: { v: 1, engineId: "zcode", sessionRef: { dbPath: "sessions.db", sessionId: "sess-1" }, poolKey: POOL, adapterVersion: "test" },
654
+ },
655
+ outcome: doneOutcome("ok"),
656
+ });
657
+ };
658
+ const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
659
+ await vi.waitFor(() => expect(service["findRecord"](handle.subagentId)).toBeUndefined());
660
+ const entries = pi.appendEntry.mock.calls.filter((c) => c[0] === "subagent-record");
661
+ // 运行中回填 entry 不存在(register 写点无 engineHandle;archive 终态侧才有)
662
+ const runningEntries = entries.slice(0, -1);
663
+ for (const c of runningEntries) {
664
+ expect((c[1] as Record<string, unknown>).engineHandle).toBeUndefined();
665
+ }
666
+ const final = entries[entries.length - 1][1] as Record<string, unknown>;
667
+ expect(final.engineHandle).toMatchObject({ poolKey: POOL });
668
+ });
669
+
564
670
  it("[D5 回归] pi 纯缺省路径 entry 不含 engine/engineFallback/engineHandle 键", async () => {
565
671
  const { service, pi } = setup(agentDir);
566
672
  const handle = await service.execute(baseOpts(agentDir));
567
673
  await vi.waitFor(() => expect(mockSpawn).toHaveBeenCalled());
568
674
 
569
- const rec = service.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
675
+ const rec = service.queries.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
570
676
  const entryJson = JSON.stringify(toSubagentRecordEntry(rec!));
571
677
  expect(entryJson).not.toContain("engineFallback");
572
678
  expect(entryJson).not.toContain("engineHandle");
@@ -589,7 +695,7 @@ describe("chat 引擎分支 U2:probe 兜底 / journal / engineHandle", () => {
589
695
  const h = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
590
696
  // 等本条终态化(record 离开内存)再发下一条——泄漏形态下第 7 条 acquire 永久
591
697
  // 排队、record 永卡 running,本 waitFor 即超时失败
592
- await vi.waitFor(() => expect(service.findRecord(h.subagentId)).toBeUndefined());
698
+ await vi.waitFor(() => expect(service.queries.findRecord(h.subagentId)).toBeUndefined());
593
699
  }
594
700
  // 7 次引擎 run 全部真实执行(第 7 次未被泄漏槽阻塞)
595
701
  expect(zcode.runs.length).toBe(7);
@@ -58,9 +58,10 @@ vi.mock("../alive-store.ts", () => ({ writeAliveMarker: vi.fn() }));
58
58
  // chatMode agent_end 早返回(continue),不读 session-pending;统一 count=0 防干扰。
59
59
  vi.mock("../session-pending.ts", () => ({
60
60
  readActivePendingFromSessionFile: vi.fn(() => ({ count: 0 })),
61
+ prunePendingCursor: vi.fn(),
61
62
  }));
62
63
 
63
- vi.mock("../temp-prompt.ts", () => ({
64
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
64
65
  writePromptToTempFile: vi.fn(async (agent: string) => {
65
66
  const safeName = agent.replace(/[^\w.-]+/g, "_");
66
67
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -68,8 +69,8 @@ vi.mock("../temp-prompt.ts", () => ({
68
69
  cleanupTempPrompt: vi.fn(async () => {}),
69
70
  }));
70
71
 
71
- import { runSpawn } from "../session-runner.ts";
72
- import type { SessionRunnerContext } from "../session-runner.ts";
72
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
73
+ import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
73
74
  import { _resetLifecycleState } from "../lifecycle-manager.ts";
74
75
  import { createRecord } from "../execution-record.ts";
75
76
  import type { ExecutionRecord } from "../types.ts";
@@ -94,7 +95,7 @@ function makeChatModeRecord(id = "sa-chat"): ExecutionRecord {
94
95
  return createRecord(id, {
95
96
  agent: "general-purpose",
96
97
  model: "test-model",
97
- mode: "sync",
98
+ mode: "background",
98
99
  task: "chat task",
99
100
  slug: "chat",
100
101
  startedAt: 1_000_000,
@@ -0,0 +1,283 @@
1
+ // src/execution/__tests__/cold-resurrect.test.ts
2
+ //
3
+ // [D4-③] coldLookupForAction 单元测试(依赖注入直测)。
4
+ //
5
+ // 背景:cold-resurrect.ts 自 SubagentService 搬移后按 ColdResurrectDeps 依赖注入
6
+ // 设计,此前仅有经 subagent-service 集成路径的 running 重建覆盖——closed 可重连
7
+ // 候选的「恢复失败守卫 / 部分恢复回边」(assertReconnectAllowed / resurrectColdRecord
8
+ // 的 wasClosed 分支)零直测。本文件锁定这些路径的可观察行为:
9
+ // - 恢复失败:worktree 绑定丢失 / 异进程活实例(closed 与 running 候选)→
10
+ // ResurrectDeniedError 且内存无残留(register 不被调用);
11
+ // - 部分恢复:closed 可重连记录 → resurrectClosed 回边翻回 running + 磁盘终态位
12
+ // 同步翻转(.finalized 删除 + .alive 刷新当前进程)+ register/transition 上报;
13
+ // - 恢复源缺失:sessionFile 缺失 / sidecar 翻转失败(目录不存在)均不阻断重生
14
+ // (best-effort 语义)。
15
+ //
16
+ // fixture 一律 mkdtempSync 自建自删(tmpdir),不触碰真实数据目录。
17
+
18
+ import * as fs from "node:fs";
19
+ import * as os from "node:os";
20
+ import * as path from "node:path";
21
+
22
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
+
24
+ import { readAliveMarker, writeAliveMarker } from "../alive-store.ts";
25
+ import { COLD_LOOKUP_SCAN_LIMIT, coldLookupForAction, type ColdResurrectDeps } from "../cold-resurrect.ts";
26
+ import type { SubagentRecord } from "../types.ts";
27
+ import { ResurrectDeniedError } from "../types.ts";
28
+
29
+ /** 磁盘/索引侧候选记录(SubagentRecord 最小合法形状 + closed 可重连缺省)。 */
30
+ function makeFound(overrides: Partial<SubagentRecord> = {}): SubagentRecord {
31
+ return {
32
+ id: "sa-cold-1",
33
+ agent: "general-purpose",
34
+ task: "cold recovery task",
35
+ slug: "cold-test",
36
+ status: "closed",
37
+ closedReason: "parent-shutdown",
38
+ mode: "background",
39
+ startedAt: 1_700_000_000_000,
40
+ rootSessionId: "root-session",
41
+ parentRecordId: undefined,
42
+ depth: 0,
43
+ endedAt: 1_700_000_100_000,
44
+ turns: 0,
45
+ totalTokens: 0,
46
+ model: "test/model-a",
47
+ thinkingLevel: undefined,
48
+ eventLog: [],
49
+ displayItems: [],
50
+ ...overrides,
51
+ };
52
+ }
53
+
54
+ interface DepOverrides {
55
+ /** findLightById(idToFile 索引直查)返回值。 */
56
+ direct?: SubagentRecord;
57
+ /** collectRecords(磁盘全扫)返回清单。 */
58
+ disk?: SubagentRecord[];
59
+ /** getSessionRootId 返回值(归属校验)。 */
60
+ rootId?: string;
61
+ /** getBaselineRecordId 返回值(直接父校验)。 */
62
+ baseline?: string;
63
+ }
64
+
65
+ /** 构造依赖注入桩(register / reportRecordTransition 可断言副作用)。 */
66
+ function makeDeps(o: DepOverrides = {}): ColdResurrectDeps {
67
+ return {
68
+ findLightById: vi.fn(() => o.direct),
69
+ collectRecords: vi.fn(() => o.disk ?? []),
70
+ register: vi.fn(),
71
+ reportRecordTransition: vi.fn(),
72
+ getSessionRootId: vi.fn(() => o.rootId ?? "root-session"),
73
+ getBaselineRecordId: vi.fn(() => o.baseline),
74
+ };
75
+ }
76
+
77
+ describe("[D4-③] coldLookupForAction 冷查/复活链", () => {
78
+ let dir: string;
79
+
80
+ beforeEach(() => {
81
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "cold-resurrect-"));
82
+ });
83
+
84
+ afterEach(() => {
85
+ fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
86
+ });
87
+
88
+ /** tmpdir 下建 session 文件 + 可选 .finalized sidecar,返回 sessionFile 路径。 */
89
+ function writeSessionFixture(opts: { finalized?: string; aliveMarker?: string } = {}): string {
90
+ const sessionFile = path.join(dir, "20260901T000000-000_sa-cold-1.jsonl");
91
+ fs.writeFileSync(sessionFile, "{}\n", "utf-8");
92
+ if (opts.finalized !== undefined) {
93
+ fs.writeFileSync(`${sessionFile}.finalized`, opts.finalized, "utf-8");
94
+ }
95
+ if (opts.aliveMarker !== undefined) {
96
+ fs.writeFileSync(`${sessionFile}.alive`, opts.aliveMarker, "utf-8");
97
+ }
98
+ return sessionFile;
99
+ }
100
+
101
+ // ── 部分恢复(closed 可重连 → 透明重生回边)──
102
+
103
+ it("closed 可重连记录(parent-shutdown)+ allowReconnect → 重生翻回 running:终态位清除、sidecar 翻转、register + transition 上报", () => {
104
+ const sessionFile = writeSessionFixture({ finalized: JSON.stringify({ reason: "parent-shutdown" }) });
105
+ const found = makeFound({ sessionFile, round: 2 });
106
+ const deps = makeDeps({ disk: [found] });
107
+
108
+ const record = coldLookupForAction(deps, "sa-cold-1", true)!;
109
+
110
+ // 重生回边:closed → running,终态语义位清除
111
+ expect(record.status).toBe("running");
112
+ expect(record.closedReason).toBeUndefined();
113
+ expect(record.endedAt).toBeUndefined();
114
+ // [v4 A-3] 跨重启恢复入口:chatMode 无条件 true
115
+ expect(record.chatMode).toBe(true);
116
+ // 身份/续聊字段从磁盘候选回填
117
+ expect(record.sessionFile).toBe(sessionFile);
118
+ expect(record.round).toBe(2);
119
+ expect(record.model).toBe("test/model-a");
120
+ expect(record.hadWorktree).toBe(false);
121
+ // register + [v8.5 D] 重生后立刻上报 transition entry(live ≡ reload)
122
+ expect(vi.mocked(deps.register)).toHaveBeenCalledTimes(1);
123
+ expect(vi.mocked(deps.register)).toHaveBeenCalledWith(record);
124
+ expect(vi.mocked(deps.reportRecordTransition)).toHaveBeenCalledTimes(1);
125
+ expect(vi.mocked(deps.reportRecordTransition)).toHaveBeenCalledWith(record);
126
+ // [review MF-8] 磁盘终态位同步翻转:.finalized 删除 + .alive 刷新为当前进程
127
+ expect(fs.existsSync(`${sessionFile}.finalized`)).toBe(false);
128
+ expect(readAliveMarker(sessionFile)).toMatchObject({ pid: process.pid, id: "sa-cold-1" });
129
+ // 冷查扫描契约:全目录兜底按 COLD_LOOKUP_SCAN_LIMIT 上限扫全量(无 root 过滤)
130
+ expect(vi.mocked(deps.collectRecords)).toHaveBeenCalledWith(COLD_LOOKUP_SCAN_LIMIT, "all", undefined);
131
+ });
132
+
133
+ it("idToFile 索引直查返回非 running 态 → 回退磁盘全扫兜底定位(closed 候选仍可重连)", () => {
134
+ const sessionFile = writeSessionFixture();
135
+ const found = makeFound({ sessionFile });
136
+ // direct 命中但 status=closed → 不直接采用,落到 collectRecords 兜底
137
+ const deps = makeDeps({ direct: found, disk: [found] });
138
+
139
+ const record = coldLookupForAction(deps, "sa-cold-1", true)!;
140
+
141
+ expect(record.status).toBe("running");
142
+ expect(vi.mocked(deps.collectRecords)).toHaveBeenCalledTimes(1);
143
+ expect(vi.mocked(deps.register)).toHaveBeenCalledWith(record);
144
+ });
145
+
146
+ it("closed disconnected(.finalized 空内容兜底死因)同样落在可重连集内", () => {
147
+ const sessionFile = writeSessionFixture({ finalized: "" });
148
+ const deps = makeDeps({ disk: [makeFound({ sessionFile, closedReason: "disconnected" })] });
149
+
150
+ const record = coldLookupForAction(deps, "sa-cold-1", true)!;
151
+
152
+ expect(record.status).toBe("running");
153
+ expect(vi.mocked(deps.register)).toHaveBeenCalledTimes(1);
154
+ });
155
+
156
+ // ── 恢复失败守卫(ResurrectDeniedError + 内存无残留)──
157
+
158
+ it("worktree 绑定丢失(跨重启 WorktreeHandle 不可序列化)→ ResurrectDeniedError 拒绝,register 不残留", () => {
159
+ const sessionFile = writeSessionFixture();
160
+ const deps = makeDeps({ disk: [makeFound({ sessionFile, worktree: true })] });
161
+
162
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(ResurrectDeniedError);
163
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(/worktree isolation/);
164
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
165
+ });
166
+
167
+ it("closed 候选仍有异进程活实例(.alive 指向活 pid)→ 拒绝双写,register 不残留", () => {
168
+ // 用本测试进程 pid 模拟「另一进程的活跃实例」——isProcessAlive 判活为真
169
+ const sessionFile = writeSessionFixture();
170
+ writeAliveMarker(sessionFile, { pid: process.pid, id: "sa-cold-1", startedAt: Date.now() });
171
+ const deps = makeDeps({ disk: [makeFound({ sessionFile })] });
172
+
173
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(ResurrectDeniedError);
174
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(
175
+ /still finishing in another process/,
176
+ );
177
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it("running 候选仍有异进程活实例(父进程重启后旧子进程尚存窗口)→ 拒绝并给恢复指引", () => {
181
+ const sessionFile = writeSessionFixture();
182
+ writeAliveMarker(sessionFile, { pid: process.pid, id: "sa-cold-1", startedAt: Date.now() });
183
+ const deps = makeDeps({ disk: [makeFound({ sessionFile, status: "running" })] });
184
+
185
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(ResurrectDeniedError);
186
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(
187
+ /currently running in another process instance/,
188
+ );
189
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
190
+ });
191
+
192
+ it("损坏 .alive marker(无法解析)→ 视为确死放行重生,marker 被当前进程覆盖写", () => {
193
+ const sessionFile = writeSessionFixture({
194
+ finalized: JSON.stringify({ reason: "parent-shutdown" }),
195
+ aliveMarker: "{not valid json",
196
+ });
197
+ const deps = makeDeps({ disk: [makeFound({ sessionFile })] });
198
+
199
+ const record = coldLookupForAction(deps, "sa-cold-1", true)!;
200
+
201
+ expect(record.status).toBe("running");
202
+ expect(readAliveMarker(sessionFile)).toMatchObject({ pid: process.pid });
203
+ expect(vi.mocked(deps.register)).toHaveBeenCalledTimes(1);
204
+ });
205
+
206
+ // ── 候选过滤 / 归属与父层校验 ──
207
+
208
+ it.each([
209
+ ["自然完成死因 gc", makeFound({ closedReason: "gc" })],
210
+ ["用户主动 close", makeFound({ closedReason: "user-close" })],
211
+ ])("不可重连死因(%s)→ 磁盘也无候选,返回 undefined", (_label, found) => {
212
+ const deps = makeDeps({ disk: [found] });
213
+
214
+ expect(coldLookupForAction(deps, "sa-cold-1", true)).toBeUndefined();
215
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
216
+ });
217
+
218
+ it("allowReconnect=false:closed 候选一律不可见(running-only 查询语义)", () => {
219
+ const sessionFile = writeSessionFixture();
220
+ const deps = makeDeps({ disk: [makeFound({ sessionFile })] });
221
+
222
+ expect(coldLookupForAction(deps, "sa-cold-1", false)).toBeUndefined();
223
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
224
+ });
225
+
226
+ it("rootSessionId 不匹配 → 返回 undefined(不区分失败形态,防跨 session 探测)", () => {
227
+ const deps = makeDeps({ disk: [makeFound({ rootSessionId: "other-root" })], rootId: "root-session" });
228
+
229
+ expect(coldLookupForAction(deps, "sa-cold-1", true)).toBeUndefined();
230
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
231
+ });
232
+
233
+ it("parentRecordId 跨层不匹配(候选有直接父)→ 原样抛 direct parent 错误(跨层导航指引)", () => {
234
+ const deps = makeDeps({
235
+ disk: [makeFound({ status: "running", parentRecordId: "sa-parent" })],
236
+ baseline: undefined,
237
+ });
238
+
239
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(/is owned by its direct parent/);
240
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(/parent=sa-parent/);
241
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
242
+ });
243
+
244
+ it("顶层候选(parentRecordId 缺失)遇嵌套进程 baseline → direct parent 错误带 (root layer) 回显", () => {
245
+ const deps = makeDeps({
246
+ disk: [makeFound({ status: "running", parentRecordId: undefined })],
247
+ baseline: "rec-baseline-child",
248
+ });
249
+
250
+ expect(() => coldLookupForAction(deps, "sa-cold-1", true)).toThrow(
251
+ /parent=\(root layer\)/,
252
+ );
253
+ expect(vi.mocked(deps.register)).not.toHaveBeenCalled();
254
+ });
255
+
256
+ // ── 恢复源缺失(best-effort 不阻断重生主流程)──
257
+
258
+ it("sessionFile 缺失的 closed 候选 → 跳过 sidecar 翻转,重生照常完成(register + transition 上报)", () => {
259
+ const deps = makeDeps({ disk: [makeFound({ sessionFile: undefined })] });
260
+
261
+ const record = coldLookupForAction(deps, "sa-cold-1", true)!;
262
+
263
+ expect(record.status).toBe("running");
264
+ expect(record.sessionFile).toBeUndefined();
265
+ expect(vi.mocked(deps.register)).toHaveBeenCalledTimes(1);
266
+ expect(vi.mocked(deps.reportRecordTransition)).toHaveBeenCalledTimes(1);
267
+ });
268
+
269
+ it("sidecar 翻转失败(sessionFile 所在目录不存在,writeAliveMarker ENOENT)→ best-effort 吞错,重生不中断", () => {
270
+ // 恢复源缺失形态:sessionFile 指向已消失的目录(推导路径过期 / 目录被清理)
271
+ const sessionFile = path.join(dir, "vanished-dir", "20260901T000000-000_sa-cold-1.jsonl");
272
+ const deps = makeDeps({ disk: [makeFound({ sessionFile })] });
273
+
274
+ const record = coldLookupForAction(deps, "sa-cold-1", true)!;
275
+
276
+ // best-effort:sidecar 翻转失败不阻断重生主流程(状态回边与注册照常)
277
+ expect(record.status).toBe("running");
278
+ expect(vi.mocked(deps.register)).toHaveBeenCalledTimes(1);
279
+ expect(vi.mocked(deps.reportRecordTransition)).toHaveBeenCalledTimes(1);
280
+ // marker 写入确实失败(目录不存在,未落盘)
281
+ expect(fs.existsSync(`${sessionFile}.alive`)).toBe(false);
282
+ });
283
+ });