@zhushanwen/subagent-core 0.2.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-43ONBFZX.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-V3VHZ2VX.js +59 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
  18. package/dist/execution/engine/engines/zcode/constants.js +39 -7
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +18234 -1277
  24. package/dist/index.d.cts +5646 -673
  25. package/dist/index.d.ts +5646 -673
  26. package/dist/index.js +17828 -1277
  27. package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
  28. package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
  29. package/package.json +7 -4
  30. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  31. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  32. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  33. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  34. package/src/__tests__/manifest-store.test.ts +10 -9
  35. package/src/__tests__/record-store-cache.test.ts +0 -2
  36. package/src/__tests__/record-store-index.test.ts +1 -2
  37. package/src/__tests__/review-fix-loop-script.test.ts +93 -3
  38. package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
  39. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  40. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  41. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  42. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  43. package/src/__tests__/session-runner.test.ts +1 -1
  44. package/src/__tests__/smoke.test.ts +9 -2
  45. package/src/core/__tests__/host-services.test.ts +2 -2
  46. package/src/core/__tests__/logger.test.ts +1 -1
  47. package/src/core/error-message.ts +9 -0
  48. package/src/core/host-services.ts +21 -5
  49. package/src/core/notify-ports.ts +18 -5
  50. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  51. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  52. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  53. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  54. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  55. package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
  56. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
  57. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  58. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  59. package/src/execution/__tests__/delivery-methods.test.ts +77 -62
  60. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  61. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  62. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  63. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  64. package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
  65. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  66. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  67. package/src/execution/__tests__/execution-record.test.ts +217 -60
  68. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  69. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  70. package/src/execution/__tests__/finalize-record.test.ts +278 -16
  71. package/src/execution/__tests__/gc-timer.test.ts +57 -2
  72. package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
  73. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  74. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  75. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  76. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  77. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  78. package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
  79. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  80. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  83. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  84. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  85. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  86. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  87. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  88. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  89. package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
  90. package/src/execution/__tests__/output-collector.test.ts +117 -6
  91. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  92. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  93. package/src/execution/__tests__/record-store.test.ts +89 -5
  94. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  95. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  96. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  97. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  98. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  105. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
  106. package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
  107. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  108. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  109. package/src/execution/__tests__/session-pending.test.ts +471 -0
  110. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  111. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  112. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  113. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  114. package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
  115. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
  116. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  117. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  118. package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
  119. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  120. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  121. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  122. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  123. package/src/execution/__tests__/spawned-children.test.ts +155 -11
  124. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  125. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  126. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  127. package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
  128. package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
  129. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  130. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
  131. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
  132. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
  133. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  134. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  135. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  136. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  137. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  138. package/src/execution/__tests__/timeout-integration.test.ts +13 -12
  139. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  140. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  141. package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
  142. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  143. package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
  144. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  145. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  146. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  147. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  148. package/src/execution/agent-registry.ts +298 -30
  149. package/src/execution/agent-result-mapper.ts +3 -0
  150. package/src/execution/agents-assembly.ts +88 -0
  151. package/src/execution/cold-resurrect.ts +199 -0
  152. package/src/execution/concurrency-pool.ts +71 -9
  153. package/src/execution/dialog-queue.ts +101 -3
  154. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  155. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  156. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  158. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  159. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  160. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  161. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  162. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
  163. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +94 -3
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engine-discovery.ts +12 -16
  181. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  182. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  183. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  184. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
  185. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  186. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  187. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  188. package/src/execution/engine/engines/pi/reader.ts +11 -0
  189. package/src/execution/engine/engines/pi/registration.ts +4 -2
  190. package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
  191. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  192. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  193. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  194. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  195. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  197. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  198. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
  199. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  200. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  202. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  203. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  209. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  210. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
  211. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  212. package/src/execution/engine/engines/zcode/connection.ts +598 -0
  213. package/src/execution/engine/engines/zcode/constants.ts +147 -14
  214. package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
  215. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  216. package/src/execution/engine/engines/zcode/preparer.ts +22 -150
  217. package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
  218. package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
  219. package/src/execution/engine/host-task-spec.ts +32 -36
  220. package/src/execution/engine/model-validation.ts +177 -0
  221. package/src/execution/engine/port.ts +65 -7
  222. package/src/execution/engine/registry.ts +53 -0
  223. package/src/execution/engine/routing.ts +75 -1
  224. package/src/execution/engine/types.ts +30 -103
  225. package/src/execution/execution-record.ts +222 -144
  226. package/src/execution/finalize-record.ts +181 -85
  227. package/src/execution/idle-gc.ts +28 -0
  228. package/src/execution/lifecycle-manager.ts +27 -3
  229. package/src/execution/lifecycle-predicates.ts +1 -1
  230. package/src/execution/manifest-store.ts +53 -62
  231. package/src/execution/notifier.ts +10 -10
  232. package/src/execution/notify-host.ts +212 -0
  233. package/src/execution/notify-ledger.ts +117 -16
  234. package/src/execution/record-entry.ts +8 -2
  235. package/src/execution/record-store.ts +148 -88
  236. package/src/execution/round-settlement.ts +93 -0
  237. package/src/execution/session-file-gc.ts +96 -62
  238. package/src/execution/session-pending.ts +213 -62
  239. package/src/execution/session-reconstructor.ts +256 -143
  240. package/src/execution/sessions-index.ts +127 -102
  241. package/src/execution/settled-watchdog.ts +311 -0
  242. package/src/execution/subagent-actions-core.ts +686 -0
  243. package/src/execution/subagent-service.ts +1106 -907
  244. package/src/execution/subprocess-agent-runner.ts +144 -97
  245. package/src/execution/types.ts +9 -0
  246. package/src/execution/ui-request-handler-factory.ts +126 -75
  247. package/src/execution/ui-request-queue.ts +50 -19
  248. package/src/execution/worktree-git-ops.ts +397 -0
  249. package/src/execution/worktree-manager.ts +72 -10
  250. package/src/execution/worktree-registry.ts +6 -12
  251. package/src/index.ts +542 -9
  252. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  253. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
  254. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  255. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  257. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  258. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
  259. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  260. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  261. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  262. package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
  263. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  264. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  265. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  266. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  267. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  268. package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
  269. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  270. package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
  271. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  272. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  273. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  274. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  275. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  276. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
  277. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  278. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  279. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  280. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  281. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  282. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  283. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  284. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  285. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  286. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  287. package/src/orchestration/agent-opts-resolver.ts +8 -8
  288. package/src/orchestration/args-meta.ts +198 -0
  289. package/src/orchestration/args-validator.ts +53 -31
  290. package/src/orchestration/execute-agent-call.ts +25 -79
  291. package/src/orchestration/file-run-store.ts +327 -0
  292. package/src/orchestration/launcher.ts +160 -104
  293. package/src/orchestration/lifecycle.ts +371 -97
  294. package/src/orchestration/models/agent-call.ts +7 -7
  295. package/src/orchestration/models/budget.ts +5 -5
  296. package/src/orchestration/models/ports.ts +4 -4
  297. package/src/orchestration/models/run-runtime.ts +15 -15
  298. package/src/orchestration/models/trace.ts +9 -9
  299. package/src/orchestration/models/types.ts +111 -22
  300. package/src/orchestration/models/workflow-run.ts +28 -28
  301. package/src/orchestration/models/workflow-script.ts +4 -4
  302. package/src/orchestration/run-snapshot.ts +266 -0
  303. package/src/orchestration/script-generate.ts +154 -0
  304. package/src/orchestration/script-lint.ts +123 -90
  305. package/src/orchestration/worker-handle.ts +10 -10
  306. package/src/orchestration/worker-host.ts +10 -10
  307. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
  308. package/src/orchestration/worker-script-builder.ts +401 -346
  309. package/src/orchestration/workflow-files.ts +37 -11
  310. package/src/orchestration/workflow-run-summary.ts +69 -0
  311. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  312. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  313. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  314. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  315. package/src/shared/__tests__/injection-render.test.ts +518 -0
  316. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  317. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  318. package/src/shared/__tests__/resource-discovery.test.ts +261 -217
  319. package/src/shared/agent-ref.ts +143 -2
  320. package/src/shared/atomic-write.ts +323 -0
  321. package/src/shared/bounded-serialize.ts +154 -0
  322. package/src/shared/injection-render.ts +279 -0
  323. package/src/shared/meta-parser.ts +182 -68
  324. package/src/shared/model-ref.ts +15 -2
  325. package/src/shared/resource-discovery.ts +97 -204
  326. package/src/shared/resource-meta.ts +14 -0
  327. package/src/shared/schema-jsonify.ts +1 -1
  328. package/src/shared/xml-injection.ts +9 -9
  329. package/workflows/README.md +9 -9
  330. package/workflows/chain.js +4 -2
  331. package/workflows/map-reduce.js +5 -3
  332. package/workflows/parallel.js +5 -3
  333. package/workflows/review-fix-loop-utils.cjs +165 -92
  334. package/workflows/review-fix-loop-utils.d.cts +287 -0
  335. package/workflows/review-fix-loop.js +18 -7
  336. package/workflows/scatter-gather.js +4 -2
  337. package/dist/chunk-3VOERJPJ.js +0 -22
  338. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  339. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  340. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  341. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  342. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  343. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  344. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  345. package/src/execution/execute-options-mapper.ts +0 -115
  346. package/src/execution/session-runner.ts +0 -1789
  347. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,117 @@
