@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,826 +0,0 @@
1
- // src/execution/__tests__/notify-ledger.test.ts
2
- //
3
- // U2 B-ledger 单测族:通知账本与 courier 的四步生命周期
4
- // (设计 docs/design/subagent-dispatch-reliability.md §3.3 D4/D5)。
5
- //
6
- // 覆盖验收面:
7
- // - 写账先于投递(顺序断言:appendEntry ledger entry 先于 sendMessage)
8
- // - settled 边沿投递 + isIdle 二次复查命中 busy 时挂回 pending
9
- // - 回执销账(ack entry 追加)后两列 entry 差集为空
10
- // - 重启恢复(新实例扫账本)重放未销账号、已销账零重发
11
- // - notifyId 幂等(重复 record / 重复投递条目可识别)
12
- // - 120s 看门狗触发(fake timers;主 session 长期无 settled 兜底 + 超时重投)
13
- // - 合并投递(同一边沿多条 pending 单条送达,content join + batch details)
14
- // - compaction 降级(ledger/ack entry 被清除后按内存态补写)
15
- // - session_start 恢复钩子(bindNotifyLedgerHost + recoverFromSession:
16
- // mock session entries 含 ledger/ack entry → 重放差集)
17
- // - notifier 四步接线(createNotifier + bindNotifyLedgerHost:notify → 写账 →
18
- // 边沿投递 → 回执销账全链路)
19
-
20
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
21
-
22
- import { clearRateLimiterState, setPiHandle } from "@zhushanwen/pi-extension-logger";
23
- import {
24
- bindNotifyLedgerHost,
25
- createNotifyLedger,
26
- getBoundNotifyLedger,
27
- NOTIFY_ACK_CUSTOM_TYPE,
28
- NOTIFY_CUSTOM_TYPE,
29
- NOTIFY_LEDGER_CUSTOM_TYPE,
30
- NOTIFY_WATCHDOG_MS,
31
- _resetNotifyLedgerForTest,
32
- type NotifyLedgerHost,
33
- } from "../notify-ledger.ts";
34
- import { createNotifier, type BgNotifyRecord, type NotifierHost } from "../notifier.ts";
35
-
36
- // ─── mock host ─────────────────────────────────────────────
37
-
38
- interface LedgerHostMock {
39
- host: NotifyLedgerHost;
40
- /** 追加的 plain custom entry(ledger/ack 两列,含 customType)。与 sessionEntries
41
- * 共享同一数组——送达 custom_message entry 也会落入(无 data 字段)。 */
42
- entries: { type: string; customType: string; data?: Record<string, unknown> }[];
43
- /** session entries 视图(回执扫描 + 恢复扫描的输入;与 entries 共享同一数组)。 */
44
- sessionEntries: unknown[];
45
- /** 送达消息(sendDelivery 调用)。 */
46
- sentMessages: { customType: string; content: string; display: boolean; details?: unknown }[];
47
- /** settled 边沿回调(测试手动触发模拟 pi agent_settled 事件)。 */
48
- settledHandlers: Array<() => void>;
49
- setIdle(idle: boolean): void;
50
- /** 送达是否同步落盘为 custom_message entry(模拟 pi:triggerTurn run 结束后落盘)。
51
- * false = 模拟回执延迟/丢失(消息滞留内存)。 */
52
- deliverPersists: { value: boolean };
53
- /** U4:sendDelivery 是否抛异常(模拟受理被拒——下方 sendDelivery 首行 throw)。 */
54
- deliveryThrows: { value: boolean };
55
- }
56
-
57
- function makeLedgerHost(): LedgerHostMock {
58
- const entries: LedgerHostMock["entries"] = [];
59
- const sessionEntries: unknown[] = entries;
60
- const sentMessages: LedgerHostMock["sentMessages"] = [];
61
- const settledHandlers: Array<() => void> = [];
62
- const idle = { value: true };
63
- const deliverPersists = { value: true };
64
- const deliveryThrows = { value: false };
65
- const host: NotifyLedgerHost = {
66
- appendLedgerEntry: (customType, data) => {
67
- entries.push({ type: "custom", customType, data: data as Record<string, unknown> | undefined });
68
- },
69
- readSessionEntries: () => sessionEntries,
70
- isIdle: () => idle.value,
71
- onAgentSettled: (handler) => {
72
- settledHandlers.push(handler);
73
- },
74
- sendDelivery: (message) => {
75
- if (deliveryThrows.value) {
76
- throw new Error("delivery rejected (mock)");
77
- }
78
- sentMessages.push(message);
79
- if (deliverPersists.value) {
80
- sessionEntries.push({
81
- type: "custom_message",
82
- customType: message.customType,
83
- content: message.content,
84
- display: message.display,
85
- details: message.details,
86
- });
87
- }
88
- },
89
- };
90
- return {
91
- host,
92
- entries,
93
- sessionEntries,
94
- sentMessages,
95
- settledHandlers,
96
- setIdle: (v: boolean) => {
97
- idle.value = v;
98
- },
99
- deliverPersists,
100
- deliveryThrows,
101
- };
102
- }
103
-
104
- /** 触发一次 settled 边沿(模拟 pi agent_settled 事件)。 */
105
- function fireSettled(mock: LedgerHostMock): void {
106
- for (const handler of mock.settledHandlers) handler();
107
- }
108
-
109
- /** 两列 entry 差集(未销账号):ledger entry 的 notifyId − ack entry 的 notifyId。 */
110
- function ledgerDiff(mock: LedgerHostMock): Set<string> {
111
- const ledgerIds = new Set<string>();
112
- const acked = new Set<string>();
113
- for (const e of mock.entries) {
114
- if (e.customType !== NOTIFY_LEDGER_CUSTOM_TYPE && e.customType !== NOTIFY_ACK_CUSTOM_TYPE) continue;
115
- const notifyId = e.data?.["notifyId"];
116
- if (typeof notifyId !== "string") continue;
117
- if (e.customType === NOTIFY_LEDGER_CUSTOM_TYPE) ledgerIds.add(notifyId);
118
- if (e.customType === NOTIFY_ACK_CUSTOM_TYPE) acked.add(notifyId);
119
- }
120
- for (const id of acked) ledgerIds.delete(id);
121
- return ledgerIds;
122
- }
123
-
124
- // ─── 账本四步生命周期 ─────────────────────────────────────
125
-
126
- describe("NotifyLedger — 四步生命周期(D4/D5)", () => {
127
- beforeEach(() => {
128
- _resetNotifyLedgerForTest();
129
- });
130
- afterEach(() => {
131
- _resetNotifyLedgerForTest();
132
- });
133
-
134
- it("①→② 写账先于投递:appendEntry(ledger) 先于 sendDelivery(顺序断言)", () => {
135
- const mock = makeLedgerHost();
136
- const ledger = createNotifyLedger(mock.host);
137
-
138
- const recorded = ledger.record("sa-1", "content-1", { notifyId: "sa-1", id: "sa-1" });
139
- expect(recorded).toBe(true);
140
- // idle → notify 后立即投递(record 内不自投,attemptDeliver 由调用方驱动;
141
- // 此处显式调用验证顺序)
142
- ledger.attemptDeliver();
143
-
144
- expect(mock.sentMessages).toHaveLength(1);
145
- // 顺序断言:账本 entry(entries[0])先于送达消息(sentMessages[0])
146
- expect(mock.entries[0]?.customType).toBe(NOTIFY_LEDGER_CUSTOM_TYPE);
147
- expect(mock.sentMessages[0]?.customType).toBe(NOTIFY_CUSTOM_TYPE);
148
- // 单条投递形态:details = record 本体(notifyId 携带在 details,不在文案)
149
- expect(mock.sentMessages[0]?.details).toMatchObject({ notifyId: "sa-1" });
150
-
151
- ledger.dispose();
152
- });
153
-
154
- it("② settled 边沿投递:busy 挂回 pending,isIdle 复查通过后边沿送达", () => {
155
- const mock = makeLedgerHost();
156
- const ledger = createNotifyLedger(mock.host);
157
-
158
- // busy:notify 后 attemptDeliver 放弃本次,消息挂回 pending
159
- mock.setIdle(false);
160
- expect(ledger.record("sa-busy", "content", { notifyId: "sa-busy" })).toBe(true);
161
- ledger.attemptDeliver();
162
- expect(mock.sentMessages).toHaveLength(0);
163
- expect(ledger.pendingCount()).toBe(1);
164
-
165
- // settled 边沿(边沿回调内 isIdle 复查):仍 busy → 继续挂 pending
166
- fireSettled(mock);
167
- expect(mock.sentMessages).toHaveLength(0);
168
- expect(ledger.pendingCount()).toBe(1);
169
-
170
- // 主 agent 空闲后的下一 settled 边沿 → 送达
171
- mock.setIdle(true);
172
- fireSettled(mock);
173
- expect(mock.sentMessages).toHaveLength(1);
174
- expect(ledger.pendingCount()).toBe(0);
175
- expect(ledger.waitingReceiptCount()).toBe(1);
176
-
177
- ledger.dispose();
178
- });
179
-
180
- it("②→③ 回执销账:送达 entry 出现后下一 settled 边沿追加 ack entry,差集为空", () => {
181
- const mock = makeLedgerHost();
182
- const ledger = createNotifyLedger(mock.host);
183
-
184
- ledger.record("sa-ack", "content", { notifyId: "sa-ack" });
185
- ledger.attemptDeliver();
186
- expect(mock.sentMessages).toHaveLength(1);
187
- expect(ledgerDiff(mock)).toEqual(new Set(["sa-ack"])); // 已送达未销账
188
-
189
- // 下一 settled 边沿:checkReceipts 扫到 custom_message entry(sendDelivery 已
190
- // 模拟落盘)→ 追加 ack entry → 两列差集为空
191
- fireSettled(mock);
192
- expect(ledgerDiff(mock)).toEqual(new Set());
193
- expect(ledger.waitingReceiptCount()).toBe(0);
194
- expect(mock.entries.some((e) => e.customType === NOTIFY_ACK_CUSTOM_TYPE)).toBe(true);
195
-
196
- ledger.dispose();
197
- });
198
-
199
- it("③ 内存不承担销账职责:回执缺失(消息滞留内存)时不销账,差集保留", () => {
200
- const mock = makeLedgerHost();
201
- const ledger = createNotifyLedger(mock.host);
202
-
203
- // 模拟消息滞留内存(如竞态窗口掉进 steer 队列):sendDelivery 不落盘
204
- mock.deliverPersists.value = false;
205
- ledger.record("sa-lost", "content", { notifyId: "sa-lost" });
206
- ledger.attemptDeliver();
207
- expect(mock.sentMessages).toHaveLength(1);
208
-
209
- fireSettled(mock);
210
- // 无 custom_message entry 回执 → 不销账(内存不销账,等看门狗重投)
211
- expect(ledgerDiff(mock)).toEqual(new Set(["sa-lost"]));
212
- expect(ledger.waitingReceiptCount()).toBe(1);
213
-
214
- ledger.dispose();
215
- });
216
-
217
- it("④ 重启恢复:新实例扫两列差集,未销账号重放、已销账零重发", () => {
218
- // 旧实例(模拟崩溃前的进程):sa-r1/sa-r2 送达销账,sa-r3 入账未投
219
- const oldMock = makeLedgerHost();
220
- const oldLedger = createNotifyLedger(oldMock.host);
221
- for (const id of ["sa-r1", "sa-r2"]) {
222
- oldLedger.record(id, `content-${id}`, { notifyId: id });
223
- }
224
- oldLedger.attemptDeliver();
225
- fireSettled(oldMock);
226
- oldLedger.record("sa-r3", "content-sa-r3", { notifyId: "sa-r3" }); // 崩溃前未投
227
- expect(ledgerDiff(oldMock)).toEqual(new Set(["sa-r3"]));
228
- oldLedger.dispose();
229
-
230
- // 重启:新实例扫同一 session 文件(sessionEntries 视图保留旧 entry 列)
231
- const newMock = makeLedgerHost();
232
- newMock.sessionEntries.push(...oldMock.sessionEntries);
233
- const newLedger = createNotifyLedger(newMock.host);
234
- const replayed = newLedger.recoverFromSession();
235
-
236
- expect(replayed).toBe(1); // 仅未销账的 sa-r3 重放
237
- expect(newMock.sentMessages).toHaveLength(1);
238
- expect(newMock.sentMessages[0]?.details).toMatchObject({ notifyId: "sa-r3" });
239
- // 已销账号零重发
240
- const sentIds = newMock.sentMessages.map((m) => (m.details as { notifyId?: string }).notifyId);
241
- expect(sentIds).not.toContain("sa-r1");
242
- expect(sentIds).not.toContain("sa-r2");
243
-
244
- newLedger.dispose();
245
- });
246
-
247
- it("④ notifyId 幂等:重复 record 同 notifyId 返回 false,不重复投递", () => {
248
- const mock = makeLedgerHost();
249
- const ledger = createNotifyLedger(mock.host);
250
-
251
- expect(ledger.record("sa-dup", "content", { notifyId: "sa-dup" })).toBe(true);
252
- expect(ledger.record("sa-dup", "content", { notifyId: "sa-dup" })).toBe(false);
253
-
254
- ledger.attemptDeliver();
255
- ledger.attemptDeliver();
256
- expect(mock.sentMessages).toHaveLength(1); // 至多一次
257
-
258
- // 销账后同 notifyId 再来(重复调用方)→ 仍幂等拒绝(已销账号绝不重发)
259
- fireSettled(mock);
260
- expect(ledger.record("sa-dup", "content", { notifyId: "sa-dup" })).toBe(false);
261
-
262
- ledger.dispose();
263
- });
264
-
265
- it("④ 重复投递条目可识别:批量送达 details.items 每项携带 notifyId(回执匹配)", () => {
266
- const mock = makeLedgerHost();
267
- const ledger = createNotifyLedger(mock.host);
268
-
269
- ledger.record("sa-m1", "c1", { notifyId: "sa-m1" });
270
- ledger.record("sa-m2", "c2", { notifyId: "sa-m2" });
271
- ledger.attemptDeliver();
272
-
273
- expect(mock.sentMessages).toHaveLength(1); // 合并为一条
274
- const msg = mock.sentMessages[0]!;
275
- expect(msg.content).toBe("c1\n\n---\n\nc2");
276
- const details = msg.details as { batch: boolean; items: Array<{ notifyId?: string }> };
277
- expect(details.batch).toBe(true);
278
- expect(details.items.map((i) => i.notifyId)).toEqual(["sa-m1", "sa-m2"]);
279
-
280
- // 批量回执匹配:items[].notifyId 命中 → 两条全部销账
281
- fireSettled(mock);
282
- expect(ledgerDiff(mock)).toEqual(new Set());
283
-
284
- ledger.dispose();
285
- });
286
- });
287
-
288
- // ─── 120s 看门狗(fake timers) ───────────────────────────
289
-
290
- describe("NotifyLedger — 120s 看门狗(D5 ②兜底触发面)", () => {
291
- beforeEach(() => {
292
- vi.useFakeTimers();
293
- _resetNotifyLedgerForTest();
294
- });
295
- afterEach(() => {
296
- _resetNotifyLedgerForTest();
297
- vi.useRealTimers();
298
- });
299
-
300
- it("主 session 长期无 settled:busy 挂起的 pending 由看门狗在空闲后补投", () => {
301
- const mock = makeLedgerHost();
302
- const ledger = createNotifyLedger(mock.host);
303
-
304
- mock.setIdle(false);
305
- ledger.record("sa-wd1", "content", { notifyId: "sa-wd1" });
306
- ledger.attemptDeliver();
307
- expect(mock.sentMessages).toHaveLength(0);
308
-
309
- // 第一个周期仍 busy → 不投
310
- vi.advanceTimersByTime(NOTIFY_WATCHDOG_MS);
311
- expect(mock.sentMessages).toHaveLength(0);
312
-
313
- // 主 agent 空闲(长期无 settled 事件)→ 下一周期看门狗补投
314
- mock.setIdle(true);
315
- vi.advanceTimersByTime(NOTIFY_WATCHDOG_MS);
316
- expect(mock.sentMessages).toHaveLength(1);
317
-
318
- ledger.dispose();
319
- });
320
-
321
- it("超时重投:sent 后一个周期无回执(消息滞留内存丢失)→ 看门狗重投", () => {
322
- const mock = makeLedgerHost();
323
- const ledger = createNotifyLedger(mock.host);
324
-
325
- // 投递但不落盘(模拟 steer 滞留后丢失——回执永不到)
326
- mock.deliverPersists.value = false;
327
- ledger.record("sa-wd2", "content", { notifyId: "sa-wd2" });
328
- ledger.attemptDeliver();
329
- expect(mock.sentMessages).toHaveLength(1);
330
-
331
- // 一个周期后:无回执 → sentAt 超时转回 pending → 本 tick 重投(at-least-once)
332
- vi.advanceTimersByTime(NOTIFY_WATCHDOG_MS);
333
- expect(mock.sentMessages).toHaveLength(2);
334
- expect(mock.sentMessages[1]?.details).toMatchObject({ notifyId: "sa-wd2" }); // 同 notifyId 可识别
335
-
336
- ledger.dispose();
337
- });
338
-
339
- it("回执正常时序不重投:送达落盘 + settled 销账后,看门狗空转零动作", () => {
340
- const mock = makeLedgerHost();
341
- const ledger = createNotifyLedger(mock.host);
342
-
343
- ledger.record("sa-wd3", "content", { notifyId: "sa-wd3" });
344
- ledger.attemptDeliver();
345
- fireSettled(mock); // 销账
346
- expect(ledgerDiff(mock)).toEqual(new Set());
347
-
348
- vi.advanceTimersByTime(NOTIFY_WATCHDOG_MS * 3);
349
- expect(mock.sentMessages).toHaveLength(1); // 无重投
350
- expect(ledgerDiff(mock)).toEqual(new Set()); // 差集保持为空
351
-
352
- ledger.dispose();
353
- });
354
- });
355
-
356
- // ─── U4 投递计数分桶(设计 §5 U4 × §2.2 三条丢失路径) ─────
357
-
358
- describe("NotifyLedger — U4 投递计数分桶", () => {
359
- beforeEach(() => {
360
- _resetNotifyLedgerForTest();
361
- clearRateLimiterState();
362
- });
363
- afterEach(() => {
364
- _resetNotifyLedgerForTest();
365
- setPiHandle(undefined);
366
- clearRateLimiterState();
367
- });
368
-
369
- it("初始快照:三桶全 0(无投递异常时零计数)", () => {
370
- const mock = makeLedgerHost();
371
- const ledger = createNotifyLedger(mock.host);
372
- expect(ledger.deliveryMetrics()).toEqual({ settleRejected: 0, watchdogReplays: 0, recoveryReplays: 0 });
373
- ledger.dispose();
374
- });
375
-
376
- it("②settleRejected 桶:sendDelivery 受理被拒 → +1 且消息挂回 pending;恢复后不再累计", () => {
377
- const mock = makeLedgerHost();
378
- const ledger = createNotifyLedger(mock.host);
379
-
380
- mock.deliveryThrows.value = true;
381
- ledger.record("sa-rj", "content", { notifyId: "sa-rj" });
382
- ledger.attemptDeliver();
383
-
384
- expect(ledger.deliveryMetrics().settleRejected).toBe(1);
385
- expect(mock.sentMessages).toHaveLength(0);
386
- expect(ledger.pendingCount()).toBe(1); // 挂回 pending,账未丢
387
-
388
- // 下一边沿受理恢复:投递成功,rejection 桶不再增长
389
- mock.deliveryThrows.value = false;
390
- ledger.attemptDeliver();
391
- expect(mock.sentMessages).toHaveLength(1);
392
- expect(ledger.deliveryMetrics().settleRejected).toBe(1);
393
-
394
- ledger.dispose();
395
- });
396
-
397
- it("①watchdogReplays 桶:销账超时重投按条数累计(同消息反复超时继续累计)", () => {
398
- vi.useFakeTimers();
399
- try {
400
- const mock = makeLedgerHost();
401
- const ledger = createNotifyLedger(mock.host);
402
-
403
- // 投递但回执永不到(消息滞留内存丢失)
404
- mock.deliverPersists.value = false;
405
- ledger.record("sa-wd", "content", { notifyId: "sa-wd" });
406
- ledger.attemptDeliver();
407
- expect(ledger.deliveryMetrics().watchdogReplays).toBe(0);
408
-
409
- // 第一个周期:销账超时 → 转回 pending 重投,桶 +1
410
- vi.advanceTimersByTime(NOTIFY_WATCHDOG_MS);
411
- expect(mock.sentMessages).toHaveLength(2);
412
- expect(ledger.deliveryMetrics().watchdogReplays).toBe(1);
413
-
414
- // 第二个周期仍无回执:同消息再重投,桶累计到 2(滞留深度可见)
415
- vi.advanceTimersByTime(NOTIFY_WATCHDOG_MS);
416
- expect(mock.sentMessages).toHaveLength(3);
417
- expect(ledger.deliveryMetrics().watchdogReplays).toBe(2);
418
-
419
- ledger.dispose();
420
- } finally {
421
- vi.useRealTimers();
422
- }
423
- });
424
-
425
- it("③recoveryReplays 桶:session_start 恢复重放条数与返回值一致,已销账零计数", () => {
426
- const mock = makeLedgerHost();
427
- mock.sessionEntries.push(
428
- { type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1, notifyId: "s-a", content: "ca", record: { notifyId: "s-a" } } },
429
- { type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1, notifyId: "s-b", content: "cb", record: { notifyId: "s-b" } } },
430
- { type: "custom", customType: NOTIFY_ACK_CUSTOM_TYPE, data: { v: 1, notifyId: "s-a" } },
431
- );
432
-
433
- const ledger = createNotifyLedger(mock.host);
434
- const replayed = ledger.recoverFromSession();
435
-
436
- expect(replayed).toBe(1); // s-b 未销账重放;s-a 已销账零重发
437
- expect(ledger.deliveryMetrics().recoveryReplays).toBe(1);
438
-
439
- // 再次恢复(幂等):差集已入账,零重发零计数
440
- expect(ledger.recoverFromSession()).toBe(0);
441
- expect(ledger.deliveryMetrics().recoveryReplays).toBe(1);
442
-
443
- ledger.dispose();
444
- });
445
-
446
- it("分桶增量经 extensionLogger 通道落日志(appendEntry,msg 固定 key + 动态值在 data)", () => {
447
- const appendEntry = vi.fn();
448
- setPiHandle({ appendEntry });
449
-
450
- const mock = makeLedgerHost();
451
- const ledger = createNotifyLedger(mock.host);
452
-
453
- mock.deliveryThrows.value = true;
454
- ledger.record("sa-log", "content", { notifyId: "sa-log" });
455
- ledger.attemptDeliver();
456
-
457
- expect(appendEntry).toHaveBeenCalledTimes(1);
458
- const [customType, data] = appendEntry.mock.calls[0] as unknown as [string, { level: string; message: string; data: Record<string, unknown> }];
459
- expect(customType).toBe("subagents:log");
460
- expect(data.level).toBe("warn");
461
- expect(data.message).toContain("notify delivery bucket [settleRejected]");
462
- expect(data.data).toEqual({ total: 1, pending: 1 });
463
-
464
- ledger.dispose();
465
- });
466
- });
467
-
468
- // ─── compaction 降级(P-B4) ───────────────────────────────
469
-
470
- describe("NotifyLedger — compaction 降级(P-B4 未验证,条件补写)", () => {
471
- beforeEach(() => {
472
- _resetNotifyLedgerForTest();
473
- });
474
- afterEach(() => {
475
- _resetNotifyLedgerForTest();
476
- });
477
-
478
- it("compaction 清除 ledger/ack entry 后按内存态补写(重建两列差集)", () => {
479
- const mock = makeLedgerHost();
480
- const ledger = createNotifyLedger(mock.host);
481
-
482
- // sa-c1 送达销账、sa-c2 入账未投
483
- ledger.record("sa-c1", "c1", { notifyId: "sa-c1" });
484
- ledger.attemptDeliver();
485
- fireSettled(mock);
486
- ledger.record("sa-c2", "c2", { notifyId: "sa-c2" });
487
- expect(ledgerDiff(mock)).toEqual(new Set(["sa-c2"]));
488
-
489
- // 模拟 compaction 清掉全部 plain custom entry(ledger/ack 两列全丢,送达
490
- // custom_message entry 假设保留——保守验证降级闭环)
491
- mock.sessionEntries.length = 0;
492
-
493
- const rewritten = ledger.compactionCheck();
494
- expect(rewritten).toBe(2); // 补写 sa-c2 的 ledger + sa-c1 的 ack
495
- // 补写后差集恢复:仍只有 sa-c2 未销账(sa-c1 销账事实随 ack 补写恢复)
496
- expect(ledgerDiff(mock)).toEqual(new Set(["sa-c2"]));
497
-
498
- ledger.dispose();
499
- });
500
-
501
- it("entry 存活时 compactionCheck no-op(零补写)", () => {
502
- const mock = makeLedgerHost();
503
- const ledger = createNotifyLedger(mock.host);
504
- ledger.record("sa-nc", "c", { notifyId: "sa-nc" });
505
- ledger.attemptDeliver();
506
- fireSettled(mock);
507
-
508
- expect(ledger.compactionCheck()).toBe(0);
509
- ledger.dispose();
510
- });
511
- });
512
-
513
- // ─── session_start 恢复钩子(bindNotifyLedgerHost) ────────
514
-
515
- describe("bindNotifyLedgerHost — session_start 恢复钩子", () => {
516
- beforeEach(() => {
517
- _resetNotifyLedgerForTest();
518
- });
519
- afterEach(() => {
520
- _resetNotifyLedgerForTest();
521
- });
522
-
523
- it("bind + recoverFromSession:mock session 含 ledger/ack entry → 重放差集", () => {
524
- const mock = makeLedgerHost();
525
- // 预置 session 文件内容(模拟重启前的落盘):2 条 ledger + 1 条 ack + 1 条已送达
526
- mock.sessionEntries.push(
527
- { type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1, notifyId: "s-a", content: "ca", record: { notifyId: "s-a" } } },
528
- { type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1, notifyId: "s-b", content: "cb", record: { notifyId: "s-b" } } },
529
- { type: "custom", customType: NOTIFY_ACK_CUSTOM_TYPE, data: { v: 1, notifyId: "s-a" } },
530
- { type: "custom_message", customType: NOTIFY_CUSTOM_TYPE, content: "ca", display: true, details: { notifyId: "s-a" } },
531
- );
532
-
533
- const ledger = bindNotifyLedgerHost(mock.host);
534
- const replayed = ledger.recoverFromSession();
535
-
536
- expect(replayed).toBe(1); // s-b 未销账重放;s-a 已销账零重发
537
- expect(mock.sentMessages).toHaveLength(1);
538
- expect(mock.sentMessages[0]?.content).toBe("cb");
539
- expect(mock.sentMessages[0]?.details).toMatchObject({ notifyId: "s-b" });
540
- });
541
-
542
- it("重复 bind 替换旧实例(/resume /fork /new 的 session_start)", () => {
543
- const mock1 = makeLedgerHost();
544
- bindNotifyLedgerHost(mock1.host);
545
- expect(bindNotifyLedgerHost(makeLedgerHost().host)).toBeDefined();
546
- // 旧实例已 dispose(settled 回调静默)
547
- fireSettled(mock1);
548
- expect(mock1.sentMessages).toHaveLength(0);
549
- });
550
-
551
- it("损坏 / 形态不符 entry 跳过不 throw(无 notifyId / data 非 object)", () => {
552
- const mock = makeLedgerHost();
553
- mock.sessionEntries.push(
554
- { type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1 } }, // 无 notifyId
555
- { type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE }, // 无 data
556
- { type: "custom", customType: "unrelated-type", data: { notifyId: "x" } },
557
- null,
558
- );
559
- const ledger = bindNotifyLedgerHost(mock.host);
560
- expect(ledger.recoverFromSession()).toBe(0);
561
- });
562
- });
563
-
564
- // ─── notifier 四步接线(createNotifier × ledger) ─────────
565
-
566
- describe("createNotifier — ledger 四步接线(U2)", () => {
567
- beforeEach(() => {
568
- _resetNotifyLedgerForTest();
569
- });
570
- afterEach(() => {
571
- _resetNotifyLedgerForTest();
572
- });
573
-
574
- /** notifier 侧 mock host:sendMessage 是 ledger sendDelivery 的下游(index.ts
575
- * 装配 sendDelivery = pi.sendMessage({triggerTurn:true}),此处直接捕获)。 */
576
- function makeNotifierHost(): NotifierHost & {
577
- sentOptions: unknown[];
578
- sentMessages: { customType: string; content: string; display: boolean; details?: unknown }[];
579
- } {
580
- const sentOptions: unknown[] = [];
581
- const sentMessages: { customType: string; content: string; display: boolean; details?: unknown }[] = [];
582
- return {
583
- sentOptions,
584
- sentMessages,
585
- sendMessage(message, options) {
586
- sentOptions.push(options);
587
- sentMessages.push(message);
588
- },
589
- hasRunningBackground: () => false,
590
- isIdle: () => true,
591
- };
592
- }
593
-
594
- function oneShotRecord(id: string, result = "done"): BgNotifyRecord {
595
- return { id, status: "closed", agent: "worker", result, startedAt: 1, endedAt:2 };
596
- }
597
-
598
- it("notify → 写账 → 投递 → 回执销账全链路(notifyId = id,G4 文案不变)", () => {
599
- const ledgerMock = makeLedgerHost();
600
- // sendDelivery 桥接到 notifier host.sendMessage(模拟 index.ts 装配)
601
- const notifierHost = makeNotifierHost();
602
- ledgerMock.host.sendDelivery = (message) => {
603
- ledgerMock.sentMessages.push(message);
604
- if (ledgerMock.deliverPersists.value) {
605
- ledgerMock.sessionEntries.push({
606
- type: "custom_message",
607
- customType: message.customType,
608
- content: message.content,
609
- display: message.display,
610
- details: message.details,
611
- });
612
- }
613
- notifierHost.sendMessage(message, { triggerTurn: true });
614
- };
615
- bindNotifyLedgerHost(ledgerMock.host);
616
-
617
- const notifier = createNotifier(notifierHost);
618
- notifier.notify(oneShotRecord("sa-n1"));
619
-
620
- // ① 写账先于 ② 投递:ledger entry 落盘且消息已送达
621
- expect(ledgerMock.entries[0]?.customType).toBe(NOTIFY_LEDGER_CUSTOM_TYPE);
622
- expect(notifierHost.sentMessages).toHaveLength(1);
623
- // 单通道断言:options 只有 triggerTurn,无 deliverAs
624
- expect(notifierHost.sentOptions[0]).toEqual({ triggerTurn: true });
625
- // G4 文案锚:one-shot 完成通知逐字节不变(notifyId 不进文案)
626
- expect(notifierHost.sentMessages[0]?.content).toBe('Subagent "worker" (sa-n1) completed. Result:\ndone');
627
- // details 携带 notifyId + outcome 物化(U3)
628
- expect(notifierHost.sentMessages[0]?.details).toMatchObject({ notifyId: "sa-n1", outcome: "completed" });
629
-
630
- // ③ 回执销账:settled 边沿 → ack entry + 差集为空
631
- fireSettled(ledgerMock);
632
- expect(ledgerDiff(ledgerMock)).toEqual(new Set());
633
-
634
- notifier.dispose();
635
- });
636
-
637
- it("chatMode round dedupe key 平移:notifyId = `${id}:${round}`,规则不变", () => {
638
- const ledgerMock = makeLedgerHost();
639
- const notifierHost = makeNotifierHost();
640
- // 桥接:ledger sendDelivery → notifier host.sendMessage(模拟 index.ts 装配)
641
- ledgerMock.host.sendDelivery = (message) => {
642
- notifierHost.sendMessage(message, { triggerTurn: true });
643
- if (ledgerMock.deliverPersists.value) {
644
- ledgerMock.sessionEntries.push({
645
- type: "custom_message",
646
- customType: message.customType,
647
- content: message.content,
648
- display: message.display,
649
- details: message.details,
650
- });
651
- }
652
- };
653
- bindNotifyLedgerHost(ledgerMock.host);
654
-
655
- const notifier = createNotifier(notifierHost);
656
- notifier.notify({ id: "sa-chat", status: "running", agent: "w", round: 1, result: "r1", startedAt: 1, endedAt: 2 });
657
- notifier.notify({ id: "sa-chat", status: "running", agent: "w", round: 2, result: "r2", startedAt: 3, endedAt: 4 });
658
- // 同 id 同 round 第二次被 notifyId 幂等吞
659
- notifier.notify({ id: "sa-chat", status: "running", agent: "w", round: 1, result: "r1-again", startedAt: 5, endedAt: 6 });
660
-
661
- expect(notifierHost.sentMessages).toHaveLength(2);
662
- expect(notifierHost.sentMessages[0]?.details).toMatchObject({ notifyId: "sa-chat:1" });
663
- expect(notifierHost.sentMessages[1]?.details).toMatchObject({ notifyId: "sa-chat:2" });
664
- // chatMode 轮次通知文案(running 分支)不变
665
- expect(notifierHost.sentMessages[0]?.content).toBe('Subagent "w" (sa-chat) finished a round. Reply:\nr1');
666
-
667
- notifier.dispose();
668
- });
669
-
670
- it("ledger 未 bind 时退回内核路径(向后兼容,options 仍单通道 triggerTurn)", () => {
671
- const notifierHost = makeNotifierHost();
672
- const notifier = createNotifier(notifierHost);
673
- notifier.notify(oneShotRecord("sa-nb"));
674
-
675
- expect(notifierHost.sentMessages).toHaveLength(1);
676
- expect(notifierHost.sentOptions[0]).toEqual({ triggerTurn: true });
677
- expect(notifierHost.sentMessages[0]?.content).toBe('Subagent "worker" (sa-nb) completed. Result:\ndone');
678
-
679
- notifier.dispose();
680
- });
681
-
682
- it("U4 warn 注入:内核路径 port.send 失败 → 警告落 extensionLogger appendEntry,console.warn 零调用", () => {
683
- const appendEntry = vi.fn();
684
- setPiHandle({ appendEntry });
685
- const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
686
- try {
687
- const notifierHost = makeNotifierHost();
688
- // sendMessage 抛异常 = port.send 同步失败 → 内核 onSendFail → warn(经注入出口)
689
- notifierHost.sendMessage = () => {
690
- throw new Error("channel closed (mock)");
691
- };
692
- const notifier = createNotifier(notifierHost);
693
- notifier.notify(oneShotRecord("sa-warn"));
694
-
695
- const logCalls = appendEntry.mock.calls.filter((c) => c[0] === "subagents:log");
696
- expect(logCalls.length).toBeGreaterThanOrEqual(1);
697
- // 按内容定位目标日志(logCalls[0] 可能是 ledger 未 bind 的 fallback warn——
698
- // 该降级留痕同样落 extensionLogger 通道,先于 port.send 失败发生)
699
- const sendFailLogged = logCalls.some((c) => JSON.stringify(c[1]).includes("port.send failed"));
700
- expect(sendFailLogged).toBe(true);
701
- expect(consoleWarnSpy).not.toHaveBeenCalled();
702
-
703
- notifier.dispose();
704
- } finally {
705
- consoleWarnSpy.mockRestore();
706
- setPiHandle(undefined);
707
- clearRateLimiterState();
708
- }
709
- });
710
-
711
- it("dispose 摘除模块级绑定:后续 notify 走内核路径,settled 边沿静默", () => {
712
- const ledgerMock = makeLedgerHost();
713
- bindNotifyLedgerHost(ledgerMock.host);
714
- const notifierHost = makeNotifierHost();
715
- const notifier = createNotifier(notifierHost);
716
- notifier.dispose();
717
-
718
- fireSettled(ledgerMock);
719
- // revive(/resume /fork /new 后):notifier 复活,但 ledger 已随 dispose 摘除
720
- // ——notify 退回内核路径
721
- notifier.revive();
722
- notifier.notify(oneShotRecord("sa-post-dispose"));
723
- expect(notifierHost.sentMessages).toHaveLength(1); // 内核路径立即投(无 ledger)
724
- });
725
- });
726
-
727
- // ─── MF-5: settled 监听单例化 ──────────────────────────────
728
- //
729
- // 回归背景:index.ts:428-430 每次 session_start 注册新 pi.on("agent_settled")
730
- // handler,旧实例 dispose 只置标志不移除物理监听(pi 0.84.1 on() 无 off)——
731
- // 长会话 N 次切换累积 N 个死 handler。修复:bind 路径首次注册模块级单例
732
- // handler(settledEdgeDispatch),后续 bind 只换 boundLedger 引用。
733
-
734
- describe("MF-5: settled 监听单例化(多次 bind 物理监听数不增)", () => {
735
- beforeEach(() => {
736
- _resetNotifyLedgerForTest();
737
- });
738
- afterEach(() => {
739
- _resetNotifyLedgerForTest();
740
- });
741
-
742
- it("连续 3 次 bind(/resume /fork /new 的 session_start)→ onAgentSettled 注册仅首次发生", () => {
743
- const mocks = [makeLedgerHost(), makeLedgerHost(), makeLedgerHost()];
744
- const registerSpies = mocks.map((mock) => {
745
- const spy = vi.fn();
746
- mock.host.onAgentSettled = spy;
747
- return spy;
748
- });
749
-
750
- bindNotifyLedgerHost(mocks[0]!.host);
751
- bindNotifyLedgerHost(mocks[1]!.host);
752
- bindNotifyLedgerHost(mocks[2]!.host);
753
-
754
- // 首次 bind 注册一次(单例 handler);后续 bind 只换 boundLedger 引用
755
- expect(registerSpies[0]).toHaveBeenCalledTimes(1);
756
- expect(registerSpies[1]).not.toHaveBeenCalled();
757
- expect(registerSpies[2]).not.toHaveBeenCalled();
758
- });
759
-
760
- it("边沿分发到当前活跃实例:旧 mock 上的边沿事件驱动新 ledger 投递,旧实例零动作", () => {
761
- const mock1 = makeLedgerHost();
762
- bindNotifyLedgerHost(mock1.host);
763
- const mock2 = makeLedgerHost();
764
- bindNotifyLedgerHost(mock2.host);
765
-
766
- // 活跃实例(mock2 的 ledger)入账 pending
767
- const ledger = getBoundNotifyLedger();
768
- expect(ledger).toBeDefined();
769
- ledger!.record("sa-active", "content", { notifyId: "sa-active" });
770
-
771
- // 旧 mock(mock1)上触发边沿——单例 handler 分发到 boundLedger(mock2)
772
- fireSettled(mock1);
773
- expect(mock2.sentMessages).toHaveLength(1);
774
- expect(mock2.sentMessages[0]?.details).toMatchObject({ notifyId: "sa-active" });
775
- // 旧实例无动作(disposed 短路语义:不会成为分发目标)
776
- expect(mock1.sentMessages).toHaveLength(0);
777
- });
778
-
779
- it("活跃实例 dispose 后边沿静默(boundLedger 摘除 → 单例 handler 无分发目标)", () => {
780
- const mock1 = makeLedgerHost();
781
- bindNotifyLedgerHost(mock1.host);
782
- getBoundNotifyLedger()!.dispose();
783
-
784
- fireSettled(mock1);
785
- expect(mock1.sentMessages).toHaveLength(0);
786
- });
787
-
788
- it("直调 createNotifyLedger 注册行为不变(默认注册 / 显式 registerSettledListener:false 跳过)", () => {
789
- const mockDefault = makeLedgerHost();
790
- const ledgerDefault = createNotifyLedger(mockDefault.host);
791
- expect(mockDefault.settledHandlers).toHaveLength(1);
792
- ledgerDefault.dispose();
793
-
794
- const mockSkip = makeLedgerHost();
795
- const ledgerSkip = createNotifyLedger(mockSkip.host, { registerSettledListener: false });
796
- expect(mockSkip.settledHandlers).toHaveLength(0);
797
- ledgerSkip.dispose();
798
- });
799
- });
800
-
801
- // ─── 常量等值钉住(notify-ledger 与 notifier/渲染器共用字符串) ──
802
-
803
- describe("notify-ledger 常量锚", () => {
804
- it("NOTIFY_CUSTOM_TYPE 与 notifier 送达 customType / ledger 回执扫描同源", async () => {
805
- const notifierModule = await import("../notifier.ts");
806
- // notifier 模块不再自带本地常量(单一常量源 = notify-ledger);用运行时行为钉住:
807
- // ledger 回执扫描匹配的 customType === notifier 送达消息的 customType。
808
- const mock = makeLedgerHost();
809
- const notifierHost: NotifierHost = {
810
- sendMessage: (message) => {
811
- mock.host.sendDelivery(message);
812
- },
813
- hasRunningBackground: () => false,
814
- isIdle: () => true,
815
- };
816
- const ledger = bindNotifyLedgerHost(mock.host);
817
- const notifier = createNotifier(notifierHost);
818
- notifier.notify({ id: "sa-cst", status: "closed", agent: "w", result: "r", startedAt: 1, endedAt: 2 });
819
- fireSettled(mock);
820
- // 全链路销账成功 === 三处 customType 同一字符串(否则回执扫描不命中)
821
- expect(ledgerDiff(mock)).toEqual(new Set());
822
- expect(typeof notifierModule.createNotifier).toBe("function");
823
- ledger.dispose();
824
- _resetNotifyLedgerForTest();
825
- });
826
- });