@zhushanwen/pi-subagent-workflow 8.6.0 → 8.8.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 (364) hide show
  1. package/README.md +5 -11
  2. package/package.json +13 -20
  3. package/relay/relay.mjs +1 -1
  4. package/skills/workflow-script-format/SKILL.md +32 -13
  5. package/src/host/__tests__/pi-host.test.ts +235 -0
  6. package/src/host/pi-host.ts +163 -0
  7. package/src/index.ts +141 -138
  8. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  9. package/src/injectors/__tests__/engine-section-stability.test.ts +343 -0
  10. package/src/injectors/__tests__/model-list-injector.test.ts +18 -21
  11. package/src/injectors/__tests__/subagent-list-injector.test.ts +59 -19
  12. package/src/injectors/__tests__/workflow-list-injector.test.ts +31 -17
  13. package/src/injectors/engine-awareness.ts +119 -0
  14. package/src/injectors/model-list-injector.ts +15 -58
  15. package/src/injectors/subagent-list-injector.ts +56 -114
  16. package/src/injectors/workflow-list-injector.ts +37 -70
  17. package/src/interface/__tests__/detectors.test.ts +48 -37
  18. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +2 -2
  19. package/src/interface/__tests__/subagent-tool-prompt.test.ts +6 -3
  20. package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +216 -0
  21. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +17 -6
  22. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +38 -2
  23. package/src/interface/bg-notify-render.ts +7 -17
  24. package/src/interface/command-actions.ts +5 -15
  25. package/src/interface/commands.ts +4 -4
  26. package/src/interface/format.ts +17 -6
  27. package/src/interface/gui-mappers.ts +18 -22
  28. package/src/interface/helpers.ts +9 -130
  29. package/src/interface/list-component.ts +5 -5
  30. package/src/interface/list-shared.ts +3 -3
  31. package/src/interface/list-view.ts +3 -3
  32. package/src/interface/subagent-actions.ts +66 -439
  33. package/src/interface/subagent-tool-schema.ts +21 -17
  34. package/src/interface/subagent-tool.ts +12 -7
  35. package/src/interface/subagents.ts +4 -4
  36. package/src/interface/tool-render.ts +37 -15
  37. package/src/interface/tool-workflow-script.ts +34 -76
  38. package/src/interface/tool-workflow.ts +57 -94
  39. package/src/interface/views/WorkflowsView.ts +8 -16
  40. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  41. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  42. package/src/interface/views/detail-content.ts +4 -4
  43. package/src/interface/views/format.ts +23 -62
  44. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +137 -225
  45. package/agents/analyst.md +0 -61
  46. package/agents/coder.md +0 -70
  47. package/agents/debugger.md +0 -67
  48. package/agents/doc-reviewer.md +0 -50
  49. package/agents/explorer.md +0 -64
  50. package/agents/general-purpose.md +0 -32
  51. package/agents/orchestrator.md +0 -63
  52. package/agents/planner.md +0 -54
  53. package/agents/researcher.md +0 -65
  54. package/agents/reviewer.md +0 -74
  55. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  56. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  57. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  58. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  59. package/src/execution/__tests__/alive-store.test.ts +0 -147
  60. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  61. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  62. package/src/execution/__tests__/bg-notify-render.test.ts +0 -329
  63. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  64. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -601
  65. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  66. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  67. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  68. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  69. package/src/execution/__tests__/config.test.ts +0 -110
  70. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  71. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  72. package/src/execution/__tests__/delivery-methods.test.ts +0 -422
  73. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  74. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  75. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  76. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  77. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  78. package/src/execution/__tests__/execution-record.test.ts +0 -1394
  79. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +0 -171
  80. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  81. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  82. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -169
  83. package/src/execution/__tests__/format.test.ts +0 -458
  84. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  85. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  86. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  87. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  88. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -242
  89. package/src/execution/__tests__/host-mode.test.ts +0 -87
  90. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  91. package/src/execution/__tests__/index-session-start.test.ts +0 -672
  92. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  93. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -383
  94. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  95. package/src/execution/__tests__/list-component.test.ts +0 -401
  96. package/src/execution/__tests__/list-fields.test.ts +0 -140
  97. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  98. package/src/execution/__tests__/model-resolver.test.ts +0 -465
  99. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  100. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  101. package/src/execution/__tests__/notifier-flush.test.ts +0 -612
  102. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  103. package/src/execution/__tests__/notify-ledger.test.ts +0 -826
  104. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  105. package/src/execution/__tests__/output-collector.test.ts +0 -358
  106. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  107. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  108. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  109. package/src/execution/__tests__/record-store.test.ts +0 -1057
  110. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  111. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  112. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  113. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  114. package/src/execution/__tests__/relay-env.test.ts +0 -42
  115. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  116. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  117. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  118. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  119. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -687
  120. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  121. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  122. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  123. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -199
  124. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  125. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  126. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  127. package/src/execution/__tests__/session-pending.test.ts +0 -197
  128. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  129. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  130. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -350
  131. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  132. package/src/execution/__tests__/spawn-args.test.ts +0 -445
  133. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  134. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  135. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  136. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  137. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  138. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  139. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  140. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  141. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  142. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  143. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  144. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  145. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  146. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  147. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -599
  148. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  149. package/src/execution/__tests__/timeout-integration.test.ts +0 -615
  150. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  151. package/src/execution/__tests__/tool-action.test.ts +0 -476
  152. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  153. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  154. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  155. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  156. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  157. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  158. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  159. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  160. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  161. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  162. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  163. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  164. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  165. package/src/execution/agent-registry.ts +0 -199
  166. package/src/execution/agent-result-mapper.ts +0 -90
  167. package/src/execution/alive-store.ts +0 -92
  168. package/src/execution/argv-mirror.ts +0 -113
  169. package/src/execution/best-effort.ts +0 -38
  170. package/src/execution/channel-registry-access.ts +0 -144
  171. package/src/execution/concurrency-pool.ts +0 -116
  172. package/src/execution/config.ts +0 -96
  173. package/src/execution/dialog-queue.ts +0 -330
  174. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  175. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  176. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  177. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  178. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  179. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  180. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  181. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  182. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  183. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  184. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  185. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  186. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  187. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  188. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  189. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  190. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  191. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  192. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  193. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  194. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  195. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  196. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  197. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  198. package/src/execution/engine/common/data-dir.ts +0 -62
  199. package/src/execution/engine/common/errors.ts +0 -183
  200. package/src/execution/engine/common/event-journal.ts +0 -254
  201. package/src/execution/engine/common/journal-replay.ts +0 -62
  202. package/src/execution/engine/common/kill-chain.ts +0 -221
  203. package/src/execution/engine/common/nesting-guard.ts +0 -50
  204. package/src/execution/engine/common/persona-router.ts +0 -108
  205. package/src/execution/engine/common/pool-manager.ts +0 -226
  206. package/src/execution/engine/common/schema-emulation.ts +0 -189
  207. package/src/execution/engine/common/session-view-projection.ts +0 -51
  208. package/src/execution/engine/engine-discovery.ts +0 -65
  209. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  210. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  211. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  212. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  213. package/src/execution/engine/engines/pi/reader.ts +0 -48
  214. package/src/execution/engine/engines/pi/registration.ts +0 -35
  215. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  216. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  217. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  218. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  219. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  220. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  221. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  222. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  223. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  224. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  225. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  226. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  227. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  228. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  229. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  230. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  231. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -658
  232. package/src/execution/engine/host-task-spec.ts +0 -47
  233. package/src/execution/engine/model-prompt.ts +0 -59
  234. package/src/execution/engine/paths.ts +0 -42
  235. package/src/execution/engine/port.ts +0 -153
  236. package/src/execution/engine/registry.ts +0 -123
  237. package/src/execution/engine/routing.ts +0 -218
  238. package/src/execution/engine/types.ts +0 -309
  239. package/src/execution/execute-options-mapper.ts +0 -112
  240. package/src/execution/execution-record.ts +0 -965
  241. package/src/execution/finalize-record.ts +0 -251
  242. package/src/execution/finalized-marker.ts +0 -51
  243. package/src/execution/get-state-handshake.ts +0 -104
  244. package/src/execution/host-mode.ts +0 -56
  245. package/src/execution/idle-gc.ts +0 -47
  246. package/src/execution/lifecycle-manager.ts +0 -513
  247. package/src/execution/lifecycle-predicates.ts +0 -65
  248. package/src/execution/manifest-store.ts +0 -256
  249. package/src/execution/model-config-service.ts +0 -219
  250. package/src/execution/model-resolver.ts +0 -248
  251. package/src/execution/notifier.ts +0 -330
  252. package/src/execution/notify-ledger.ts +0 -580
  253. package/src/execution/output-collector.ts +0 -228
  254. package/src/execution/path-encoding.ts +0 -60
  255. package/src/execution/pi-invocation.ts +0 -120
  256. package/src/execution/record-entry.ts +0 -132
  257. package/src/execution/record-store.ts +0 -1234
  258. package/src/execution/relay-env.ts +0 -37
  259. package/src/execution/session-context-resolver.ts +0 -64
  260. package/src/execution/session-file-gc.ts +0 -120
  261. package/src/execution/session-pending.ts +0 -164
  262. package/src/execution/session-reconstructor.ts +0 -678
  263. package/src/execution/session-runner.ts +0 -1781
  264. package/src/execution/sessions-index.ts +0 -304
  265. package/src/execution/spawn-event-adapter.ts +0 -363
  266. package/src/execution/stdin-writer.ts +0 -198
  267. package/src/execution/stream-sink.ts +0 -126
  268. package/src/execution/subagent-service.ts +0 -2141
  269. package/src/execution/subprocess-agent-runner.ts +0 -317
  270. package/src/execution/temp-prompt.ts +0 -62
  271. package/src/execution/tombstone-store.ts +0 -72
  272. package/src/execution/turn-limiter.ts +0 -102
  273. package/src/execution/types.ts +0 -973
  274. package/src/execution/ui-channels.ts +0 -216
  275. package/src/execution/ui-interaction-model.ts +0 -48
  276. package/src/execution/ui-request-handler-factory.ts +0 -212
  277. package/src/execution/ui-request-observability.ts +0 -81
  278. package/src/execution/ui-request-queue.ts +0 -184
  279. package/src/execution/worktree-manager.ts +0 -688
  280. package/src/execution/worktree-registry.ts +0 -222
  281. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -349
  282. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -202
  283. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -152
  284. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  285. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  286. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -809
  287. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  288. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  289. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  290. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -451
  291. package/src/orchestration/__tests__/jsonl-run-store-corrupt-entry.test.ts +0 -150
  292. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  293. package/src/orchestration/__tests__/jsonl-run-store-retention.test.ts +0 -202
  294. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  295. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -600
  296. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  297. package/src/orchestration/__tests__/lifecycle.test.ts +0 -659
  298. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +0 -95
  299. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -2025
  300. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  301. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -201
  302. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  303. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +0 -368
  304. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  305. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  306. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -563
  307. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  308. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -129
  309. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  310. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  311. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  312. package/src/orchestration/agent-opts-resolver.ts +0 -174
  313. package/src/orchestration/args-validator.ts +0 -127
  314. package/src/orchestration/config-loader.ts +0 -315
  315. package/src/orchestration/error-recovery.ts +0 -1018
  316. package/src/orchestration/execute-agent-call.ts +0 -256
  317. package/src/orchestration/launcher.ts +0 -455
  318. package/src/orchestration/lifecycle.ts +0 -399
  319. package/src/orchestration/models/__tests__/budget.test.ts +0 -307
  320. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  321. package/src/orchestration/models/agent-call.ts +0 -83
  322. package/src/orchestration/models/budget.ts +0 -118
  323. package/src/orchestration/models/ports.ts +0 -164
  324. package/src/orchestration/models/run-runtime.ts +0 -104
  325. package/src/orchestration/models/run-spec.ts +0 -83
  326. package/src/orchestration/models/run-state.ts +0 -44
  327. package/src/orchestration/models/trace.ts +0 -183
  328. package/src/orchestration/models/types.ts +0 -301
  329. package/src/orchestration/models/workflow-run.ts +0 -254
  330. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  331. package/src/orchestration/models/workflow-script.ts +0 -117
  332. package/src/orchestration/script-lint.ts +0 -766
  333. package/src/orchestration/skill-discovery.ts +0 -115
  334. package/src/orchestration/worker-handle.ts +0 -115
  335. package/src/orchestration/worker-host.ts +0 -98
  336. package/src/orchestration/worker-script-builder.ts +0 -421
  337. package/src/orchestration/workflow-files.ts +0 -85
  338. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  339. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  340. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  341. package/src/shared/__tests__/model-ref.test.ts +0 -306
  342. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  343. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  344. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  345. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  346. package/src/shared/__tests__/timer-delay.test.ts +0 -61
  347. package/src/shared/agent-event.ts +0 -13
  348. package/src/shared/agent-ref.ts +0 -57
  349. package/src/shared/meta-parser.ts +0 -261
  350. package/src/shared/model-ref.ts +0 -286
  351. package/src/shared/resource-discovery.ts +0 -811
  352. package/src/shared/resource-meta.ts +0 -65
  353. package/src/shared/schema-env.ts +0 -44
  354. package/src/shared/schema-jsonify.ts +0 -58
  355. package/src/shared/timer-delay.ts +0 -54
  356. package/src/shared/xml-injection.ts +0 -35
  357. package/workflows/README.md +0 -81
  358. package/workflows/_shared/agent-refs.cjs +0 -40
  359. package/workflows/chain.js +0 -131
  360. package/workflows/map-reduce.js +0 -174
  361. package/workflows/parallel.js +0 -148
  362. package/workflows/review-fix-loop-utils.cjs +0 -1309
  363. package/workflows/review-fix-loop.js +0 -1404
  364. package/workflows/scatter-gather.js +0 -178