1
+ // src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts
2
+ //
3
+ // [LC-5/T6①] armIdleTimer 超时回调身份比对(按值守卫,对齐 session-runner
4
+ // removeChildRegistration 先例)。
5
+ //
6
+ // 覆盖(设计 §4.3 LC-5 + §11-2):
7
+ // 1. 「旧 timer 已到期、回调迟到执行」与「同 recordId re-arm」精确交错 → 守卫不误删
8
+ // 新条目(旧代码此处误删 = 新 timer 脱管、disarm 失效 → turn 中途误杀)。
9
+ // 2. 无交错时旧 timer 到点删除自己的条目 + 后续 re-arm 正常(既有语义不回归)。
10
+ // 3. fake timers 下「re-arm 发生在旧 timer 到期回调处置中」的同轮交错端到端:
11
+ // 新 timer 存活、到点正常触发、Map 清理干净。
12
+ //
13
+ // 交错构造说明(§11-2 勘误锚点):sinon fake clock 的同步 tick 模型在「时间推进」的
14
+ // 同一同步流程内立即执行到期回调,不存在「已到期、回调已入队、尚未执行」的中间态
15
+ // 暴露给 clearTimeout——纯 fake timers 无法表达 Node 真实窗口(fire 后回调入
16
+ // macrotask 队列,clearTimeout 不可撤销)。用例 1/2 以手动排程 fake(stub setTimeout
17
+ // 捕获回调、测试显式控制 fire 时机)精确复现该中间态;用例 3 补真实 clock 语义下的
18
+ // 同轮交错端到端回归。
19
+
20
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
21
+
22
+ import {
23
+ _resetLifecycleState,
24
+ armIdleTimer,
25
+ disarmIdleTimer,
26
+ hasIdleTimer,
27
+ } from "../lifecycle-manager.ts";
28
+
29
+ /** 手动排程 fake 捕获的到期回调(fire = 模拟 timer 到点,时机由用例显式控制)。 */
30
+ let scheduled: Array<{ fire: () => void; timer: object }>;
31
+
32
+ beforeEach(() => {
33
+ _resetLifecycleState();
34
+ scheduled = [];
35
+ // 手动排程模型:setTimeout 只登记不排程;clearTimeout no-op(「已入队回调不可撤销」
36
+ // 的真实语义由用例不 fire 被撤销者来表达)。
37
+ vi.stubGlobal(
38
+ "setTimeout",
39
+ (cb: () => void, _delay?: number) => {
40
+ void _delay;
41
+ const timer: object = {};
42
+ scheduled.push({ fire: cb, timer });
43
+ return Object.assign(timer, { unref() {} });
44
+ },
45
+ );
46
+ vi.stubGlobal("clearTimeout", (_t?: unknown) => {
47
+ void _t;
48
+ });
49
+ });
50
+
51
+ afterEach(() => {
52
+ _resetLifecycleState();
53
+ vi.unstubAllGlobals();
54
+ vi.useRealTimers();
55
+ });
56
+
57
+ describe("[LC-5] armIdleTimer 超时回调身份比对", () => {
58
+ it("旧 timer 到点回调迟到执行(re-arm 已覆盖条目)→ 守卫不误删新条目,disarm 仍有效", () => {
59
+ const onTimeoutA = vi.fn();
60
+ const onTimeoutB = vi.fn();
61
+
62
+ armIdleTimer("sa-x", onTimeoutA, 1000); // timerA 入手 排程表,Map: timerA
63
+ // re-arm(agent_settled 刷新):disarm 旧 timer(真实 Node 中 clearTimeout 对已
64
+ // 入队回调无效)→ timerB 入表,Map: timerB。此后 timerA 的回调才迟到执行。
65
+ armIdleTimer("sa-x", onTimeoutB, 1000);
66
+ expect(hasIdleTimer("sa-x")).toBe(true);
67
+
68
+ scheduled[0]?.fire(); // 旧 timerA 回调迟到执行
69
+
70
+ expect(onTimeoutA).toHaveBeenCalledTimes(1); // 旧超时回调本身照常触发
71
+ expect(hasIdleTimer("sa-x")).toBe(true); // [核心] 新条目未被误删(旧代码此处 false = 脱管)
72
+
73
+ disarmIdleTimer("sa-x"); // B 未脱管:disarm 有效
74
+ expect(hasIdleTimer("sa-x")).toBe(false);
75
+ expect(onTimeoutB).not.toHaveBeenCalled();
76
+ });
77
+
78
+ it("无交错:旧 timer 到点删除自己的条目(守卫不改变既有语义),后续 re-arm 正常", () => {
79
+ const onTimeoutA = vi.fn();
80
+ const onTimeoutB = vi.fn();
81
+
82
+ armIdleTimer("sa-y", onTimeoutA, 1000);
83
+ expect(hasIdleTimer("sa-y")).toBe(true);
84
+
85
+ scheduled[0]?.fire(); // 正常到点:Map 中是自己 → delete → 回调
86
+
87
+ expect(onTimeoutA).toHaveBeenCalledTimes(1);
88
+ expect(hasIdleTimer("sa-y")).toBe(false); // 旧条目删除正常(不残留失效 entry)
89
+
90
+ armIdleTimer("sa-y", onTimeoutB, 1000); // 后续 re-arm 正常
91
+ expect(hasIdleTimer("sa-y")).toBe(true);
92
+ scheduled[1]?.fire();
93
+ expect(onTimeoutB).toHaveBeenCalledTimes(1);
94
+ expect(hasIdleTimer("sa-y")).toBe(false);
95
+ });
96
+
97
+ it("fake timers 同轮交错:re-arm 发生在旧 timer 到期回调处置中 → 新 timer 存活到点触发", () => {
98
+ vi.useFakeTimers();
99
+ const onTimeoutB = vi.fn();
100
+ const onTimeoutA = vi.fn(() => {
101
+ // 到期回调处置中同步 re-arm(同轮交错的 fake-timer 可达形态)
102
+ armIdleTimer("sa-z", onTimeoutB, 1000);
103
+ });
104
+
105
+ armIdleTimer("sa-z", onTimeoutA, 1000);
106
+ vi.advanceTimersByTime(1000); // A 到点:删自己 → onTimeoutA → re-arm B
107
+
108
+ expect(onTimeoutA).toHaveBeenCalledTimes(1);
109
+ expect(hasIdleTimer("sa-z")).toBe(true); // B armed,交错不丢 timer
110
+
111
+ vi.advanceTimersByTime(999);
112
+ expect(onTimeoutB).not.toHaveBeenCalled(); // B 从 arm 时刻重新计时
113
+ vi.advanceTimersByTime(1);
114
+ expect(onTimeoutB).toHaveBeenCalledTimes(1); // B 存活到点正常触发
115
+ expect(hasIdleTimer("sa-z")).toBe(false); // 触发后 Map 清理干净
116
+ });
117
+ });
@@ -9,6 +9,15 @@
9
9
 
