@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,1017 @@
1
+ // src/execution/__tests__/subagent-actions-core.test.ts
2
+ //
3
+ // ⛔4 行为快照等值测试(sink 设计 docs/design/subagent-core-sink-design.md §5.4 ⛔4 /
4
+ // impl-plan u-core-actions):六 handler(start/list/cancel/message/close/fork-from)
5
+ // 的校验、守卫链、归属判定、终态映射,迁移前后行为逐项一致。
6
+ //
7
+ // 期望值形态:**硬编码自 pi-sw 现实现(extensions/universal/subagent-workflow
8
+ // src/interface/subagent-actions.ts)迁移前实测输出**——临时探针以 stub SubagentService
9
+ // 驱动 pi-sw 六 handler,dump 实际 JSON(含错误文案逐字 / execute/deliver/close 调用
10
+ // 参数),本文件将输出固化为字面量。core 测试不 import pi-sw 源码(core vitest 红线:
11
+ // pi-coding-agent 零运行时触点);pi 侧收缩改造(u-sw-actions)后以同款探针复测即得
12
+ // 「迁移前后逐项一致」的可证伪断言。
13
+ //
14
+ // stub SubagentService 仅实现 handler 触达的方法子集(形态同 pi-sw tool-action.test.ts),
15
+ // 领域内核的分支逻辑由 stub 返回值驱动——真实 service 行为归属 subagent-service 各自测试。
16
+ //
17
+ // duration 确定性:recordToListItem 对无 endedAt 的 running record 经 computeElapsedSeconds
18
+ // 读 Date.now()——本文件用 fake timers 固定时钟(now = 探针首跑实值 1788189209000),
19
+ // 使快照期望值与实测值逐字可比。
20
+
21
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
22
+
23
+ import {
24
+ BG_MESSAGE,
25
+ DEFAULT_LIST_LIMIT,
26
+ FORK_FROM_DEFAULT_PROMPT,
27
+ MAX_LIST_LIMIT,
28
+ NOTIFY_CONTRACT,
29
+ cancelHandler,
30
+ closeHandler,
31
+ endedMessageGuard,
32
+ forkFromHandler,
33
+ listHandler,
34
+ mapExternalState,
35
+ messageHandler,
36
+ recordToListItem,
37
+ startHandler,
38
+ wrapForkFromPrompt,
39
+ } from "../subagent-actions-core.ts";
40
+ import { ResurrectDeniedError } from "../types.ts";
41
+ import type {
42
+ ExecutionHandle,
43
+ ExecutionRecord,
44
+ SubagentRecord,
45
+ SubagentToolDetails,
46
+ } from "../types.ts";
47
+ import type { SubagentService } from "../subagent-service.ts";
48
+
49
+ // ── 时钟固定(duration 快照确定性,见文件头)──
50
+ const FROZEN_NOW = 1788189209000;
51
+
52
+ beforeAll(() => {
53
+ vi.useFakeTimers({ now: FROZEN_NOW });
54
+ });
55
+ afterAll(() => {
56
+ vi.useRealTimers();
57
+ });
58
+
59
+ // ── stub 工厂(与迁移前探针同形态,期望值由此驱动产生)──
60
+
61
+ function makeDetails(over: Partial<SubagentToolDetails> = {}): SubagentToolDetails {
62
+ return {
63
+ status: "running",
64
+ mode: "background",
65
+ agent: "/home/u/agents/worker.md",
66
+ model: "prov/m1",
67
+ thinkingLevel: undefined,
68
+ slug: "src-slug",
69
+ turns: 1,
70
+ totalTokens: 10,
71
+ elapsedSeconds: 1,
72
+ eventLog: [],
73
+ displayItems: [],
74
+ result: undefined,
75
+ error: undefined,
76
+ ...over,
77
+ } as SubagentToolDetails;
78
+ }
79
+
80
+ function makeExecRecord(over: Partial<ExecutionRecord> = {}): ExecutionRecord {
81
+ return {
82
+ id: "bg-1",
83
+ agent: "/home/u/agents/reader.md",
84
+ model: "prov/m1",
85
+ thinkingLevel: undefined,
86
+ mode: "background",
87
+ task: "t",
88
+ slug: "src-slug",
89
+ startedAt: 1000,
90
+ rootSessionId: "root-A",
91
+ parentRecordId: undefined,
92
+ depth: 0,
93
+ status: "running",
94
+ turns: [],
95
+ turnCount: 1,
96
+ totalTokens: 10,
97
+ lastError: undefined,
98
+ endedAt: undefined,
99
+ result: undefined,
100
+ error: undefined,
101
+ agentResult: undefined,
102
+ controller: undefined,
103
+ chatMode: true,
104
+ sessionFile: "sess-1.jsonl",
105
+ ...over,
106
+ } as ExecutionRecord;
107
+ }
108
+
109
+ function makeRec(over: Partial<SubagentRecord> = {}): SubagentRecord {
110
+ return {
111
+ id: "bg-1",
112
+ agent: "/home/u/agents/reader.md",
113
+ task: "t",
114
+ slug: "src-slug",
115
+ status: "running",
116
+ mode: "background",
117
+ startedAt: 1000,
118
+ rootSessionId: "root-A",
119
+ parentRecordId: undefined,
120
+ depth: 0,
121
+ endedAt: undefined,
122
+ turns: 1,
123
+ totalTokens: 42,
124
+ model: "prov/m1",
125
+ thinkingLevel: undefined,
126
+ eventLog: [],
127
+ displayItems: [],
128
+ sessionFile: "sess-1.jsonl",
129
+ chatMode: true,
130
+ ...over,
131
+ } as SubagentRecord;
132
+ }
133
+
134
+ function makeService(over: Record<string, unknown> = {}): SubagentService {
135
+ // 聚合面形态对齐 subagent-service.ts D4:queries(读模型)与 chatActions(对话
136
+ // action 面)分组挂载,mock 按同构 key 装配(over 仍传平铺 key,最小化快照用例改动)。
137
+ const m = {
138
+ execute: vi.fn(),
139
+ cancel: vi.fn(() => false),
140
+ findRecord: vi.fn(() => undefined),
141
+ collectRecords: vi.fn(() => [] as SubagentRecord[]),
142
+ getFullRecord: vi.fn(() => undefined as SubagentRecord | undefined),
143
+ lookupRecordAnyState: vi.fn(() => undefined as SubagentRecord | undefined),
144
+ getRecordForAction: vi.fn(),
145
+ closeSubagent: vi.fn(),
146
+ deliverChatMessage: vi.fn(),
147
+ ...over,
148
+ };
149
+ return {
150
+ execute: m.execute,
151
+ cancel: m.cancel,
152
+ queries: {
153
+ findRecord: m.findRecord,
154
+ lookupRecordAnyState: m.lookupRecordAnyState,
155
+ collectRecords: m.collectRecords,
156
+ getFullRecord: m.getFullRecord,
157
+ onChange: vi.fn(() => () => {}),
158
+ },
159
+ chatActions: {
160
+ getRecordForAction: m.getRecordForAction,
161
+ closeSubagent: m.closeSubagent,
162
+ deliverChatMessage: m.deliverChatMessage,
163
+ },
164
+ } as unknown as SubagentService;
165
+ }
166
+
167
+ /** 实测 reject 输出的捕获形态:{ errorName, message }。 */
168
+ async function errOf(fn: () => unknown): Promise<{ errorName: string; message: string }> {
169
+ try {
170
+ await fn();
171
+ } catch (err) {
172
+ return {
173
+ errorName: err instanceof Error ? err.name : typeof err,
174
+ message: err instanceof Error ? err.message : String(err),
175
+ };
176
+ }
177
+ throw new Error("expected handler rejection, got resolve");
178
+ }
179
+
180
+ const CTX_MODEL = { id: "m1", name: "Model One", provider: "prov", reasoning: true };
181
+
182
+ // ============================================================
183
+ // 终态映射 / list 投影
184
+ // ============================================================
185
+ describe("⛔4 mapExternalState / recordToListItem(终态映射,快照 = pi-sw 实测)", () => {
186
+ it("ExecutionStatus → ExternalState 两态映射", () => {
187
+ expect(mapExternalState("running")).toBe("active");
188
+ expect(mapExternalState("closed")).toBe("ended");
189
+ });
190
+
191
+ it("running record → item(displayAgentName 短名 + duration 实时 + resumable)", () => {
192
+ expect(
193
+ recordToListItem(
194
+ makeRec({
195
+ id: "bg-r1",
196
+ agent: "/home/u/agents/reader.md",
197
+ slug: "read-doc",
198
+ status: "running",
199
+ startedAt: 1000,
200
+ endedAt: undefined,
201
+ totalTokens: 42,
202
+ sessionFile: "sess-1.jsonl",
203
+ parentRecordId: "bg-0",
204
+ resumable: true,
205
+ }),
206
+ ),
207
+ ).toEqual({
208
+ subagentId: "bg-r1",
209
+ agent: "reader",
210
+ slug: "read-doc",
211
+ state: "active",
212
+ status: "running",
213
+ mode: "background",
214
+ duration: 1788189208, // floor((FROZEN_NOW - 1000) / 1000),与探针首跑实值一致
215
+ model: "prov/m1",
216
+ totalTokens: 42,
217
+ sessionFile: "sess-1.jsonl",
218
+ parent: "bg-0",
219
+ resumable: true,
220
+ });
221
+ });
222
+
223
+ it("closed record:outcome 一等直读 + duration = (endedAt - startedAt)/1000", () => {
224
+ expect(
225
+ recordToListItem(
226
+ makeRec({
227
+ id: "bg-c1",
228
+ status: "closed",
229
+ closedReason: "gc",
230
+ outcome: "completed",
231
+ startedAt: 1000,
232
+ endedAt: 9000,
233
+ resumable: false,
234
+ }),
235
+ ),
236
+ ).toEqual({
237
+ subagentId: "bg-c1",
238
+ agent: "reader",
239
+ slug: "src-slug",
240
+ state: "ended",
241
+ status: "closed",
242
+ mode: "background",
243
+ duration: 8,
244
+ model: "prov/m1",
245
+ totalTokens: 42,
246
+ sessionFile: "sess-1.jsonl",
247
+ parent: undefined,
248
+ resumable: false,
249
+ outcome: "completed",
250
+ });
251
+ });
252
+
253
+ it("closed 存量(无 outcome 字段)→ deriveOutcome 兜底派生四形态", () => {
254
+ const base = { id: "bg-c2", status: "closed" as const, startedAt: 1000, endedAt: 3000 };
255
+ // gc + 无 error → completed
256
+ expect(recordToListItem(makeRec({ ...base, closedReason: "gc" })).outcome).toBe("completed");
257
+ // cancelled → cancelled
258
+ expect(
259
+ recordToListItem(makeRec({ ...base, id: "bg-c3", closedReason: "cancelled" })).outcome,
260
+ ).toBe("cancelled");
261
+ // gc + error → failed
262
+ expect(
263
+ recordToListItem(makeRec({ ...base, id: "bg-c4", closedReason: "gc", error: "boom" })).outcome,
264
+ ).toBe("failed");
265
+ // disconnected + 无 error → completed(派生语义,非 cancelled——快照实测)
266
+ expect(
267
+ recordToListItem(makeRec({ ...base, id: "bg-c5", closedReason: "disconnected" })).outcome,
268
+ ).toBe("completed");
269
+ });
270
+ });
271
+
272
+ // ============================================================
273
+ // startHandler
274
+ // ============================================================
275
+ describe("⛔4 startHandler(校验 + 启动,快照 = pi-sw 实测)", () => {
276
+ it("缺 input / task 空白 / slug 缺失或空白 / slug 超长 → 逐字错误文案", async () => {
277
+ expect(await errOf(() => startHandler(makeService(), undefined, undefined))).toEqual({
278
+ errorName: "Error",
279
+ message:
280
+ "action:'start' requires task and slug (top-level fields). " +
281
+ 'Correct: {"action":"start","task":"<your task>","slug":"<kebab-case>"}',
282
+ });
283
+ expect(await errOf(() => startHandler(makeService(), { task: " ", slug: "x" }, undefined))).toEqual({
284
+ errorName: "Error",
285
+ message:
286
+ "task is required for action:'start' (top-level field, must not be whitespace-only). " +
287
+ 'Correct: {"action":"start","task":"...","slug":"..."}',
288
+ });
289
+ expect(await errOf(() => startHandler(makeService(), { task: "ok" }, undefined))).toEqual({
290
+ errorName: "Error",
291
+ message:
292
+ "slug is required for action:'start' (top-level field, must not be whitespace-only). " +
293
+ 'Correct: {"action":"start","task":"...","slug":"<kebab-case>"}',
294
+ });
295
+ expect(await errOf(() => startHandler(makeService(), { task: "ok", slug: " " }, undefined))).toEqual({
296
+ errorName: "Error",
297
+ message:
298
+ "slug is required for action:'start' (top-level field, must not be whitespace-only). " +
299
+ 'Correct: {"action":"start","task":"...","slug":"<kebab-case>"}',
300
+ });
301
+ expect(
302
+ await errOf(() => startHandler(makeService(), { task: "ok", slug: "a".repeat(36) }, undefined)),
303
+ ).toEqual({
304
+ errorName: "Error",
305
+ message:
306
+ 'slug must be ≤35 chars (got 36). Shorten to a kebab-case label, e.g. "fix-login", "extract-urls".',
307
+ });
308
+ });
309
+
310
+ it("正常启动 → 领域对象全字段 + execute 参数(task/slug trim、拍平透传、ctxModel/signal)", async () => {
311
+ const execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
312
+ mode: "background",
313
+ subagentId: "bg-9-abc",
314
+ sessionFile: "sess-9.jsonl",
315
+ details: makeDetails({ status: "running", slug: "long-running", model: "prov/resolved" }),
316
+ }));
317
+ const signal = new AbortController().signal;
318
+ const r = await startHandler(
319
+ makeService({ execute }),
320
+ {
321
+ task: " long task ",
322
+ slug: "long-running",
323
+ agent: "/x/agent.md",
324
+ model: "prov/m2",
325
+ thinkingLevel: "high",
326
+ skillPath: "/s/SKILL.md",
327
+ appendSystemPrompt: ["a"],
328
+ schema: { type: "object" },
329
+ maxTurns: 5,
330
+ graceTurns: 1,
331
+ fork: false,
332
+ worktree: false,
333
+ cwd: "/w",
334
+ conversation: true,
335
+ idleTimeoutMs: 1000,
336
+ engine: "pi",
337
+ },
338
+ signal,
339
+ CTX_MODEL,
340
+ );
341
+ expect(r).toEqual({
342
+ kind: "bg",
343
+ subagentId: "bg-9-abc",
344
+ sessionFile: "sess-9.jsonl",
345
+ slug: "long-running",
346
+ model: "prov/resolved",
347
+ response: {
348
+ status: "running",
349
+ mode: "background",
350
+ message: "detached, will notify on completion (auto-injected message, do not poll)",
351
+ notifyContract: "ledger+at-least-once",
352
+ },
353
+ });
354
+ expect(execute).toHaveBeenCalledTimes(1);
355
+ expect(execute.mock.calls[0]![0]).toEqual({
356
+ task: "long task", // trim 生效
357
+ slug: "long-running",
358
+ agent: "/x/agent.md",
359
+ model: "prov/m2",
360
+ thinkingLevel: "high",
361
+ skillPath: "/s/SKILL.md",
362
+ appendSystemPrompt: ["a"],
363
+ schema: { type: "object" },
364
+ maxTurns: 5,
365
+ graceTurns: 1,
366
+ fork: false,
367
+ worktree: false,
368
+ cwd: "/w",
369
+ conversation: true,
370
+ idleTimeoutMs: 1000,
371
+ engine: "pi",
372
+ ctxModel: CTX_MODEL,
373
+ signal,
374
+ });
375
+ // 常量与响应同源(宿主 adapter 复用面)
376
+ expect(BG_MESSAGE).toBe("detached, will notify on completion (auto-injected message, do not poll)");
377
+ expect(NOTIFY_CONTRACT).toBe("ledger+at-least-once");
378
+ });
379
+ });
380
+
381
+ // ============================================================
382
+ // listHandler
383
+ // ============================================================
384
+ describe("⛔4 listHandler(limit 夹紧 + 过滤 + enrich,快照 = pi-sw 实测)", () => {
385
+ it("includeFinished:true → collectRecords(20,'all') + getFullRecord 补全投影", () => {
386
+ const collectRecords = vi.fn(() => [
387
+ makeRec({ id: "bg-1", slug: "one" }),
388
+ makeRec({ id: "bg-2", slug: "two", status: "closed", endedAt: 5000 }),
389
+ ]);
390
+ const getFullRecord = vi.fn((id: string) =>
391
+ id === "bg-1" ? makeRec({ id: "bg-1", slug: "one", model: "prov/full", totalTokens: 77 }) : undefined,
392
+ );
393
+ const r = listHandler(makeService({ collectRecords, getFullRecord }), { includeFinished: true });
394
+ expect(r).toEqual({
395
+ response: {
396
+ running: 1,
397
+ items: [
398
+ {
399
+ subagentId: "bg-1",
400
+ agent: "reader",
401
+ slug: "one",
402
+ state: "active",
403
+ status: "running",
404
+ mode: "background",
405
+ duration: 1788189208,
406
+ model: "prov/full",
407
+ totalTokens: 77,
408
+ sessionFile: "sess-1.jsonl",
409
+ parent: undefined,
410
+ resumable: true,
411
+ },
412
+ {
413
+ subagentId: "bg-2",
414
+ agent: "reader",
415
+ slug: "two",
416
+ state: "ended",
417
+ status: "closed",
418
+ mode: "background",
419
+ duration: 4,
420
+ model: "prov/m1",
421
+ totalTokens: 42,
422
+ sessionFile: "sess-1.jsonl",
423
+ parent: undefined,
424
+ resumable: false,
425
+ outcome: "completed",
426
+ },
427
+ ],
428
+ },
429
+ });
430
+ expect(collectRecords).toHaveBeenCalledWith(20, "all");
431
+ // light record(getFullRecord undefined)回退原样投影
432
+ expect(getFullRecord).toHaveBeenCalledWith("bg-2");
433
+ });
434
+
435
+ it("缺省 → collectRecords(20,'running')", () => {
436
+ const collectRecords = vi.fn(() => [] as SubagentRecord[]);
437
+ listHandler(makeService({ collectRecords }), undefined);
438
+ expect(collectRecords).toHaveBeenCalledWith(20, "running");
439
+ });
440
+
441
+ it("limit 夹紧:500 → 100 上限;0 → 1 下限(非默认值)", () => {
442
+ const high = vi.fn(() => [] as SubagentRecord[]);
443
+ listHandler(makeService({ collectRecords: high }), { includeFinished: true, limit: 500 });
444
+ expect(high).toHaveBeenCalledWith(100, "all");
445
+
446
+ const low = vi.fn(() => [] as SubagentRecord[]);
447
+ listHandler(makeService({ collectRecords: low }), { includeFinished: true, limit: 0 });
448
+ expect(low).toHaveBeenCalledWith(1, "all");
449
+
450
+ expect(DEFAULT_LIST_LIMIT).toBe(20);
451
+ expect(MAX_LIST_LIMIT).toBe(100);
452
+ });
453
+ });
454
+
455
+ // ============================================================
456
+ // cancelHandler
457
+ // ============================================================
458
+ describe("⛔4 cancelHandler(守卫 + 归属判定 + CAS 失败映射,快照 = pi-sw 实测)", () => {
459
+ it("id 空白 → 逐字错误文案", async () => {
460
+ expect(await errOf(() => cancelHandler(makeService(), { subagentId: " " }))).toEqual({
461
+ errorName: "Error",
462
+ message: "cancelParam.subagentId is required for action:'cancel'",
463
+ });
464
+ });
465
+
466
+ it("findRecord 无 + 全树无 → not found 文案", async () => {
467
+ expect(await errOf(() => cancelHandler(makeService(), { subagentId: "bg-x" }))).toEqual({
468
+ errorName: "Error",
469
+ message:
470
+ 'No subagent record with id "bg-x". It may have finished — use action:\'list\' with includeFinished:true to verify.',
471
+ });
472
+ });
473
+
474
+ it("归属判定:findRecord 无但全树列出同 id running(异进程)→ owned-by-another-process 文案", async () => {
475
+ expect(
476
+ await errOf(() =>
477
+ cancelHandler(
478
+ makeService({ collectRecords: vi.fn(() => [makeRec({ id: "bg-9", status: "running" })]) }),
479
+ { subagentId: "bg-9" },
480
+ ),
481
+ ),
482
+ ).toEqual({
483
+ errorName: "Error",
484
+ message:
485
+ 'Subagent record "bg-9" is running but owned by another process in the tree ' +
486
+ "(it was spawned by a different subagent process) — this process cannot cancel it; " +
487
+ "cancel only works for subagents spawned by the current process.",
488
+ });
489
+ });
490
+
491
+ it("归属判定边界:全树列出但已终态 → 回落 not found 文案(非 owned-by)", async () => {
492
+ expect(
493
+ await errOf(() =>
494
+ cancelHandler(
495
+ makeService({ collectRecords: vi.fn(() => [makeRec({ id: "bg-9", status: "closed", endedAt: 5000 })]) }),
496
+ { subagentId: "bg-9" },
497
+ ),
498
+ ),
499
+ ).toEqual({
500
+ errorName: "Error",
501
+ message:
502
+ 'No subagent record with id "bg-9". It may have finished — use action:\'list\' with includeFinished:true to verify.',
503
+ });
504
+ });
505
+
506
+ it("mode 非 background → unsupported mode 文案(守卫分支完整性)", async () => {
507
+ expect(
508
+ await errOf(() =>
509
+ cancelHandler(
510
+ // "sync" 是历史已裁撤值(ExecutionMode 现仅 "background")——经 as never
511
+ // 注入非法值专测守卫分支,运行时守卫按 !== "background" 拦截
512
+ makeService({ findRecord: vi.fn(() => makeRec({ id: "bg-1", mode: "sync" as never })) }),
513
+ { subagentId: "bg-1" },
514
+ ),
515
+ ),
516
+ ).toEqual({
517
+ errorName: "Error",
518
+ message: "Cannot cancel subagent bg-1 (unsupported mode: sync)",
519
+ });
520
+ });
521
+
522
+ it("chatMode → close(force:true) 别名路径 + cancel 响应", async () => {
523
+ const closeSubagent = vi.fn(async () => {});
524
+ const chatRecord = makeExecRecord({ id: "bg-1", chatMode: true });
525
+ const r = await cancelHandler(
526
+ makeService({
527
+ findRecord: vi.fn(() => makeRec({ id: "bg-1", chatMode: true })),
528
+ getRecordForAction: vi.fn(() => chatRecord),
529
+ closeSubagent,
530
+ }),
531
+ { subagentId: "bg-1" },
532
+ );
533
+ expect(r).toEqual({ subagentId: "bg-1", response: { cancelled: true } });
534
+ expect(closeSubagent).toHaveBeenCalledWith(chatRecord, true);
535
+ });
536
+
537
+ it("CAS 失败 + re-query evicted → 'unknown (evicted from memory)' 文案", async () => {
538
+ let calls = 0;
539
+ expect(
540
+ await errOf(() =>
541
+ cancelHandler(
542
+ makeService({
543
+ findRecord: vi.fn(() => {
544
+ calls += 1;
545
+ return calls === 1 ? makeRec({ id: "bg-1", chatMode: undefined }) : undefined;
546
+ }),
547
+ cancel: vi.fn(() => false),
548
+ }),
549
+ { subagentId: "bg-1" },
550
+ ),
551
+ ),
552
+ ).toEqual({
553
+ errorName: "Error",
554
+ message: "Subagent bg-1 could not be cancelled (it likely just finished; status: unknown (evicted from memory))",
555
+ });
556
+ });
557
+
558
+ it("CAS 失败 + re-query 有记录 → 当前真实 status 进文案(closed)", async () => {
559
+ let calls = 0;
560
+ expect(
561
+ await errOf(() =>
562
+ cancelHandler(
563
+ makeService({
564
+ findRecord: vi.fn(() => {
565
+ calls += 1;
566
+ return calls === 1
567
+ ? makeRec({ id: "bg-1", chatMode: undefined })
568
+ : makeRec({ id: "bg-1", status: "closed", endedAt: 5000 });
569
+ }),
570
+ cancel: vi.fn(() => false),
571
+ }),
572
+ { subagentId: "bg-1" },
573
+ ),
574
+ ),
575
+ ).toEqual({
576
+ errorName: "Error",
577
+ message: "Subagent bg-1 could not be cancelled (it likely just finished; status: closed)",
578
+ });
579
+ });
580
+
581
+ it("cancel 成功 → cancelled:true", async () => {
582
+ const r = await cancelHandler(
583
+ makeService({
584
+ findRecord: vi.fn(() => makeRec({ id: "bg-1", chatMode: undefined })),
585
+ cancel: vi.fn(() => true),
586
+ }),
587
+ { subagentId: "bg-1" },
588
+ );
589
+ expect(r).toEqual({ subagentId: "bg-1", response: { cancelled: true } });
590
+ });
591
+ });
592
+
593
+ // ============================================================
594
+ // messageHandler + endedMessageGuard(归属判定 + 文案分流)
595
+ // ============================================================
596
+ describe("⛔4 messageHandler(守卫 + upgrade + 投递,快照 = pi-sw 实测)", () => {
597
+ it("id/text 空白 → 逐字错误文案", async () => {
598
+ expect(await errOf(() => messageHandler(makeService(), { subagentId: " ", text: "hi" }))).toEqual({
599
+ errorName: "Error",
600
+ message: "messageParam.subagentId is required for action:'message'",
601
+ });
602
+ expect(await errOf(() => messageHandler(makeService(), { subagentId: "bg-1", text: " " }))).toEqual({
603
+ errorName: "Error",
604
+ message:
605
+ "messageParam.text is required for action:'message' (must not be whitespace-only). " +
606
+ 'Correct: {"action":"message","messageParam":{"subagentId":"sa-...","text":"your follow-up"}}',
607
+ });
608
+ });
609
+
610
+ it("chatMode record → deliverChatMessage(text trim + interrupt) + 领域对象", async () => {
611
+ const deliverChatMessage = vi.fn(async () => {});
612
+ const chatRecord = makeExecRecord({ id: "bg-1", chatMode: true, slug: "src-slug" });
613
+ const r = await messageHandler(
614
+ makeService({ getRecordForAction: vi.fn(() => chatRecord), deliverChatMessage }),
615
+ { subagentId: "bg-1", text: " go on ", interrupt: true },
616
+ );
617
+ expect(r).toEqual({
618
+ kind: "message",
619
+ subagentId: "bg-1",
620
+ slug: "src-slug",
621
+ response: { delivered: true },
622
+ });
623
+ expect(deliverChatMessage).toHaveBeenCalledWith(chatRecord, "go on", true);
624
+ });
625
+
626
+ it("one-shot upgrade:非 chatMode running record 收 message → 置位 chatMode 后投递", async () => {
627
+ const deliverChatMessage = vi.fn(async () => {});
628
+ const upgradeRec = makeExecRecord({ id: "bg-1", chatMode: false, status: "running" });
629
+ await messageHandler(
630
+ makeService({ getRecordForAction: vi.fn(() => upgradeRec), deliverChatMessage }),
631
+ { subagentId: "bg-1", text: "hi" },
632
+ );
633
+ expect(upgradeRec.chatMode).toBe(true);
634
+ expect(deliverChatMessage).toHaveBeenCalledWith(upgradeRec, "hi", false);
635
+ });
636
+
637
+ it("getRecordForAction 拒绝 + 无终态快照 → 原错误透传(文案最准原则)", async () => {
638
+ expect(
639
+ await errOf(() =>
640
+ messageHandler(
641
+ makeService({
642
+ getRecordForAction: vi.fn(() => {
643
+ throw new Error('No subagent record with id "bg-404" (it may have finished).');
644
+ }),
645
+ lookupRecordAnyState: vi.fn(() => undefined),
646
+ }),
647
+ { subagentId: "bg-404", text: "hi" },
648
+ ),
649
+ ),
650
+ ).toEqual({
651
+ errorName: "Error",
652
+ message: 'No subagent record with id "bg-404" (it may have finished).',
653
+ });
654
+ });
655
+
656
+ it("ResurrectDeniedError → 原样透传(实例身份保持,不被 fork-from 指引改写)", async () => {
657
+ const original = new ResurrectDeniedError("subagent bg-1 cannot be transparently resumed (worktree binding lost)");
658
+ let caught: unknown;
659
+ try {
660
+ await messageHandler(
661
+ makeService({
662
+ getRecordForAction: vi.fn(() => {
663
+ throw original;
664
+ }),
665
+ lookupRecordAnyState: vi.fn(() => makeRec({ id: "bg-1", status: "closed", closedReason: "disconnected" })),
666
+ }),
667
+ { subagentId: "bg-1", text: "hi" },
668
+ );
669
+ } catch (err) {
670
+ caught = err;
671
+ }
672
+ expect(caught).toBe(original);
673
+ expect(caught instanceof ResurrectDeniedError).toBe(true);
674
+ });
675
+
676
+ it("endedMessageGuard 分流:closed+user-close / cancelled → 主动关闭文案", async () => {
677
+ const mkSvc = (closedReason: "user-close" | "cancelled") =>
678
+ makeService({
679
+ getRecordForAction: vi.fn(() => {
680
+ throw new Error("not found or not owned");
681
+ }),
682
+ lookupRecordAnyState: vi.fn(() =>
683
+ makeRec({ id: "bg-1", status: "closed", closedReason, sessionFile: "sess-1.jsonl" }),
684
+ ),
685
+ });
686
+ expect(await errOf(() => messageHandler(mkSvc("user-close"), { subagentId: "bg-1", text: "hi" }))).toEqual({
687
+ errorName: "Error",
688
+ message:
689
+ "subagent bg-1 was deliberately closed by user (closedReason: user-close) — " +
690
+ "it cannot be messaged or resumed; nothing can reattach to it. " +
691
+ "Recovery: start a new subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.",
692
+ });
693
+ expect(await errOf(() => messageHandler(mkSvc("cancelled"), { subagentId: "bg-1", text: "hi" }))).toEqual({
694
+ errorName: "Error",
695
+ message:
696
+ "subagent bg-1 was deliberately closed by user (closedReason: cancelled) — " +
697
+ "it cannot be messaged or resumed; nothing can reattach to it. " +
698
+ "Recovery: start a new subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.",
699
+ });
700
+ });
701
+
702
+ it("endedMessageGuard 分流:closed 断联类 → reconnectable 文案(describeClosedContext 逐字)", async () => {
703
+ const mkSvc = (closedReason: ExecutionRecord["closedReason"], sessionFile?: string) =>
704
+ makeService({
705
+ getRecordForAction: vi.fn(() => {
706
+ throw new Error("not found or not owned");
707
+ }),
708
+ lookupRecordAnyState: vi.fn(() => makeRec({ id: "bg-1", status: "closed", closedReason, sessionFile })),
709
+ });
710
+ const expectedTail =
711
+ 'Recovery: resume from that history with {"action":"fork-from","forkFromParam":{"sourceSubagentId":"bg-1"}}, ' +
712
+ "or read key points directly from the session file.";
713
+ // parent-shutdown 专属短语
714
+ expect((await errOf(() => messageHandler(mkSvc("parent-shutdown", "sess-1.jsonl"), { subagentId: "bg-1", text: "hi" }))).message).toBe(
715
+ "subagent bg-1 is ended but reconnectable (closedReason: parent-shutdown" +
716
+ " — it was disconnected when the previous parent session exited" +
717
+ "). Its conversation history is intact at sess-1.jsonl. " + expectedTail,
718
+ );
719
+ // disconnected 专属短语
720
+ expect((await errOf(() => messageHandler(mkSvc("disconnected", "sess-1.jsonl"), { subagentId: "bg-1", text: "hi" }))).message).toBe(
721
+ "subagent bg-1 is ended but reconnectable (closedReason: disconnected" +
722
+ " — it ended in a previous session (exact cause unknown)" +
723
+ "). Its conversation history is intact at sess-1.jsonl. " + expectedTail,
724
+ );
725
+ // 无 closedReason → "unknown" + 空短语;无 sessionFile → "(session file unavailable)"
726
+ expect((await errOf(() => messageHandler(mkSvc(undefined, undefined), { subagentId: "bg-1", text: "hi" }))).message).toBe(
727
+ "subagent bg-1 is ended but reconnectable (closedReason: unknown). " +
728
+ "Its conversation history is intact at (session file unavailable). " + expectedTail,
729
+ );
730
+ });
731
+
732
+ it("endedMessageGuard 分流:running 异归属(不同 session 树)→ fork-from branch 指引", async () => {
733
+ const mkSvc = (sessionFile?: string) =>
734
+ makeService({
735
+ getRecordForAction: vi.fn(() => {
736
+ throw new Error("not found or not owned");
737
+ }),
738
+ lookupRecordAnyState: vi.fn(() =>
739
+ makeRec({ id: "bg-1", status: "running", rootSessionId: "root-B", sessionFile }),
740
+ ),
741
+ });
742
+ // 有 sessionFile → 追加 source session
743
+ expect((await errOf(() => messageHandler(mkSvc("sess-1.jsonl"), { subagentId: "bg-1", text: "hi" }))).message).toBe(
744
+ "subagent bg-1 is alive but belongs to a different session tree than this one. You cannot message it from here. " +
745
+ 'Recovery: branch from its history with {"action":"fork-from","forkFromParam":{"sourceSubagentId":"bg-1"}}' +
746
+ " (source session: sess-1.jsonl); otherwise start a new subagent.",
747
+ );
748
+ // 无 sessionFile → 无追加
749
+ expect((await errOf(() => messageHandler(mkSvc(undefined), { subagentId: "bg-1", text: "hi" }))).message).toBe(
750
+ "subagent bg-1 is alive but belongs to a different session tree than this one. You cannot message it from here. " +
751
+ 'Recovery: branch from its history with {"action":"fork-from","forkFromParam":{"sourceSubagentId":"bg-1"}}' +
752
+ "; otherwise start a new subagent.",
753
+ );
754
+ });
755
+
756
+ it("endedMessageGuard 直测:非 Error 原始值 + 无快照 → 包装为 Error(String(original))", () => {
757
+ const e = endedMessageGuard(makeService({ lookupRecordAnyState: vi.fn(() => undefined) }), "bg-1", "plain string failure");
758
+ expect(e.name).toBe("Error");
759
+ expect(e.message).toBe("plain string failure");
760
+ });
761
+ });
762
+
763
+ // ============================================================
764
+ // closeHandler
765
+ // ============================================================
766
+ describe("⛔4 closeHandler(force 语义透传,快照 = pi-sw 实测)", () => {
767
+ it("id 空白 → 逐字错误文案", async () => {
768
+ expect(await errOf(() => closeHandler(makeService(), { subagentId: "" }))).toEqual({
769
+ errorName: "Error",
770
+ message: "closeParam.subagentId is required for action:'close'",
771
+ });
772
+ });
773
+
774
+ it("force:true / 缺省 false → closeSubagent(record, force) 透传 + closed:true", async () => {
775
+ const record = makeExecRecord({ id: "bg-1" });
776
+ const closeForce = vi.fn(async () => {});
777
+ const r1 = await closeHandler(
778
+ makeService({ getRecordForAction: vi.fn(() => record), closeSubagent: closeForce }),
779
+ { subagentId: "bg-1", force: true },
780
+ );
781
+ expect(r1).toEqual({ kind: "close", subagentId: "bg-1", response: { closed: true } });
782
+ const closeGrace = vi.fn(async () => {});
783
+ const r2 = await closeHandler(
784
+ makeService({ getRecordForAction: vi.fn(() => record), closeSubagent: closeGrace }),
785
+ { subagentId: "bg-1" },
786
+ );
787
+ expect(r2).toEqual({ kind: "close", subagentId: "bg-1", response: { closed: true } });
788
+ expect(closeForce).toHaveBeenCalledWith(record, true);
789
+ expect(closeGrace).toHaveBeenCalledWith(record, false);
790
+ });
791
+ });
792
+
793
+ // ============================================================
794
+ // forkFromHandler(守卫链 1–6)
795
+ // ============================================================
796
+ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快照 = pi-sw 实测)", () => {
797
+ function makeForkService(source: SubagentRecord | undefined, execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
798
+ mode: "background",
799
+ subagentId: "bg-new-1",
800
+ sessionFile: "sess-new.jsonl",
801
+ details: makeDetails(),
802
+ })), inMemory?: SubagentRecord) {
803
+ return makeService({
804
+ findRecord: vi.fn(() => inMemory),
805
+ lookupRecordAnyState: vi.fn(() => source),
806
+ execute,
807
+ });
808
+ }
809
+
810
+ it("sourceSubagentId 空白 → 逐字错误文案", async () => {
811
+ expect(await errOf(() => forkFromHandler(makeService(), { sourceSubagentId: " " }))).toEqual({
812
+ errorName: "Error",
813
+ message: "forkFromParam.sourceSubagentId is required for action:'fork-from'",
814
+ });
815
+ });
816
+
817
+ it("守卫 1:本进程内存 running → still-active 文案", async () => {
818
+ expect(
819
+ await errOf(() =>
820
+ forkFromHandler(makeForkService(undefined, undefined, makeRec({ id: "bg-1" })), { sourceSubagentId: "bg-1" }),
821
+ ),
822
+ ).toEqual({
823
+ errorName: "Error",
824
+ message:
825
+ "subagent bg-1 is still active in this process — use action:'message' to continue it directly. " +
826
+ "If you want a parallel branch from its history, close it first (action:'close'), then fork-from.",
827
+ });
828
+ });
829
+
830
+ it("守卫 2:全态查无 → garbage-collected 文案", async () => {
831
+ expect(await errOf(() => forkFromHandler(makeForkService(undefined), { sourceSubagentId: "bg-404" }))).toEqual({
832
+ errorName: "Error",
833
+ message:
834
+ 'No subagent record with id "bg-404". It may never have existed or been garbage-collected — ' +
835
+ "use action:'list' with includeFinished:true to verify the id.",
836
+ });
837
+ });
838
+
839
+ it("守卫 3:externalInstance 活 pid marker → another-process 文案", async () => {
840
+ expect(
841
+ await errOf(() =>
842
+ forkFromHandler(
843
+ makeForkService(makeRec({ id: "bg-1", externalInstance: { pid: 4321, id: "p-1", startedAt: 5 } })),
844
+ { sourceSubagentId: "bg-1" },
845
+ ),
846
+ ),
847
+ ).toEqual({
848
+ errorName: "Error",
849
+ message:
850
+ "subagent bg-1 is still running in another process (alive pid marker present). " +
851
+ "Recovery: wait until it finishes, or operate it in its own session; then retry fork-from.",
852
+ });
853
+ });
854
+
855
+ it("守卫 3 不误伤:running 快照(无活 pid,跨重启重建)放行 → 正常 fork", async () => {
856
+ const r = await forkFromHandler(
857
+ makeForkService(makeRec({ id: "bg-1", status: "running", slug: "rebuilt", sessionFile: "sess-1.jsonl" })),
858
+ { sourceSubagentId: "bg-1" },
859
+ );
860
+ expect(r).toEqual({
861
+ kind: "fork-from",
862
+ subagentId: "bg-new-1",
863
+ sourceSessionFile: "sess-1.jsonl",
864
+ response: { newSubagentId: "bg-new-1", sourceSessionFile: "sess-1.jsonl" },
865
+ });
866
+ });
867
+
868
+ it("守卫 4:cancelled / user-close → deliberately-closed 文案(guard 与 message 一致性)", async () => {
869
+ for (const closedReason of ["cancelled", "user-close"] as const) {
870
+ expect(
871
+ await errOf(() =>
872
+ forkFromHandler(
873
+ makeForkService(makeRec({ id: "bg-1", status: "closed", closedReason, sessionFile: "sess-1.jsonl" })),
874
+ { sourceSubagentId: "bg-1" },
875
+ ),
876
+ ),
877
+ ).toEqual({
878
+ errorName: "Error",
879
+ message:
880
+ `subagent bg-1 was deliberately closed by user (closedReason: ${closedReason}) — ` +
881
+ "deliberately-closed records cannot be resumed or branched from; nothing can reattach to them. " +
882
+ "Recovery: start a fresh subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.",
883
+ });
884
+ }
885
+ });
886
+
887
+ it("守卫 5:worktree 记录 → isolation-lost 文案(含 sessionFile 路径插值)", async () => {
888
+ expect(
889
+ await errOf(() =>
890
+ forkFromHandler(
891
+ makeForkService(makeRec({ id: "bg-1", status: "closed", closedReason: "gc", worktree: true, sessionFile: "sess-1.jsonl" })),
892
+ { sourceSubagentId: "bg-1" },
893
+ ),
894
+ ),
895
+ ).toEqual({
896
+ errorName: "Error",
897
+ message:
898
+ "subagent bg-1 was created with worktree isolation; that binding was lost when its parent process ended. " +
899
+ "Resuming from its history would run outside the original worktree isolation. " +
900
+ "Recovery: start a new subagent with action:'start' and carry over key findings manually " +
901
+ "(read sess-1.jsonl if needed).",
902
+ });
903
+ });
904
+
905
+ it("守卫 6:无子 session 文件 → no-child-session 文案", async () => {
906
+ expect(
907
+ await errOf(() =>
908
+ forkFromHandler(
909
+ makeForkService(makeRec({ id: "bg-1", status: "closed", closedReason: "gc", sessionFile: undefined })),
910
+ { sourceSubagentId: "bg-1" },
911
+ ),
912
+ ),
913
+ ).toEqual({
914
+ errorName: "Error",
915
+ message:
916
+ "subagent bg-1 has no child session file to inherit from (it never started successfully). " +
917
+ "Recovery: start a fresh subagent (action:'start') describing the task again.",
918
+ });
919
+ });
920
+
921
+ it("正常:显式 prompt → wrapForkFromPrompt 包装 + slug 派生 'src-slug-resumed' + execute 参数", async () => {
922
+ const execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
923
+ mode: "background",
924
+ subagentId: "bg-new-1",
925
+ sessionFile: "sess-new.jsonl",
926
+ details: makeDetails(),
927
+ }));
928
+ const r = await forkFromHandler(
929
+ makeForkService(
930
+ makeRec({ id: "bg-1", slug: "src-slug", agent: "/home/u/agents/reader.md", sessionFile: "sess-1.jsonl" }),
931
+ execute,
932
+ ),
933
+ { sourceSubagentId: "bg-1", prompt: " continue the work " },
934
+ );
935
+ expect(r).toEqual({
936
+ kind: "fork-from",
937
+ subagentId: "bg-new-1",
938
+ sourceSessionFile: "sess-1.jsonl",
939
+ response: { newSubagentId: "bg-new-1", sourceSessionFile: "sess-1.jsonl" },
940
+ });
941
+ expect(execute).toHaveBeenCalledWith({
942
+ task:
943
+ "continue the work\n\n(You are continuing a previous subagent's inherited conversation via --fork. " +
944
+ "Reconstruct state from that history first — what was done, decided, and remains — then execute the instruction above.)",
945
+ slug: "src-slug-resumed",
946
+ forkFromSessionFile: "sess-1.jsonl",
947
+ });
948
+ });
949
+
950
+ it("无 prompt → FORK_FROM_DEFAULT_PROMPT 逐字", async () => {
951
+ const execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
952
+ mode: "background",
953
+ subagentId: "bg-new-2",
954
+ sessionFile: "sess-new-2.jsonl",
955
+ details: makeDetails(),
956
+ }));
957
+ await forkFromHandler(
958
+ makeForkService(makeRec({ id: "bg-1", slug: "src-slug", sessionFile: "sess-1.jsonl" }), execute),
959
+ { sourceSubagentId: "bg-1", prompt: undefined },
960
+ );
961
+ expect(execute).toHaveBeenCalledWith({
962
+ task: FORK_FROM_DEFAULT_PROMPT,
963
+ slug: "src-slug-resumed",
964
+ forkFromSessionFile: "sess-1.jsonl",
965
+ });
966
+ expect(FORK_FROM_DEFAULT_PROMPT).toBe(
967
+ "You are taking over work from a previous subagent whose full conversation history you inherited (--fork). " +
968
+ "First reconstruct state from that history: list what was already done, decided, and left unfinished (a few bullet lines). " +
969
+ "Then continue the remaining work to completion.",
970
+ );
971
+ expect(wrapForkFromPrompt("p")).toBe(
972
+ "p\n\n(You are continuing a previous subagent's inherited conversation via --fork. " +
973
+ "Reconstruct state from that history first — what was done, decided, and remains — then execute the instruction above.)",
974
+ );
975
+ });
976
+
977
+ it("slug 派生:34 字符源 slug 截 27 + '-resumed';fallback 链 slug||agent||'resumed'", async () => {
978
+ const execute34 = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
979
+ mode: "background",
980
+ subagentId: "bg-new-3",
981
+ sessionFile: undefined,
982
+ details: makeDetails(),
983
+ }));
984
+ await forkFromHandler(
985
+ makeForkService(makeRec({ id: "bg-1", slug: "s".repeat(34), sessionFile: "sess-1.jsonl" }), execute34),
986
+ { sourceSubagentId: "bg-1", prompt: "p" },
987
+ );
988
+ expect(execute34.mock.calls[0]![0].slug).toBe(`${"s".repeat(27)}-resumed`);
989
+
990
+ const executeAgent = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
991
+ mode: "background",
992
+ subagentId: "bg-new-4",
993
+ sessionFile: undefined,
994
+ details: makeDetails(),
995
+ }));
996
+ await forkFromHandler(
997
+ makeForkService(
998
+ makeRec({ id: "bg-1", slug: "", agent: "/home/u/agents/helper.md", sessionFile: "sess-1.jsonl" }),
999
+ executeAgent,
1000
+ ),
1001
+ { sourceSubagentId: "bg-1", prompt: "p" },
1002
+ );
1003
+ expect(executeAgent.mock.calls[0]![0].slug).toBe("/home/u/agents/helper.md-resumed");
1004
+
1005
+ const executeResumed = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
1006
+ mode: "background",
1007
+ subagentId: "bg-new-5",
1008
+ sessionFile: undefined,
1009
+ details: makeDetails(),
1010
+ }));
1011
+ await forkFromHandler(
1012
+ makeForkService(makeRec({ id: "bg-1", slug: "", agent: "", sessionFile: "sess-1.jsonl" }), executeResumed),
1013
+ { sourceSubagentId: "bg-1", prompt: "p" },
1014
+ );
1015
+ expect(executeResumed.mock.calls[0]![0].slug).toBe("resumed-resumed");
1016
+ });
1017
+ });