@zhushanwen/pi-subagent-workflow 8.6.0 → 8.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/package.json +9 -13
  2. package/relay/relay.mjs +1 -1
  3. package/src/host/__tests__/pi-host.test.ts +214 -0
  4. package/src/host/pi-host.ts +109 -0
  5. package/src/index.ts +89 -32
  6. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  7. package/src/injectors/__tests__/engine-section-stability.test.ts +341 -0
  8. package/src/injectors/__tests__/subagent-list-injector.test.ts +5 -5
  9. package/src/injectors/__tests__/workflow-list-injector.test.ts +5 -5
  10. package/src/injectors/engine-awareness.ts +123 -0
  11. package/src/injectors/model-list-injector.ts +1 -1
  12. package/src/injectors/subagent-list-injector.ts +8 -8
  13. package/src/injectors/workflow-list-injector.ts +8 -8
  14. package/src/interface/__tests__/detectors.test.ts +3 -3
  15. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +2 -2
  16. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +2 -2
  17. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
  18. package/src/interface/bg-notify-render.ts +4 -4
  19. package/src/interface/commands.ts +3 -3
  20. package/src/interface/format.ts +2 -2
  21. package/src/interface/helpers.ts +1 -1
  22. package/src/interface/list-component.ts +4 -4
  23. package/src/interface/list-shared.ts +2 -2
  24. package/src/interface/list-view.ts +2 -2
  25. package/src/interface/subagent-actions.ts +263 -11
  26. package/src/interface/subagent-tool-schema.ts +20 -13
  27. package/src/interface/subagent-tool.ts +11 -6
  28. package/src/interface/subagents.ts +3 -3
  29. package/src/interface/tool-render.ts +34 -2
  30. package/src/interface/tool-workflow-script.ts +4 -4
  31. package/src/interface/tool-workflow.ts +6 -6
  32. package/src/interface/views/WorkflowsView.ts +5 -5
  33. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  34. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  35. package/src/interface/views/detail-content.ts +4 -4
  36. package/src/interface/views/format.ts +4 -4
  37. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +11 -11
  38. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  39. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  40. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  41. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  42. package/src/execution/__tests__/alive-store.test.ts +0 -147
  43. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  44. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  45. package/src/execution/__tests__/bg-notify-render.test.ts +0 -329
  46. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  47. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -601
  48. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  49. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  50. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  51. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  52. package/src/execution/__tests__/config.test.ts +0 -110
  53. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  54. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  55. package/src/execution/__tests__/delivery-methods.test.ts +0 -422
  56. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  57. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  58. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  59. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  60. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  61. package/src/execution/__tests__/execution-record.test.ts +0 -1394
  62. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +0 -171
  63. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  64. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  65. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -169
  66. package/src/execution/__tests__/format.test.ts +0 -458
  67. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  68. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  69. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  70. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  71. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -242
  72. package/src/execution/__tests__/host-mode.test.ts +0 -87
  73. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  74. package/src/execution/__tests__/index-session-start.test.ts +0 -672
  75. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  76. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -383
  77. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  78. package/src/execution/__tests__/list-component.test.ts +0 -401
  79. package/src/execution/__tests__/list-fields.test.ts +0 -140
  80. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  81. package/src/execution/__tests__/model-resolver.test.ts +0 -465
  82. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  83. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  84. package/src/execution/__tests__/notifier-flush.test.ts +0 -612
  85. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  86. package/src/execution/__tests__/notify-ledger.test.ts +0 -826
  87. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  88. package/src/execution/__tests__/output-collector.test.ts +0 -358
  89. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  90. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  91. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  92. package/src/execution/__tests__/record-store.test.ts +0 -1057
  93. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  94. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  95. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  96. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  97. package/src/execution/__tests__/relay-env.test.ts +0 -42
  98. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  99. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  100. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  101. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  102. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -687
  103. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  104. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  105. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  106. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -199
  107. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  108. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  109. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  110. package/src/execution/__tests__/session-pending.test.ts +0 -197
  111. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  112. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  113. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -350
  114. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  115. package/src/execution/__tests__/spawn-args.test.ts +0 -445
  116. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  117. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  118. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  119. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  120. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  121. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  122. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  123. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  124. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  125. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  126. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  127. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  128. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  129. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  130. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -599
  131. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  132. package/src/execution/__tests__/timeout-integration.test.ts +0 -615
  133. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  134. package/src/execution/__tests__/tool-action.test.ts +0 -476
  135. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  136. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  137. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  138. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  139. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  140. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  141. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  142. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  143. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  144. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  145. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  146. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  147. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  148. package/src/execution/agent-registry.ts +0 -199
  149. package/src/execution/agent-result-mapper.ts +0 -90
  150. package/src/execution/alive-store.ts +0 -92
  151. package/src/execution/argv-mirror.ts +0 -113
  152. package/src/execution/best-effort.ts +0 -38
  153. package/src/execution/channel-registry-access.ts +0 -144
  154. package/src/execution/concurrency-pool.ts +0 -116
  155. package/src/execution/config.ts +0 -96
  156. package/src/execution/dialog-queue.ts +0 -330
  157. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  158. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  159. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  160. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  161. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  162. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  163. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  164. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  165. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  166. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  167. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  168. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  169. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  170. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  171. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  172. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  173. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  174. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  175. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  176. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  177. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  178. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  179. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  180. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  181. package/src/execution/engine/common/data-dir.ts +0 -62
  182. package/src/execution/engine/common/errors.ts +0 -183
  183. package/src/execution/engine/common/event-journal.ts +0 -254
  184. package/src/execution/engine/common/journal-replay.ts +0 -62
  185. package/src/execution/engine/common/kill-chain.ts +0 -221
  186. package/src/execution/engine/common/nesting-guard.ts +0 -50
  187. package/src/execution/engine/common/persona-router.ts +0 -108
  188. package/src/execution/engine/common/pool-manager.ts +0 -226
  189. package/src/execution/engine/common/schema-emulation.ts +0 -189
  190. package/src/execution/engine/common/session-view-projection.ts +0 -51
  191. package/src/execution/engine/engine-discovery.ts +0 -65
  192. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  193. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  194. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  195. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  196. package/src/execution/engine/engines/pi/reader.ts +0 -48
  197. package/src/execution/engine/engines/pi/registration.ts +0 -35
  198. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  199. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  200. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  201. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  202. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  203. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  204. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  207. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  208. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  209. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  210. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  211. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  212. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  213. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  214. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -658
  215. package/src/execution/engine/host-task-spec.ts +0 -47
  216. package/src/execution/engine/model-prompt.ts +0 -59
  217. package/src/execution/engine/paths.ts +0 -42
  218. package/src/execution/engine/port.ts +0 -153
  219. package/src/execution/engine/registry.ts +0 -123
  220. package/src/execution/engine/routing.ts +0 -218
  221. package/src/execution/engine/types.ts +0 -309
  222. package/src/execution/execute-options-mapper.ts +0 -112
  223. package/src/execution/execution-record.ts +0 -965
  224. package/src/execution/finalize-record.ts +0 -251
  225. package/src/execution/finalized-marker.ts +0 -51
  226. package/src/execution/get-state-handshake.ts +0 -104
  227. package/src/execution/host-mode.ts +0 -56
  228. package/src/execution/idle-gc.ts +0 -47
  229. package/src/execution/lifecycle-manager.ts +0 -513
  230. package/src/execution/lifecycle-predicates.ts +0 -65
  231. package/src/execution/manifest-store.ts +0 -256
  232. package/src/execution/model-config-service.ts +0 -219
  233. package/src/execution/model-resolver.ts +0 -248
  234. package/src/execution/notifier.ts +0 -330
  235. package/src/execution/notify-ledger.ts +0 -580
  236. package/src/execution/output-collector.ts +0 -228
  237. package/src/execution/path-encoding.ts +0 -60
  238. package/src/execution/pi-invocation.ts +0 -120
  239. package/src/execution/record-entry.ts +0 -132
  240. package/src/execution/record-store.ts +0 -1234
  241. package/src/execution/relay-env.ts +0 -37
  242. package/src/execution/session-context-resolver.ts +0 -64
  243. package/src/execution/session-file-gc.ts +0 -120
  244. package/src/execution/session-pending.ts +0 -164
  245. package/src/execution/session-reconstructor.ts +0 -678
  246. package/src/execution/session-runner.ts +0 -1781
  247. package/src/execution/sessions-index.ts +0 -304
  248. package/src/execution/spawn-event-adapter.ts +0 -363
  249. package/src/execution/stdin-writer.ts +0 -198
  250. package/src/execution/stream-sink.ts +0 -126
  251. package/src/execution/subagent-service.ts +0 -2141
  252. package/src/execution/subprocess-agent-runner.ts +0 -317
  253. package/src/execution/temp-prompt.ts +0 -62
  254. package/src/execution/tombstone-store.ts +0 -72
  255. package/src/execution/turn-limiter.ts +0 -102
  256. package/src/execution/types.ts +0 -973
  257. package/src/execution/ui-channels.ts +0 -216
  258. package/src/execution/ui-interaction-model.ts +0 -48
  259. package/src/execution/ui-request-handler-factory.ts +0 -212
  260. package/src/execution/ui-request-observability.ts +0 -81
  261. package/src/execution/ui-request-queue.ts +0 -184
  262. package/src/execution/worktree-manager.ts +0 -688
  263. package/src/execution/worktree-registry.ts +0 -222
  264. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -349
  265. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -202
  266. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -152
  267. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  268. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  269. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -809
  270. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  271. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  272. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  273. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -451
  274. package/src/orchestration/__tests__/jsonl-run-store-corrupt-entry.test.ts +0 -150
  275. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  276. package/src/orchestration/__tests__/jsonl-run-store-retention.test.ts +0 -202
  277. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  278. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -600
  279. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  280. package/src/orchestration/__tests__/lifecycle.test.ts +0 -659
  281. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +0 -95
  282. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -2025
  283. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  284. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -201
  285. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  286. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +0 -368
  287. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  288. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  289. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -563
  290. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  291. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -129
  292. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  293. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  294. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  295. package/src/orchestration/agent-opts-resolver.ts +0 -174
  296. package/src/orchestration/args-validator.ts +0 -127
  297. package/src/orchestration/config-loader.ts +0 -315
  298. package/src/orchestration/error-recovery.ts +0 -1018
  299. package/src/orchestration/execute-agent-call.ts +0 -256
  300. package/src/orchestration/launcher.ts +0 -455
  301. package/src/orchestration/lifecycle.ts +0 -399
  302. package/src/orchestration/models/__tests__/budget.test.ts +0 -307
  303. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  304. package/src/orchestration/models/agent-call.ts +0 -83
  305. package/src/orchestration/models/budget.ts +0 -118
  306. package/src/orchestration/models/ports.ts +0 -164
  307. package/src/orchestration/models/run-runtime.ts +0 -104
  308. package/src/orchestration/models/run-spec.ts +0 -83
  309. package/src/orchestration/models/run-state.ts +0 -44
  310. package/src/orchestration/models/trace.ts +0 -183
  311. package/src/orchestration/models/types.ts +0 -301
  312. package/src/orchestration/models/workflow-run.ts +0 -254
  313. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  314. package/src/orchestration/models/workflow-script.ts +0 -117
  315. package/src/orchestration/script-lint.ts +0 -766
  316. package/src/orchestration/skill-discovery.ts +0 -115
  317. package/src/orchestration/worker-handle.ts +0 -115
  318. package/src/orchestration/worker-host.ts +0 -98
  319. package/src/orchestration/worker-script-builder.ts +0 -421
  320. package/src/orchestration/workflow-files.ts +0 -85
  321. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  322. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  323. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  324. package/src/shared/__tests__/model-ref.test.ts +0 -306
  325. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  326. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  327. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  328. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  329. package/src/shared/__tests__/timer-delay.test.ts +0 -61
  330. package/src/shared/agent-event.ts +0 -13
  331. package/src/shared/agent-ref.ts +0 -57
  332. package/src/shared/meta-parser.ts +0 -261
  333. package/src/shared/model-ref.ts +0 -286
  334. package/src/shared/resource-discovery.ts +0 -811
  335. package/src/shared/resource-meta.ts +0 -65
  336. package/src/shared/schema-env.ts +0 -44
  337. package/src/shared/schema-jsonify.ts +0 -58
  338. package/src/shared/timer-delay.ts +0 -54
  339. package/src/shared/xml-injection.ts +0 -35
  340. package/workflows/README.md +0 -81
  341. package/workflows/_shared/agent-refs.cjs +0 -40
  342. package/workflows/chain.js +0 -131
  343. package/workflows/map-reduce.js +0 -174
  344. package/workflows/parallel.js +0 -148
  345. package/workflows/review-fix-loop-utils.cjs +0 -1309
  346. package/workflows/review-fix-loop.js +0 -1404
  347. 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
- });