@zhushanwen/subagent-core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
  2. package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
  3. package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
  4. package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
  5. package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
  6. package/dist/execution/engine/engines/zcode/constants.js +17 -27
  7. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  8. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  9. package/dist/execution/engine/engines/zcode/reader.js +2 -2
  10. package/dist/index.cjs +17100 -14082
  11. package/dist/index.d.cts +5336 -4033
  12. package/dist/index.d.ts +5336 -4033
  13. package/dist/index.js +17020 -14055
  14. package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
  16. package/dist.bundle/index.cjs +34933 -0
  17. package/package.json +3 -3
  18. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  19. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  20. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  21. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  22. package/src/__tests__/manifest-store.test.ts +10 -9
  23. package/src/__tests__/record-store-cache.test.ts +0 -2
  24. package/src/__tests__/record-store-index.test.ts +1 -2
  25. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  26. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  27. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  28. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  29. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  30. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  31. package/src/__tests__/session-runner.test.ts +1 -1
  32. package/src/core/__tests__/host-services.test.ts +2 -2
  33. package/src/core/__tests__/logger.test.ts +1 -1
  34. package/src/core/error-message.ts +9 -0
  35. package/src/core/host-services.ts +21 -5
  36. package/src/core/notify-ports.ts +18 -5
  37. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  38. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  39. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  40. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  41. package/src/execution/__tests__/alive-store.test.ts +1 -1
  42. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  43. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  44. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  45. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  46. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  47. package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
  48. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  49. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
  50. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  51. package/src/execution/__tests__/config.test.ts +1 -1
  52. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  53. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  54. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  55. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  56. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  57. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  58. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  59. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  60. package/src/execution/__tests__/execution-record.test.ts +217 -60
  61. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  62. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
  63. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  64. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  65. package/src/execution/__tests__/gc-timer.test.ts +56 -3
  66. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  67. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  68. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  69. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  70. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  71. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  72. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  73. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  74. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  75. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  76. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  77. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  78. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  79. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  80. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  81. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  82. package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
  83. package/src/execution/__tests__/output-collector.test.ts +117 -6
  84. package/src/execution/__tests__/pi-invocation.test.ts +36 -4
  85. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  86. package/src/execution/__tests__/record-store.test.ts +10 -9
  87. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  88. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  89. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  90. package/src/execution/__tests__/relay-env.test.ts +12 -1
  91. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  92. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  93. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  94. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  95. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  96. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  97. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  98. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  99. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  100. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  101. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  102. package/src/execution/__tests__/session-file-gc.test.ts +36 -1
  103. package/src/execution/__tests__/session-pending.test.ts +257 -14
  104. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  105. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  106. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  107. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  108. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  109. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  110. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  112. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  113. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +3 -3
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  117. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  118. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  119. package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
  120. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  122. package/src/execution/__tests__/subagent-actions-core.test.ts +48 -22
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  124. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
  125. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  126. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  127. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  128. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  129. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  130. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  131. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  132. package/src/execution/__tests__/sync-collect-recovery.test.ts +1117 -0
  133. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  134. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  135. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  136. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  137. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  138. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  139. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  140. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  141. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  142. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  143. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  144. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  145. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  146. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  147. package/src/execution/agent-registry.ts +74 -30
  148. package/src/execution/agent-result-mapper.ts +3 -0
  149. package/src/execution/cold-resurrect.ts +199 -0
  150. package/src/execution/collect-coordinator.ts +200 -0
  151. package/src/execution/config.ts +77 -5
  152. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  153. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  154. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  155. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  156. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  157. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  158. package/src/execution/engine/__tests__/common/pool-manager.test.ts +213 -53
  159. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  160. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  161. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  162. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  163. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  181. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  182. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  183. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  184. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  185. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  186. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  187. package/src/execution/engine/engines/pi/reader.ts +11 -0
  188. package/src/execution/engine/engines/pi/registration.ts +4 -2
  189. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  190. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  191. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  192. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  193. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  194. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  195. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  196. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  197. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  198. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  199. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  200. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  209. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  210. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  211. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  212. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  213. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  214. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  215. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  216. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  217. package/src/execution/engine/host-task-spec.ts +32 -36
  218. package/src/execution/engine/model-validation.ts +177 -0
  219. package/src/execution/engine/port.ts +24 -6
  220. package/src/execution/engine/routing.ts +75 -1
  221. package/src/execution/engine/types.ts +30 -103
  222. package/src/execution/execution-record.ts +229 -144
  223. package/src/execution/finalize-record.ts +141 -99
  224. package/src/execution/idle-gc.ts +28 -0
  225. package/src/execution/lifecycle-predicates.ts +1 -1
  226. package/src/execution/notifier.ts +234 -25
  227. package/src/execution/notify-host.ts +232 -0
  228. package/src/execution/record-entry.ts +16 -0
  229. package/src/execution/record-store.ts +280 -103
  230. package/src/execution/round-settlement.ts +93 -0
  231. package/src/execution/session-file-gc.ts +96 -62
  232. package/src/execution/session-reconstructor.ts +256 -143
  233. package/src/execution/sessions-index.ts +117 -47
  234. package/src/execution/settled-watchdog.ts +264 -52
  235. package/src/execution/subagent-actions-core.ts +84 -28
  236. package/src/execution/subagent-service.ts +1225 -964
  237. package/src/execution/subprocess-agent-runner.ts +144 -97
  238. package/src/execution/sync-rebuild.ts +87 -0
  239. package/src/execution/types.ts +72 -0
  240. package/src/execution/ui-request-handler-factory.ts +102 -74
  241. package/src/execution/ui-request-queue.ts +50 -19
  242. package/src/index.ts +442 -316
  243. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  244. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  245. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  246. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  247. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  248. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  249. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  250. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  251. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  252. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  253. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  254. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  255. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  256. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  257. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  258. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  259. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  260. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  261. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  262. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  263. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  264. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  265. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  266. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  267. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  268. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  269. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  270. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  271. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  272. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  273. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  274. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  275. package/src/orchestration/agent-opts-resolver.ts +1 -1
  276. package/src/orchestration/args-validator.ts +53 -31
  277. package/src/orchestration/execute-agent-call.ts +19 -73
  278. package/src/orchestration/launcher.ts +128 -81
  279. package/src/orchestration/lifecycle.ts +43 -45
  280. package/src/orchestration/models/ports.ts +4 -4
  281. package/src/orchestration/models/run-runtime.ts +2 -2
  282. package/src/orchestration/models/trace.ts +1 -1
  283. package/src/orchestration/models/types.ts +111 -22
  284. package/src/orchestration/models/workflow-run.ts +1 -1
  285. package/src/orchestration/script-lint.ts +91 -58
  286. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  287. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  288. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  289. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  290. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  291. package/src/shared/atomic-write.ts +12 -9
  292. package/src/shared/meta-parser.ts +158 -84
  293. package/src/shared/model-ref.ts +15 -2
  294. package/src/shared/resource-discovery.ts +9 -183
  295. package/src/shared/schema-jsonify.ts +1 -1
  296. package/workflows/review-fix-loop-utils.cjs +151 -89
  297. package/workflows/review-fix-loop-utils.d.cts +287 -0
  298. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  299. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  300. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  301. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  302. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  303. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  305. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  306. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  307. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  308. package/src/execution/execute-options-mapper.ts +0 -115
  309. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,84 @@