10
10
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
11
 
12
+ // [LC-7/T7①] Mock 共享 logger:env 非法值回落默认的 warn 留痕可被断言
13
+ //(对齐 channel-registry-handshake.test.ts 模式;vi.mock 自动 hoist 到 import 前)。
14
+ const { loggerMock } = vi.hoisted(() => ({
15
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
16
+ }));
17
+ vi.mock("../../core/logger.ts", () => ({
18
+ getLogger: () => loggerMock,
19
+ }));
20
+
12
21
  import {
13
22
  DEFAULT_IDLE_TIMEOUT_MS,
14
23
  DEFAULT_MAX_ALIVE_PROCESSES,
@@ -30,6 +39,9 @@ describe("lifecycle-manager — V2 §5.2 模块 1", () => {
30
39
  beforeEach(() => {
31
40
  vi.useFakeTimers();
32
41
  _resetLifecycleState();
42
+ loggerMock.debug.mockClear();
43
+ loggerMock.warn.mockClear();
44
+ loggerMock.error.mockClear();
33
45
  // [F-4 同源修复] env 隔离:「默认超时」用例依赖 XYZ_SUBAGENT_IDLE_TIMEOUT_MS
34
46
  // 未设基线(getEnvIdleTimeoutMs 会覆盖 DEFAULT),宿主 export 即假红。空串 = 未设。
35
47
  vi.stubEnv("XYZ_SUBAGENT_IDLE_TIMEOUT_MS", "");
@@ -138,6 +150,33 @@ describe("lifecycle-manager — V2 §5.2 模块 1", () => {
138
150
  expect(() => armIdleTimer("sa-env-overflow", onTimeout)).toThrowError(/2147483647/);
139
151
  expect(hasIdleTimer("sa-env-overflow")).toBe(false);
140
152
  });
153
+
154
+ // [LC-7/T7①] env 非法值回落默认不再静默:warn 留痕(env 名 + 实际值 + 生效行为)。
155
+ // 「以为设了极长保活、实际回落 5min」的语义漂移必须可诊断。
156
+ it("[LC-7] env 非法值('30m' 非纯数字)→ 回落 DEFAULT_IDLE_TIMEOUT_MS 且 warn 留痕", () => {
157
+ vi.stubEnv("XYZ_SUBAGENT_IDLE_TIMEOUT_MS", "30m");
158
+ const onTimeout = vi.fn();
159
+ armIdleTimer("sa-lc7-invalid", onTimeout); // 不传 timeoutMs → env(非法)→ DEFAULT
160
+
161
+ // 生效行为 = 默认 5min(非法值没有按字面 '30m' 也不按禁用处理)
162
+ vi.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT_MS - 1);
163
+ expect(onTimeout).not.toHaveBeenCalled();
164
+ vi.advanceTimersByTime(1);
165
+ expect(onTimeout).toHaveBeenCalledTimes(1);
166
+
167
+ expect(loggerMock.warn).toHaveBeenCalledTimes(1);
168
+ const msg = String(loggerMock.warn.mock.calls[0]?.[0] ?? "");
169
+ expect(msg).toContain("XYZ_SUBAGENT_IDLE_TIMEOUT_MS"); // env 变量名
170
+ expect(msg).toContain("30m"); // 实际值
171
+ expect(msg).toContain("DEFAULT_IDLE_TIMEOUT_MS"); // 生效行为:回落默认
172
+ });
173
+
174
+ it("[LC-7] env 未设(空串基线)→ 静默走 DEFAULT,零 warn(未配置不是异常)", () => {
175
+ const onTimeout = vi.fn();
176
+ armIdleTimer("sa-lc7-clean", onTimeout);
177
+ expect(hasIdleTimer("sa-lc7-clean")).toBe(true);
178
+ expect(loggerMock.warn).not.toHaveBeenCalled();
179
+ });
141
180
  });
142
181
 
143
182
  // ============================================================
@@ -10,7 +10,7 @@ import {
10
10
  disarmIdleTimer,
11
11
  _resetLifecycleState,
12
12
  } from "../lifecycle-manager.ts";
13
- import { spawnedChildren } from "../session-runner.ts";
13
+ import { spawnedChildren } from "../engine/engines/pi/session-runner.ts";
14
14
  import type { ChildProcess } from "node:child_process";
15
15
  import type { ExecutionRecord } from "../types.ts";
16
16
 
@@ -25,7 +25,6 @@ function makeRecord(overrides: Partial<ExecutionRecord> = {}): ExecutionRecord {
25
25
  thinkingLevel: undefined,
26
26
  mode: "background",
27
27
  task: "test",
28
- slug: "test",
29
28
  startedAt: Date.now(),
30
29
  rootSessionId: "root",
31
30
  parentRecordId: undefined,
@@ -22,7 +22,7 @@ function makeBaseManifest(overrides: Partial<ManifestRecord> = {}): ManifestReco
22
22
  id: "rec-test",
23
23
  rootSessionId: "session-main",
24
24
  agentName: "worker",
25
- status: "completed",
25
+ status: "closed",
26
26
  createdAt: 1000,
27
27
  ...overrides,
28
28
  };
@@ -38,7 +38,7 @@ describe("ManifestStore — parentRecordId 落盘 (M3a)", () => {
38
38
  });
39
39
 
40
40
  afterEach(() => {
41
- fs.rmSync(tmpDir, { recursive: true, force: true });
41
+ fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
42
42
  });
43
43
 
44
44
  // ── TC-m3a-new-record-parentid ──
@@ -71,7 +71,7 @@ describe("ManifestStore — parentRecordId 落盘 (M3a)", () => {
71
71
  id: "rec-old",
72
72
  rootSessionId: "session-main",
73
73
  agentName: "worker",
74
- status: "completed",
74
+ status: "closed",
75
75
  createdAt: 2000,
76
76
  // 无 parentRecordId —— 旧版本写的
77
77
  };
@@ -104,7 +104,7 @@ describe("ManifestStore — parentRecordId 落盘 (M3a)", () => {
104
104
  id: "rec-minimal",
105
105
  rootSessionId: "session-main",
106
106
  agentName: "worker",
107
- status: "completed",
107
+ status: "closed",
108
108
  createdAt: 3000,
109
109
  };
110
110
  fs.writeFileSync(
@@ -0,0 +1,99 @@
1
+ // src/execution/__tests__/manifest-store-tmp-recovery.test.ts
2
+ //
3
+ // [u-svc / T5④ / PS-13] recoverTmpFiles 循环内 per-file 容错:
4
+ // 单个 tmp 文件操作失败(ENOENT——并发回收/外部清理抢先)只 warn + 跳过,
5
+ // 不再中断整轮——剩余 tmp 继续处理;返回值形态不变(跳过者不计数)。
6
+
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+
9
+ const { loggerMock } = vi.hoisted(() => ({
10
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
11
+ }));
12
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
13
+
14
+ // fs partial mock:unlinkSync 可按路径注错,其余转发真实实现。
15
+ const { unlinkSyncMock } = vi.hoisted(() => ({ unlinkSyncMock: vi.fn() }));
16
+ vi.mock("node:fs", async (importOriginal) => {
17
+ const actual = await importOriginal<typeof import("node:fs")>();
18
+ return {
19
+ ...actual,
20
+ unlinkSync: unlinkSyncMock,
21
+ default: { ...actual, unlinkSync: unlinkSyncMock },
22
+ };
23
+ });
24
+
25
+ import * as fs from "node:fs";
26
+ import * as os from "node:os";
27
+ import * as path from "node:path";
28
+
29
+ import { ManifestStore } from "../manifest-store.ts";
30
+
31
+ const VALID_MANIFEST = {
32
+ id: "sa-good",
33
+ rootSessionId: "root-1",
34
+ agentName: "general-purpose",
35
+ createdAt: 1,
36
+ status: "running",
37
+ };
38
+
39
+ describe("T5④ recoverTmpFiles per-file tolerance", () => {
40
+ let dir: string;
41
+ let store: ManifestStore;
42
+
43
+ beforeEach(async () => {
44
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "manifest-tmp-recovery-"));
45
+ store = new ManifestStore(dir);
46
+ // 真实 unlinkSync(mock 模块对象上的 unlinkSync 已被替换,不能自引用转发)
47
+ const actualFs = await vi.importActual<typeof import("node:fs")>("node:fs");
48
+ unlinkSyncMock.mockReset();
49
+ unlinkSyncMock.mockImplementation((p: fs.PathLike) => actualFs.unlinkSync(p));
50
+ loggerMock.warn.mockClear();
51
+ loggerMock.error.mockClear();
52
+ loggerMock.debug.mockClear();
53
+ });
54
+
55
+ afterEach(() => {
56
+ vi.restoreAllMocks();
57
+ fs.rmSync(dir, { recursive: true, force: true });
58
+ });
59
+
60
+ it("processes remaining tmp files when one unlink fails mid-loop", async () => {
61
+ const actualFs = await vi.importActual<typeof import("node:fs")>("node:fs");
62
+ // 三个 tmp:good(合法 → promote)、stale(manifest sa-stale.json 已存在 → 删)、
63
+ // doomed(unlink ENOENT)
64
+ fs.writeFileSync(path.join(dir, "sa-good.json.tmp.111"), JSON.stringify(VALID_MANIFEST));
65
+ fs.writeFileSync(path.join(dir, "sa-stale.json"), JSON.stringify(VALID_MANIFEST));
66
+ fs.writeFileSync(path.join(dir, "sa-stale.json.tmp.222"), "{}");
67
+ fs.writeFileSync(path.join(dir, "sa-doomed.json.tmp.333"), "not json");
68
+ const doomedPath = path.join(dir, "sa-doomed.json.tmp.333");
69
+ unlinkSyncMock.mockImplementation((p: fs.PathLike) => {
70
+ if (String(p) === doomedPath) {
71
+ const err = new Error("ENOENT: file vanished") as NodeJS.ErrnoException;
72
+ err.code = "ENOENT";
73
+ throw err;
74
+ }
75
+ return actualFs.unlinkSync(p);
76
+ });
77
+
78
+ const result = await store.recoverTmpFiles();
79
+
80
+ // 整轮不中断:doomed 之外的 tmp 全部处理完
81
+ expect(result.recovered).toBe(1); // good promote
82
+ expect(result.deleted).toBe(1); // stale 删
83
+ // good 被提升为正式 manifest
84
+ expect(fs.existsSync(path.join(dir, "sa-good.json"))).toBe(true);
85
+ // 失败留痕(warn 级,含文件名)
86
+ expect(loggerMock.warn).toHaveBeenCalledWith(
87
+ expect.stringContaining("sa-doomed.json.tmp.333"),
88
+ expect.objectContaining({ detail: expect.stringContaining("ENOENT") }),
89
+ );
90
+ expect(loggerMock.warn).toHaveBeenCalledWith(expect.stringContaining("1 of 3 tmp file(s)"));
91
+ });
92
+
93
+ it("returns counts unchanged and no warnings when all tmp files succeed", async () => {
94
+ fs.writeFileSync(path.join(dir, "sa-ok.json.tmp.444"), JSON.stringify(VALID_MANIFEST));
95
+ const result = await store.recoverTmpFiles();
96
+ expect(result).toEqual({ deleted: 0, recovered: 1 });
97
+ expect(loggerMock.warn).not.toHaveBeenCalled();
98
+ });
99
+ });
@@ -0,0 +1,113 @@
1
+ // src/execution/__tests__/max-turns-to-watchdog-ms.test.ts
2
+ //
3
+ // U3(subagent-core-sink-design):computeWatchdogMs 语义化更名 maxTurnsToWatchdogMs
4
+ // + floor 语义文档化。本文件锚定换算纯函数语义:
5
+ // - floor 断言(设计 §4 S2):maxTurnsToWatchdogMs(2) >= 1_800_000——zsw 曾自实现
6
+ // 无 floor 版本致 maxTurns=2 被 10min 误杀(设计 §2.1 例 1),floor 是两宿主
7
+ // 预算一致性的锚点(G1)。
8
+ // - 边界:0.5 轮 / 缺省(不挂 watchdog)/ 极大值。
9
+ //
10
+ // 纯函数换算,无 timer 参与,无需 fake timers。
11
+ import { describe, expect, it, vi } from "vitest";
12
+
13
+ // [LC-7/T7①] Mock 共享 logger:非法 env 值的 warn 留痕可被断言
14
+ //(对齐 channel-registry-handshake.test.ts 模式;vi.mock 自动 hoist 到 import 前)。
15
+ const { loggerMock } = vi.hoisted(() => ({
16
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
17
+ }));
18
+ vi.mock("../../core/logger.ts", () => ({
19
+ getLogger: () => loggerMock,
20
+ }));
21
+
22
+ import {
23
+ maxTurnsToWatchdogMs,
24
+ resolveSpawnWatchdogMs,
25
+ SPAWN_WATCHDOG_ENV,
26
+ } from "../engine/engines/pi/session-runner.ts";
27
+
28
+ const MIN_MS = 60_000;
29
+ /** watchdog floor(session-runner WATCHDOG_FLOOR_MINUTES=30 的毫秒值)。 */
30
+ const FLOOR_MS = 30 * MIN_MS;
31
+ /** Node setTimeout delay 安全域上界(>2^31-1 被置 1ms 立即触发,assertSafeTimerDelay 拦截域)。 */
32
+ const TIMER_SAFE_MAX = 2 ** 31 - 1;
33
+
34
+ describe("maxTurnsToWatchdogMs(U3 watchdog 换算,floor 语义)", () => {
35
+ it("floor 断言(S2):maxTurns=2 → >= 1_800_000(floor 生效,非 2×5=10min)", () => {
36
+ expect(maxTurnsToWatchdogMs(2)).toBeGreaterThanOrEqual(1_800_000);
37
+ // 精确值锚定:floor 是 max 不是「至少再加余量」
38
+ expect(maxTurnsToWatchdogMs(2)).toBe(FLOOR_MS);
39
+ });
40
+
41
+ it("0.5 轮(小数)→ floor 30min(不出现 2.5min 的短 watchdog)", () => {
42
+ expect(maxTurnsToWatchdogMs(0.5)).toBe(FLOOR_MS);
43
+ });
44
+
45
+ it("maxTurns=6 → 恰为 floor 临界(max(30min, 30min))", () => {
46
+ expect(maxTurnsToWatchdogMs(6)).toBe(FLOOR_MS);
47
+ });
48
+
49
+ it("线性段:maxTurns=20 → 100min;maxTurns=100 → 500min(M-1 长任务不被 30min 误杀)", () => {
50
+ expect(maxTurnsToWatchdogMs(20)).toBe(100 * MIN_MS);
51
+ expect(maxTurnsToWatchdogMs(100)).toBe(500 * MIN_MS);
52
+ });
53
+
54
+ it("极大值 → 保持纯算术结果(> 2^31-1 溢出域;挂载前由 assertSafeTimerDelay fail-fast 拦截)", () => {
55
+ expect(maxTurnsToWatchdogMs(Number.MAX_SAFE_INTEGER)).toBeGreaterThan(TIMER_SAFE_MAX);
56
+ expect(maxTurnsToWatchdogMs(Number.POSITIVE_INFINITY)).toBe(Number.POSITIVE_INFINITY);
57
+ });
58
+
59
+ it("上层防线:换算链路入口对极大值 fail-fast(resolveSpawnWatchdogMs(Infinity) 抛出)", () => {
60
+ expect(() => resolveSpawnWatchdogMs(Number.POSITIVE_INFINITY)).toThrow();
61
+ });
62
+
63
+ it("缺省(maxTurns 未传/null 且 env 未设)→ resolveSpawnWatchdogMs 返回 undefined(不挂 watchdog)", () => {
64
+ // 空串与未设同义(getEnvSpawnWatchdogMs 对 falsy raw 返回 undefined);
65
+ // 显式 stub 防御宿主 shell export 污染(vitest.setup.ts 已在模块加载前净化)。
66
+ vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
67
+ try {
68
+ expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
69
+ expect(resolveSpawnWatchdogMs(null)).toBeUndefined();
70
+ } finally {
71
+ vi.unstubAllEnvs();
72
+ }
73
+ });
74
+
75
+ // [LC-7/T7①] env 非法值不再静默失效:warn 留痕(env 名 + 实际值 + 生效行为)。
76
+ it("[LC-7] env 设非法值('30m' 非纯数字)→ undefined(不挂 watchdog)且 warn 留痕", () => {
77
+ vi.stubEnv(SPAWN_WATCHDOG_ENV, "30m");
78
+ try {
79
+ expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
80
+ expect(loggerMock.warn).toHaveBeenCalledTimes(1);
81
+ const msg = String(loggerMock.warn.mock.calls[0]?.[0] ?? "");
82
+ expect(msg).toContain("XYZ_SUBAGENT_SPAWN_WATCHDOG_MS"); // env 变量名
83
+ expect(msg).toContain("30m"); // 实际值
84
+ expect(msg).toContain("NOT armed"); // 生效行为:等价关闭,运维可诊断「以为有兜底、实际裸奔」
85
+ } finally {
86
+ loggerMock.warn.mockClear();
87
+ vi.unstubAllEnvs();
88
+ }
89
+ });
90
+
91
+ it("[LC-7] env 设 <=0 → 同样 warn 留痕(不挂 watchdog,语义与非法值一致可见)", () => {
92
+ vi.stubEnv(SPAWN_WATCHDOG_ENV, "-5");
93
+ try {
94
+ expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
95
+ expect(loggerMock.warn).toHaveBeenCalledTimes(1);
96
+ expect(String(loggerMock.warn.mock.calls[0]?.[0] ?? "")).toContain("-5");
97
+ } finally {
98
+ loggerMock.warn.mockClear();
99
+ vi.unstubAllEnvs();
100
+ }
101
+ });
102
+
103
+ it("[LC-7] env 未设(空串)→ 静默 undefined,零 warn(未配置不是异常,不制造噪音)", () => {
104
+ vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
105
+ try {
106
+ expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
107
+ expect(loggerMock.warn).not.toHaveBeenCalled();
108
+ } finally {
109
+ loggerMock.warn.mockClear();
110
+ vi.unstubAllEnvs();
111
+ }
112
+ });
113
+ });
@@ -422,12 +422,12 @@ describe("ModelConfigService: ctx.model plumb-through (EA-4)", () => {
422
422
  tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subagents-resolver-test-"));
423
423
  });
424
424
  afterEach(() => {
425
- fs.rmSync(tmpDir, { recursive: true, force: true });
425
+ fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
426
426
  });
427
427
 
428
428
  /** 构造已 initModel 的 ModelConfigService,ctxModel 注入缓存。 */
429
429
  function makeService(ctxModel: ModelInfo | undefined, registry: ModelRegistryLike = makeRegistry([])): ModelConfigService {
430
- const svc = new ModelConfigService({ agentDir: tmpDir });
430
+ const svc = new ModelConfigService({ agentDir: tmpDir, cwd: tmpDir });
431
431
  svc.initModel({
432
432
  modelRegistry: registry,
433
433
  sessionId: "test-session",
@@ -9,96 +9,28 @@
9
9
  // TC-3d: 子进程 execute 创建的 record.rootSessionId = env 贯穿的真 ROOT
10
10
  // TC-3e: 子进程 collectRecords 用 sessionRootId 过滤
11
11
  //
12
- // mock 策略与 recursive-visibility-baseline.test.ts 一致。
13
-
14
- import type { PassThrough } from "node:stream";
12
+ // mock 策略与 recursive-visibility-baseline.test.ts 一致(已收敛
13
+ // ./helpers/subagent-service-mocks.ts 四文件共享单源)。
15
14
 
16
15
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
17
16
 
18
- // ── mock modules ──
19
-
20
- vi.mock("node:child_process", async () => {
21
- const { EventEmitter } = await import("node:events");
22
- const { PassThrough } = await import("node:stream");
23
-
24
- class FakeChild extends EventEmitter {
25
- pid = 12345;
26
- stdout = new PassThrough();
27
- stderr = new PassThrough();
28
- killed = false;
29
- killSignal: string | undefined;
30
- kill(sig?: string): boolean {
31
- this.killed = true;
32
- this.killSignal = sig;
33
- return true;
34
- }
35
- }
36
-
37
- return {
38
- spawn: vi.fn(() => new FakeChild()),
39
- // buildEnvBlock 用 execFile 异步取 git branch:默认 err-first 兜底(catch → branch="")
40
- execFile: vi.fn(
41
- (
42
- _cmd: string,
43
- _args: readonly string[],
44
- _opts: unknown,
45
- cb: (err: Error | null, stdout?: string, stderr?: string) => void,
46
- ) => cb(new Error("execFile not configured in this test")),
47
- ),
48
- };
49
- });
17
+ import {
18
+ aliveStoreModule,
19
+ childProcessModule,
20
+ finalizedMarkerModule,
21
+ fsSyncModule,
22
+ manifestStoreModule,
23
+ tempPromptModule,
24
+ } from "./helpers/subagent-service-mocks.ts";
50
25
 
51
- vi.mock("node:fs", async () => {
52
- const actual = await import("node:fs");
53
- return {
54
- default: {
55
- ...actual,
56
- mkdirSync: vi.fn(),
57
- existsSync: vi.fn(() => false),
58
- appendFileSync: vi.fn(),
59
- writeFileSync: vi.fn(),
60
- readdirSync: vi.fn(() => []),
61
- },
62
- mkdirSync: vi.fn(),
63
- existsSync: vi.fn(() => false),
64
- appendFileSync: vi.fn(),
65
- writeFileSync: vi.fn(),
66
- readdirSync: vi.fn(() => []),
67
- promises: actual.promises,
68
- };
69
- });
70
-
71
- vi.mock("../alive-store.ts", async (importOriginal) => {
72
- const actual = await importOriginal<typeof import("../alive-store.ts")>();
73
- return {
74
- ...actual,
75
- writeAliveMarker: vi.fn(),
76
- removeAliveMarker: vi.fn(),
77
- };
78
- });
79
-
80
- vi.mock("../finalized-marker.ts", () => ({
81
- writeFinalized: vi.fn(),
82
- readFinalized: vi.fn(() => false),
83
- }));
84
-
85
- vi.mock("../manifest-store.ts", () => {
86
- class FakeManifestStore {
87
- writeManifest = vi.fn(async () => {});
88
- readManifest = vi.fn(async () => null);
89
- listAllSync = vi.fn(() => []);
90
- recoverTmpFiles = vi.fn(async () => []);
91
- }
92
- return { ManifestStore: vi.fn(function (_recordsDir: string) { return new FakeManifestStore(); }) };
93
- });
26
+ // ── mock modules ──
94
27
 
95
- vi.mock("../temp-prompt.ts", () => ({
96
- writePromptToTempFile: vi.fn(async (agent: string) => {
97
- const safeName = agent.replace(/[^\w.-]+/g, "_");
98
- return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
99
- }),
100
- cleanupTempPrompt: vi.fn(async () => {}),
101
- }));
28
+ vi.mock("node:child_process", () => childProcessModule());
29
+ vi.mock("node:fs", async (importOriginal) => fsSyncModule(await importOriginal<typeof import("node:fs")>()));
30
+ vi.mock("../alive-store.ts", async (importOriginal) => aliveStoreModule(await importOriginal<typeof import("../alive-store.ts")>()));
31
+ vi.mock("../finalized-marker.ts", () => finalizedMarkerModule());
32
+ vi.mock("../manifest-store.ts", () => manifestStoreModule());
33
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => tempPromptModule());
102
34
 
103
35
  import { spawn } from "node:child_process";
104
36
 
@@ -119,42 +51,10 @@ const ENV_ROOT_CWD = "PI_SUBAGENT_ROOT_CWD";
119
51
 
120
52
  // ── helpers ──
121
53
 
122
- interface FakeChild {
123
- pid: number;
124
- stdout: PassThrough;
125
- stderr: PassThrough;
126
- killed: boolean;
127
- killSignal: string | undefined;
128
- kill(sig?: string): boolean;
129
- emit(event: string, ...args: unknown[]): boolean;
130
- }
131
-
132
- function lastSpawnedChild(): FakeChild {
133
- const result = mockSpawn.mock.results.at(-1);
134
- if (!result) throw new Error("spawn was not called yet");
135
- return result.value as FakeChild;
136
- }
137
-
138
54
  function getLastSpawnEnv(): Record<string, string | undefined> {
139
55
  return (mockSpawn.mock.calls.at(-1)?.[2]?.env as Record<string, string | undefined>) ?? {};
140
56
  }
141
57
 
142
- function sessionHeader(id = "env-prop-session"): Record<string, unknown> {
143
- return { type: "session", id, timestamp: "2026-08-11T00-00-00-000Z", cwd: "/tmp/test" };
144
- }
145
-
146
- function emitStdoutLine(child: FakeChild, obj: Record<string, unknown>): void {
147
- child.stdout.write(`${JSON.stringify(obj)}\n`);
148
- }
149
-
150
- async function driveChildToCompletion(child: FakeChild, events: Record<string, unknown>[] = []): Promise<void> {
151
- emitStdoutLine(child, sessionHeader());
152
- for (const e of events) emitStdoutLine(child, e);
153
- child.stdout.end();
154
- child.stderr.end();
155
- child.emit("close", 0);
156
- }
157
-
158
58
  function makeEmptyRegistry(): ModelRegistryLike {
159
59
  return { getAvailable: () => [], find: () => undefined, hasConfiguredAuth: () => true };
160
60
  }
@@ -167,7 +67,7 @@ const ctxModel: ModelInfo = { id: "m", name: "M", provider: "p", reasoning: fals
167
67
 
168
68
  function setupService(sessionId: string, env?: Record<string, string>): SubagentService {
169
69
  const agentDir = "/tmp/env-prop-it";
170
- const modelService = new ModelConfigService({ agentDir });
70
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
171
71
  modelService.initModel({
172
72
  modelRegistry: makeEmptyRegistry(),
173
73
  sessionId,
@@ -225,8 +125,8 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
225
125
  const sessionRootId = Reflect.get(service, "sessionRootId") as string;
226
126
  expect(sessionRootId).toBe("root-main-session");
227
127
 
228
- // B execCtxBaseline 记录了 B 自己的身份(recordId + depth
229
- const baseline = Reflect.get(service, "execCtxBaseline") as { recordId: string; depth: number };
128
+ // B 的嵌套基线记录了 B 自己的身份(recordId + depth;[D3-⑤] execNesting 公共层)
129
+ const baseline = (Reflect.get(service, "execNesting") as { baseline(): { recordId: string; depth: number } | null }).baseline()!;
230
130
  expect(baseline.recordId).toBe("sa-a-record");
231
131
  expect(baseline.depth).toBe(1);
232
132
  });
@@ -241,6 +141,7 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
241
141
  // execute 创建 record(模拟 B 内创建 C)
242
142
  const handle = await service.execute({
243
143
  task: "nested task",
144
+ slug: "test",
244
145
  ctxModel,
245
146
  });
246
147
  const store = Reflect.get(service, "store") as RecordStore;
@@ -268,6 +169,7 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
268
169
  agent: "worker",
269
170
  model: "test/model",
270
171
  mode: "background",
172
+ slug: "t",
271
173
  task: "deep nested",
272
174
  startedAt: Date.now(),
273
175
  rootSessionId: "root-main-session",
@@ -277,7 +179,7 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
277
179
  store.register(recordC);
278
180
 
279
181
  // collectRecords(不带 filter)用 this.sessionRootId 过滤
280
- const viaService = service.collectRecords(100);
182
+ const viaService = service.queries.collectRecords(100);
281
183
  const ids = viaService.map((r) => r.id);
282
184
 
283
185
  // C 的 record 归 ROOT(rootSessionId=root-main-session),能被子进程查到
@@ -195,6 +195,7 @@ describe("TC-2: collectRecords 返回全树(含深层)", () => {
195
195
  agent: "worker",
196
196
  model: "test/model",
197
197
  mode: "background",
198
+ slug: "t",
198
199
  task: "task C",
199
200
  startedAt: 3000,
200
201
  rootSessionId: ROOT_SESSION,
@@ -286,6 +287,7 @@ describe("TC-2: collectRecords 返回全树(含深层)", () => {
286
287
  agent: "worker",
287
288
  model: "test/model",
288
289
  mode: "background",
290
+ slug: "t",
289
291
  task: "task C",
290
292
  startedAt: 3000,
291
293
  rootSessionId: ROOT_SESSION,