@zhushanwen/subagent-core 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-KXVBIVOU.js +16 -0
  3. package/dist/chunk-T2SYBW3I.js +24 -0
  4. package/dist/chunk-VURUAGMM.js +2305 -0
  5. package/dist/{types-Dv4QhSJ_.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +835 -668
  6. package/dist/{types-Dv4QhSJ_.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +835 -668
  7. package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
  8. package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
  9. package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
  10. package/dist/execution/engine/engine-discovery-scan.js +20 -0
  11. package/dist/execution/engine/paths.cjs +6 -23
  12. package/dist/execution/engine/paths.d.cts +1 -8
  13. package/dist/execution/engine/paths.d.ts +1 -8
  14. package/dist/execution/engine/paths.js +1 -1
  15. package/dist/execution/relay-env.cjs +12 -28
  16. package/dist/execution/relay-env.d.cts +1 -33
  17. package/dist/execution/relay-env.d.ts +1 -33
  18. package/dist/execution/relay-env.js +5 -3
  19. package/dist/index.cjs +14123 -15467
  20. package/dist/index.d.cts +1179 -1540
  21. package/dist/index.d.ts +1179 -1540
  22. package/dist/index.js +7694 -10986
  23. package/dist.bundle/index.cjs +33485 -0
  24. package/package.json +20 -26
  25. package/src/__tests__/record-store-last-line.test.ts +16 -7
  26. package/src/core/error-message.ts +7 -6
  27. package/src/core/host-services.ts +8 -2
  28. package/src/core/notify-ports.ts +17 -2
  29. package/src/execution/__tests__/agent-registry.test.ts +1 -1
  30. package/src/execution/__tests__/alive-store.test.ts +1 -1
  31. package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
  32. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
  33. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  34. package/src/execution/__tests__/collect-coordinator-service.test.ts +316 -0
  35. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  36. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +229 -0
  37. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  38. package/src/execution/__tests__/config.test.ts +1 -1
  39. package/src/execution/__tests__/delivery-methods.test.ts +179 -237
  40. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
  41. package/src/execution/__tests__/execute-nesting.test.ts +20 -13
  42. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +21 -34
  43. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  44. package/src/execution/__tests__/gc-timer.test.ts +8 -23
  45. package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
  46. package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
  47. package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
  48. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
  49. package/src/execution/__tests__/idle-gc.test.ts +200 -0
  50. package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
  51. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  52. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
  53. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  54. package/src/execution/__tests__/notify-ledger.test.ts +27 -0
  55. package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
  56. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  57. package/src/execution/__tests__/record-store.test.ts +60 -6
  58. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
  59. package/src/execution/__tests__/relay-env.test.ts +12 -1
  60. package/src/execution/__tests__/round-supervisor.test.ts +293 -0
  61. package/src/execution/__tests__/session-file-gc.test.ts +1 -1
  62. package/src/execution/__tests__/session-pending.test.ts +119 -17
  63. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  64. package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
  65. package/src/execution/__tests__/subagent-actions-core.test.ts +6 -0
  66. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +14 -11
  67. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
  68. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
  69. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
  70. package/src/execution/__tests__/subagent-service.test.ts +2 -2
  71. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
  72. package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
  73. package/src/execution/__tests__/sync-collect-recovery.test.ts +1102 -0
  74. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  75. package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
  76. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  77. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  78. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  79. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  80. package/src/execution/agent-registry.ts +6 -2
  81. package/src/execution/collect-coordinator.ts +200 -0
  82. package/src/execution/config.ts +77 -5
  83. package/src/execution/dialog-queue.ts +6 -73
  84. package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
  85. package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
  86. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  87. package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
  88. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
  89. package/src/execution/engine/__tests__/common/pool-manager.test.ts +2 -2
  90. package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
  91. package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
  92. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
  93. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
  94. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
  95. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
  96. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
  97. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
  98. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
  99. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +82 -55
  100. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
  101. package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
  102. package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
  103. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +8 -3
  104. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
  105. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
  106. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
  107. package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
  108. package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
  109. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
  110. package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
  111. package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
  112. package/src/execution/engine/__tests__/registry.test.ts +206 -1
  113. package/src/execution/engine/__tests__/routing.test.ts +97 -0
  114. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
  115. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
  116. package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
  117. package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
  118. package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
  119. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
  120. package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
  121. package/src/execution/engine/client/client-options.ts +103 -0
  122. package/src/execution/engine/client/engine-client.ts +687 -0
  123. package/src/execution/engine/client/mirror.ts +167 -0
  124. package/src/execution/engine/client/pid-file.ts +392 -0
  125. package/src/execution/engine/client/reaper.ts +95 -0
  126. package/src/execution/engine/client/remote-engine.ts +441 -0
  127. package/src/execution/engine/client/reverse-router.ts +255 -0
  128. package/src/execution/engine/common/capability-gate.ts +109 -22
  129. package/src/execution/engine/common/errors.ts +9 -25
  130. package/src/execution/engine/common/event-journal.ts +4 -50
  131. package/src/execution/engine/common/journal-replay.ts +3 -1
  132. package/src/execution/engine/common/kill-chain.ts +18 -251
  133. package/src/execution/engine/common/nesting-guard.ts +18 -114
  134. package/src/execution/engine/common/pool-manager.ts +4 -0
  135. package/src/execution/engine/common/session-view-service.ts +56 -84
  136. package/src/execution/engine/config.ts +151 -0
  137. package/src/execution/engine/d8-compat.ts +346 -0
  138. package/src/execution/engine/engine-discovery-roots.ts +107 -0
  139. package/src/execution/engine/engine-discovery-scan.ts +269 -0
  140. package/src/execution/engine/engine-discovery.ts +30 -7
  141. package/src/execution/engine/engine-inspect-package.ts +282 -0
  142. package/src/execution/engine/engine-manifest.ts +244 -0
  143. package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
  144. package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
  145. package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
  146. package/src/execution/engine/host/host-bridge.ts +150 -0
  147. package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
  148. package/src/execution/engine/host/pi-host-binding.ts +91 -0
  149. package/src/execution/engine/host/spawned-children.ts +139 -0
  150. package/src/execution/engine/host-task-spec.ts +29 -2
  151. package/src/execution/engine/model-validation.ts +55 -2
  152. package/src/execution/engine/paths.ts +16 -41
  153. package/src/execution/engine/port.ts +48 -2
  154. package/src/execution/engine/registry.ts +153 -34
  155. package/src/execution/engine/routing.ts +225 -52
  156. package/src/execution/engine/types.ts +25 -200
  157. package/src/execution/execution-record.ts +7 -0
  158. package/src/execution/finalize-record.ts +40 -11
  159. package/src/execution/idle-gc.ts +89 -14
  160. package/src/execution/lifecycle-manager.ts +10 -7
  161. package/src/execution/lifecycle-predicates.ts +10 -8
  162. package/src/execution/notifier.ts +234 -25
  163. package/src/execution/notify-host.ts +21 -1
  164. package/src/execution/record-entry.ts +16 -0
  165. package/src/execution/record-store.ts +189 -31
  166. package/src/execution/relay-env.ts +19 -36
  167. package/src/execution/round-supervisor/domain.ts +89 -0
  168. package/src/execution/round-supervisor/index.ts +33 -0
  169. package/src/execution/round-supervisor/notify-accounting.ts +87 -0
  170. package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
  171. package/src/execution/round-supervisor/service-binding.test.ts +661 -0
  172. package/src/execution/round-supervisor/service-binding.ts +273 -0
  173. package/src/execution/round-supervisor/supervisor.ts +426 -0
  174. package/src/execution/session-pending.ts +68 -74
  175. package/src/execution/settled-watchdog.ts +39 -0
  176. package/src/execution/subagent-actions-core.ts +62 -6
  177. package/src/execution/subagent-service.ts +1161 -406
  178. package/src/execution/subprocess-agent-runner.ts +16 -8
  179. package/src/execution/sync-rebuild.ts +87 -0
  180. package/src/execution/types.ts +104 -117
  181. package/src/execution/ui-channels.ts +13 -214
  182. package/src/execution/ui-request-handler-factory.ts +7 -4
  183. package/src/execution/ui-request-observability.ts +2 -2
  184. package/src/execution/workflow-state-root.ts +67 -0
  185. package/src/execution/worktree-git-ops.ts +5 -0
  186. package/src/execution/worktree-manager.ts +11 -1
  187. package/src/index.ts +55 -26
  188. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  189. package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
  190. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  191. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  192. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  193. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  194. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  195. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  196. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  197. package/src/orchestration/execute-agent-call.ts +1 -1
  198. package/src/orchestration/file-run-store.ts +144 -59
  199. package/src/orchestration/models/types.ts +43 -42
  200. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  201. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  202. package/src/shared/zcode-model-ref.ts +31 -0
  203. package/dist/chunk-43ONBFZX.js +0 -240
  204. package/dist/chunk-A4IVZWVX.js +0 -31
  205. package/dist/chunk-APZY4IME.js +0 -27
  206. package/dist/chunk-V3VHZ2VX.js +0 -59
  207. package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
  208. package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
  209. package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
  210. package/dist/execution/engine/engines/zcode/constants.js +0 -54
  211. package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
  212. package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
  213. package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
  214. package/dist/execution/engine/engines/zcode/reader.js +0 -9
  215. package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
  216. package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
  217. package/src/__tests__/session-runner.test.ts +0 -69
  218. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  219. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
  220. package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
  221. package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
  222. package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
  223. package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
  224. package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
  225. package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
  226. package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
  227. package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
  228. package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
  229. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
  230. package/src/execution/__tests__/output-collector.test.ts +0 -469
  231. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  232. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
  233. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  234. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
  235. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
  236. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
  237. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
  238. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  239. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  240. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  241. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
  242. package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
  243. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
  244. package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
  245. package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
  246. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
  247. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
  248. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
  249. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
  250. package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
  251. package/src/execution/__tests__/spawn-args.test.ts +0 -446
  252. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
  253. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  254. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
  255. package/src/execution/__tests__/spawned-children.test.ts +0 -236
  256. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  257. package/src/execution/__tests__/stdin-writer.test.ts +0 -464
  258. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
  259. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  260. package/src/execution/__tests__/timeout-integration.test.ts +0 -616
  261. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  262. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  263. package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
  264. package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
  265. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
  266. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  267. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
  268. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  269. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
  270. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  271. package/src/execution/engine/common/schema-emulation.ts +0 -189
  272. package/src/execution/engine/common/session-view-projection.ts +0 -51
  273. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
  274. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  275. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
  276. package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
  277. package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
  278. package/src/execution/engine/engines/pi/output-collector.ts +0 -317
  279. package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
  280. package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
  281. package/src/execution/engine/engines/pi/reader.ts +0 -59
  282. package/src/execution/engine/engines/pi/registration.ts +0 -37
  283. package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
  284. package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
  285. package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
  286. package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
  287. package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
  288. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
  289. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
  290. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
  291. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
  292. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
  293. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
  294. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  295. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
  296. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
  297. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
  298. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
  299. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
  300. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
  301. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
  302. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
  303. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
  305. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
  306. package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
  307. package/src/execution/engine/engines/zcode/connection.ts +0 -598
  308. package/src/execution/engine/engines/zcode/constants.ts +0 -176
  309. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
  310. package/src/execution/engine/engines/zcode/parser.ts +0 -102
  311. package/src/execution/engine/engines/zcode/preparer.ts +0 -235
  312. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  313. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  314. package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
  315. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
  316. package/src/execution/round-settlement.ts +0 -93
  317. package/src/execution/ui-request-queue.ts +0 -215
  318. package/src/shared/schema-env.ts +0 -44
@@ -1,490 +0,0 @@
1
- // src/execution/__tests__/ask-user-transit-e2e.test.ts
2
- //
3
- // #34 跨进程 ask_user transit 完整 e2e 测试。
4
- //
5
- // 验证完整链路:
6
- // FakeChild.stdout emit extension_ui_request
7
- // → runSpawn stdout pump(parseSpawnLine 解析)
8
- // → createUiRequestQueue 入队 + handleUiRequest
9
- // → ctx.uiRequestHandler(由 createUiRequestHandlerForMode 构造)
10
- // → channel registry 命中 'ask_user' → channel handler 返回固定答案
11
- // → respond 回写 child.stdin(extension_ui_response)
12
- //
13
- // 与单元测试的差异:ui-request-handler.test.ts 只测 parseSpawnLine + parseChannel +
14
- // createUiRequestQueue 各层独立;本文件串起**所有层**(adapter → queue → factory →
15
- // channel handler → respond),覆盖跨进程协议透传的真实链路,是 ask_user 功能的
16
- // 端到端契约验证。
17
- //
18
- // 关键被测对象:
19
- // 1. spawn-event-adapter.parseSpawnLine:识别 extension_ui_request 行
20
- // 2. session-runner.runSpawn 的 stdout pump:调 enqueueUiRequest
21
- // 3. ui-request-queue.createUiRequestQueue:FIFO + handleUiRequest
22
- // 4. ui-request-handler-factory.createUiRequestHandlerForMode:channel 路由 + 默认转发
23
- // 5. stdin-writer.respond:回写 extension_ui_response
24
- //
25
- // mock 策略:与 run-spawn-integration.test.ts 一致(共享 helpers/spawn-mock.ts),
26
- // 额外注入 ctx.uiRequestHandler + ctx.dialogQueue + channel registry。
27
-
28
- import { spawn } from "node:child_process";
29
- import * as fs from "node:fs";
30
-
31
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
32
-
33
- // ── mock modules(与 run-spawn-*.test.ts 一致)──
34
-
35
- vi.mock("node:child_process", async () => {
36
- const { FakeChild } = await import("./helpers/spawn-mock.ts");
37
- return {
38
- spawn: vi.fn(() => new FakeChild()),
39
- // buildEnvBlock 的 git branch 调用(execFile 异步):默认 err-first 兜底 → catch → branch=""
40
- execFile: vi.fn(
41
- (
42
- _cmd: string,
43
- _args: readonly string[],
44
- _opts: unknown,
45
- cb: (err: Error | null, stdout?: string, stderr?: string) => void,
46
- ) => cb(new Error("execFile not configured in this test")),
47
- ),
48
- };
49
- });
50
-
51
- vi.mock("node:fs", async () => {
52
- const actual = await import("node:fs");
53
- return {
54
- default: {
55
- ...actual,
56
- mkdirSync: vi.fn(),
57
- existsSync: vi.fn(() => false),
58
- appendFileSync: vi.fn(),
59
- writeFileSync: vi.fn(),
60
- readdirSync: vi.fn(() => []),
61
- },
62
- mkdirSync: vi.fn(),
63
- existsSync: vi.fn(() => false),
64
- appendFileSync: vi.fn(),
65
- writeFileSync: vi.fn(),
66
- readdirSync: vi.fn(() => []),
67
- promises: actual.promises,
68
- };
69
- });
70
-
71
- vi.mock("../alive-store.ts", () => ({
72
- writeAliveMarker: vi.fn(),
73
- }));
74
-
75
- vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
76
- writePromptToTempFile: vi.fn(async (agent: string) => {
77
- const safeName = agent.replace(/[^\w.-]+/g, "_");
78
- return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
79
- }),
80
- cleanupTempPrompt: vi.fn(async () => {}),
81
- }));
82
-
83
- import { DialogGlobalQueue } from "../dialog-queue.ts";
84
- import { runSpawn, type SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
85
- import { type ChannelHandler,createUiChannelRegistry } from "../ui-channels.ts";
86
- import { createUiRequestHandlerForMode } from "../ui-request-handler-factory.ts";
87
- import {
88
- emitStdoutLine,
89
- type FakeChild,
90
- lastSpawnedChild as lastSpawnedChildOf,
91
- makeCtx as makeCtxBase,
92
- makeOpts,
93
- makeRecord,
94
- sessionHeader,
95
- waitForSpawn as waitForSpawnOf,
96
- } from "./helpers/spawn-mock.ts";
97
-
98
- const mockSpawn = vi.mocked(spawn);
99
- const mockExistsSync = vi.mocked(fs.existsSync);
100
-
101
- const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
102
- const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
103
-
104
- // ── ask_user 协议样本(真实 Pi 格式)──
105
-
106
- const ASK_USER_MARKER = "\0XYZ_ASK_USER";
107
-
108
- interface AskUserPayload {
109
- questions: Array<{
110
- question: string;
111
- options: Array<{ label: string }>;
112
- }>;
113
- allowCancel: boolean;
114
- }
115
-
116
- const askUserPayload: AskUserPayload = {
117
- questions: [
118
- {
119
- question: "What is your preference?",
120
- options: [{ label: "Option A" }, { label: "Option B" }],
121
- },
122
- ],
123
- allowCancel: true,
124
- };
125
-
126
- /** 构造 Pi 原生 extension_ui_request stdout 行(ask_user 借道 select 通道)。 */
127
- function askUserLine(id: string): string {
128
- return JSON.stringify({
129
- type: "extension_ui_request",
130
- id,
131
- method: "select",
132
- title: ASK_USER_MARKER,
133
- options: [JSON.stringify(askUserPayload)],
134
- });
135
- }
136
-
137
- /** 读出 child.stdin 已缓冲的全部字节。 */
138
- function readStdin(child: FakeChild): string {
139
- child.stdin.pause();
140
- return child.stdin.read()?.toString() ?? "";
141
- }
142
-
143
- /** 从 child.stdin 按行拆分(去空行 + JSON.parse)。 */
144
- function readStdinLines(child: FakeChild): unknown[] {
145
- return readStdin(child)
146
- .split("\n")
147
- .map((l) => l.trim())
148
- .filter((l) => l.length > 0)
149
- .map((l) => JSON.parse(l));
150
- }
151
-
152
- /** 构造 ctx,注入 uiRequestHandler(由 createUiRequestHandlerForMode 构建)。 */
153
- function makeAskUserCtx(
154
- registry: ReturnType<typeof createUiChannelRegistry>,
155
- dialogQueue: DialogGlobalQueue,
156
- overrides: Partial<SessionRunnerContext> = {},
157
- ): SessionRunnerContext {
158
- // ctx.ui / ctx.mode 是 createUiRequestHandlerForMode 的入参。
159
- // mode='rpc' → hostMode='gui'(全透传),dialog 进 dialogQueue 串行。
160
- // ctx.ui 在 channel 命中时不被调用(channel handler 直接返回),故 ui 用 stub 即可。
161
- const ctx = {
162
- cwd: "/tmp/test",
163
- mode: "rpc" as const,
164
- sessionManager: {
165
- getSessionId: () => "s1",
166
- getSessionFile: () => undefined,
167
- getSessionDir: () => "/tmp/test/sessions",
168
- },
169
- modelRegistry: undefined,
170
- model: undefined,
171
- } as unknown as SessionRunnerContext;
172
- const handler = createUiRequestHandlerForMode(ctx as never, registry, dialogQueue);
173
- return makeCtxBase({
174
- ...overrides,
175
- uiRequestHandler: handler,
176
- dialogQueue,
177
- });
178
- }
179
-
180
- beforeEach(() => {
181
- vi.clearAllMocks();
182
- mockExistsSync.mockReturnValue(false);
183
- // 静默 stdin-writer / factory 的 warn(序列化失败降级等场景)
184
- vi.spyOn(console, "warn").mockImplementation(() => {});
185
- vi.spyOn(console, "error").mockImplementation(() => {});
186
- });
187
-
188
- afterEach(() => {
189
- vi.restoreAllMocks();
190
- });
191
-
192
- // ============================================================
193
- // E2E:完整 transit 链路(adapter → queue → factory → channel → respond)
194
- // ============================================================
195
-
196
- describe("ask_user 跨进程 transit e2e (#34)", () => {
197
- it("channel handler 命中 ask_user → 返回的 value 经 respond 回写 child.stdin", async () => {
198
- // 1. 注册 ask_user channel handler,返回固定答案(模拟用户选了 Option A)
199
- const registry = createUiChannelRegistry();
200
- const answer = JSON.stringify({ q0: "Option A" });
201
- const channelHandler: ChannelHandler = vi.fn(async () => ({ value: answer }));
202
- registry.register("ask_user", channelHandler);
203
-
204
- const dialogQueue = new DialogGlobalQueue();
205
- const ctx = makeAskUserCtx(registry, dialogQueue);
206
-
207
- // 2. 启动 runSpawn(不 await,spawn 后异步 emit 请求)
208
- const record = makeRecord();
209
- const promise = runSpawn(record, "Task: ask-user-e2e", makeOpts(), ctx);
210
-
211
- await waitForSpawn();
212
- const child = lastSpawnedChild();
213
-
214
- // 3. emit session header(runSpawn 需要 header 才能完成 close 路径)+ ask_user 请求
215
- emitStdoutLine(child, sessionHeader("sess-ask-1"));
216
- child.stdout.write(askUserLine("req-ask-1") + "\n");
217
-
218
- // 4. 给 stdout pump + channel handler (async) + respond 时间执行
219
- // PassThrough data listener flush + microtask + handler promise resolve 需若干 tick。
220
- await new Promise((r) => setTimeout(r, 30));
221
-
222
- // 5. 收尾:让 runSpawn resolve
223
- child.stdout.end();
224
- child.emit("close", 0);
225
- await promise;
226
-
227
- // 6. 断言 child.stdin 收到 extension_ui_response(value 分支)
228
- const responseLines = readStdinLines(child).filter(
229
- (l): l is { type: string; id: string; value?: string } =>
230
- typeof l === "object" && l !== null && (l as { type?: string }).type === "extension_ui_response",
231
- );
232
- expect(responseLines.length).toBe(1);
233
- expect(responseLines[0]!.id).toBe("req-ask-1");
234
- expect(responseLines[0]!.value).toBe(answer);
235
-
236
- // 7. channel handler 被调用一次,入参是 UiRequest(含 channel='ask_user' + payload)
237
- expect(channelHandler).toHaveBeenCalledTimes(1);
238
- const handlerArg = (channelHandler as ReturnType<typeof vi.fn>).mock.calls[0]?.[0] as {
239
- method: string;
240
- channel: string;
241
- channelPayload: AskUserPayload;
242
- id: string;
243
- };
244
- expect(handlerArg.method).toBe("select");
245
- expect(handlerArg.channel).toBe("ask_user");
246
- expect(handlerArg.id).toBe("req-ask-1");
247
- expect(handlerArg.channelPayload).toEqual(askUserPayload);
248
- });
249
-
250
- it("channel handler 返回 {confirmed:true} → respond 写 confirmed 分支", async () => {
251
- const registry = createUiChannelRegistry();
252
- const channelHandler: ChannelHandler = vi.fn(async () => ({ confirmed: true }));
253
- registry.register("ask_user", channelHandler);
254
-
255
- const dialogQueue = new DialogGlobalQueue();
256
- const ctx = makeAskUserCtx(registry, dialogQueue);
257
-
258
- const record = makeRecord();
259
- const promise = runSpawn(record, "Task: confirm-e2e", makeOpts(), ctx);
260
-
261
- await waitForSpawn();
262
- const child = lastSpawnedChild();
263
-
264
- emitStdoutLine(child, sessionHeader("sess-confirm"));
265
- child.stdout.write(askUserLine("req-confirm") + "\n");
266
- await new Promise((r) => setTimeout(r, 30));
267
-
268
- child.stdout.end();
269
- child.emit("close", 0);
270
- await promise;
271
-
272
- const responseLines = readStdinLines(child).filter(
273
- (l): l is { type: string; id: string; confirmed?: boolean } =>
274
- typeof l === "object" && l !== null && (l as { type?: string }).type === "extension_ui_response",
275
- );
276
- expect(responseLines).toHaveLength(1);
277
- expect(responseLines[0]!.id).toBe("req-confirm");
278
- expect(responseLines[0]!.confirmed).toBe(true);
279
- });
280
-
281
- it("channel handler 返回 {cancelled:true} → respond 写 cancelled 分支", async () => {
282
- const registry = createUiChannelRegistry();
283
- // 模拟用户取消(allowCancel 场景)
284
- const channelHandler: ChannelHandler = vi.fn(async () => ({ cancelled: true }));
285
- registry.register("ask_user", channelHandler);
286
-
287
- const dialogQueue = new DialogGlobalQueue();
288
- const ctx = makeAskUserCtx(registry, dialogQueue);
289
-
290
- const record = makeRecord();
291
- const promise = runSpawn(record, "Task: cancel-e2e", makeOpts(), ctx);
292
-
293
- await waitForSpawn();
294
- const child = lastSpawnedChild();
295
-
296
- emitStdoutLine(child, sessionHeader("sess-cancel"));
297
- child.stdout.write(askUserLine("req-cancel") + "\n");
298
- await new Promise((r) => setTimeout(r, 30));
299
-
300
- child.stdout.end();
301
- child.emit("close", 0);
302
- await promise;
303
-
304
- const responseLines = readStdinLines(child).filter(
305
- (l): l is { type: string; id: string; cancelled?: boolean } =>
306
- typeof l === "object" && l !== null && (l as { type?: string }).type === "extension_ui_response",
307
- );
308
- expect(responseLines).toHaveLength(1);
309
- expect(responseLines[0]!.id).toBe("req-cancel");
310
- expect(responseLines[0]!.cancelled).toBe(true);
311
- });
312
-
313
- it("多个 ask_user 请求 FIFO 串行 → 按到达顺序回写,channel handler 不并发", async () => {
314
- // L1 per-child 队列(createUiRequestQueue)保证同子进程内 FIFO 串行。
315
- // 验证:连续 emit 两个 ask_user,channel handler 按顺序被调,response 按顺序写回。
316
- const registry = createUiChannelRegistry();
317
- const callOrder: string[] = [];
318
- const channelHandler: ChannelHandler = vi.fn(async (req: unknown) => {
319
- callOrder.push((req as { id: string }).id);
320
- // 加延迟让两个请求有机会并发(若队列没串行)
321
- await new Promise((r) => setTimeout(r, 15));
322
- return { value: `ans-${(req as { id: string }).id}` };
323
- });
324
- registry.register("ask_user", channelHandler);
325
-
326
- const dialogQueue = new DialogGlobalQueue();
327
- const ctx = makeAskUserCtx(registry, dialogQueue);
328
-
329
- const record = makeRecord();
330
- const promise = runSpawn(record, "Task: fifo-e2e", makeOpts(), ctx);
331
-
332
- await waitForSpawn();
333
- const child = lastSpawnedChild();
334
-
335
- emitStdoutLine(child, sessionHeader("sess-fifo"));
336
- child.stdout.write(askUserLine("req-fifo-1") + "\n");
337
- child.stdout.write(askUserLine("req-fifo-2") + "\n");
338
- // 等两个 handler 都执行完(每个 ~15ms + overhead)
339
- await new Promise((r) => setTimeout(r, 80));
340
-
341
- child.stdout.end();
342
- child.emit("close", 0);
343
- await promise;
344
-
345
- // 串行:handler 按到达顺序被调,无并发
346
- expect(callOrder).toEqual(["req-fifo-1", "req-fifo-2"]);
347
- expect(channelHandler).toHaveBeenCalledTimes(2);
348
-
349
- // 两个 response 都写回,按 id 可找到
350
- const responses = readStdinLines(child).filter(
351
- (l): l is { type: string; id: string; value?: string } =>
352
- typeof l === "object" && l !== null && (l as { type?: string }).type === "extension_ui_response",
353
- );
354
- const ids = responses.map((r) => r.id).sort();
355
- expect(ids).toEqual(["req-fifo-1", "req-fifo-2"]);
356
- const r1 = responses.find((r) => r.id === "req-fifo-1");
357
- const r2 = responses.find((r) => r.id === "req-fifo-2");
358
- expect(r1?.value).toBe("ans-req-fifo-1");
359
- expect(r2?.value).toBe("ans-req-fifo-2");
360
- });
361
-
362
- it("channel 未注册 → defaultDialogForward 回 cancelled(ask_user 扩展未安装兜底)", async () => {
363
- // 不注册 ask_user channel → channelHandler 为 undefined → defaultDialogForward
364
- // defaultDialogForward 调 ctx.ui.select(这里 ctx.ui 是 stub,select 返 undefined → cancelled)
365
- const registry = createUiChannelRegistry();
366
- const dialogQueue = new DialogGlobalQueue();
367
-
368
- // ctx.ui.select 返回 undefined(模拟用户取消 / stub)
369
- const selectSpy = vi.fn(async () => undefined);
370
- const ctxBase = makeCtxBase();
371
- const fakeCtx = {
372
- cwd: "/tmp/test",
373
- mode: "rpc" as const,
374
- sessionManager: {
375
- getSessionId: () => "s1",
376
- getSessionFile: () => undefined,
377
- getSessionDir: () => "/tmp/test/sessions",
378
- },
379
- modelRegistry: undefined,
380
- model: undefined,
381
- ui: { select: selectSpy },
382
- } as never;
383
- const handler = createUiRequestHandlerForMode(fakeCtx, registry, dialogQueue);
384
- const ctx = { ...ctxBase, uiRequestHandler: handler, dialogQueue };
385
-
386
- const record = makeRecord();
387
- const promise = runSpawn(record, "Task: no-channel", makeOpts(), ctx);
388
-
389
- await waitForSpawn();
390
- const child = lastSpawnedChild();
391
-
392
- emitStdoutLine(child, sessionHeader("sess-nochannel"));
393
- child.stdout.write(askUserLine("req-nochannel") + "\n");
394
- await new Promise((r) => setTimeout(r, 30));
395
-
396
- child.stdout.end();
397
- child.emit("close", 0);
398
- await promise;
399
-
400
- // ctx.ui.select 被调(defaultDialogForward 兜底)
401
- expect(selectSpy).toHaveBeenCalledTimes(1);
402
- // response 是 cancelled(select 返 undefined → cancelled)
403
- const responses = readStdinLines(child).filter(
404
- (l): l is { type: string; id: string; cancelled?: boolean } =>
405
- typeof l === "object" && l !== null && (l as { type?: string }).type === "extension_ui_response",
406
- );
407
- expect(responses).toHaveLength(1);
408
- expect(responses[0]!.id).toBe("req-nochannel");
409
- expect(responses[0]!.cancelled).toBe(true);
410
- });
411
-
412
- it("channel handler 抛错 → respond 写 cancelled(队列不卡死)", async () => {
413
- const registry = createUiChannelRegistry();
414
- const channelHandler: ChannelHandler = vi.fn(async () => {
415
- throw new Error("handler boom");
416
- });
417
- registry.register("ask_user", channelHandler);
418
-
419
- const dialogQueue = new DialogGlobalQueue();
420
- const ctx = makeAskUserCtx(registry, dialogQueue);
421
-
422
- const record = makeRecord();
423
- const promise = runSpawn(record, "Task: handler-throws", makeOpts(), ctx);
424
-
425
- await waitForSpawn();
426
- const child = lastSpawnedChild();
427
-
428
- emitStdoutLine(child, sessionHeader("sess-throws"));
429
- child.stdout.write(askUserLine("req-throws") + "\n");
430
- await new Promise((r) => setTimeout(r, 30));
431
-
432
- child.stdout.end();
433
- child.emit("close", 0);
434
- await promise;
435
-
436
- // ui-request-queue 的 handleUiRequest catch handler 抛错 → respond cancelled
437
- const responses = readStdinLines(child).filter(
438
- (l): l is { type: string; id: string; cancelled?: boolean } =>
439
- typeof l === "object" && l !== null && (l as { type?: string }).type === "extension_ui_response",
440
- );
441
- expect(responses).toHaveLength(1);
442
- expect(responses[0]!.id).toBe("req-throws");
443
- expect(responses[0]!.cancelled).toBe(true);
444
- });
445
-
446
- it("child close 后 pending ask_user 的 handler 完成 → 不再写 stdin(signal aborted)", async () => {
447
- // 验证 R3:子进程退出时 AbortController.abort,handler 完成后 respond 跳过写入。
448
- // 构造:channel handler 在 child close 后才 resolve(模拟慢用户响应 + 子进程先退出)。
449
- const registry = createUiChannelRegistry();
450
- let resolveHandler: ((v: { value: string }) => void) | undefined;
451
- const channelHandler: ChannelHandler = vi.fn(
452
- () => new Promise<{ value: string }>((resolve) => {
453
- resolveHandler = resolve;
454
- }),
455
- );
456
- registry.register("ask_user", channelHandler);
457
-
458
- const dialogQueue = new DialogGlobalQueue();
459
- const ctx = makeAskUserCtx(registry, dialogQueue);
460
-
461
- const record = makeRecord();
462
- const promise = runSpawn(record, "Task: abort-pending", makeOpts(), ctx);
463
-
464
- await waitForSpawn();
465
- const child = lastSpawnedChild();
466
-
467
- emitStdoutLine(child, sessionHeader("sess-abort"));
468
- child.stdout.write(askUserLine("req-abort") + "\n");
469
- // 让 handler 入队(但未 resolve)
470
- await new Promise((r) => setTimeout(r, 20));
471
-
472
- // 子进程先退出(handler 仍 pending)→ onClose abort
473
- child.stdout.end();
474
- child.emit("close", 0);
475
- await promise;
476
-
477
- // 现在 handler 仍 pending;resolve 它(模拟延迟响应)
478
- expect(resolveHandler).toBeDefined();
479
- resolveHandler!({ value: "late-answer" });
480
- // 给 finally 块执行时间
481
- await new Promise((r) => setTimeout(r, 20));
482
-
483
- // child.stdin 不应收到 extension_ui_response(signal.aborted → respond 跳过)
484
- const responses = readStdinLines(child).filter(
485
- (l): l is { type: string; id: string } =>
486
- typeof l === "object" && l !== null && (l as { type?: string }).type === "extension_ui_response",
487
- );
488
- expect(responses).toHaveLength(0);
489
- });
490
- });
@@ -1,191 +0,0 @@
1
- // src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts
2
- //
3
- // [V2 决策 2] chatMode 首轮闭环——runSpawn resolve 时机(Step 4a 改动 1)。
4
- //
5
- // 改动 1:chatMode agent_settled 时提前 resolve runSpawn 的 exitCode promise(exit code 0),
6
- // 让 runAndFinalize 拿到首轮 result,进程仍保活(idle timer armed)。非 chatMode 不变
7
- //(agent_end kill → close resolve)。
8
- //
9
- // 本文件验证 resolve 时机(run-spawn-chatmode-settled.test.ts 验证 idle timer + 回调挂载点,
10
- // 收尾靠 emit close 让 resolve——隐含 agent_settled 不 resolve;本文件显式断言 agent_settled
11
- // 即 resolve,且进程未 close)。
12
- //
13
- // mock 结构与 run-spawn-chatmode-settled.test.ts 一致(FakeChild + session-pending count=0)。
14
-
15
- import { spawn } from "node:child_process";
16
- import * as fs from "node:fs";
17
-
18
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
19
-
20
- vi.mock("node:child_process", async () => {
21
- const { FakeChild } = await import("./helpers/spawn-mock.ts");
22
- return {
23
- spawn: vi.fn(() => new FakeChild()),
24
- // buildEnvBlock 的 git branch 调用(execFile 异步):默认 err-first 兜底 → catch → branch=""
25
- execFile: vi.fn(
26
- (
27
- _cmd: string,
28
- _args: readonly string[],
29
- _opts: unknown,
30
- cb: (err: Error | null, stdout?: string, stderr?: string) => void,
31
- ) => cb(new Error("execFile not configured in this test")),
32
- ),
33
- };
34
- });
35
-
36
- vi.mock("node:fs", async () => {
37
- const actual = await import("node:fs");
38
- return {
39
- default: {
40
- ...actual,
41
- mkdirSync: vi.fn(),
42
- existsSync: vi.fn(() => false),
43
- appendFileSync: vi.fn(),
44
- writeFileSync: vi.fn(),
45
- readdirSync: vi.fn(() => []),
46
- },
47
- mkdirSync: vi.fn(),
48
- existsSync: vi.fn(() => false),
49
- appendFileSync: vi.fn(),
50
- writeFileSync: vi.fn(),
51
- readdirSync: vi.fn(() => []),
52
- promises: actual.promises,
53
- };
54
- });
55
-
56
- vi.mock("../alive-store.ts", () => ({ writeAliveMarker: vi.fn() }));
57
-
58
- // chatMode agent_end 早返回(continue),不读 session-pending;统一 count=0 防干扰。
59
- vi.mock("../session-pending.ts", () => ({
60
- readActivePendingFromSessionFile: vi.fn(() => ({ count: 0 })),
61
- prunePendingCursor: vi.fn(),
62
- }));
63
-
64
- vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
65
- writePromptToTempFile: vi.fn(async (agent: string) => {
66
- const safeName = agent.replace(/[^\w.-]+/g, "_");
67
- return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
68
- }),
69
- cleanupTempPrompt: vi.fn(async () => {}),
70
- }));
71
-
72
- import { runSpawn } from "../engine/engines/pi/session-runner.ts";
73
- import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
74
- import { _resetLifecycleState } from "../lifecycle-manager.ts";
75
- import { createRecord } from "../execution-record.ts";
76
- import type { ExecutionRecord } from "../types.ts";
77
- import {
78
- emitStdoutLine,
79
- type FakeChild,
80
- lastSpawnedChild as lastSpawnedChildOf,
81
- makeCtx,
82
- makeOpts,
83
- makeRecord,
84
- sessionHeader,
85
- waitForSpawn as waitForSpawnOf,
86
- } from "./helpers/spawn-mock.ts";
87
-
88
- const mockSpawn = vi.mocked(spawn);
89
-
90
- const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
91
- const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
92
-
93
- /** 构造 chatMode record(spawn-mock.makeRecord 默认非 chatMode,此处显式开启)。 */
94
- function makeChatModeRecord(id = "sa-chat"): ExecutionRecord {
95
- return createRecord(id, {
96
- agent: "general-purpose",
97
- model: "test-model",
98
- mode: "background",
99
- task: "chat task",
100
- slug: "chat",
101
- startedAt: 1_000_000,
102
- rootSessionId: "root-session",
103
- parentRecordId: undefined,
104
- depth: 0,
105
- chatMode: true,
106
- });
107
- }
108
-
109
- /** 用 race 探测 promise 是否在指定时间内 settle(resolve 或 reject)。 */
110
- async function settlesWithin(p: Promise<unknown>, ms: number): Promise<boolean> {
111
- return Promise.race([
112
- p.then(
113
- () => true,
114
- () => true,
115
- ),
116
- new Promise<boolean>((r) => setTimeout(() => r(false), ms)),
117
- ]);
118
- }
119
-
120
- describe("[V2 决策 2] chatMode 首轮闭环:runSpawn resolve 时机(改动 1)", () => {
121
- beforeEach(() => {
122
- vi.clearAllMocks();
123
- _resetLifecycleState();
124
- });
125
-
126
- afterEach(() => {
127
- vi.restoreAllMocks();
128
- vi.useRealTimers();
129
- _resetLifecycleState();
130
- });
131
-
132
- it("chatMode agent_settled → runSpawn 提前 resolve(不等 close),进程仍保活", async () => {
133
- const record = makeChatModeRecord("sa-first-resolve");
134
- const promise = runSpawn(record, "Task: first-round", makeOpts(), makeCtx());
135
-
136
- await waitForSpawn();
137
- const child = lastSpawnedChild();
138
-
139
- emitStdoutLine(child, sessionHeader());
140
- emitStdoutLine(child, { type: "agent_end", willRetry: false });
141
- // 让 stdout flush 处理 agent_end(chatMode 分支 continue,不 kill)
142
- await new Promise((r) => setTimeout(r, 10));
143
- emitStdoutLine(child, { type: "agent_settled" });
144
-
145
- // [改动 1 核心] agent_settled 后 runSpawn 立即 resolve(resolveRun(0)),不等 close。
146
- // 若改动 1 未生效,promise 会 hang 等 close,settlesWithin 返回 false。
147
- expect(await settlesWithin(promise, 500)).toBe(true);
148
- const result = await promise;
149
- expect(result.success).toBe(true);
150
- expect(result.sessionId).toBe("sess-abc");
151
-
152
- // 进程仍保活:agent_settled 只 resolve promise,未 kill、未 emit close。
153
- // idle timer(5min 真实 setTimeout)armed 但未到期。
154
- expect(child.killed).toBe(false);
155
-
156
- // 收尾:手动 close(模拟后续 idle timer 超时 kill / 外部终止)。
157
- // close handler 的 cleanup 仍执行(resolve(code) 是 no-op,Promise 已 resolve)。
158
- child.stdout.end();
159
- child.stderr.end();
160
- child.emit("close", 0);
161
- });
162
-
163
- it("非 chatMode runSpawn 仍在 close resolve(agent_settled 不触发 resolve)", async () => {
164
- // makeRecord 默认非 chatMode
165
- const record = makeRecord("sa-nonchat-resolve");
166
- const promise = runSpawn(record, "Task: nonchat", makeOpts(), makeCtx());
167
-
168
- await waitForSpawn();
169
- const child = lastSpawnedChild();
170
-
171
- emitStdoutLine(child, sessionHeader());
172
- emitStdoutLine(child, { type: "agent_end", willRetry: false });
173
- // 非 chatMode agent_end → count=0 → SIGTERM kill
174
- await new Promise((r) => setTimeout(r, 10));
175
- expect(child.killed).toBe(true);
176
-
177
- // agent_settled 在非 chatMode 下被忽略(handler 直接 return,不调 resolveRun)
178
- emitStdoutLine(child, { type: "agent_settled" });
179
- await new Promise((r) => setTimeout(r, 20));
180
-
181
- // [回归防护] agent_settled 不 resolve 非 chatMode runSpawn——promise 仍 pending(等 close)。
182
- expect(await settlesWithin(promise, 200)).toBe(false);
183
-
184
- // emit close 才 resolve(现有行为,改动 1 不影响非 chatMode)
185
- child.stdout.end();
186
- child.stderr.end();
187
- child.emit("close", 143);
188
- const result = await promise;
189
- expect(result.success).toBe(true); // 信号终止(143 ≥ threshold)视为正常完成
190
- });
191
- });