@@ -1,933 +0,0 @@
1
- // src/__tests__/run-spawn-integration.test.ts
2
- //
3
- // runSpawn 集成测试(spawn 改造核心函数)。
4
- //
5
- // runSpawn 负责 spawn pi 子进程、pump stdout JSON 事件流、signal/maxTurns 终止、
6
- // identity 补写、exitCode 判定。此前完全无集成测试——本文件覆盖审查发现的关键路径。
7
- //
8
- // mock 策略(参考 worktree-manager.test.ts 的 mock 模式):
9
- // - node:child_process.spawn → 返回 FakeChild(EventEmitter + PassThrough),
10
- // 测试用控制器 emit data/close/error 控制时序。
11
- // - node:child_process.execFile → err-first callback 默认兜底(buildEnvBlock 的 git
12
- // branch 调用失败 → catch → branch="")。
13
- // - node:fs 同步方法 → mock(mkdirSync/existsSync/appendFileSync/writeFileSync 等),
14
- // 避免 sessionDir/sessionFile 触碰真实文件系统。
15
- // - fs.promises.* → 保留真实实现(temp-prompt 整体被 mock,不触发真实 I/O)。
16
- // - temp-prompt → mock(writePromptToTempFile 返回固定路径,消除 fake-timers flaky)。
17
- // - alive-store.writeAliveMarker → mock(避免写 .alive sidecar)。
18
- //
19
- // mock 工厂 + FakeChild class + 工具函数(lastSpawnedChild/waitForSpawn/emitStdoutLine/
20
- // sessionHeader/makeRecord/makeOpts/makeCtx)抽到 helpers/spawn-mock.ts,与
21
- // run-spawn-edges.test.ts / run-spawn-rpc-mode.test.ts 三文件共享。vi.mock 工厂内用
22
- // `await import("./helpers/spawn-mock.ts")` 取回 FakeChild(绕开 vitest 的 hoisting 限制——
23
- // 工厂函数体不能引用顶层 import 变量,但 async 工厂内的 await import 是运行时求值)。
24
-
25
- import { spawn } from "node:child_process";
26
- import * as fs from "node:fs";
27
-
28
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
29
-
30
- // ── mock modules(工厂体共享自 helpers/spawn-mock.ts;vi.mock 必须各文件独立声明)──
31
-
32
- vi.mock("node:child_process", async () => {
33
- const { FakeChild } = await import("./helpers/spawn-mock.ts");
34
- return {
35
- spawn: vi.fn(() => new FakeChild()),
36
- // buildEnvBlock 的 git branch 调用(execFile 异步):默认 err-first 兜底 → catch → branch=""
37
- execFile: vi.fn(
38
- (
39
- _cmd: string,
40
- _args: readonly string[],
41
- _opts: unknown,
42
- cb: (err: Error | null, stdout?: string, stderr?: string) => void,
43
- ) => cb(new Error("execFile not configured in this test")),
44
- ),
45
- };
46
- });
47
-
48
- // node:fs:同步方法 mock(runSpawn 用到的全部),promises 保留真实实现(temp-prompt 用)。
49
- vi.mock("node:fs", async () => {
50
- const actual = await import("node:fs");
51
- return {
52
- default: {
53
- ...actual,
54
- mkdirSync: vi.fn(),
55
- existsSync: vi.fn(() => false),
56
- appendFileSync: vi.fn(),
57
- writeFileSync: vi.fn(),
58
- readdirSync: vi.fn(() => []),
59
- },
60
- mkdirSync: vi.fn(),
61
- existsSync: vi.fn(() => false),
62
- appendFileSync: vi.fn(),
63
- writeFileSync: vi.fn(),
64
- readdirSync: vi.fn(() => []),
65
- promises: actual.promises,
66
- };
67
- });
68
-
69
- vi.mock("../alive-store.ts", () => ({
70
- writeAliveMarker: vi.fn(),
71
- }));
72
-
73
- // temp-prompt:mock 掉真实 fs.promises I/O(mkdtemp/writeFile/rm)。
74
- // 原先保留真实实现导致 fake-timers 测试偶发 flaky——writePromptToTempFile 的真实异步
75
- // I/O 在 CI 慢机器上无法在 advanceTimersByTimeAsync 的有限步数内 resolve,spawn 永不触发。
76
- vi.mock("../temp-prompt.ts", () => ({
77
- writePromptToTempFile: vi.fn(async (agent: string) => {
78
- const safeName = agent.replace(/[^\w.-]+/g, "_");
79
- return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
80
- }),
81
- cleanupTempPrompt: vi.fn(async () => {}),
82
- }));
83
-
84
- import { runSpawn } from "../session-runner.ts";
85
- import {
86
- emitStdoutLine,
87
- type FakeChild,
88
- lastSpawnedChild as lastSpawnedChildOf,
89
- makeCtx,
90
- makeOpts,
91
- makeRecord,
92
- mockSessionFileExists as mockSessionFileExistsOf,
93
- sessionHeader,
94
- waitForSpawn as waitForSpawnOf,
95
- } from "./helpers/spawn-mock.ts";
96
-
97
- const mockSpawn = vi.mocked(spawn);
98
- const mockExistsSync = vi.mocked(fs.existsSync);
99
- const mockAppendFileSync = vi.mocked(fs.appendFileSync);
100
- const mockMkdirSync = vi.mocked(fs.mkdirSync);
101
-
102
- // 绑定到本文件 mockSpawn 的 lastSpawnedChild/waitForSpawn(需读 mockSpawn.mock.results)
103
- const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
104
- const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
105
- const mockSessionFileExists = (p: string): void => mockSessionFileExistsOf(mockExistsSync, p);
106
-
107
- // ============================================================
108
- // 测试
109
- // ============================================================
110
-
111
- describe("runSpawn", () => {
112
- beforeEach(() => {
113
- vi.clearAllMocks();
114
- // existsSync 默认 false(sessionFile 不存在兜底路径)
115
- mockExistsSync.mockReturnValue(false);
116
- });
117
-
118
- afterEach(() => {
119
- vi.restoreAllMocks();
120
- });
121
-
122
- // ── 1. 正常路径(happy path)──
123
- describe("正常路径", () => {
124
- it("header + turn_end 事件 + close(0) → success=true,record.sessionFile 被设置,turnCount 正确", async () => {
125
- const record = makeRecord();
126
- const promise = runSpawn(record, "Task: hello", makeOpts(), makeCtx());
127
-
128
- // 等待 spawn 被调用拿到 child
129
- // 等待 spawn 被调用拿到 child
130
- await waitForSpawn();
131
- const child = lastSpawnedChild();
132
-
133
- const expectedSessionFile =
134
- "/tmp/test/agents/subagents/--tmp-test--/sessions/2026-07-03T12-00-00-000Z_sess-abc.jsonl";
135
- // 进程退出后 existsSync(record.sessionFile) 校验通过 → 补写 identity
136
- mockSessionFileExists(expectedSessionFile);
137
-
138
- // emit stdout:header + 2 个 turn_end
139
- emitStdoutLine(child, sessionHeader("sess-abc"));
140
- emitStdoutLine(child, { type: "turn_end" });
141
- emitStdoutLine(child, { type: "turn_end" });
142
- child.stdout.end();
143
- child.emit("close", 0);
144
-
145
- const result = await promise;
146
-
147
- expect(result.success).toBe(true);
148
- expect(result.turns).toBe(2); // 2 个 turn_end
149
- expect(record.turnCount).toBe(2);
150
- // record.sessionFile 由 header 推导路径回填
151
- expect(record.sessionFile).toBe(expectedSessionFile);
152
- expect(result.sessionFile).toBe(expectedSessionFile);
153
- // sessionId 来自 header.id
154
- expect(result.sessionId).toBe("sess-abc");
155
- });
156
-
157
- it("collectResult 文本/toolCalls 从 record 派生(text_delta + tool 调用累积进 turns)", async () => {
158
- const record = makeRecord();
159
- const promise = runSpawn(record, "Task: write", makeOpts(), makeCtx());
160
-
161
- // 等待 spawn 被调用拿到 child
162
- await waitForSpawn();
163
- const child = lastSpawnedChild();
164
- mockSessionFileExists(
165
- "/tmp/test/agents/subagents/--tmp-test--/sessions/2026-07-03T12-00-00-000Z_sess-x.jsonl",
166
- );
167
-
168
- emitStdoutLine(child, sessionHeader("sess-x"));
169
- emitStdoutLine(child, { type: "message_update", assistantMessageEvent: { type: "text_delta", delta: "Hello " } });
170
- emitStdoutLine(child, { type: "message_update", assistantMessageEvent: { type: "text_delta", delta: "world" } });
171
- emitStdoutLine(child, { type: "tool_execution_start", toolCallId: "t1", toolName: "bash", args: { command: "ls" } });
172
- emitStdoutLine(child, { type: "tool_execution_end", toolCallId: "t1", toolName: "bash", result: { details: "file.txt" } });
173
- emitStdoutLine(child, { type: "turn_end" });
174
- child.stdout.end();
175
- child.emit("close", 0);
176
-
177
- const result = await promise;
178
-
179
- expect(result.success).toBe(true);
180
- expect(result.text).toBe("Hello world");
181
- expect(result.toolCalls).toHaveLength(1);
182
- expect(result.toolCalls[0]!.toolName).toBe("bash");
183
- expect(result.toolCalls[0]!.result?.details).toBe("file.txt");
184
- });
185
- });
186
-
187
- // ── 2. signal abort → child.kill(cancel 路径)──
188
- describe("signal abort", () => {
189
- it("abort signal 触发后 child.kill(SIGTERM) 被调用,success=false", async () => {
190
- const controller = new AbortController();
191
- const record = makeRecord();
192
- const promise = runSpawn(
193
- record,
194
- "Task: long",
195
- makeOpts({ signal: controller.signal }),
196
- makeCtx(),
197
- );
198
-
199
- // 等待 spawn 被调用拿到 child
200
- await waitForSpawn();
201
- const child = lastSpawnedChild();
202
-
203
- // abort 必须在 spawn 之后(addEventListener 已注册)。
204
- // 用 queueMicrotask 延迟到当前微任务清空后触发,确保 listener 已挂载。
205
- queueMicrotask(() => controller.abort());
206
-
207
- // emit header + close(被 kill 后子进程退出,signal 终止 exitCode>=128 或 null)
208
- emitStdoutLine(child, sessionHeader());
209
- child.stdout.end();
210
- child.emit("close", 143); // SIGTERM = 128+15
211
-
212
- const result = await promise;
213
-
214
- expect(child.killed).toBe(true);
215
- expect(child.killSignal).toBe("SIGTERM");
216
- // signal.aborted 路径:success=false,但 error 为 undefined(取消不算 error)
217
- expect(result.success).toBe(false);
218
- expect(result.error).toBeUndefined();
219
- });
220
- });
221
-
222
- // ── 3. maxTurns → limiter.abort → proc.kill ──
223
- describe("maxTurns 限制", () => {
224
- it("maxTurns=1, graceTurns=0 → 超出 turn 后 child.kill 被调用,success=true(达限视为正常完成)", async () => {
225
- const record = makeRecord();
226
- const promise = runSpawn(
227
- record,
228
- "Task: bounded",
229
- makeOpts({ maxTurns: 1, graceTurns: 0 }),
230
- makeCtx(),
231
- );
232
-
233
- // 等待 spawn 被调用拿到 child
234
- await waitForSpawn();
235
- const child = lastSpawnedChild();
236
-
237
- emitStdoutLine(child, sessionHeader());
238
- // 第 1 个 turn_end:turn=1 >= maxTurns=1 → steer(noop)
239
- emitStdoutLine(child, { type: "turn_end" });
240
- // 第 2 个 turn_end:turn=2 >= maxTurns(1)+graceTurns(0)=1 → abort → proc.kill
241
- emitStdoutLine(child, { type: "turn_end" });
242
- child.stdout.end();
243
- // 被 kill 后信号终止(exitCode>=128),视为正常完成
244
- child.emit("close", 143);
245
-
246
- const result = await promise;
247
-
248
- expect(child.killed).toBe(true);
249
- expect(child.killSignal).toBe("SIGTERM");
250
- // maxTurns kill 视为正常完成(exitCode>=128 走信号终止分支 → success=true)
251
- expect(result.success).toBe(true);
252
- });
253
-
254
- it("maxTurns=2, graceTurns=2 → steer 后需 graceTurns 到达才 kill(第 4 turn 才 abort)", async () => {
255
- const record = makeRecord();
256
- const promise = runSpawn(
257
- record,
258
- "Task: grace",
259
- makeOpts({ maxTurns: 2, graceTurns: 2 }),
260
- makeCtx(),
261
- );
262
-
263
- // 等待 spawn 被调用拿到 child
264
- await waitForSpawn();
265
- const child = lastSpawnedChild();
266
-
267
- // emit header + 4 个 turn_end。limiter 逻辑(maxTurns=2, graceTurns=2):
268
- // turn=1: 1<2,无动作
269
- // turn=2: 2>=limit → steer(仅一次)
270
- // turn=3: 3<limit+grace(4),仍在宽限内
271
- // turn=4: 4>=4 → abort → proc.kill(SIGTERM)
272
- // 注:PassThrough 流加 data 监听器后,缓冲块逐个在后续微任务中 flush,close 前 await
273
- // 较长时间窗(20ms)确保全部 turn_end 已被 data 处理器消费(含 limiter.abort → kill)。
274
- emitStdoutLine(child, sessionHeader());
275
- emitStdoutLine(child, { type: "turn_end" }); // turn=1
276
- emitStdoutLine(child, { type: "turn_end" }); // turn=2: steer
277
- emitStdoutLine(child, { type: "turn_end" }); // turn=3: 宽限内
278
- emitStdoutLine(child, { type: "turn_end" }); // turn=4: abort → kill
279
- child.stdout.end();
280
- await new Promise((r) => setTimeout(r, 20));
281
- child.emit("close", 143);
282
-
283
- const result = await promise;
284
-
285
- // 第 4 个 turn_end 触发 abort → child.kill(SIGTERM)
286
- expect(child.killed).toBe(true);
287
- expect(child.killSignal).toBe("SIGTERM");
288
- expect(record.turnCount).toBe(4);
289
- // exitCode>=128(信号终止)→ success=true(达限视为正常完成)
290
- expect(result.success).toBe(true);
291
- });
292
- });
293
-
294
- // ── 4. 子进程非零退出(exitCode < 128)──
295
- describe("非零退出码", () => {
296
- it("close(1) + stderr 内容 → success=false,error 含 stderr", async () => {
297
- const record = makeRecord();
298
- const promise = runSpawn(record, "Task: fail", makeOpts(), makeCtx());
299
-
300
- // 等待 spawn 被调用拿到 child
301
- await waitForSpawn();
302
- const child = lastSpawnedChild();
303
-
304
- emitStdoutLine(child, sessionHeader());
305
- child.stderr.write("Error: something went wrong\n");
306
- child.stderr.end();
307
- child.stdout.end();
308
- child.emit("close", 1);
309
-
310
- const result = await promise;
311
-
312
- expect(result.success).toBe(false);
313
- expect(result.error).toContain("something went wrong");
314
- });
315
-
316
- it("close(1) 无 stderr → error 含 exit code 信息", async () => {
317
- const record = makeRecord();
318
- const promise = runSpawn(record, "Task: fail", makeOpts(), makeCtx());
319
-
320
- // 等待 spawn 被调用拿到 child
321
- await waitForSpawn();
322
- const child = lastSpawnedChild();
323
-
324
- child.stdout.end();
325
- child.stderr.end();
326
- child.emit("close", 1);
327
-
328
- const result = await promise;
329
-
330
- expect(result.success).toBe(false);
331
- expect(result.error).toContain("exited with code 1");
332
- });
333
- });
334
-
335
- // ── 5. 子进程被信号终止(exitCode = null 或 >= 128)──
336
- describe("信号终止", () => {
337
- it("close(null) → success=true(信号终止视为正常,非 aborted)", async () => {
338
- const record = makeRecord();
339
- const promise = runSpawn(record, "Task: sig", makeOpts(), makeCtx());
340
-
341
- // 等待 spawn 被调用拿到 child
342
- await waitForSpawn();
343
- const child = lastSpawnedChild();
344
-
345
- emitStdoutLine(child, sessionHeader());
346
- child.stdout.end();
347
- child.stderr.end();
348
- child.emit("close", null);
349
-
350
- const result = await promise;
351
-
352
- expect(result.success).toBe(true);
353
- });
354
-
355
- it("close(143)(SIGTERM,>=128)无 signal → success=true", async () => {
356
- const record = makeRecord();
357
- const promise = runSpawn(record, "Task: sig143", makeOpts(), makeCtx());
358
-
359
- // 等待 spawn 被调用拿到 child
360
- await waitForSpawn();
361
- const child = lastSpawnedChild();
362
-
363
- emitStdoutLine(child, sessionHeader());
364
- child.stdout.end();
365
- child.stderr.end();
366
- child.emit("close", 143);
367
-
368
- const result = await promise;
369
-
370
- expect(result.success).toBe(true);
371
- });
372
- });
373
-
374
- // ── 6. spawn 本身失败(error 事件,如 ENOENT)──
375
- describe("spawn error 事件", () => {
376
- it("child.emit('error') → success=false,error 含错误信息,record.lastError 被设置", async () => {
377
- const record = makeRecord();
378
- const promise = runSpawn(record, "Task: enoent", makeOpts(), makeCtx());
379
-
380
- // 等待 spawn 被调用拿到 child
381
- await waitForSpawn();
382
- const child = lastSpawnedChild();
383
-
384
- // spawn error 事件(command not found 等)——runSpawn 的 error handler resolve(128)
385
- child.emit("error", new Error("spawn ENOENT"));
386
-
387
- const result = await promise;
388
-
389
- expect(result.success).toBe(false);
390
- expect(result.error).toContain("spawn ENOENT");
391
- expect(record.lastError).toContain("spawn ENOENT");
392
- });
393
-
394
- it("[worktree-reaper-fix] ENOENT error 消息拼 spawnCwd(避免误诊 node 被卸载)", async () => {
395
- const record = makeRecord();
396
- const promise = runSpawn(record, "Task: enoent-cwd", makeOpts(), makeCtx());
397
-
398
- await waitForSpawn();
399
- const child = lastSpawnedChild();
400
-
401
- // ENOENT 且带 code(Node spawn 失败的真实形态)——error handler 必须拼 spawnCwd
402
- const err = Object.assign(new Error("spawn ENOENT"), { code: "ENOENT" });
403
- child.emit("error", err);
404
-
405
- const result = await promise;
406
-
407
- expect(result.success).toBe(false);
408
- // makeCtx().cwd = "/tmp/test",无 worktree 时 spawnCwd = ctx.cwd
409
- expect(record.lastError).toContain("/tmp/test");
410
- });
411
- });
412
-
413
- // ── 7. identity 写入职责(M4 / V2 决策 5:迁移到子进程 session_start)──
414
- describe("identity 写入职责", () => {
415
- it("正常 close 后 session-runner 不再 fs 补写 identity(M4 迁移到子进程 session_start)", async () => {
416
- const record = makeRecord();
417
- const promise = runSpawn(record, "Task: identity", makeOpts(), makeCtx());
418
-
419
- // 等待 spawn 被调用拿到 child
420
- await waitForSpawn();
421
- const child = lastSpawnedChild();
422
-
423
- const sessionFile =
424
- "/tmp/test/agents/subagents/--tmp-test--/sessions/2026-07-03T12-00-00-000Z_sess-id.jsonl";
425
- mockSessionFileExists(sessionFile);
426
-
427
- emitStdoutLine(child, sessionHeader("sess-id"));
428
- emitStdoutLine(child, { type: "turn_end" });
429
- child.stdout.end();
430
- child.emit("close", 0);
431
-
432
- await promise;
433
-
434
- // [M4 / V2 决策 5] identity 不再由 session-runner fs.appendFileSync 补写——
435
- // 改由子进程 session_start hook 用 pi.appendEntry 写(pi 自动生成 id/parentId,
436
- // 修复旧 fs 补写缺 id/parentId 污染 _buildIndex 的 bug)。identity 写入由
437
- // index-session-start-identity.test.ts 覆盖;此处守护 session-runner 不再 fs 写。
438
- expect(mockAppendFileSync).not.toHaveBeenCalled();
439
- });
440
-
441
- it("sessionFile 不存在(existsSync=false)→ 不补写 identity(不调 appendFileSync)", async () => {
442
- const record = makeRecord();
443
- const promise = runSpawn(record, "Task: nofile", makeOpts(), makeCtx());
444
-
445
- // 等待 spawn 被调用拿到 child
446
- await waitForSpawn();
447
- const child = lastSpawnedChild();
448
-
449
- // existsSync 全返回 false(默认)→ record.sessionFile 经兜底查找仍不存在
450
- emitStdoutLine(child, sessionHeader("sess-none"));
451
- child.stdout.end();
452
- child.emit("close", 0);
453
-
454
- await promise;
455
-
456
- expect(mockAppendFileSync).not.toHaveBeenCalled();
457
- });
458
- });
459
-
460
- // ── 8. fork depth 环境变量传递 ──
461
- describe("fork depth 环境变量", () => {
462
- it("fork=true, parentForkDepth=2 → spawn env 含 PI_SUBAGENT_FORK_DEPTH=3", async () => {
463
- const record = makeRecord();
464
- const promise = runSpawn(
465
- record,
466
- "Task: fork",
467
- makeOpts({ fork: true, parentForkDepth: 2 }),
468
- makeCtx({ mainSessionFile: "/tmp/main.jsonl" }),
469
- );
470
-
471
- // 等待 spawn 被调用拿到 child
472
- await waitForSpawn();
473
- const child = lastSpawnedChild();
474
-
475
- emitStdoutLine(child, sessionHeader());
476
- child.stdout.end();
477
- child.emit("close", 0);
478
-
479
- await promise;
480
-
481
- // spawn 第 3 参数是 options(含 env)
482
- const spawnOpts = mockSpawn.mock.calls[0]?.[2] as { env: Record<string, string> };
483
- expect(spawnOpts.env.PI_SUBAGENT_FORK_DEPTH).toBe("3");
484
- });
485
-
486
- it("fork=false → spawn env 不含 PI_SUBAGENT_FORK_DEPTH", async () => {
487
- const record = makeRecord();
488
- const promise = runSpawn(record, "Task: nofork", makeOpts(), makeCtx());
489
-
490
- // 等待 spawn 被调用拿到 child
491
- await waitForSpawn();
492
- const child = lastSpawnedChild();
493
-
494
- emitStdoutLine(child, sessionHeader());
495
- child.stdout.end();
496
- child.emit("close", 0);
497
-
498
- await promise;
499
-
500
- const spawnOpts = mockSpawn.mock.calls[0]?.[2] as { env: Record<string, string | undefined> };
501
- expect(spawnOpts.env.PI_SUBAGENT_FORK_DEPTH).toBeUndefined();
502
- });
503
-
504
- it("spawn 参数含 --session-dir 且 cwd 来自 ctx.cwd", async () => {
505
- const record = makeRecord();
506
- const promise = runSpawn(record, "Task: args", makeOpts(), makeCtx());
507
-
508
- // 等待 spawn 被调用拿到 child
509
- await waitForSpawn();
510
- const child = lastSpawnedChild();
511
-
512
- emitStdoutLine(child, sessionHeader());
513
- child.stdout.end();
514
- child.emit("close", 0);
515
-
516
- await promise;
517
-
518
- // spawn(command, args, opts):args 是数组,应含 --session-dir
519
- const args = mockSpawn.mock.calls[0]?.[1] as string[];
520
- const sessionDirIdx = args.indexOf("--session-dir");
521
- expect(sessionDirIdx).toBeGreaterThanOrEqual(0);
522
- expect(args[sessionDirIdx + 1]).toContain("--tmp-test--");
523
- // spawn opts.cwd 来自 ctx.cwd(无 worktree 时)
524
- const spawnOpts = mockSpawn.mock.calls[0]?.[2] as { cwd: string };
525
- expect(spawnOpts.cwd).toBe("/tmp/test");
526
- });
527
- });
528
-
529
- // ── 9. onEvent 回调 ──
530
- describe("onEvent 回调", () => {
531
- it("stdout 事件经 handleSdkEvent 翻译后回调 opts.onEvent", async () => {
532
- const onEvent = vi.fn();
533
- const record = makeRecord();
534
- const promise = runSpawn(record, "Task: events", makeOpts({ onEvent }), makeCtx());
535
-
536
- // 等待 spawn 被调用拿到 child
537
- await waitForSpawn();
538
- const child = lastSpawnedChild();
539
-
540
- emitStdoutLine(child, sessionHeader());
541
- emitStdoutLine(child, { type: "turn_end" });
542
- child.stdout.end();
543
- child.emit("close", 0);
544
-
545
- await promise;
546
-
547
- // onEvent 至少被 turn_end 调过一次(onEvent 签名是单参数 event)
548
- const turnEndCalls = onEvent.mock.calls.filter(([ev]) => (ev as { type: string }).type === "turn_end");
549
- expect(turnEndCalls.length).toBeGreaterThanOrEqual(1);
550
- });
551
- });
552
-
553
- // ── 10. sessionDir 创建 ──
554
- describe("sessionDir 创建", () => {
555
- it("mkdirSync 以 recursive 创建 sessionDir", async () => {
556
- const record = makeRecord();
557
- const promise = runSpawn(record, "Task: mkdir", makeOpts(), makeCtx());
558
-
559
- // 等待 spawn 被调用拿到 child
560
- await waitForSpawn();
561
- const child = lastSpawnedChild();
562
-
563
- emitStdoutLine(child, sessionHeader());
564
- child.stdout.end();
565
- child.emit("close", 0);
566
-
567
- await promise;
568
-
569
- expect(mockMkdirSync).toHaveBeenCalledWith(
570
- "/tmp/test/agents/subagents/--tmp-test--/sessions",
571
- { recursive: true },
572
- );
573
- });
574
- });
575
-
576
- // ── 11. 临时 prompt 文件清理 ──
577
- describe("临时 prompt 文件清理", () => {
578
- it("有 appendSystemPrompt → 创建临时文件并在 finally 清理(spawn 退出后目录消失)", async () => {
579
- const record = makeRecord();
580
- const promise = runSpawn(
581
- record,
582
- "Task: cleanup",
583
- makeOpts({ appendSystemPrompt: ["extra instructions"] }),
584
- makeCtx(),
585
- );
586
-
587
- // 等待 spawn 被调用拿到 child
588
- await waitForSpawn();
589
- const child = lastSpawnedChild();
590
-
591
- emitStdoutLine(child, sessionHeader());
592
- child.stdout.end();
593
- child.emit("close", 0);
594
-
595
- await promise;
596
-
597
- // temp-prompt 已 mock(见文件顶部 vi.mock),返回固定文件名;
598
- // 验证 spawn args 含 --append-system-prompt(说明临时文件路径被传给子进程)
599
- const args = mockSpawn.mock.calls[0]?.[1] as string[];
600
- const appendIdx = args.indexOf("--append-system-prompt");
601
- expect(appendIdx).toBeGreaterThanOrEqual(0);
602
- expect(args[appendIdx + 1]).toMatch(/prompt-general-purpose\.md$/);
603
- });
604
- });
605
-
606
- // ── 12. watchdog 超时兜底(R1)──
607
- //
608
- // [R0/R1] SPAWN_WATCHDOG_MS = 30 * 60 * 1000(session-runner.ts 顶部)。
609
- // 子进程卡死在单 tool 内(turn_end 永不触发)时 limiter 失效,watchdog timer 兜底 SIGTERM,
610
- // 防止 background 槽位/资源泄漏。timer 加了 .unref()(不阻止 Node 退出)。
611
- //
612
- // fake timers 方案 A:describe 块内 beforeEach useFakeTimers、afterEach useRealTimers。
613
- // 难点:waitForSpawn 内部用 `await new Promise(r => setTimeout(r, 5))` 轮询,fake timers 下
614
- // 该 setTimeout 不会自动触发——改用 vi.advanceTimersByTimeAsync 推进时间,它会同步 flush
615
- // 已到期的 timer + 让挂起的真实 I/O(writePromptToTempFile 的 fs.promises)resolve。
616
- // 关键验证:unref 的 timer 在 fake timers 下仍能被 advanceTimersByTime 触发(vitest fake
617
- // timers 忽略 unref)。
618
- describe("watchdog 超时兜底 (R1)", () => {
619
- beforeEach(() => {
620
- vi.useFakeTimers();
621
- });
622
-
623
- afterEach(() => {
624
- vi.useRealTimers();
625
- });
626
-
627
- /**
628
- * fake timers 下推进时间直到 spawn 被调用(替代 waitForSpawn,返回 child 控制器)。
629
- *
630
- * [快照语义] 同 helpers/spawn-mock.ts 的 waitForSpawn:记调用时 baseline,等待其后的
631
- * **新** spawn——支持同一测试/文件内多次 runSpawn。
632
- *
633
- * runSpawn 在 mkdirSync + writePromptToTempFile(真实 fs.promises I/O)之后才调 spawn。
634
- * 每次推进 10ms 让轮询 setTimeout 触发;同时 advanceTimersByTimeAsync flush 已 resolve
635
- * 的 I/O promise,使 runSpawn 继续走到 spawn。
636
- */
637
- async function waitForSpawnFake(timeoutSteps = 200): Promise<FakeChild> {
638
- const baseline = mockSpawn.mock.results.length;
639
- for (let i = 0; i < timeoutSteps; i++) {
640
- if (mockSpawn.mock.results.length > baseline) break;
641
- await vi.advanceTimersByTimeAsync(10);
642
- }
643
- if (mockSpawn.mock.results.length <= baseline) {
644
- throw new Error("spawn was not called (fake timers did not progress to spawn)");
645
- }
646
- return lastSpawnedChild();
647
- }
648
-
649
- it("watchdog 超时(>30min)→ child.kill(SIGTERM) 被调用", async () => {
650
- const record = makeRecord();
651
- // 不 await——runSpawn 内部 await 子进程 close,watchdog 触发 kill 后还需 emit close 才 resolve
652
- // [M-1] maxTurns=6 使 computeWatchdogMs 返回下限 30min(max(30min, 6*5min) = 30min)
653
- const promise = runSpawn(record, "Task: hang", makeOpts({ maxTurns: 6 }), makeCtx());
654
-
655
- const child = await waitForSpawnFake();
656
-
657
- // spawn 后 child.killed 应为 false(watchdog 尚未触发)
658
- expect(child.killed).toBe(false);
659
-
660
- // 推进时间越过 watchdog 阈值(30 * 60 * 1000 + 100ms 余量)
661
- await vi.advanceTimersByTimeAsync(30 * 60 * 1000 + 100);
662
-
663
- // watchdog 触发 child.kill("SIGTERM")
664
- expect(child.killed).toBe(true);
665
- expect(child.killSignal).toBe("SIGTERM");
666
-
667
- // 收尾:emit close 让 runSpawn 的 await promise resolve(避免悬挂)
668
- emitStdoutLine(child, sessionHeader());
669
- child.stdout.end();
670
- child.emit("close", 143); // SIGTERM = 128+15
671
-
672
- const result = await promise;
673
- // 信号终止(>=128)视为正常完成
674
- expect(result.success).toBe(true);
675
- });
676
-
677
- it("正常 close(0) → clearTimeout 生效,watchdog 不触发 kill", async () => {
678
- const record = makeRecord();
679
- // [M-1] maxTurns=6 使 watchdog=30min(与上面 watchdog 触发测试同基准)
680
- const promise = runSpawn(record, "Task: quick", makeOpts({ maxTurns: 6 }), makeCtx());
681
-
682
- const child = await waitForSpawnFake();
683
-
684
- // 正常完成:emit header + close(0)
685
- emitStdoutLine(child, sessionHeader());
686
- child.stdout.end();
687
- child.emit("close", 0);
688
-
689
- const result = await promise;
690
- expect(result.success).toBe(true);
691
-
692
- // close 后 runSpawn 已 clearTimeout(watchdog);推进 30+ 分钟验证 watchdog 未触发 kill
693
- await vi.advanceTimersByTimeAsync(30 * 60 * 1000 + 100);
694
-
695
- expect(child.killed).toBe(false);
696
- expect(child.killSignal).toBeUndefined();
697
- });
698
- });
699
-
700
- // ── 13. stderr 截断防 OOM(R2)──
701
- //
702
- // [R2] stderrBuffer = (stderrBuffer + data).slice(-STDERR_MAX_CHARS)(STDERR_MAX_CHARS=64*1024)。
703
- // 失控子进程打满 stderr 会耗尽父进程内存;保留尾部 64KB 用于 error 诊断。
704
- describe("stderr 截断防 OOM (R2)", () => {
705
- it("stderr > 64KB → error 中的 stderr 内容是尾部 64KB(尾部标记保留,头部标记被截断)", async () => {
706
- const record = makeRecord();
707
- const promise = runSpawn(record, "Task: noisy stderr", makeOpts(), makeCtx());
708
-
709
- await waitForSpawn();
710
- const child = lastSpawnedChild();
711
-
712
- // 头部标记(应被截断):足够长的前缀让 64KB 窗口推过它
713
- const HEAD_MARKER = "HEAD_MARKER_HEAD_MARKER";
714
- child.stderr.write(HEAD_MARKER);
715
- // 填充 > 64KB 的噪声(用 "x" 重复),把头部标记推出 64KB 尾部窗口
716
- child.stderr.write("x".repeat(70 * 1024));
717
- // 尾部标记(应在截断后仍保留)
718
- child.stderr.write("TAIL_MARKER_TAIL_MARKER");
719
-
720
- emitStdoutLine(child, sessionHeader());
721
- child.stderr.end();
722
- child.stdout.end();
723
- child.emit("close", 1); // 非零退出 → error 含 stderr
724
-
725
- const result = await promise;
726
-
727
- // 非零退出码 → success=false
728
- expect(result.success).toBe(false);
729
- // error 含尾部标记(证明保留了尾部 64KB)
730
- expect(result.error).toContain("TAIL_MARKER_TAIL_MARKER");
731
- // error 不含头部标记(证明头部被截断丢弃)
732
- expect(result.error).not.toContain(HEAD_MARKER);
733
- });
734
- });
735
-
736
- // ── 14. UTF-8 编码跨 chunk 安全(R3)──
737
- //
738
- // [R3] child.stdout.setEncoding("utf8") 让流按字符边界切分,避免多字节 UTF-8
739
- //(CJK/emoji)跨 chunk 时 toString() 产生 U+FFFD 替换符导致 JSON.parse 失败。
740
- //
741
- // 测法选择:用流状态(readableEncoding)验证 setEncoding("utf8") 被 runSpawn 调用过。
742
- // 为什么不用 spy:runSpawn 在 spawn() 返回后同步调用 setEncoding,而 waitForSpawn 拿到
743
- // child 时调用已完成,无法事后挂 spy 捕获。readableEncoding 是 PassThrough(Readable) 暴露的
744
- // 只读属性,setEncoding("utf8") 后值变为 "utf8",是"编码确实被设置"的可靠证据。
745
- // 端到端分片写多字节字符无法真正模拟"字节边界切断"(PassThrough + setEncoding 已在更高层
746
- // 合并),故补一个"分片写入仍能解析"的用例佐证行缓冲健壮性。
747
- describe("UTF-8 编码跨 chunk 安全 (R3)", () => {
748
- it("runSpawn 对 child.stdout 和 child.stderr 调用 setEncoding('utf8')(readableEncoding 验证)", async () => {
749
- const record = makeRecord();
750
- const promise = runSpawn(record, "Task: utf8", makeOpts(), makeCtx());
751
-
752
- await waitForSpawn();
753
- const child = lastSpawnedChild();
754
-
755
- // spawn 后 runSpawn 已同步调用 setEncoding("utf8"):readableEncoding 反映该调用结果
756
- expect(child.stdout.readableEncoding).toBe("utf8");
757
- expect(child.stderr.readableEncoding).toBe("utf8");
758
-
759
- emitStdoutLine(child, sessionHeader());
760
- child.stdout.end();
761
- child.stderr.end();
762
- child.emit("close", 0);
763
-
764
- const result = await promise;
765
- expect(result.success).toBe(true);
766
- });
767
-
768
- it("stdout 多次分片写入同一 JSON 行仍能正确解析 turn_end(行缓冲合并)", async () => {
769
- // 端到端验证:数据分两次 write,runSpawn 的行缓冲(stdoutBuffer += data)合并后解析。
770
- // 此用例验证"分片写入仍能解析",setEncoding("utf8") 已由上一用例覆盖。
771
- const record = makeRecord();
772
- const promise = runSpawn(record, "Task: split", makeOpts(), makeCtx());
773
-
774
- await waitForSpawn();
775
- const child = lastSpawnedChild();
776
-
777
- // header 完整一行
778
- emitStdoutLine(child, sessionHeader());
779
- // turn_end 拆成两半写入(跨 chunk 模拟)
780
- child.stdout.write('{"type":"turn_e');
781
- child.stdout.write('nd"}\n');
782
- child.stdout.end();
783
- child.emit("close", 0);
784
-
785
- const result = await promise;
786
-
787
- expect(result.success).toBe(true);
788
- expect(record.turnCount).toBe(1); // 拆开的 turn_end 仍被解析为 1 次
789
- });
790
- });
791
-
792
- // ── 15. stdin prompt 注入 ──
793
- //
794
- // [RPC prompt 修复] pi runRpcMode 只通过 stdin RpcCommand 驱动——positional task arg
795
- // / -p flag 在 rpc mode 下被 resolveAppMode 无视。runSpawn 必须在 spawn 后主动写
796
- // {type:"prompt",message:<task>} 到 child.stdin,否则子进程阻塞、totalTokens 恒 0。
797
- // sendPromptCommand 在 spawn + setEncoding 后同步执行,waitForSpawn 拿到 child 时
798
- // 命令已在 stdin 缓冲。PassThrough.write 无 reader 时缓冲全部数据,可事后读出断言。
799
- describe("stdin prompt 注入", () => {
800
- it("spawn 后向 stdin 写一行 {type:prompt} 且 message 含 task 文本", async () => {
801
- const record = makeRecord();
802
- const taskText = "Task: hello-prompt-injection";
803
- const promise = runSpawn(record, taskText, makeOpts(), makeCtx());
804
-
805
- await waitForSpawn();
806
- const child = lastSpawnedChild();
807
-
808
- // sendPromptCommand 已同步执行——PassThrough 缓冲了写入的命令,读出来断言。
809
- // pause() 让 PassThrough 切到暂停模式(默认 flow 模式下数据缓冲在内部),
810
- // 然后 read() 取出全部已缓冲内容。
811
- child.stdin.pause();
812
- const buffered = child.stdin.read()?.toString() ?? "";
813
-
814
- // 收尾:让 runSpawn resolve(避免悬挂)
815
- emitStdoutLine(child, sessionHeader());
816
- child.stdout.end();
817
- child.stderr.end();
818
- child.emit("close", 0);
819
- await promise;
820
-
821
- // 断言:缓冲含合法 JSON,type=prompt,message 含 task 文本
822
- const lines = buffered.trim().split("\n");
823
- expect(lines.length).toBeGreaterThanOrEqual(1);
824
- const cmd = JSON.parse(lines[0]!) as { type: string; message: string; id?: string };
825
- expect(cmd.type).toBe("prompt");
826
- expect(cmd.message).toBe(taskText);
827
- expect(typeof cmd.id).toBe("string");
828
- });
829
-
830
- it("child.stdin.destroyed → sendPromptCommand 不抛错(guard 生效)", async () => {
831
- const record = makeRecord();
832
- const promise = runSpawn(record, "Task: destroyed-stdin", makeOpts(), makeCtx());
833
-
834
- await waitForSpawn();
835
- const child = lastSpawnedChild();
836
-
837
- // destroy stdin 模拟子进程已关闭输入通道;sendPromptCommand 在 spawn 时已执行过一次
838
- //(stdin 当时未 destroyed),这里仅验证后续不抛。此用例主要保护 guard 逻辑——
839
- // 收尾正常 close 即证明无异常抛出中断 runSpawn。
840
- child.stdin.destroy();
841
- emitStdoutLine(child, sessionHeader());
842
- child.stdout.end();
843
- child.stderr.end();
844
- child.emit("close", 0);
845
-
846
- const result = await promise;
847
- expect(result.success).toBe(true);
848
- });
849
- });
850
-
851
- // 注:C1(orphan 进程兜底)与 M8(stdout 边界)describe 块已移至 run-spawn-edges.test.ts,
852
- // 拆分以保持本文件 < 1000 行(pre-commit hook 限制)。两文件各自独立声明文件级 mock。
853
-
854
- // ============================================================
855
- // E2E:runSpawn 从主进程 process.argv 镜像 extension/approve flag 到子进程
856
- // 验证完整链路:runSpawn 真实执行 → 读 process.argv → buildSpawnArgs 拼参 →
857
- // spawn 收到的 args 含镜像 flag。spawn 被 mock(FakeChild),但 runSpawn 体内逻辑
858
- // 全真实跑,process.argv 用真实进程变量(测试中临时覆写 + 还原)。
859
- // ============================================================
860
- describe("E2E: 镜像主进程 argv flag 到子进程 spawn args", () => {
861
- const originalArgv = process.argv;
862
-
863
- afterEach(() => {
864
- process.argv = originalArgv;
865
- });
866
-
867
- it("主进程 argv 含 --extension/--no-extensions/--approve → 子进程 spawn args 全部镜像", async () => {
868
- // 模拟主 pi 进程启动参数(xyz-agent runtime 启动 pi 的真实形态)
869
- process.argv = [
870
- "bun", "/path/to/pi",
871
- "--mode", "rpc",
872
- "--no-extensions",
873
- "--approve",
874
- "--extension", "/exts/goal",
875
- "--extension", "/exts/todo",
876
- "--session-dir", "/sessions",
877
- ];
878
-
879
- const record = makeRecord();
880
- const promise = runSpawn(record, "Task", makeOpts(), makeCtx());
881
- await waitForSpawn();
882
- const child = lastSpawnedChild();
883
- mockSessionFileExists(
884
- "/tmp/test/agents/subagents/--tmp-test--/sessions/2026-07-03T12-00-00-000Z_sess-mirror.jsonl",
885
- );
886
- emitStdoutLine(child, sessionHeader("sess-mirror"));
887
- child.stdout.end();
888
- child.stderr.end();
889
- child.emit("close", 0);
890
- await promise;
891
-
892
- // 断言 spawn 收到的调用参数含全部镜像 flag
893
- const spawnCall = mockSpawn.mock.calls[0];
894
- // spawn(command, args, options) → args 是第二个参数
895
- const spawnArgs = spawnCall[1] as string[];
896
-
897
- expect(spawnArgs).toContain("--no-extensions");
898
- expect(spawnArgs).toContain("--approve");
899
- // 两个 extension 路径都镜像,顺序保留
900
- const extIdxs = spawnArgs
901
- .map((a, i) => (a === "--extension" ? i : -1))
902
- .filter((i) => i >= 0);
903
- expect(extIdxs).toHaveLength(2);
904
- expect(spawnArgs[extIdxs[0] + 1]).toBe("/exts/goal");
905
- expect(spawnArgs[extIdxs[1] + 1]).toBe("/exts/todo");
906
- });
907
-
908
- it("主进程 argv 无目标 flag → 子进程 spawn args 不含镜像 flag(向后兼容)", async () => {
909
- // 模拟纯 pi CLI 直接跑(无 extension/approve 配置)
910
- process.argv = ["bun", "/path/to/pi", "--mode", "rpc"];
911
-
912
- const record = makeRecord();
913
- const promise = runSpawn(record, "Task", makeOpts(), makeCtx());
914
- await waitForSpawn();
915
- const child = lastSpawnedChild();
916
- mockSessionFileExists(
917
- "/tmp/test/agents/subagents/--tmp-test--/sessions/2026-07-03T12-00-00-000Zsess-noflag.jsonl",
918
- );
919
- emitStdoutLine(child, sessionHeader("sess-noflag"));
920
- child.stdout.end();
921
- child.stderr.end();
922
- child.emit("close", 0);
923
- await promise;
924
-
925
- const spawnCall = mockSpawn.mock.calls[0];
926
- const spawnArgs = spawnCall[1] as string[];
927
-
928
- expect(spawnArgs).not.toContain("--no-extensions");
929
- expect(spawnArgs).not.toContain("--approve");
930
- expect(spawnArgs).not.toContain("--extension");
931
- });
932
- });
933
- });