@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,220 @@
1
+ // src/execution/__tests__/descendant-sweep-guards.test.ts
2
+ //
3
+ // [S-17c / T2-②] sweepDescendantsOfSession 守卫分支补充(与 descendant-sweep.test.ts
4
+ // 互补,不改动其既有用例)。
5
+ //
6
+ // 覆盖该文件未锁定的分支:
7
+ // - register entry 缺 sessionId(TODO T5 marker fallback 锚点)→ skipped 留痕不杀;
8
+ // - readProcessCmdline 两条失败边:ps 非零退出(status≠0/error)与 spawnSync 同步
9
+ // 抛错 → cmdline undefined → "cmdline probe failed" 保守跳过;
10
+ // - killPidWithEscalation 的 SIGTERM 发送失败(EPERM 等权限/垂死窗口)→ debug
11
+ // 留痕不武装升级(30s 后无 SIGKILL);
12
+ // - SIGKILL 升级窗口内目标自行退出(ESRCH)→ 吞掉不崩(目标已达成)。
13
+ //
14
+ // 真实 fs(临时目录)+ mock spawnSync / process.kill / logger——同 descendant-sweep
15
+ // 布局,新增 logger mock 用于 SIGTERM 失败留痕断言。
16
+
17
+ import { spawnSync } from "node:child_process";
18
+ import * as fs from "node:fs";
19
+ import * as os from "node:os";
20
+ import * as path from "node:path";
21
+
22
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
+
24
+ const { loggerMock } = vi.hoisted(() => ({
25
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
26
+ }));
27
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
28
+
29
+ // 只 mock spawnSync(ps 探测受控);spawn 等其余实现保留 actual(本文件不经 runSpawn)。
30
+ vi.mock("node:child_process", async () => {
31
+ const actual = await import("node:child_process");
32
+ return {
33
+ ...actual,
34
+ spawnSync: vi.fn(),
35
+ };
36
+ });
37
+
38
+ import { sweepDescendantsOfSession } from "../engine/engines/pi/session-runner.ts";
39
+ import { clearPendingCursors } from "../session-pending.ts";
40
+ import { writeAliveMarker } from "../alive-store.ts";
41
+
42
+ const mockSpawnSync = vi.mocked(spawnSync);
43
+
44
+ const ALIVE_PID = 424_242;
45
+
46
+ /** process.kill 受控:signal=0 探活与真实信号分别处置,信号调用记录到 kills。 */
47
+ const kills: Array<{ pid: number; signal: string | number }> = [];
48
+
49
+ function entryLine(customType: string, data: Record<string, unknown>): string {
50
+ return `${JSON.stringify({
51
+ type: "custom",
52
+ customType,
53
+ data,
54
+ timestamp: "2026-09-01T00:00:00.000Z",
55
+ id: `e-${Math.random().toString(36).slice(2)}`,
56
+ })}\n`;
57
+ }
58
+
59
+ function registerData(fields: Record<string, unknown>): Record<string, unknown> {
60
+ return { id: "bg-1", type: "session", name: "desc", status: "active", registeredAt: 1, ...fields };
61
+ }
62
+
63
+ /** ps -p <pid> -o command= 的完整 SpawnSyncReturns 形态(字段补齐省 as 链)。 */
64
+ function psResult(stdout: string, status = 0): ReturnType<typeof spawnSync> {
65
+ return {
66
+ pid: 0,
67
+ output: [null, stdout, ""],
68
+ stdout,
69
+ stderr: "",
70
+ status,
71
+ signal: null,
72
+ } as ReturnType<typeof spawnSync>;
73
+ }
74
+
75
+ let sessionDir = "";
76
+
77
+ beforeEach(() => {
78
+ vi.clearAllMocks();
79
+ clearPendingCursors();
80
+ kills.length = 0;
81
+ sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), "desc-sweep-guard-"));
82
+ vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
83
+ if (signal === undefined || signal === 0) return true; // 探活恒真(目标在窗口内存活)
84
+ kills.push({ pid, signal });
85
+ return true;
86
+ }) as typeof process.kill);
87
+ });
88
+
89
+ afterEach(() => {
90
+ vi.restoreAllMocks();
91
+ clearPendingCursors();
92
+ fs.rmSync(sessionDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
93
+ });
94
+
95
+ /** 层主 + 一个带 .alive marker 的直接后代(标准单层树)。 */
96
+ function writeSingleLevelTree(): { rootFile: string; childFile: string } {
97
+ const rootFile = path.join(sessionDir, "20260901T000000-000_root-sess.jsonl");
98
+ const childFile = path.join(sessionDir, "20260901T000001-000_child-sess.jsonl");
99
+ fs.writeFileSync(rootFile, entryLine("pending:register", registerData({ sessionId: "child-sess" })));
100
+ fs.writeFileSync(childFile, "other entry line\n");
101
+ writeAliveMarker(childFile, { pid: ALIVE_PID, id: "child-sess", startedAt: Date.now() });
102
+ return { rootFile, childFile };
103
+ }
104
+
105
+ /** ps 探测默认桩:存活 pid 返回 pi rpc 形态。 */
106
+ function stubCmdlineByPid(): void {
107
+ mockSpawnSync.mockImplementation((_cmd: string, args?: readonly string[]) => {
108
+ const pid = Number(args?.[1]);
109
+ return psResult(`node /usr/local/bin/pi --mode rpc --session-dir /tmp/sess-${pid}\n`);
110
+ });
111
+ }
112
+
113
+ describe("[S-17c] sweep 守卫:pending 项缺 sessionId(T5 marker fallback 锚点)", () => {
114
+ it("register entry 无 sessionId → skipped 留痕(reason 指向 T5 兜底),不杀任何进程", () => {
115
+ stubCmdlineByPid();
116
+ const rootFile = path.join(sessionDir, "20260901T000000-000_root2.jsonl");
117
+ fs.writeFileSync(rootFile, entryLine("pending:register", registerData({ sessionId: undefined })));
118
+
119
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-nosessid");
120
+
121
+ expect(r.killed).toHaveLength(0);
122
+ expect(r.skipped).toHaveLength(1);
123
+ // sessionId 字段回退为 pending 操作 id(诊断线索),reason 说明 marker 兜底锚点
124
+ expect(r.skipped[0]).toMatchObject({
125
+ sessionId: "bg-1",
126
+ reason: expect.stringContaining("no sessionId (marker-based fallback pending T5)"),
127
+ });
128
+ expect(kills).toHaveLength(0);
129
+ });
130
+ });
131
+
132
+ describe("[S-17c] sweep 守卫:readProcessCmdline 失败边(保守跳过不动手)", () => {
133
+ it.each([
134
+ [
135
+ "ps 非零退出(进程刚死,ps -p 无此进程)",
136
+ () =>
137
+ mockSpawnSync.mockImplementation(() => psResult("", 1)),
138
+ ],
139
+ [
140
+ "spawnSync 同步抛错(ps 不可用)",
141
+ () =>
142
+ mockSpawnSync.mockImplementation(() => {
143
+ throw new Error("ps binary missing");
144
+ }),
145
+ ],
146
+ ])("%s → cmdline probe failed,跳过不杀", (_label, setupProbe) => {
147
+ const { rootFile } = writeSingleLevelTree();
148
+ setupProbe();
149
+
150
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-ps-fail");
151
+
152
+ // [防误杀] 探测失败 = 无法证明是 pi rpc 形态 → 一律不动手
153
+ expect(r.killed).toHaveLength(0);
154
+ expect(r.skipped).toHaveLength(1);
155
+ expect(r.skipped[0]).toMatchObject({
156
+ pid: ALIVE_PID,
157
+ reason: expect.stringContaining("cmdline probe failed (ps unavailable)"),
158
+ });
159
+ expect(kills).toHaveLength(0);
160
+ });
161
+ });
162
+
163
+ describe("[S-17c] killPidWithEscalation:SIGTERM/ SIGKILL 失败边", () => {
164
+ it("SIGTERM 发送即失败(权限/垂死窗口 EPERM)→ debug 留痕不武装升级,30s 后无 SIGKILL", async () => {
165
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
166
+ stubCmdlineByPid();
167
+ vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
168
+ if (signal === undefined || signal === 0) return true;
169
+ if (signal === "SIGTERM") {
170
+ const err = new Error("operation not permitted") as NodeJS.ErrnoException;
171
+ err.code = "EPERM";
172
+ throw err;
173
+ }
174
+ kills.push({ pid, signal });
175
+ return true;
176
+ }) as typeof process.kill);
177
+
178
+ const { rootFile } = writeSingleLevelTree();
179
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-sigterm-fail");
180
+
181
+ // killed 列表记录的是处置意图;实际 SIGTERM 未送达(失败留痕,best-effort continue)
182
+ expect(r.killed).toEqual([ALIVE_PID]);
183
+ expect(loggerMock.debug).toHaveBeenCalledWith(
184
+ expect.stringContaining(`SIGTERM to pid ${ALIVE_PID} failed (best-effort continue)`),
185
+ );
186
+
187
+ // 无升级 timer:SIGTERM 失败后直接 return,越过 30s 窗口也不补发 SIGKILL
188
+ await vi.advanceTimersByTimeAsync(30_000);
189
+ expect(kills).toHaveLength(0);
190
+ });
191
+
192
+ it("SIGKILL 升级窗口内目标自行退出(ESRCH)→ 吞掉不崩,SIGTERM 已达即目标达成", async () => {
193
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
194
+ stubCmdlineByPid();
195
+ // 探活恒真(fire 时窗口内仍活);SIGTERM 送达成功;SIGKILL 时进程已退出(ESRCH)
196
+ vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
197
+ if (signal === undefined || signal === 0) return true;
198
+ if (signal === "SIGKILL") {
199
+ const err = new Error("No such process") as NodeJS.ErrnoException;
200
+ err.code = "ESRCH";
201
+ throw err;
202
+ }
203
+ kills.push({ pid, signal });
204
+ return true;
205
+ }) as typeof process.kill);
206
+
207
+ const { rootFile } = writeSingleLevelTree();
208
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-sigkill-esrch");
209
+
210
+ expect(r.killed).toEqual([ALIVE_PID]);
211
+ expect(kills).toEqual([{ pid: ALIVE_PID, signal: "SIGTERM" }]);
212
+
213
+ // 升级 fire:ESRCH 是预期终态(进程在窗口内自行退出),吞掉不崩不重试
214
+ await vi.advanceTimersByTimeAsync(30_000);
215
+ expect(kills).toEqual([{ pid: ALIVE_PID, signal: "SIGTERM" }]);
216
+ expect(loggerMock.debug).not.toHaveBeenCalledWith(
217
+ expect.stringContaining("SIGTERM to pid"),
218
+ );
219
+ });
220
+ });
@@ -0,0 +1,269 @@
1
+ // src/execution/__tests__/descendant-sweep.test.ts
2
+ //
3
+ // [T2-② / P-T2b 主路径] 后代级联补杀 sweep + session-pending 差集清单导出。
4
+ //
5
+ // 设计:docs/design/subagent-core-unbounded-wait-audit.md §7.2 T2-②;P-T2b 裁决
6
+ //(probe/p-t2b-report.md):pi SIGTERM 不级联孤儿后台后代(NO-CASCADE 三次稳定复现),
7
+ // 补杀为主路径。覆盖:
8
+ // - listActivePendingFromSessionFile:register−unregister 差集、sessionId 提取、
9
+ // 与 count 口径共享增量游标(交错调用不错位)。
10
+ // - sweepDescendantsOfSession:迭代展开至叶、pid 存活校验、cmdline(pi/--mode rpc)
11
+ // 防误杀校验、反查失败 skip(T5 marker fallback TODO 锚点)。
12
+ // - looksLikePiRpcProcess:命中 / 拒绝形态矩阵。
13
+ // - killPidWithEscalation:SIGTERM 后 30s 仍活升级 SIGKILL(fake timers)。
14
+ //
15
+ // 真实 fs(临时目录)+ mock process.kill / spawnSync——session-pending 增量游标与
16
+ // findSessionFileByHeaderId 的文件形态语义被真实执行。
17
+
18
+ import { spawnSync } from "node:child_process";
19
+ import * as fs from "node:fs";
20
+ import * as os from "node:os";
21
+ import * as path from "node:path";
22
+
23
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
24
+
25
+ // 只 mock spawnSync(ps 探测受控);spawn 等其余实现保留 actual(本文件不经 runSpawn)。
26
+ vi.mock("node:child_process", async () => {
27
+ const actual = await import("node:child_process");
28
+ return {
29
+ ...actual,
30
+ spawnSync: vi.fn(),
31
+ };
32
+ });
33
+
34
+ import {
35
+ looksLikePiRpcProcess,
36
+ sweepDescendantsOfSession,
37
+ } from "../engine/engines/pi/session-runner.ts";
38
+ import {
39
+ clearPendingCursors,
40
+ listActivePendingFromSessionFile,
41
+ readActivePendingFromSessionFile,
42
+ } from "../session-pending.ts";
43
+ import { writeAliveMarker } from "../alive-store.ts";
44
+
45
+ const mockSpawnSync = vi.mocked(spawnSync);
46
+
47
+ const ALIVE_PID_CHILD = 424_242;
48
+ const ALIVE_PID_GRAND = 424_243;
49
+ const DEAD_PID = 999_999;
50
+
51
+ /** process.kill 受控:signal=0 探活(死 pid 抛 ESRCH),其余记录到 kills。 */
52
+ const kills: Array<{ pid: number; signal: string | number }> = [];
53
+
54
+ function entryLine(customType: string, data: Record<string, unknown>): string {
55
+ return `${JSON.stringify({
56
+ type: "custom",
57
+ customType,
58
+ data,
59
+ timestamp: "2026-09-01T00:00:00.000Z",
60
+ id: `e-${Math.random().toString(36).slice(2)}`,
61
+ })}\n`;
62
+ }
63
+
64
+ function registerData(id: string, sessionId: string): Record<string, unknown> {
65
+ return { id, type: "session", name: `desc-${id}`, status: "active", registeredAt: 1, expiresAt: undefined, sessionId };
66
+ }
67
+
68
+ let sessionDir = "";
69
+
70
+ beforeEach(() => {
71
+ clearPendingCursors();
72
+ kills.length = 0;
73
+ mockSpawnSync.mockReset();
74
+ sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), "desc-sweep-"));
75
+ vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
76
+ if ((signal ?? 0) === 0) {
77
+ if (pid === DEAD_PID) {
78
+ const err = new Error("No such process") as NodeJS.ErrnoException;
79
+ err.code = "ESRCH";
80
+ throw err;
81
+ }
82
+ return true;
83
+ }
84
+ kills.push({ pid, signal: signal as string | number });
85
+ return true;
86
+ }) as typeof process.kill);
87
+ });
88
+
89
+ afterEach(() => {
90
+ vi.restoreAllMocks();
91
+ clearPendingCursors();
92
+ fs.rmSync(sessionDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
93
+ });
94
+
95
+ /** 标准三层树:root(层主)→ child-sess → grand-sess(叶)。 */
96
+ function writeThreeLevelTree(): { rootFile: string; childFile: string; grandFile: string } {
97
+ const rootFile = path.join(sessionDir, "20260901T000000-000_root-sess.jsonl");
98
+ const childFile = path.join(sessionDir, "20260901T000001-000_child-sess.jsonl");
99
+ const grandFile = path.join(sessionDir, "20260901T000002-000_grand-sess.jsonl");
100
+ // 层主:注册了 child(未 unregister)
101
+ fs.writeFileSync(rootFile, entryLine("pending:register", registerData("bg-1", "child-sess")));
102
+ // child:注册了 grand(未 unregister)——sweep 须迭代至叶
103
+ fs.writeFileSync(childFile, entryLine("pending:register", registerData("bg-2", "grand-sess")));
104
+ // grand:叶,无 pending
105
+ fs.writeFileSync(grandFile, "other entry line\n");
106
+ // .alive sidecar:pid 线索来源
107
+ writeAliveMarker(childFile, { pid: ALIVE_PID_CHILD, id: "child-sess", startedAt: Date.now() });
108
+ writeAliveMarker(grandFile, { pid: ALIVE_PID_GRAND, id: "grand-sess", startedAt: Date.now() });
109
+ return { rootFile, childFile, grandFile };
110
+ }
111
+
112
+ /** ps -p <pid> -o command= 的完整 SpawnSyncReturns 形态(测试桩用,字段补齐省 as 链)。 */
113
+ function psResult(stdout: string): ReturnType<typeof spawnSync> {
114
+ return {
115
+ pid: 0,
116
+ output: [null, stdout, ""],
117
+ stdout,
118
+ stderr: "",
119
+ status: 0,
120
+ signal: null,
121
+ } as ReturnType<typeof spawnSync>;
122
+ }
123
+
124
+ /** ps 探测默认桩:全部存活 pid 返回 pi rpc 形态。 */
125
+ function stubCmdlineByPid(): void {
126
+ mockSpawnSync.mockImplementation((_cmd: string, args?: readonly string[]) => {
127
+ const pid = Number(args?.[1]);
128
+ return psResult(`node /usr/local/bin/pi --mode rpc --no-extensions --session-dir /tmp/sess-${pid}\n`);
129
+ });
130
+ }
131
+
132
+ describe("[T2-②] listActivePendingFromSessionFile(差集清单)", () => {
133
+ it("register−unregister 差集 + sessionId/type 提取", () => {
134
+ const f = path.join(sessionDir, "a.jsonl");
135
+ fs.writeFileSync(
136
+ f,
137
+ entryLine("pending:register", registerData("bg-1", "sess-1")) +
138
+ entryLine("pending:register", registerData("bg-2", "sess-2")) +
139
+ entryLine("pending:unregister", { id: "bg-1" }),
140
+ );
141
+ const r = listActivePendingFromSessionFile(f);
142
+ expect(r.error).toBeUndefined();
143
+ expect(r.items).toHaveLength(1);
144
+ expect(r.items[0]).toMatchObject({ id: "bg-2", sessionId: "sess-2", type: "session" });
145
+ });
146
+
147
+ it("count 与 list 交错调用共享增量游标(结果一致、不丢条目)", () => {
148
+ const f = path.join(sessionDir, "b.jsonl");
149
+ fs.writeFileSync(f, entryLine("pending:register", registerData("bg-1", "sess-1")));
150
+ // 先 count(建立游标),后 append 再 list——增量读入新行
151
+ const c1 = readActivePendingFromSessionFile(f);
152
+ expect(c1.count).toBe(0); // 端口缺席时缺省 0(core notify-ports 默认实现)
153
+ fs.appendFileSync(f, entryLine("pending:register", registerData("bg-2", "sess-2")));
154
+ const r = listActivePendingFromSessionFile(f);
155
+ expect(r.items.map((i) => i.id).sort()).toEqual(["bg-1", "bg-2"]);
156
+ });
157
+
158
+ it("sessionFile undefined / 不可读 → error 面空清单", () => {
159
+ expect(listActivePendingFromSessionFile(undefined).error).toBeDefined();
160
+ expect(listActivePendingFromSessionFile(path.join(sessionDir, "missing.jsonl")).error).toBeDefined();
161
+ });
162
+ });
163
+
164
+ describe("[T2-②] sweepDescendantsOfSession(迭代补杀)", () => {
165
+ it("层主→后代→孙迭代展开至叶,双校验通过者逐个 SIGTERM", () => {
166
+ const { rootFile } = writeThreeLevelTree();
167
+ stubCmdlineByPid();
168
+
169
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "keep-alive watchdog");
170
+ expect(r.killed.sort()).toEqual([ALIVE_PID_CHILD, ALIVE_PID_GRAND].sort());
171
+ expect(r.skipped).toHaveLength(0);
172
+ const sigterms = kills.filter((k) => k.signal === "SIGTERM").map((k) => k.pid);
173
+ expect(sigterms.sort()).toEqual([ALIVE_PID_CHILD, ALIVE_PID_GRAND].sort());
174
+ });
175
+
176
+ it("SIGTERM 后 30s 仍存活 → 升级 SIGKILL(fake timers)", async () => {
177
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
178
+ const { rootFile } = writeThreeLevelTree();
179
+ stubCmdlineByPid();
180
+
181
+ sweepDescendantsOfSession(rootFile, sessionDir, "test-escalation");
182
+ expect(kills.filter((k) => k.signal === "SIGKILL")).toHaveLength(0);
183
+
184
+ await vi.advanceTimersByTimeAsync(30_000);
185
+ const sigkills = kills.filter((k) => k.signal === "SIGKILL").map((k) => k.pid);
186
+ expect(sigkills.sort()).toEqual([ALIVE_PID_CHILD, ALIVE_PID_GRAND].sort());
187
+ });
188
+
189
+ it("cmdline 非 pi --mode rpc → pid 复用守卫拦截,不杀(但迭代继续展开其后代)", () => {
190
+ const { rootFile, childFile, grandFile } = writeThreeLevelTree();
191
+ // 伪造无关进程占住 child 的 pid;grand 正常
192
+ mockSpawnSync.mockImplementation((_cmd: string, args?: readonly string[]) => {
193
+ const pid = Number(args?.[1]);
194
+ return psResult(
195
+ pid === ALIVE_PID_CHILD ? "vim notes.txt\n" : "pi --mode rpc\n",
196
+ );
197
+ });
198
+ void childFile;
199
+ void grandFile;
200
+
201
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-guard");
202
+ expect(r.killed).toEqual([ALIVE_PID_GRAND]); // child 被拦,grand 不受影响
203
+ expect(r.skipped).toHaveLength(1);
204
+ expect(r.skipped[0]).toMatchObject({ sessionId: "child-sess", pid: ALIVE_PID_CHILD });
205
+ expect(r.skipped[0].reason).toContain("pid reuse guard");
206
+ expect(kills.map((k) => k.pid)).toEqual([ALIVE_PID_GRAND]);
207
+ });
208
+
209
+ it("存活校验不过(pid 已死)→ skip 不杀", () => {
210
+ const { rootFile, childFile } = writeThreeLevelTree();
211
+ // child 的 marker 指向死 pid
212
+ writeAliveMarker(childFile, { pid: DEAD_PID, id: "child-sess", startedAt: Date.now() });
213
+ stubCmdlineByPid();
214
+
215
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-dead");
216
+ expect(r.killed).toEqual([ALIVE_PID_GRAND]);
217
+ expect(r.skipped[0]).toMatchObject({ pid: DEAD_PID, reason: expect.stringContaining("not alive") });
218
+ });
219
+
220
+ it("sessionId 反查失败 / 无 alive marker → skip 留 T5 锚点原因,不中断其余分支", () => {
221
+ const rootFile = path.join(sessionDir, "20260901T000010-000_root2.jsonl");
222
+ const childFile = path.join(sessionDir, "20260901T000011-000_naked-sess.jsonl");
223
+ // 两条 register:missing-sess 反查不到;naked-sess 有文件但无 .alive marker
224
+ fs.writeFileSync(
225
+ rootFile,
226
+ entryLine("pending:register", registerData("bg-m", "missing-sess")) +
227
+ entryLine("pending:register", { id: "bg-n", type: "session", name: "naked", status: "active", registeredAt: 1, expiresAt: undefined, sessionId: "naked-sess" }),
228
+ );
229
+ fs.writeFileSync(childFile, "");
230
+
231
+ const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-skip");
232
+ expect(r.killed).toHaveLength(0);
233
+ expect(r.skipped).toHaveLength(2);
234
+ const reasons = r.skipped.map((s) => s.reason).join(" | ");
235
+ expect(reasons).toContain("not found in sessionDir");
236
+ expect(reasons).toContain("pending T5");
237
+ expect(reasons).toContain("no alive marker");
238
+ });
239
+
240
+ it("rootSessionFile undefined → 空 result(无层主快照不扫)", () => {
241
+ stubCmdlineByPid();
242
+ const r = sweepDescendantsOfSession(undefined, sessionDir, "test-empty");
243
+ expect(r.killed).toHaveLength(0);
244
+ expect(r.skipped).toHaveLength(0);
245
+ expect(kills).toHaveLength(0);
246
+ });
247
+ });
248
+
249
+ describe("[T2-②] looksLikePiRpcProcess(pid 复用防误杀校验)", () => {
250
+ it.each([
251
+ ["pi --mode rpc --no-extensions --session-dir /tmp/x", true],
252
+ ["node /usr/local/bin/pi --mode rpc --session-dir /tmp/x", true],
253
+ ["/opt/pi/bin/pi --mode=rpc", true],
254
+ ["pi.js --mode rpc", true],
255
+ ])("命中 pi rpc 形态: %s", (cmdline, expected) => {
256
+ expect(looksLikePiRpcProcess(cmdline)).toBe(expected);
257
+ });
258
+
259
+ it.each([
260
+ ["vim notes.txt"],
261
+ ["nginx: master process"],
262
+ ["tail -f /dev/null"],
263
+ ["node server.js --mode rpc"], // 有 --mode rpc 但命令不是 pi
264
+ ["pi --mode json"], // pi 但非 rpc
265
+ ["api --mode rpc"], // 含 "pi" 子串但非 pi 词形
266
+ ])("拒绝无关形态: %s", (cmdline) => {
267
+ expect(looksLikePiRpcProcess(cmdline)).toBe(false);
268
+ });
269
+ });