1
+ // journal-wiring.test.ts —— [D3-③ journal 接线合一] host helper 单测(两域共用的
2
+ // 唯一实现:writer 创建 + retarget + journaling onEvent + handle 回填 + close)。
3
+ // 设计权威源:docs/design/subagent-dual-track-convergence.md §3.3 D3-③ + 双轨清单 #6。
4
+ //
5
+ // 覆盖:①占位池 key 初始路径与 retarget 后路径权威(getter 反映 retarget);②先落盘
6
+ // 再转发(forwardEvents);③handle 回填写终态路径;④close 幂等不抛。
7
+
8
+ import * as fs from "node:fs";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
11
+
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
13
+
14
+ import { JOURNAL_INITIAL_POOL_KEY, wireEventJournal } from "../../common/journal-wiring.ts";
15
+ import { resolveJournalPath } from "../../paths.ts";
16
+ import type { AgentEvent } from "../../types.ts";
17
+ import type { EngineHandle } from "../../types.ts";
18
+
19
+ let dataRoot: string;
20
+ const PREV_DATA_DIR = process.env["XYZ_AGENT_DATA_DIR"];
21
+
22
+ beforeEach(() => {
23
+ dataRoot = fs.mkdtempSync(path.join(os.tmpdir(), "journal-wiring-"));
24
+ process.env["XYZ_AGENT_DATA_DIR"] = dataRoot;
25
+ });
26
+
27
+ afterEach(() => {
28
+ if (PREV_DATA_DIR === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
29
+ else process.env["XYZ_AGENT_DATA_DIR"] = PREV_DATA_DIR;
30
+ fs.rmSync(dataRoot, { recursive: true, force: true });
31
+ });
32
+
33
+ function makeHandle(): EngineHandle {
34
+ return { data: { v: 1, engineId: "zcode", sessionRef: {}, poolKey: "p-m1", adapterVersion: "t" } };
35
+ }
36
+
37
+ describe("wireEventJournal(D3-③ host helper 唯一实现)", () => {
38
+ it("初始路径用占位池 key;onPoolResolved retarget 后 path getter 反映实际池 key(路径权威 = writer)", () => {
39
+ const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-1" });
40
+ expect(JOURNAL_INITIAL_POOL_KEY).toBe("shared");
41
+ expect(wiring.path).toBe(resolveJournalPath(dataRoot, "zcode", "shared", "sa-1"));
42
+ wiring.onPoolResolved("home-p-m1");
43
+ expect(wiring.path).toBe(resolveJournalPath(dataRoot, "zcode", "home-p-m1", "sa-1"));
44
+ });
45
+
46
+ it("journaling onEvent:先落盘再转发(forwardEvents 收到同一事件)", async () => {
47
+ const forwarded: AgentEvent[] = [];
48
+ const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-2", forwardEvents: (e) => forwarded.push(e) });
49
+ wiring.onEvent({ type: "turn_end" });
50
+ wiring.onEvent({ type: "message_end", usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 } });
51
+ await wiring.close();
52
+ expect(forwarded.map((e) => e.type)).toEqual(["turn_end", "message_end"]);
53
+ // 落盘真实发生(close flush 后文件存在且行数吻合——先落盘语义由文件内容见证)
54
+ const lines = fs.readFileSync(wiring.path, "utf8").trim().split("\n");
55
+ expect(lines).toHaveLength(2);
56
+ expect(JSON.parse(lines[0]).taskId).toBe("sa-2");
57
+ expect(JSON.parse(lines[0]).engineId).toBe("zcode");
58
+ });
59
+
60
+ it("chat 域形态(无 forwardEvents):事件只进 journal,不抛", async () => {
61
+ const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-3" });
62
+ wiring.onEvent({ type: "turn_end" });
63
+ await wiring.close();
64
+ expect(fs.existsSync(wiring.path)).toBe(true);
65
+ });
66
+
67
+ it("handle 回填:backfillHandle 写 retarget 后的终态路径(read ②级自描述定位符)", async () => {
68
+ const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-4" });
69
+ wiring.onPoolResolved("home-p-m1");
70
+ wiring.onEvent({ type: "turn_end" });
71
+ const handle = makeHandle();
72
+ wiring.backfillHandle(handle);
73
+ expect(handle.data.journalPath).toBe(resolveJournalPath(dataRoot, "zcode", "home-p-m1", "sa-4"));
74
+ await wiring.close();
75
+ expect(fs.existsSync(handle.data.journalPath!)).toBe(true);
76
+ });
77
+
78
+ it("close 幂等(重复调用不抛);无事件任务不产生空 journal 文件", async () => {
79
+ const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-5" });
80
+ await wiring.close();
81
+ await expect(wiring.close()).resolves.toBeUndefined();
82
+ expect(fs.existsSync(wiring.path)).toBe(false);
83
+ });
84
+ });
@@ -37,8 +37,8 @@ function makeFakeChild(): {
37
37
  child,
38
38
  signals,
39
39
  emitExit(code, signal) {
40
- child.exitCode = code;
41
- child.signalCode = signal;
40
+ (child as { exitCode: number | null }).exitCode = code;
41
+ (child as { signalCode: string | null }).signalCode = signal;
42
42
  for (const l of listeners.splice(0)) l(code, signal);
43
43
  },
44
44
  };
