@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,629 +0,0 @@
1
- // src/execution/__tests__/subagent-service-message-close.test.ts
2
- //
3
- // M2-B3 service 层:getRecordForAction 归属守卫 + closeSubagent 行为分流 + closeChatIdle。
4
- //
5
- // 沿用 run-and-finalize-chatmode.test.ts 的 mock 模式(真实 SubagentService + mock runSpawn +
6
- // ServiceInternals cast 暴露 store)。验证 M2-B3 新增的归属守卫与 close 行为分流。
7
-
8
- import * as fs from "node:fs";
9
- import * as os from "node:os";
10
- import * as path from "node:path";
11
- import type { ChildProcess } from "node:child_process";
12
-
13
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
-
15
- // mock logger(doFinalizeRecord manifest 写入降级路径用)
16
- const { loggerMock } = vi.hoisted(() => ({
17
- loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn(), info: vi.fn() },
18
- }));
19
- vi.mock("@zhushanwen/pi-extension-logger", () => ({ getLogger: () => loggerMock }));
20
-
21
- // mock session-runner(import 链需要 runSpawn/killAllSpawnedChildren/getChildByRecord 存在)
22
- vi.mock("../session-runner.ts", () => ({
23
- runSpawn: vi.fn(),
24
- killAllSpawnedChildren: vi.fn(),
25
- getChildByRecord: vi.fn(() => undefined),
26
- }));
27
-
28
- import { createRecord, updateFromEvent } from "../execution-record.ts";
29
- import { ModelConfigService } from "../model-config-service.ts";
30
- import { RecordStore } from "../record-store.ts";
31
- import { SubagentService } from "../subagent-service.ts";
32
- import type { PiLike } from "../subagent-service.ts";
33
- import type { ExecutionRecord } from "../types.ts";
34
- import { getChildByRecord } from "../session-runner.ts";
35
- import {
36
- armIdleTimer,
37
- disarmIdleTimer,
38
- hasIdleTimer,
39
- _resetLifecycleState,
40
- } from "../lifecycle-manager.ts";
41
-
42
- function makeTmpAgentDir(): string {
43
- return fs.mkdtempSync(path.join(os.tmpdir(), "msg-close-svc-"));
44
- }
45
-
46
- function makePi(): PiLike & {
47
- appendEntry: ReturnType<typeof vi.fn>;
48
- events: { emit: ReturnType<typeof vi.fn> };
49
- sendMessage: ReturnType<typeof vi.fn>;
50
- } {
51
- return { appendEntry: vi.fn(), events: { emit: vi.fn() }, sendMessage: vi.fn() } as unknown as PiLike & {
52
- appendEntry: ReturnType<typeof vi.fn>;
53
- events: { emit: ReturnType<typeof vi.fn> };
54
- sendMessage: ReturnType<typeof vi.fn>;
55
- };
56
- }
57
-
58
- /** 构造 chatMode background record(带 controller,模拟真实 background record)。 */
59
- function makeRecord(overrides: Partial<ExecutionRecord> & { id?: string } = {}): ExecutionRecord {
60
- const { id = "sa-test", ...rest } = overrides;
61
- const r = createRecord(id, {
62
- agent: "general-purpose",
63
- model: "test/model",
64
- mode: "background",
65
- task: "test",
66
- slug: "test",
67
- startedAt: 1000,
68
- rootSessionId: "root-session",
69
- chatMode: true,
70
- controller: new AbortController(),
71
- });
72
- Object.assign(r, rest);
73
- return r;
74
- }
75
-
76
- interface ServiceInternals {
77
- store: RecordStore;
78
- sessionRootId: string | null;
79
- }
80
-
81
- type MockPi = ReturnType<typeof makePi>;
82
-
83
- function setup(): {
84
- agentDir: string;
85
- service: SubagentService;
86
- store: RecordStore;
87
- sessionRootId: string;
88
- pi: MockPi;
89
- } {
90
- const agentDir = makeTmpAgentDir();
91
- const modelService = new ModelConfigService({ agentDir });
92
- const service = new SubagentService({ cwd: agentDir, modelService });
93
- // [C2] pi 提为外层引用(对照 real-chain :139-140 形态)——close 现状语义断言需读
94
- // pi.sendMessage.mock.calls(末条通知 content 指针行),内联构造不保留引用。
95
- const pi = makePi();
96
- service.initSession({ pi, sessionId: "root-session" });
97
- const internals = service as unknown as ServiceInternals;
98
- return { agentDir, service, store: internals.store, sessionRootId: internals.sessionRootId!, pi };
99
- }
100
-
101
- // ============================================================
102
- // getRecordForAction 归属守卫(决策 3)
103
- // ============================================================
104
-
105
- describe("getRecordForAction 归属守卫(决策 3)", () => {
106
- let agentDir: string;
107
- let service: SubagentService;
108
- let store: RecordStore;
109
- let sessionRootId: string;
110
-
111
- beforeEach(() => {
112
- ({ agentDir, service, store, sessionRootId } = setup());
113
- });
114
-
115
- afterEach(() => {
116
- service.dispose();
117
- fs.rmSync(agentDir, { recursive: true, force: true });
118
- });
119
-
120
- it("正常(rootSessionId 匹配)→ 返回可变 record", () => {
121
- const record = makeRecord({ rootSessionId: sessionRootId });
122
- store.register(record);
123
- expect(service.getRecordForAction(record.id)).toBe(record);
124
- });
125
-
126
- it("不存在 → throw not found or not owned", () => {
127
- expect(() => service.getRecordForAction("sa-nonexistent")).toThrow(/not found or not owned/);
128
- });
129
-
130
- it("rootSessionId 不匹配 → throw not found or not owned(不区分 not found vs not owned,防信息泄露)", () => {
131
- const record = makeRecord({ id: "sa-other", rootSessionId: "other-session" });
132
- store.register(record);
133
- expect(() => service.getRecordForAction("sa-other")).toThrow(/not found or not owned/);
134
- });
135
- });
136
-
137
- // ============================================================
138
- // closeSubagent 行为分流
139
- // ============================================================
140
-
141
- describe("closeSubagent 行为分流", () => {
142
- let agentDir: string;
143
- let service: SubagentService;
144
- let store: RecordStore;
145
- let pi: MockPi;
146
-
147
- beforeEach(() => {
148
- ({ agentDir, service, store, pi } = setup());
149
- });
150
-
151
- afterEach(() => {
152
- service.dispose();
153
- fs.rmSync(agentDir, { recursive: true, force: true });
154
- });
155
-
156
- it("running + force:false + 有活进程 → 置 closeAfterRound=true(不立即终态,等轮完)", async () => {
157
- // v4 B-1:closeSubagent running 分支按 isResumable 区分——有活进程(isResumable=false)
158
- // 走 closeAfterRound;无活进程(isResumable=true)走 closeChatIdle。本用例 mock 活进程。
159
- vi.mocked(getChildByRecord).mockReturnValueOnce({ killed: false } as never);
160
- const record = makeRecord({ status: "running" });
161
- store.register(record);
162
-
163
- await service.closeSubagent(record, false);
164
-
165
- expect(record.closeAfterRound).toBe(true);
166
- expect(record.status).toBe("running"); // 仍 running
167
- expect(store.getMutable(record.id)).toBe(record); // 留内存
168
- });
169
-
170
- it("running + 无活进程(旧 idle)→ closeChatIdle(closed 终态化 + archive)", async () => {
171
- // v4 B-1:旧 idle 折入 running。running + force:false + isResumable=true(getChildByRecord
172
- // 默认 mock 返回 undefined = 无活进程)→ closeChatIdle 立即终态化。
173
- const record = makeRecord({ status: "running", round: 1 });
174
- record.sessionFile = path.join(agentDir, "test.jsonl");
175
- store.register(record);
176
-
177
- await service.closeSubagent(record, false);
178
-
179
- expect(record.status).toBe("closed");
180
- expect(store.getMutable(record.id)).toBeUndefined(); // archived
181
- });
182
-
183
- it("running + force:true → cancelBackground(closed+cancelled 终态化 + archive)", async () => {
184
- const record = makeRecord({ status: "running", sessionFile: path.join(agentDir, "run.jsonl") });
185
- store.register(record);
186
-
187
- await service.closeSubagent(record, true);
188
-
189
- // v4 B-1:cancelled 折入 closed(closedReason='cancelled' 区分用户取消语义)
190
- expect(record.status).toBe("closed");
191
- expect(record.closedReason).toBe("cancelled");
192
- expect(store.getMutable(record.id)).toBeUndefined(); // archived
193
- });
194
-
195
- it("终态(done)→ 幂等 no-op(不改状态、不 archive)", async () => {
196
- const record = makeRecord({ status: "closed" });
197
- store.register(record);
198
-
199
- await service.closeSubagent(record, false);
200
-
201
- expect(record.status).toBe("closed"); // 不变
202
- });
203
-
204
- // ── [M5] Path A(isIdle timer armed + 保活进程)立即终态化 ─────────────
205
-
206
- it("[M5] Path A(timer armed + 活进程)+ force:false → 立即终态化(closed+user-close + kill 进程 + disarm timer),不置 closeAfterRound", async () => {
207
- // 旧代码 Path A 走 else 置 closeAfterRound,但 chatMode 轮完成恒经 agent_settled →
208
- // onRoundSettled → runAndFinalize early return(isIdle 恒 true),标志无人消费,
209
- // tool 却返回 {closed:true}(谎报)。
210
- const killFn = vi.fn(() => true);
211
- const child = { killed: false, kill: killFn } as unknown as ChildProcess;
212
- vi.mocked(getChildByRecord).mockReturnValueOnce(child);
213
- const record = makeRecord({ status: "running", round: 1 });
214
- record.sessionFile = path.join(agentDir, "path-a.jsonl");
215
- store.register(record);
216
- armIdleTimer(record.id, () => {}); // Path A:轮次完成、进程保活等待续聊
217
- expect(hasIdleTimer(record.id)).toBe(true);
218
-
219
- try {
220
- await service.closeSubagent(record, false);
221
- } finally {
222
- disarmIdleTimer(record.id); // 断言失败路径的兜底清理
223
- }
224
-
225
- expect(record.closeAfterRound).toBeUndefined(); // 不置标志
226
- expect(record.status).toBe("closed");
227
- expect(record.closedReason).toBe("user-close");
228
- expect(store.getMutable(record.id)).toBeUndefined(); // archived
229
- expect(killFn).toHaveBeenCalledWith("SIGTERM"); // 保活进程回收
230
- expect(hasIdleTimer(record.id)).toBe(false); // timer disarmed
231
- });
232
-
233
- it("[M5] Path B(无活进程、timer 未 armed)+ force:false → 立即终态化(同旧 isResumable 行为)+ 终态通知", async () => {
234
- const record = makeRecord({ status: "running", round: 1 });
235
- record.sessionFile = path.join(agentDir, "path-b.jsonl");
236
- store.register(record);
237
-
238
- await service.closeSubagent(record, false);
239
-
240
- expect(record.status).toBe("closed");
241
- expect(record.closedReason).toBe("user-close");
242
- expect(store.getMutable(record.id)).toBeUndefined();
243
- // [C-1] close 终态通知(设计 D2 路径②):chatMode close 后父 agent 收到终态通知——
244
- // 正文空串(doneResult.text 恒空)+ 轮次统计 + Full transcript 指针行
245
- expect(pi.sendMessage).toHaveBeenCalledTimes(1);
246
- const sent = pi.sendMessage.mock.calls[0]![0] as { content: string };
247
- expect(sent.content).toContain("completed after 1 round.");
248
- expect(sent.content).toContain(`\n\nFull transcript: ${record.sessionFile}`);
249
- });
250
-
251
- // ── [M6] cancelBackground 显式 kill(chatMode 热路径轮中 cancel)────────
252
-
253
- it("[M6] running + force:true + 活进程(热路径轮中)→ cancelBackground 显式 SIGTERM + disarm idle timer", async () => {
254
- // chatMode 首轮 agent_settled 后 runSpawn 提前 resolveRun(0) 返回,
255
- // abort→kill listener 已被 removeEventListener——热路径轮中 cancel 只能靠显式 kill。
256
- const killFn = vi.fn(() => true);
257
- const child = { killed: false, kill: killFn } as unknown as ChildProcess;
258
- vi.mocked(getChildByRecord).mockReturnValueOnce(child);
259
- const record = makeRecord({ status: "running", sessionFile: path.join(agentDir, "hot.jsonl") });
260
- record.controller = new AbortController();
261
- store.register(record);
262
- armIdleTimer(record.id, () => {}); // Path A 等待续聊态(cancel 时必须 disarm)
263
- expect(hasIdleTimer(record.id)).toBe(true);
264
-
265
- try {
266
- await service.closeSubagent(record, true);
267
- } finally {
268
- disarmIdleTimer(record.id); // 断言失败路径的兜底清理
269
- }
270
-
271
- expect(killFn).toHaveBeenCalledWith("SIGTERM"); // 子进程收到 kill
272
- expect(record.status).toBe("closed");
273
- expect(record.closedReason).toBe("cancelled");
274
- expect(hasIdleTimer(record.id)).toBe(false); // timer disarmed
275
- expect(store.getMutable(record.id)).toBeUndefined(); // archived
276
- });
277
- });
278
-
279
- // ============================================================
280
- // [M5] closeAfterRound 消费点:onRoundSettled(chatMode 热路径轮完成)
281
- // ============================================================
282
-
283
- describe("[M5] closeAfterRound 消费点挂 onRoundSettled(chatMode 轮完成终态化)", () => {
284
- let agentDir: string;
285
- let service: SubagentService;
286
- let store: RecordStore;
287
-
288
- beforeEach(() => {
289
- vi.mocked(getChildByRecord).mockReset();
290
- vi.mocked(getChildByRecord).mockReturnValue(undefined);
291
- ({ agentDir, service, store } = setup());
292
- });
293
-
294
- afterEach(() => {
295
- service.dispose();
296
- _resetLifecycleState();
297
- fs.rmSync(agentDir, { recursive: true, force: true });
298
- });
299
-
300
- it("热路径轮完成(onRoundSettled)+ closeAfterRound=true → 消费标志终态化(closed+user-close + archive)", async () => {
301
- // 热路径轮不经 runAndFinalize(deliverMessage 直接 sendPromptCommand),旧消费点
302
- // (runAndFinalize CAS 分支)不可达——唯一可达的收尾点是 onRoundSettled。
303
- const killFn = vi.fn(() => true);
304
- const child = { killed: false, kill: killFn } as unknown as ChildProcess;
305
- // mockReturnValue(非 Once):notifyComplete → hasRunningBackground → hasLiveProcessHandle
306
- // 也会读 getChildByRecord,Once mock 会被提前消费掉。
307
- vi.mocked(getChildByRecord).mockReturnValue(child);
308
- const internals = service as unknown as { buildSessionRunnerContext(): { onRoundSettled?: (r: ExecutionRecord) => void } };
309
- const ctx = internals.buildSessionRunnerContext();
310
- expect(typeof ctx.onRoundSettled).toBe("function");
311
-
312
- const record = makeRecord({ id: "sa-m5", status: "running" });
313
- record.sessionFile = path.join(agentDir, "m5.jsonl");
314
- store.register(record);
315
- record.closeAfterRound = true; // busy 轮中 close(force:false) 置的标志
316
- armIdleTimer(record.id, () => {}); // session-runner 在 onRoundSettled 前已 arm(时序还原)
317
- try {
318
- ctx.onRoundSettled!(record);
319
- // closeAfterRoundSettled 是 async(finalizeRecord 内 manifest 写入)——等微任务收尾完成
320
- await vi.waitFor(() => expect(store.getMutable(record.id)).toBeUndefined());
321
- } finally {
322
- disarmIdleTimer(record.id);
323
- }
324
-
325
- expect(record.closeAfterRound).toBeUndefined(); // 标志已消费
326
- expect(record.status).toBe("closed");
327
- expect(record.closedReason).toBe("user-close");
328
- expect(killFn).toHaveBeenCalledWith("SIGTERM"); // 保活进程回收
329
- expect(hasIdleTimer(record.id)).toBe(false);
330
- });
331
-
332
- it("onRoundSettled 无 closeAfterRound → 不终态化(record 保持 running-resumable,轮完成通知照常)", async () => {
333
- const internals = service as unknown as { buildSessionRunnerContext(): { onRoundSettled?: (r: ExecutionRecord) => void } };
334
- const ctx = internals.buildSessionRunnerContext();
335
- const record = makeRecord({ id: "sa-m5-keep", status: "running" });
336
- store.register(record);
337
- armIdleTimer(record.id, () => {});
338
- try {
339
- ctx.onRoundSettled!(record);
340
- } finally {
341
- disarmIdleTimer(record.id);
342
- }
343
-
344
- expect(record.status).toBe("running"); // 不终态化
345
- expect(record.round).toBe(1); // round 累加照常
346
- expect(store.getMutable(record.id)).toBe(record); // 留内存
347
- });
348
- });
349
-
350
- // ============================================================
351
- // [C2 wave2] close 现状语义 + sessionFile 条件透传
352
- // ============================================================
353
-
354
- describe("[C2] close 现状语义 + sessionFile 条件透传", () => {
355
- let agentDir: string;
356
- let service: SubagentService;
357
- let store: RecordStore;
358
- let pi: MockPi;
359
-
360
- beforeEach(() => {
361
- vi.mocked(getChildByRecord).mockReset();
362
- // 默认无活进程(Path B / isResumable 形态);hasRunningBackground 亦读此 mock
363
- vi.mocked(getChildByRecord).mockReturnValue(undefined);
364
- ({ agentDir, service, store, pi } = setup());
365
- });
366
-
367
- afterEach(() => {
368
- service.dispose();
369
- _resetLifecycleState();
370
- fs.rmSync(agentDir, { recursive: true, force: true });
371
- });
372
-
373
- it("close 语义:轮次通知(含指针行)发出后 closeAfterRoundSettled 终态化 + 终态通知,两条都送达", async () => {
374
- // [C-1] 修复后语义:终态通知经 notifyClosed 显式发送(dedup 身份 = 裸 id,与轮次通知
375
- // 的 id:round key 区分)——close 后父 agent 收到「最后一轮轮次通知 + 终态通知」两条。
376
- const internals = service as unknown as { buildSessionRunnerContext(): { onRoundSettled?: (r: ExecutionRecord) => void } };
377
- const ctx = internals.buildSessionRunnerContext();
378
- const record = makeRecord({ id: "sa-c2-close", status: "running" });
379
- record.sessionFile = path.join(agentDir, "c2-close.jsonl");
380
- store.register(record);
381
- record.closeAfterRound = true; // busy 轮中 close(force:false) 置的标志
382
- armIdleTimer(record.id, () => {});
383
- try {
384
- ctx.onRoundSettled!(record);
385
- // closeAfterRoundSettled 是 async(finalizeRecord 链)——终态通知在链末尾的
386
- // notifyClosed,等它发出而不是只等 archive(archive 在链中段,先于通知)
387
- await vi.waitFor(() => expect(pi.sendMessage).toHaveBeenCalledTimes(2));
388
- await vi.waitFor(() => expect(store.getMutable(record.id)).toBeUndefined());
389
- } finally {
390
- disarmIdleTimer(record.id);
391
- }
392
-
393
- // 轮次通知 1 条 + 终态通知 1 条(dedup 不吞)
394
- expect(pi.sendMessage).toHaveBeenCalledTimes(2);
395
- // 第 1 条:轮次通知含 Full transcript 指针行(chatMode:true + sessionFile 有值 → 透传)
396
- const roundMsg = pi.sendMessage.mock.calls[0]![0] as { content: string };
397
- expect(roundMsg.content).toContain("finished a round");
398
- expect(roundMsg.content).toContain(`\n\nFull transcript: ${record.sessionFile}`);
399
- // 第 2 条:终态通知含轮次统计 + 指针行
400
- const closeMsg = pi.sendMessage.mock.calls[1]![0] as { content: string };
401
- expect(closeMsg.content).toContain("completed after 1 round.");
402
- expect(closeMsg.content).toContain(`\n\nFull transcript: ${record.sessionFile}`);
403
- });
404
-
405
- it("one-shot 条件透传(R4 必选):chatMode:false + sessionFile 有值 → 通知不透传 sessionFile、content 无指针行", () => {
406
- // 锁死 toNotifyRecord 的 chatMode 条件(C2C2 契约)——漏加条件时 notifier 单测不红
407
- //(notifier 层只见最终字段),此用例走真实 SubagentService + toNotifyRecord 路径必红。
408
- // 同时服务 C2TC6 结构级断言。
409
- const record = makeRecord({ id: "sa-c2-oneshot", status: "running", chatMode: false, result: "done" });
410
- record.sessionFile = path.join(agentDir, "c2-oneshot.jsonl");
411
- store.register(record);
412
-
413
- // running + 无活进程(getChildByRecord mock undefined)→ isResumable 放行 →
414
- // 非 chatMode 映射 closed 通知,立即 flush(无其他 running background)
415
- const svc = service as unknown as { notifyComplete(r: ExecutionRecord): void };
416
- svc.notifyComplete(record);
417
-
418
- expect(pi.sendMessage).toHaveBeenCalledTimes(1);
419
- const msg = pi.sendMessage.mock.calls[0]![0] as {
420
- content: string;
421
- details?: { sessionFile?: string };
422
- };
423
- // 结构级:one-shot 通知的 BgNotifyRecord 不含 sessionFile
424
- expect(msg.details?.sessionFile).toBeUndefined();
425
- // 语义级:正文无指针行,且与改造前逐字节一致(G4)
426
- expect(msg.content).not.toContain("Full transcript");
427
- expect(msg.content).toBe('Subagent "general-purpose" (sa-c2-oneshot) completed. Result:\ndone');
428
- });
429
-
430
- it("chatMode:true 对照透传:isResumable 放行的轮次通知携带 sessionFile + 指针行(漏加 chatMode 条件时本用例红)", () => {
431
- const record = makeRecord({ id: "sa-c2-chat", status: "running", result: "round reply" });
432
- record.sessionFile = path.join(agentDir, "c2-chat.jsonl");
433
- store.register(record);
434
-
435
- const svc = service as unknown as { notifyComplete(r: ExecutionRecord): void };
436
- svc.notifyComplete(record);
437
-
438
- expect(pi.sendMessage).toHaveBeenCalledTimes(1);
439
- const msg = pi.sendMessage.mock.calls[0]![0] as {
440
- content: string;
441
- details?: { sessionFile?: string };
442
- };
443
- expect(msg.details?.sessionFile).toBe(path.join(agentDir, "c2-chat.jsonl"));
444
- expect(msg.content).toContain(`\n\nFull transcript: ${path.join(agentDir, "c2-chat.jsonl")}`);
445
- });
446
- });
447
-
448
- // ============================================================
449
- // [C-4] chatMode 轮次增量(onRoundSettled 写点)+ close 终态通知
450
- // ============================================================
451
-
452
- describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
453
- let agentDir: string;
454
- let service: SubagentService;
455
- let store: RecordStore;
456
- let pi: MockPi;
457
-
458
- beforeEach(() => {
459
- vi.mocked(getChildByRecord).mockReset();
460
- // 默认无活进程:hasRunningBackground=false → notify 立即 flush,sendMessage 同步可断言
461
- vi.mocked(getChildByRecord).mockReturnValue(undefined);
462
- ({ agentDir, service, store, pi } = setup());
463
- });
464
-
465
- afterEach(() => {
466
- service.dispose();
467
- _resetLifecycleState();
468
- fs.rmSync(agentDir, { recursive: true, force: true });
469
- });
470
-
471
- /** 真实回调链(与生产一致:agent_settled → onRoundSettled),turns 经 updateFromEvent 累积。 */
472
- function settleRound(record: ExecutionRecord): void {
473
- (service as unknown as {
474
- buildSessionRunnerContext(): { onRoundSettled?: (r: ExecutionRecord) => void };
475
- }).buildSessionRunnerContext().onRoundSettled!(record);
476
- }
477
-
478
- it("空增量轮:通知正文占位 (no output this round),不含上一轮文本(D5,onRoundSettled 写点)", () => {
479
- const record = makeRecord({ id: "sa-empty-round", status: "running" });
480
- record.sessionFile = path.join(agentDir, "empty-round.jsonl");
481
- store.register(record);
482
- armIdleTimer(record.id, () => {});
483
- try {
484
- // 第 1 轮:有真实回复
485
- updateFromEvent(record, { type: "text_delta", delta: "ROUND-ONE-REPLY" });
486
- updateFromEvent(record, { type: "turn_end" });
487
- settleRound(record);
488
-
489
- // 第 2 轮:零新事件(纯空转/工具轮形态)→ 空增量
490
- settleRound(record);
491
- } finally {
492
- disarmIdleTimer(record.id);
493
- }
494
-
495
- expect(pi.sendMessage).toHaveBeenCalledTimes(2);
496
- const round1 = pi.sendMessage.mock.calls[0]![0] as { content: string };
497
- expect(round1.content).toContain("ROUND-ONE-REPLY");
498
- // D5 核心:空增量轮占位,不沿用旧 result(= 上一轮增量 → 父 agent 误读为原样重复回复)
499
- const round2 = pi.sendMessage.mock.calls[1]![0] as { content: string };
500
- expect(round2.content).toContain("(no output this round)");
501
- expect(round2.content).not.toContain("ROUND-ONE-REPLY");
502
- });
503
-
504
- it("多轮增量:第 2 条通知只含第 2 轮文本、不含第 1 轮(D1/G1,roundBaseTurnIndex 推进)", () => {
505
- const record = makeRecord({ id: "sa-multi-round", status: "running" });
506
- record.sessionFile = path.join(agentDir, "multi-round.jsonl");
507
- store.register(record);
508
- armIdleTimer(record.id, () => {});
509
- try {
510
- // 第 1 轮
511
- updateFromEvent(record, { type: "text_delta", delta: "ALPHA-ROUND-ONE" });
512
- updateFromEvent(record, { type: "turn_end" });
513
- settleRound(record);
514
-
515
- // 第 2 轮:turn_end 已闭合前轮 turn → text_delta 开新 turn
516
- updateFromEvent(record, { type: "text_delta", delta: "BETA-ROUND-TWO" });
517
- updateFromEvent(record, { type: "turn_end" });
518
- settleRound(record);
519
- } finally {
520
- disarmIdleTimer(record.id);
521
- }
522
-
523
- expect(pi.sendMessage).toHaveBeenCalledTimes(2);
524
- const round1 = pi.sendMessage.mock.calls[0]![0] as { content: string };
525
- expect(round1.content).toContain("ALPHA-ROUND-ONE");
526
- expect(round1.content).not.toContain("BETA-ROUND-TWO");
527
- const round2 = pi.sendMessage.mock.calls[1]![0] as { content: string };
528
- expect(round2.content).toContain("BETA-ROUND-TWO");
529
- expect(round2.content).not.toContain("ALPHA-ROUND-ONE");
530
- // base 推进写点:第 2 轮后边界 = 2 个 turn(第 3 轮增量将从新 turn 起)
531
- expect(record.roundBaseTurnIndex).toBe(2);
532
- });
533
-
534
- it("[C-1] idle close(closeChatIdle)后父侧收到终态通知:正文空串 + Full transcript 指针行,轮次通知与终态通知都送达(dedup 不吞)", async () => {
535
- const record = makeRecord({ id: "sa-idle-close", status: "running" });
536
- record.sessionFile = path.join(agentDir, "idle-close.jsonl");
537
- store.register(record);
538
- armIdleTimer(record.id, () => {}); // Path A:轮次完成、进程保活等待续聊
539
- try {
540
- updateFromEvent(record, { type: "text_delta", delta: "LAST-ROUND-INCREMENT" });
541
- updateFromEvent(record, { type: "turn_end" });
542
- settleRound(record); // 第 1 条:轮次通知
543
- await service.closeSubagent(record, false); // idle close(D2 路径②)
544
- } finally {
545
- disarmIdleTimer(record.id);
546
- }
547
-
548
- // dedup 不吞:轮次通知(key=id:1)+ 终态通知(key=裸 id)两条都送达
549
- expect(pi.sendMessage).toHaveBeenCalledTimes(2);
550
- const roundMsg = pi.sendMessage.mock.calls[0]![0] as { content: string };
551
- expect(roundMsg.content).toContain("finished a round");
552
- expect(roundMsg.content).toContain("LAST-ROUND-INCREMENT");
553
- // 终态通知:正文空串占位(不含上一轮增量,D2 路径②)+ 轮次统计 + 指针行
554
- const closeMsg = pi.sendMessage.mock.calls[1]![0] as { content: string };
555
- expect(closeMsg.content).toContain("completed after 1 round.");
556
- expect(closeMsg.content).toContain(`\n\nFull transcript: ${record.sessionFile}`);
557
- expect(closeMsg.content).not.toContain("LAST-ROUND-INCREMENT");
558
- });
559
-
560
- it("[C-1] 路径①(closeAfterRoundSettled):轮次通知与终态通知两条都送达,终态携带本轮增量 + 轮次统计 + 指针行", async () => {
561
- const record = makeRecord({ id: "sa-path-one", status: "running" });
562
- record.sessionFile = path.join(agentDir, "path-one.jsonl");
563
- store.register(record);
564
- record.closeAfterRound = true; // busy 轮中 close(force:false) 置的标志
565
- armIdleTimer(record.id, () => {});
566
- try {
567
- updateFromEvent(record, { type: "text_delta", delta: "PATH-ONE-ROUND-REPLY" });
568
- updateFromEvent(record, { type: "turn_end" });
569
- settleRound(record); // 轮次通知 + 消费标志 → closeAfterRoundSettled 终态化
570
- // 终态通知在 async finalize 链末尾的 notifyClosed——直接等它发出
571
- await vi.waitFor(() => expect(pi.sendMessage).toHaveBeenCalledTimes(2));
572
- await vi.waitFor(() => expect(store.getMutable(record.id)).toBeUndefined());
573
- } finally {
574
- disarmIdleTimer(record.id);
575
- }
576
-
577
- expect(pi.sendMessage).toHaveBeenCalledTimes(2);
578
- const roundMsg = pi.sendMessage.mock.calls[0]![0] as { content: string };
579
- expect(roundMsg.content).toContain("finished a round");
580
- expect(roundMsg.content).toContain("PATH-ONE-ROUND-REPLY");
581
- // 路径①终态:合成 result 沿用 record.result(= 本轮增量,D2 路径①)+ 统计 + 指针
582
- const closeMsg = pi.sendMessage.mock.calls[1]![0] as { content: string };
583
- expect(closeMsg.content).toContain("completed after 1 round.");
584
- expect(closeMsg.content).toContain("PATH-ONE-ROUND-REPLY");
585
- expect(closeMsg.content).toContain(`\n\nFull transcript: ${record.sessionFile}`);
586
- });
587
-
588
- it("[G4] 对照:one-shot(chatMode:false)close → 不发终态通知(现状行为字节不变)", async () => {
589
- const record = makeRecord({ id: "sa-oneshot-close", status: "running", chatMode: false });
590
- record.sessionFile = path.join(agentDir, "oneshot-close.jsonl");
591
- store.register(record);
592
-
593
- await service.closeSubagent(record, false); // resumable → closeChatIdle → notifyClosed 拒绝
594
-
595
- expect(record.status).toBe("closed");
596
- expect(pi.sendMessage).not.toHaveBeenCalled();
597
- });
598
-
599
- it("[W16 P-1 回归] close 终态 subagent-record entry 的 result == 轮终真实值(closeChatIdle 不抹空)", async () => {
600
- // 完整生命周期:register(entry: running)→ 轮终(record.result = 轮终真实值)→
601
- // idle close(archive 落 close 终态 entry——D4 重建源)。
602
- // 缺陷形态(verifier W16 实测复现):closeChatIdle 合成 text:"" 经 completeRecord
603
- // 覆盖 record.result → close entry 的 result=''(轮终 'ok' 被抹空),W18 消费后
604
- // 重开 session result 回退空串。修复:doneResult.text 沿用 record.result。
605
- const record = makeRecord({ id: "sa-close-entry-result", status: "running" });
606
- record.sessionFile = path.join(agentDir, "close-entry-result.jsonl");
607
- store.register(record);
608
- armIdleTimer(record.id, () => {});
609
- try {
610
- updateFromEvent(record, { type: "text_delta", delta: "ok" });
611
- updateFromEvent(record, { type: "turn_end" });
612
- settleRound(record); // 轮终:onRoundSettled 写 record.result = "ok"
613
- expect(record.result).toBe("ok"); // 前提锚点:轮终真实值在位
614
- await service.closeSubagent(record, false); // idle close → completeRecord + archive
615
- } finally {
616
- disarmIdleTimer(record.id);
617
- }
618
-
619
- // appendEntry 序列中的 subagent-record 快照:register(running)→ close(closed)。
620
- const snapshots = pi.appendEntry.mock.calls
621
- .filter(([type]) => type === "subagent-record")
622
- .map(([, data]) => data as { status: string; result?: string });
623
- expect(snapshots.length).toBeGreaterThanOrEqual(2);
624
- const closeEntry = snapshots[snapshots.length - 1]!;
625
- expect(closeEntry.status).toBe("closed");
626
- // 修复断言:close 终态 entry 保持轮终真实值(缺陷形态为 '')
627
- expect(closeEntry.result).toBe("ok");
628
- });
629
- });