@@ -88,13 +88,40 @@ describe("killChain", () => {
88
88
  });
89
89
  });
90
90
 
91
+ describe("killChain grace 参数化(D3-① 杀链合一:pi=30s / zcode=5s,现状语义逐字节保持)", () => {
92
+ it("pi 参数形态(graceMs=30s + unrefTimers + escalationNote):29s 未升级,越 30s 窗口 SIGKILL", async () => {
93
+ const { child, signals } = makeFakeChild();
94
+ const p = killChain(child, {
95
+ graceMs: 30_000,
96
+ unrefTimers: true,
97
+ escalationNote: "child sa-x (source: spawn watchdog)",
98
+ });
99
+ expect(signals).toEqual(["SIGTERM"]); // SIGTERM 同步发出(信号序不变)
100
+ await vi.advanceTimersByTimeAsync(29_000);
101
+ expect(signals).toEqual(["SIGTERM"]); // 窗口内不升级(非贴边断言)
102
+ await vi.advanceTimersByTimeAsync(1_100); // 越过 30s 优雅窗口
103
+ expect(signals).toEqual(["SIGTERM", "SIGKILL"]); // 升级 SIGKILL(race-F4 语义)
104
+ await vi.advanceTimersByTimeAsync(10_000); // 有界收尸窗口
105
+ expect(await p).toBe("killed");
106
+ });
107
+
108
+ it("escalationNote 缺省(zcode 现状形态):graceMs=5s 越窗 SIGKILL,升级路径同构", async () => {
109
+ const { child, signals } = makeFakeChild();
110
+ const p = killChain(child, { graceMs: 5_000 });
111
+ await vi.advanceTimersByTimeAsync(5_000);
112
+ expect(signals).toEqual(["SIGTERM", "SIGKILL"]);
113
+ await vi.advanceTimersByTimeAsync(10_000);
114
+ expect(await p).toBe("killed");
115
+ });
116
+ });
117
+
91
118
  describe("synthesizeTimeoutOutcome", () => {
92
119
  it("合成 engine_timeout 终态:error 含 slug + stdout 尾部 + engine: pi 重跑建议;exitCode=null", () => {
93
120
  const outcome = synthesizeTimeoutOutcome(
94
- { task: "review files", slug: "review-files" },
121
+ { prompt: "review files", description: "review-files" },
95
122
  "last stdout lines...",
96
123
  );
97
- expect(outcome.error).toContain("engine_timeout");
124
+ expect(outcome.error!).toContain("engine_timeout");
98
125
  expect(outcome.error).toContain("review-files");
99
126
  expect(outcome.error).toContain("last stdout lines...");
100
127
  expect(outcome.error).toMatch(/`engine: pi`/);
@@ -105,13 +132,13 @@ describe("synthesizeTimeoutOutcome", () => {
105
132
 
106
133
  it("stdout 尾部超 2000 字截断;engineId 可指定", () => {
107
134
  const outcome = synthesizeTimeoutOutcome(
108
- { task: "t", slug: "s" },
135
+ { prompt: "t", description: "s" },
109
136
  "y".repeat(3_000),
110
137
  "zcode",
111
138
  );
112
139
  expect(outcome.engineId).toBe("zcode");
113
- expect(outcome.error).toContain("...");
114
- expect(outcome.error.length).toBeLessThan(3_000);
140
+ expect(outcome.error!).toContain("...");
141
+ expect(outcome.error!.length).toBeLessThan(3_000);
115
142
  });
116
143
  });
117
144
 
@@ -17,13 +17,14 @@ import type { EngineCapabilities } from "../../types.ts";
17
17
  const CAPS_FILE: EngineCapabilities = {
18
18
  schemaEnforcement: "emulated", steer: "unsupported", conversation: "unsupported",
19
19
  personaInjection: "file", eventGranularity: "coarse", sandbox: "none",
20
- sessionRead: "partial", resume: "cold", interrupt: "kill-only", permissionMode: "native",
20
+ sessionRead: "partial", resume: "cold", interrupt: "kill-only", permissionMode: "native", maxTurns: false,
21
21
  };
22
22
  const CAPS_FLAG: EngineCapabilities = { ...CAPS_FILE, personaInjection: "flag" };
23
23
  const CAPS_PROMPT: EngineCapabilities = { ...CAPS_FILE, personaInjection: "prompt" };
24
24
 
25
+ // [D6 合流] PersonaSpec 收拢层删除后,路由入参是 PersonaContent(结构子集:
26
+ // appendSystemPrompt 正文 + skillPath 引用行;agentRef 已裁撤)
25
27
  const persona = {
26
- agentRef: "reviewer",
27
28
  skillPath: "/skills/review.md",
28
29
  appendSystemPrompt: ["You are a careful code reviewer.", "Be terse."],
29
30
  };
@@ -34,7 +35,6 @@ describe("applyPersona 三策略路由", () => {
34
35
  expect(routing.promptSegment).toBe("");
35
36
  expect(routing.fileCandidate).toBeDefined();
36
37
  expect(routing.fileCandidate?.suggestedPath).toBe("persona.md");
37
- expect(routing.fileCandidate?.content).toContain("# Agent: reviewer");
38
38
  expect(routing.fileCandidate?.content).toContain("/skills/review.md");
39
39
  expect(routing.fileCandidate?.content).toContain("You are a careful code reviewer.");
40
40
  });
@@ -51,7 +51,6 @@ describe("applyPersona 三策略路由", () => {
51
51
  it("prompt 策略:promptSegment 为带结构头的人设段(拼进最终 prompt)", () => {
52
52
  const routing = applyPersona(persona, CAPS_PROMPT);
53
53
  expect(routing.promptSegment.startsWith("## Persona")).toBe(true);
54
- expect(routing.promptSegment).toContain("# Agent: reviewer");
55
54
  expect(routing.promptSegment).toContain("You are a careful code reviewer.");
56
55
  });
57
56
 
@@ -61,7 +60,7 @@ describe("applyPersona 三策略路由", () => {
61
60
  expect(applyPersona({}, CAPS_PROMPT)).toEqual({ promptSegment: "" });
62
61
  });
63
62
 
64
- it("仅 appendSystemPrompt:file 通道照常落文件(agentRef/skillPath 头部行缺省)", () => {
63
+ it("仅 appendSystemPrompt:file 通道照常落文件(skillPath 头部行缺省)", () => {
65
64
  const routing = applyPersona({ appendSystemPrompt: ["only body"] }, CAPS_FILE);
66
65
  expect(routing.fileCandidate?.content).toBe("only body");
67
66
  });
@@ -1,10 +1,12 @@
1
- // pool-manager.test.ts —— 池 acquire/release 引用计数 / 归零整池删除 / journal 保留
2
- // / 清理失败标记 / spawnedFiles 单次清理(D5 + §3.3.9)。
1
+ // pool-manager.test.ts —— 池 acquire/release 引用计数(refs.json 文件形态)/
2
+ // 归零整池删除 / journal 保留与跟随 record 删除 / 重启恢复 / TTL 兜底 /
3
+ // 清理失败标记 / spawnedFiles 单次清理(D5 + §3.3.9 + D8 接线)。
3
4
  //
4
5
  // 三视角:①构建者——计数归零才删、删除边界三硬规则逐条;②使用者——release 幂等
5
6
  // 安全(无引用 no-op);③观察者——清理失败有 .pool-cleanup-failed 标记(可观测)。
6
7
 
7
- import { mkdirSync, mkdtempSync, existsSync, readdirSync, rmSync, writeFileSync } from "node:fs";
8
+ import * as nodeFs from "node:fs";
9
+ import { mkdirSync, mkdtempSync, existsSync, readdirSync, readFileSync, rmSync, writeFileSync, utimesSync } from "node:fs";
8
10
  import { tmpdir } from "node:os";
9
11
  import { join } from "node:path";
10
12
 
@@ -12,10 +14,11 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
12
14
 
13
15
  import {
14
16
  acquirePool,
17
+ cleanupExpiredPoolRefs,
15
18
  cleanupSpawnedFiles,
16
19
  POOL_CLEANUP_FAILED_MARKER,
20
+ REFS_JSON_FILENAME,
17
21
  releasePoolRef,
18
- resetPoolRegistryForTests,
19
22
  } from "../../common/pool-manager.ts";
20
23
  import { resolvePoolDir } from "../../paths.ts";
21
24
 
@@ -23,132 +26,289 @@ let tmpRoot: string;
23
26
 
24
27
  beforeEach(() => {
25
28
  tmpRoot = mkdtempSync(join(tmpdir(), "engine-pool-test-"));
26
- resetPoolRegistryForTests();
27
29
  });
28
30
 
29
31
  afterEach(() => {
30
- rmSync(tmpRoot, { recursive: true, force: true });
32
+ rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
31
33
  });
32
34
 
33
- describe("acquirePool", () => {
34
- it("mkdir -p 返回池目录(路径经 resolvePoolDir 派生),重复 acquire 幂等", async () => {
35
- const dir1 = await acquirePool(tmpRoot, "zcode", "reviewer");
36
- const dir2 = await acquirePool(tmpRoot, "zcode", "reviewer");
35
+ /** 读池 refs.json 的解析形态。 */
36
+ function readRefs(poolDir: string): { v: number; refs: Record<string, { taskId: string; ts: number }> } {
37
+ return JSON.parse(readFileSync(join(poolDir, REFS_JSON_FILENAME), "utf8")) as {
38
+ v: number;
39
+ refs: Record<string, { taskId: string; ts: number }>;
40
+ };
41
+ }
42
+
43
+ describe("acquirePool(refs.json 文件形态)", () => {
44
+ it("mkdir -p 返回池目录(路径经 resolvePoolDir 派生),refs.json 登记 taskId", () => {
45
+ const dir1 = acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
46
+ const dir2 = acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
37
47
  expect(dir1).toBe(resolvePoolDir(tmpRoot, "zcode", "reviewer"));
38
48
  expect(dir1).toBe(dir2);
39
49
  expect(existsSync(dir1)).toBe(true);
50
+ const refs = readRefs(dir1);
51
+ expect(refs.v).toBe(1);
52
+ expect(Object.keys(refs.refs)).toEqual(["bg-1"]);
53
+ expect(refs.refs["bg-1"]!.taskId).toBe("bg-1");
54
+ expect(typeof refs.refs["bg-1"]!.ts).toBe("number");
55
+ });
56
+
57
+ it("同 taskId 重复 acquire 幂等(刷新 ts 不加条目);不同 taskId 独立登记", () => {
58
+ const dir = acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
59
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
60
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-2");
61
+ expect(Object.keys(readRefs(dir).refs).sort()).toEqual(["bg-1", "bg-2"]);
40
62
  });
41
63
 
42
- it("poolKey 经 sanitizeSeg 归一(脏字符不进文件系统路径)", async () => {
43
- const dir = await acquirePool(tmpRoot, "zcode", "weird/key..name");
64
+ it("poolKey 经 sanitizeSeg 归一(脏字符不进文件系统路径)", () => {
65
+ const dir = acquirePool(tmpRoot, "zcode", "weird/key..name", "bg-1");
44
66
  expect(dir).toBe(resolvePoolDir(tmpRoot, "zcode", "weird-key-name"));
45
67
  });
68
+
69
+ it("refs.json 损坏 → 空登记起步(重建语义,不 throw)", () => {
70
+ const dir = acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
71
+ writeFileSync(join(dir, REFS_JSON_FILENAME), "{ torn", "utf8");
72
+ // 损坏后再次 acquire:从空 refs 起步,新 taskId 正常登记
73
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-9");
74
+ expect(Object.keys(readRefs(dir).refs)).toEqual(["bg-9"]);
75
+ });
46
76
  });
47
77
 
48
78
  describe("releasePoolRef 引用计数与整池删除", () => {
49
- it("双引用:release 一次池仍在(原生状态未删),归零才整池删除", async () => {
79
+ it("双引用(不同 taskId):release 一次池仍在(原生状态未删),归零才整池删除", () => {
50
80
  const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
51
- await acquirePool(tmpRoot, "zcode", "reviewer");
52
- await acquirePool(tmpRoot, "zcode", "reviewer");
81
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
82
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-2");
53
83
  mkdirSync(join(poolDir, "home"), { recursive: true });
54
84
  writeFileSync(join(poolDir, "home", "config.json"), "{}");
55
85
 
56
- await releasePoolRef(tmpRoot, "zcode", "reviewer");
86
+ releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-1");
57
87
  expect(existsSync(join(poolDir, "home"))).toBe(true); // 计数 1,不删
88
+ expect(Object.keys(readRefs(poolDir).refs)).toEqual(["bg-2"]);
58
89
 
59
- await releasePoolRef(tmpRoot, "zcode", "reviewer"); // 归零
90
+ releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-2"); // 归零
60
91
  expect(existsSync(join(poolDir, "home"))).toBe(false); // 原生状态已删
61
92
  });
62
93
 
63
- it("journal-*.jsonl 不随池删(生命周期跟随 record),目录保留", async () => {
94
+ it("release 删该 taskId 的 journal(生命周期跟随 record),其他 journal 保留", () => {
64
95
  const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
65
- await acquirePool(tmpRoot, "zcode", "reviewer");
96
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
97
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-2");
66
98
  writeFileSync(join(poolDir, "journal-bg-1.jsonl"), "{}\n");
99
+ writeFileSync(join(poolDir, "journal-bg-2.jsonl"), "{}\n");
67
100
  mkdirSync(join(poolDir, "home"), { recursive: true });
68
101
 
69
- await releasePoolRef(tmpRoot, "zcode", "reviewer");
70
- expect(existsSync(join(poolDir, "journal-bg-1.jsonl"))).toBe(true);
71
- expect(existsSync(join(poolDir, "home"))).toBe(false);
102
+ releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-1");
103
+ expect(existsSync(join(poolDir, "journal-bg-1.jsonl"))).toBe(false); // 自己的 journal 随 record 删
104
+ expect(existsSync(join(poolDir, "journal-bg-2.jsonl"))).toBe(true); // 他人的 journal 不动
105
+ expect(existsSync(join(poolDir, "home"))).toBe(true); // 计数 1,原生状态不删
106
+ });
107
+
108
+ it("归零删池时未 release 的 journal 不随池删(目录保留)", () => {
109
+ const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
110
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
111
+ // 孤儿 journal(无 refs 条目对应——refs 丢失场景的既有残留)
112
+ writeFileSync(join(poolDir, "journal-bg-99.jsonl"), "{}\n");
113
+ mkdirSync(join(poolDir, "home"), { recursive: true });
114
+
115
+ releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-1"); // 归零
116
+ expect(existsSync(join(poolDir, "home"))).toBe(false); // 原生状态已删
117
+ expect(existsSync(join(poolDir, "journal-bg-99.jsonl"))).toBe(true); // journal 不随池删
72
118
  // 只剩 journal:目录本体保留(journal 还在用)
73
119
  expect(existsSync(poolDir)).toBe(true);
74
- expect(readdirSync(poolDir)).toEqual(["journal-bg-1.jsonl"]);
120
+ expect(readdirSync(poolDir)).toEqual(["journal-bg-99.jsonl"]);
121
+ });
122
+
123
+ it("无引用的 release(进程重启后 refs 丢失)→ journal 仍删(record 已死),但保守不删池", () => {
124
+ const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
125
+ mkdirSync(join(poolDir, "home"), { recursive: true });
126
+ writeFileSync(join(poolDir, "journal-bg-1.jsonl"), "{}\n");
127
+ releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-1");
128
+ expect(existsSync(join(poolDir, "home"))).toBe(true); // 无归零证据,不删池
129
+ expect(existsSync(join(poolDir, "journal-bg-1.jsonl"))).toBe(false); // journal 跟随 record
75
130
  });
76
131
 
77
- it("无引用的 release(进程重启后 GC)→ 保守 no-op 不删池", async () => {
132
+ it("refs.json 落盘即重启恢复:acquire 后不经内存态,release 一次即归零删池", () => {
78
133
  const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
134
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
79
135
  mkdirSync(join(poolDir, "home"), { recursive: true });
80
- await releasePoolRef(tmpRoot, "zcode", "reviewer");
81
- expect(existsSync(join(poolDir, "home"))).toBe(true);
136
+ writeFileSync(join(poolDir, "home", "config.json"), "{}");
137
+ // 「重启」语义 = 计数只存在于 refs.json(无进程内缓存)——直接 release 即验证
138
+ releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-1");
139
+ expect(existsSync(join(poolDir, "home"))).toBe(false); // 归零证据来自文件
82
140
  });
83
141
 
84
- it("池目录不存在时 release 归零 → 无原生状态可清,视为成功不抛", async () => {
85
- await acquirePool(tmpRoot, "zcode", "ghost");
142
+ it("池目录不存在时 release → 无原生状态可清,视为成功不抛", () => {
143
+ acquirePool(tmpRoot, "zcode", "ghost", "bg-1");
86
144
  // 归零但目录已被外部删除
87
- rmSync(resolvePoolDir(tmpRoot, "zcode", "ghost"), { recursive: true, force: true });
88
- await expect(releasePoolRef(tmpRoot, "zcode", "ghost")).resolves.toBeUndefined();
145
+ rmSync(resolvePoolDir(tmpRoot, "zcode", "ghost"), { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
146
+ expect(() => releasePoolRef(tmpRoot, "zcode", "ghost", "bg-1")).not.toThrow();
89
147
  });
90
148
 
91
- it("删除失败置 .pool-cleanup-failed 标记文件(可观测不静默),不 throw", async () => {
149
+ it("删除失败置 .pool-cleanup-failed 标记文件(可观测不静默),不 throw", () => {
92
150
  const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
93
- await acquirePool(tmpRoot, "zcode", "reviewer");
151
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
94
152
  mkdirSync(join(poolDir, "home"), { recursive: true });
95
- writeFileSync(join(poolDir, "journal-bg-1.jsonl"), "{}\n");
153
+ writeFileSync(join(poolDir, "journal-bg-2.jsonl"), "{}\n");
96
154
 
97
- // 注入失败 fs:rm 对 home 目录 reject(标记写入仍走真实 fs——失败要可观测)
98
- const realFs = await import("node:fs/promises");
155
+ // 注入失败 fs:rmSync 对 home 目录 throw(标记写入仍走真实 fs——失败要可观测)
156
+ const fs = nodeFs;
99
157
  const injected = {
100
- mkdir: (p: string, o: { recursive: boolean }) => realFs.mkdir(p, o),
101
- readdir: (p: string) => realFs.readdir(p, { withFileTypes: true }),
102
- rm: async (p: string, o: { recursive: boolean; force: boolean }) => {
158
+ mkdirSync: (p: string, o: { recursive: boolean }) => fs.mkdirSync(p, o),
159
+ readdirSync: (p: string) => fs.readdirSync(p, { withFileTypes: true }),
160
+ statSync: (p: string) => fs.statSync(p),
161
+ existsSync: (p: string) => fs.existsSync(p),
162
+ readFileSync: (p: string) => fs.readFileSync(p, "utf8"),
163
+ writeFileSync: (p: string, d: string) => fs.writeFileSync(p, d, "utf8"),
164
+ renameSync: (from: string, to: string) => fs.renameSync(from, to),
165
+ rmSync: (p: string, o: { recursive: boolean; force: boolean }) => {
103
166
  if (p === join(poolDir, "home")) throw new Error("EACCES: permission denied");
104
- return realFs.rm(p, o);
167
+ return fs.rmSync(p, o);
105
168
  },
106
- rmdir: (p: string) => realFs.rmdir(p),
107
- writeFile: (p: string, d: string) => realFs.writeFile(p, d, "utf8"),
169
+ rmdirSync: (p: string) => fs.rmdirSync(p),
108
170
  };
109
171
 
110
- await expect(releasePoolRef(tmpRoot, "zcode", "reviewer", injected)).resolves.toBeUndefined();
172
+ expect(() => releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-1", injected)).not.toThrow();
111
173
  const markerPath = join(poolDir, POOL_CLEANUP_FAILED_MARKER);
112
174
  expect(existsSync(markerPath)).toBe(true);
113
175
  // 标记内容是失败清单 JSON(含失败条目与原因)
114
- const payload = JSON.parse(
115
- (await import("node:fs")).readFileSync(markerPath, "utf8"),
116
- ) as { failures: string[] };
176
+ const payload = JSON.parse(readFileSync(markerPath, "utf8")) as { failures: string[] };
117
177
  expect(payload.failures.length).toBe(1);
118
178
  expect(payload.failures[0]).toContain("home");
119
179
  expect(payload.failures[0]).toContain("EACCES");
120
180
  });
181
+
182
+ it("refs.json 写失败(release 移除条目时)置 .pool-cleanup-failed 标记,不 throw", () => {
183
+ const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
184
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-1");
185
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-2");
186
+
187
+ const fs = nodeFs;
188
+ const injected = {
189
+ mkdirSync: (p: string, o: { recursive: boolean }) => fs.mkdirSync(p, o),
190
+ readdirSync: (p: string) => fs.readdirSync(p, { withFileTypes: true }),
191
+ statSync: (p: string) => fs.statSync(p),
192
+ existsSync: (p: string) => fs.existsSync(p),
193
+ readFileSync: (p: string) => fs.readFileSync(p, "utf8"),
194
+ writeFileSync: (p: string, d: string) => {
195
+ if (p.includes(REFS_JSON_FILENAME)) throw new Error("ENOSPC: no space left");
196
+ return fs.writeFileSync(p, d, "utf8");
197
+ },
198
+ renameSync: (from: string, to: string) => fs.renameSync(from, to),
199
+ rmSync: (p: string, o: { recursive: boolean; force: boolean }) => fs.rmSync(p, o),
200
+ rmdirSync: (p: string) => fs.rmdirSync(p),
201
+ };
202
+
203
+ expect(() => releasePoolRef(tmpRoot, "zcode", "reviewer", "bg-1", injected)).not.toThrow();
204
+ expect(existsSync(join(poolDir, POOL_CLEANUP_FAILED_MARKER))).toBe(true);
205
+ // 池未被误删(写失败时无归零证据落盘)
206
+ expect(existsSync(poolDir)).toBe(true);
207
+ });
208
+ });
209
+
210
+ describe("cleanupExpiredPoolRefs(TTL 兜底)", () => {
211
+ /** 30 天 TTL 常数(与 session-file-gc 同尺度)。 */
212
+ const TTL_MS = 30 * 24 * 60 * 60 * 1000;
213
+
214
+ it("超龄 journal + refs 条目回收,归零删池内原生状态;未超龄不动", () => {
215
+ const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
216
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-old");
217
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-new");
218
+ mkdirSync(join(poolDir, "home"), { recursive: true });
219
+ writeFileSync(join(poolDir, "journal-bg-old.jsonl"), "{}\n");
220
+ writeFileSync(join(poolDir, "journal-bg-new.jsonl"), "{}\n");
221
+
222
+ // mtime 推到 31 天前(仅 old 的 journal)
223
+ const aged = Date.now() / 1000 - 31 * 86400;
224
+ utimesSync(join(poolDir, "journal-bg-old.jsonl"), aged, aged);
225
+
226
+ cleanupExpiredPoolRefs(tmpRoot, TTL_MS);
227
+
228
+ expect(existsSync(join(poolDir, "journal-bg-old.jsonl"))).toBe(false); // 超龄回收
229
+ expect(existsSync(join(poolDir, "journal-bg-new.jsonl"))).toBe(true); // 未超龄保留
230
+ expect(Object.keys(readRefs(poolDir).refs)).toEqual(["bg-new"]); // 条目同步移除
231
+ expect(existsSync(join(poolDir, "home"))).toBe(true); // 未归零,原生状态不删
232
+ });
233
+
234
+ it("全部条目超龄 → 归零删池原生状态 + refs.json;未超龄 journal 挡住目录删除", () => {
235
+ const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
236
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-old");
237
+ mkdirSync(join(poolDir, "home"), { recursive: true });
238
+ writeFileSync(join(poolDir, "journal-bg-old.jsonl"), "{}\n");
239
+ // 无 refs 对应的未超龄孤儿 journal(refs 丢失场景)——目录应保留
240
+ writeFileSync(join(poolDir, "journal-bg-live.jsonl"), "{}\n");
241
+
242
+ const aged = Date.now() / 1000 - 31 * 86400;
243
+ utimesSync(join(poolDir, "journal-bg-old.jsonl"), aged, aged);
244
+
245
+ cleanupExpiredPoolRefs(tmpRoot, TTL_MS);
246
+
247
+ expect(existsSync(join(poolDir, "journal-bg-old.jsonl"))).toBe(false);
248
+ expect(existsSync(join(poolDir, "journal-bg-live.jsonl"))).toBe(true);
249
+ expect(existsSync(join(poolDir, "home"))).toBe(false); // 原生状态删
250
+ expect(existsSync(join(poolDir, REFS_JSON_FILENAME))).toBe(false); // 空池 refs.json 随归零清理
251
+ expect(existsSync(poolDir)).toBe(true); // 剩未超龄 journal,目录保留
252
+ });
253
+
254
+ it("refs 条目 ts 超龄且 journal 不存在 → 孤儿条目移除(防 refs 永久留置)", () => {
255
+ const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
256
+ acquirePool(tmpRoot, "zcode", "reviewer", "bg-orphan");
257
+ // journal 从未落盘(run 从未产出事件);条目 ts 已超龄 → 移除 → 归零(池内仅
258
+ // refs.json,随归零清理,目录随之移除)
259
+ const refsPath = join(poolDir, REFS_JSON_FILENAME);
260
+ const refs = JSON.parse(readFileSync(refsPath, "utf8")) as { refs: Record<string, { ts: number }> };
261
+ refs.refs["bg-orphan"]!.ts = Date.now() - 31 * 86400 * 1000;
262
+ writeFileSync(refsPath, JSON.stringify(refs));
263
+
264
+ cleanupExpiredPoolRefs(tmpRoot, TTL_MS);
265
+ // 归零路径:refs.json 清理(无幻影引用残留);空目录本体保留——与「只剩 journal
266
+ // 保留目录」同语义(防与并发 acquire 的 mkdir/写 refs 竞争,目录无害)
267
+ expect(existsSync(refsPath)).toBe(false);
268
+ expect(readdirSync(poolDir)).toEqual([]);
269
+ });
270
+
271
+ it("无主超龄 journal(refs 丢失)回收;engines 根不存在时 no-op", () => {
272
+ const poolDir = resolvePoolDir(tmpRoot, "zcode", "reviewer");
273
+ mkdirSync(poolDir, { recursive: true });
274
+ writeFileSync(join(poolDir, "journal-bg-x.jsonl"), "{}\n");
275
+ const aged = Date.now() / 1000 - 31 * 86400;
276
+ utimesSync(join(poolDir, "journal-bg-x.jsonl"), aged, aged);
277
+
278
+ cleanupExpiredPoolRefs(tmpRoot, TTL_MS);
279
+ expect(existsSync(join(poolDir, "journal-bg-x.jsonl"))).toBe(false);
280
+
281
+ expect(() => cleanupExpiredPoolRefs(join(tmpRoot, "absent"), TTL_MS)).not.toThrow();
282
+ });
121
283
  });
122
284
 
123
285
  describe("cleanupSpawnedFiles(单次性产物)", () => {
124
- it("keepForResume=true → 全部保留(resume 续接原 session,不重写产物)", async () => {
286
+ it("keepForResume=true → 全部保留(resume 续接原 session,不重写产物)", () => {
125
287
  const f = join(tmpRoot, "prompt.txt");
126
288
  writeFileSync(f, "x");
127
- await cleanupSpawnedFiles([f], { keepForResume: true });
289
+ cleanupSpawnedFiles([f], { keepForResume: true });
128
290
  expect(existsSync(f)).toBe(true);
129
291
  });
130
292
 
131
- it("keepForResume=false → 删除;目录产物递归删", async () => {
293
+ it("keepForResume=false → 删除;目录产物递归删", () => {
132
294
  const file = join(tmpRoot, "prompt.txt");
133
295
  const dir = join(tmpRoot, "persona");
134
296
  writeFileSync(file, "x");
135
297
  mkdirSync(dir, { recursive: true });
136
298
  writeFileSync(join(dir, "persona.md"), "y");
137
- await cleanupSpawnedFiles([file, dir], { keepForResume: false });
299
+ cleanupSpawnedFiles([file, dir], { keepForResume: false });
138
300
  expect(existsSync(file)).toBe(false);
139
301
  expect(existsSync(dir)).toBe(false);
140
302
  });
141
303
 
142
- it("不存在的路径幂等(ENOENT 忽略);单条失败不 throw 其余继续", async () => {
304
+ it("不存在的路径幂等(ENOENT 忽略);单条失败不 throw 其余继续", () => {
143
305
  const good = join(tmpRoot, "good.txt");
144
306
  writeFileSync(good, "x");
145
307
  // 真实 fs 下很难制造 rm 失败——用「父路径为文件」制造 ENOTDIR 失败:
146
308
  // rm(recursive, force) 对 «file/child» 形态报 ENOTDIR(force 只豁免 ENOENT)
147
309
  const blocked = join(tmpRoot, "plain-file", "child");
148
310
  writeFileSync(join(tmpRoot, "plain-file"), "not a dir");
149
- await expect(
150
- cleanupSpawnedFiles([blocked, good], { keepForResume: false }),
151
- ).resolves.toBeUndefined();
311
+ expect(() => cleanupSpawnedFiles([blocked, good], { keepForResume: false })).not.toThrow();
152
312
  expect(existsSync(good)).toBe(false);
153
313
  });
154
314
  });