@zhushanwen/subagent-core 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-KXVBIVOU.js +16 -0
  3. package/dist/chunk-T2SYBW3I.js +24 -0
  4. package/dist/chunk-VURUAGMM.js +2305 -0
  5. package/dist/{types-Dv4QhSJ_.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +835 -668
  6. package/dist/{types-Dv4QhSJ_.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +835 -668
  7. package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
  8. package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
  9. package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
  10. package/dist/execution/engine/engine-discovery-scan.js +20 -0
  11. package/dist/execution/engine/paths.cjs +6 -23
  12. package/dist/execution/engine/paths.d.cts +1 -8
  13. package/dist/execution/engine/paths.d.ts +1 -8
  14. package/dist/execution/engine/paths.js +1 -1
  15. package/dist/execution/relay-env.cjs +12 -28
  16. package/dist/execution/relay-env.d.cts +1 -33
  17. package/dist/execution/relay-env.d.ts +1 -33
  18. package/dist/execution/relay-env.js +5 -3
  19. package/dist/index.cjs +14123 -15467
  20. package/dist/index.d.cts +1179 -1540
  21. package/dist/index.d.ts +1179 -1540
  22. package/dist/index.js +7694 -10986
  23. package/dist.bundle/index.cjs +33485 -0
  24. package/package.json +20 -26
  25. package/src/__tests__/record-store-last-line.test.ts +16 -7
  26. package/src/core/error-message.ts +7 -6
  27. package/src/core/host-services.ts +8 -2
  28. package/src/core/notify-ports.ts +17 -2
  29. package/src/execution/__tests__/agent-registry.test.ts +1 -1
  30. package/src/execution/__tests__/alive-store.test.ts +1 -1
  31. package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
  32. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
  33. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  34. package/src/execution/__tests__/collect-coordinator-service.test.ts +316 -0
  35. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  36. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +229 -0
  37. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  38. package/src/execution/__tests__/config.test.ts +1 -1
  39. package/src/execution/__tests__/delivery-methods.test.ts +179 -237
  40. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
  41. package/src/execution/__tests__/execute-nesting.test.ts +20 -13
  42. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +21 -34
  43. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  44. package/src/execution/__tests__/gc-timer.test.ts +8 -23
  45. package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
  46. package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
  47. package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
  48. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
  49. package/src/execution/__tests__/idle-gc.test.ts +200 -0
  50. package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
  51. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  52. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
  53. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  54. package/src/execution/__tests__/notify-ledger.test.ts +27 -0
  55. package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
  56. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  57. package/src/execution/__tests__/record-store.test.ts +60 -6
  58. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
  59. package/src/execution/__tests__/relay-env.test.ts +12 -1
  60. package/src/execution/__tests__/round-supervisor.test.ts +293 -0
  61. package/src/execution/__tests__/session-file-gc.test.ts +1 -1
  62. package/src/execution/__tests__/session-pending.test.ts +119 -17
  63. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  64. package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
  65. package/src/execution/__tests__/subagent-actions-core.test.ts +6 -0
  66. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +14 -11
  67. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
  68. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
  69. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
  70. package/src/execution/__tests__/subagent-service.test.ts +2 -2
  71. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
  72. package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
  73. package/src/execution/__tests__/sync-collect-recovery.test.ts +1102 -0
  74. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  75. package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
  76. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  77. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  78. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  79. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  80. package/src/execution/agent-registry.ts +6 -2
  81. package/src/execution/collect-coordinator.ts +200 -0
  82. package/src/execution/config.ts +77 -5
  83. package/src/execution/dialog-queue.ts +6 -73
  84. package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
  85. package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
  86. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  87. package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
  88. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
  89. package/src/execution/engine/__tests__/common/pool-manager.test.ts +2 -2
  90. package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
  91. package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
  92. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
  93. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
  94. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
  95. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
  96. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
  97. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
  98. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
  99. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +82 -55
  100. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
  101. package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
  102. package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
  103. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +8 -3
  104. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
  105. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
  106. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
  107. package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
  108. package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
  109. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
  110. package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
  111. package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
  112. package/src/execution/engine/__tests__/registry.test.ts +206 -1
  113. package/src/execution/engine/__tests__/routing.test.ts +97 -0
  114. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
  115. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
  116. package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
  117. package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
  118. package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
  119. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
  120. package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
  121. package/src/execution/engine/client/client-options.ts +103 -0
  122. package/src/execution/engine/client/engine-client.ts +687 -0
  123. package/src/execution/engine/client/mirror.ts +167 -0
  124. package/src/execution/engine/client/pid-file.ts +392 -0
  125. package/src/execution/engine/client/reaper.ts +95 -0
  126. package/src/execution/engine/client/remote-engine.ts +441 -0
  127. package/src/execution/engine/client/reverse-router.ts +255 -0
  128. package/src/execution/engine/common/capability-gate.ts +109 -22
  129. package/src/execution/engine/common/errors.ts +9 -25
  130. package/src/execution/engine/common/event-journal.ts +4 -50
  131. package/src/execution/engine/common/journal-replay.ts +3 -1
  132. package/src/execution/engine/common/kill-chain.ts +18 -251
  133. package/src/execution/engine/common/nesting-guard.ts +18 -114
  134. package/src/execution/engine/common/pool-manager.ts +4 -0
  135. package/src/execution/engine/common/session-view-service.ts +56 -84
  136. package/src/execution/engine/config.ts +151 -0
  137. package/src/execution/engine/d8-compat.ts +346 -0
  138. package/src/execution/engine/engine-discovery-roots.ts +107 -0
  139. package/src/execution/engine/engine-discovery-scan.ts +269 -0
  140. package/src/execution/engine/engine-discovery.ts +30 -7
  141. package/src/execution/engine/engine-inspect-package.ts +282 -0
  142. package/src/execution/engine/engine-manifest.ts +244 -0
  143. package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
  144. package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
  145. package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
  146. package/src/execution/engine/host/host-bridge.ts +150 -0
  147. package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
  148. package/src/execution/engine/host/pi-host-binding.ts +91 -0
  149. package/src/execution/engine/host/spawned-children.ts +139 -0
  150. package/src/execution/engine/host-task-spec.ts +29 -2
  151. package/src/execution/engine/model-validation.ts +55 -2
  152. package/src/execution/engine/paths.ts +16 -41
  153. package/src/execution/engine/port.ts +48 -2
  154. package/src/execution/engine/registry.ts +153 -34
  155. package/src/execution/engine/routing.ts +225 -52
  156. package/src/execution/engine/types.ts +25 -200
  157. package/src/execution/execution-record.ts +7 -0
  158. package/src/execution/finalize-record.ts +40 -11
  159. package/src/execution/idle-gc.ts +89 -14
  160. package/src/execution/lifecycle-manager.ts +10 -7
  161. package/src/execution/lifecycle-predicates.ts +10 -8
  162. package/src/execution/notifier.ts +234 -25
  163. package/src/execution/notify-host.ts +21 -1
  164. package/src/execution/record-entry.ts +16 -0
  165. package/src/execution/record-store.ts +189 -31
  166. package/src/execution/relay-env.ts +19 -36
  167. package/src/execution/round-supervisor/domain.ts +89 -0
  168. package/src/execution/round-supervisor/index.ts +33 -0
  169. package/src/execution/round-supervisor/notify-accounting.ts +87 -0
  170. package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
  171. package/src/execution/round-supervisor/service-binding.test.ts +661 -0
  172. package/src/execution/round-supervisor/service-binding.ts +273 -0
  173. package/src/execution/round-supervisor/supervisor.ts +426 -0
  174. package/src/execution/session-pending.ts +68 -74
  175. package/src/execution/settled-watchdog.ts +39 -0
  176. package/src/execution/subagent-actions-core.ts +62 -6
  177. package/src/execution/subagent-service.ts +1161 -406
  178. package/src/execution/subprocess-agent-runner.ts +16 -8
  179. package/src/execution/sync-rebuild.ts +87 -0
  180. package/src/execution/types.ts +104 -117
  181. package/src/execution/ui-channels.ts +13 -214
  182. package/src/execution/ui-request-handler-factory.ts +7 -4
  183. package/src/execution/ui-request-observability.ts +2 -2
  184. package/src/execution/workflow-state-root.ts +67 -0
  185. package/src/execution/worktree-git-ops.ts +5 -0
  186. package/src/execution/worktree-manager.ts +11 -1
  187. package/src/index.ts +55 -26
  188. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  189. package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
  190. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  191. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  192. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  193. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  194. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  195. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  196. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  197. package/src/orchestration/execute-agent-call.ts +1 -1
  198. package/src/orchestration/file-run-store.ts +144 -59
  199. package/src/orchestration/models/types.ts +43 -42
  200. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  201. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  202. package/src/shared/zcode-model-ref.ts +31 -0
  203. package/dist/chunk-43ONBFZX.js +0 -240
  204. package/dist/chunk-A4IVZWVX.js +0 -31
  205. package/dist/chunk-APZY4IME.js +0 -27
  206. package/dist/chunk-V3VHZ2VX.js +0 -59
  207. package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
  208. package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
  209. package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
  210. package/dist/execution/engine/engines/zcode/constants.js +0 -54
  211. package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
  212. package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
  213. package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
  214. package/dist/execution/engine/engines/zcode/reader.js +0 -9
  215. package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
  216. package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
  217. package/src/__tests__/session-runner.test.ts +0 -69
  218. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  219. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
  220. package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
  221. package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
  222. package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
  223. package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
  224. package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
  225. package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
  226. package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
  227. package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
  228. package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
  229. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
  230. package/src/execution/__tests__/output-collector.test.ts +0 -469
  231. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  232. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
  233. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  234. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
  235. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
  236. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
  237. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
  238. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  239. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  240. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  241. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
  242. package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
  243. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
  244. package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
  245. package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
  246. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
  247. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
  248. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
  249. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
  250. package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
  251. package/src/execution/__tests__/spawn-args.test.ts +0 -446
  252. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
  253. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  254. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
  255. package/src/execution/__tests__/spawned-children.test.ts +0 -236
  256. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  257. package/src/execution/__tests__/stdin-writer.test.ts +0 -464
  258. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
  259. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  260. package/src/execution/__tests__/timeout-integration.test.ts +0 -616
  261. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  262. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  263. package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
  264. package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
  265. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
  266. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  267. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
  268. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  269. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
  270. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  271. package/src/execution/engine/common/schema-emulation.ts +0 -189
  272. package/src/execution/engine/common/session-view-projection.ts +0 -51
  273. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
  274. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  275. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
  276. package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
  277. package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
  278. package/src/execution/engine/engines/pi/output-collector.ts +0 -317
  279. package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
  280. package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
  281. package/src/execution/engine/engines/pi/reader.ts +0 -59
  282. package/src/execution/engine/engines/pi/registration.ts +0 -37
  283. package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
  284. package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
  285. package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
  286. package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
  287. package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
  288. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
  289. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
  290. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
  291. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
  292. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
  293. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
  294. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  295. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
  296. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
  297. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
  298. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
  299. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
  300. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
  301. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
  302. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
  303. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
  305. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
  306. package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
  307. package/src/execution/engine/engines/zcode/connection.ts +0 -598
  308. package/src/execution/engine/engines/zcode/constants.ts +0 -176
  309. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
  310. package/src/execution/engine/engines/zcode/parser.ts +0 -102
  311. package/src/execution/engine/engines/zcode/preparer.ts +0 -235
  312. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  313. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  314. package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
  315. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
  316. package/src/execution/round-settlement.ts +0 -93
  317. package/src/execution/ui-request-queue.ts +0 -215
  318. package/src/shared/schema-env.ts +0 -44
@@ -20,7 +20,7 @@ describe("tombstone-store", () => {
20
20
  sessionFile = path.join(tmpDir, "2026-01-01_uuid.jsonl");
21
21
  });
22
22
  afterEach(() => {
23
- fs.rmSync(tmpDir, { recursive: true, force: true });
23
+ fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
24
24
  });
25
25
 
26
26
  describe("write → read 往返", () => {
@@ -0,0 +1,232 @@
1
+ // src/execution/__tests__/workflow-state-root.test.ts
2
+ //
3
+ // [F-1 修复] pi 宿主 WorkflowRun state 读侧装配同源布局测试。
4
+ //
5
+ // 背景(装配错位事故面):round-supervisor sweep 与 idle-gc 曾用 FileRunStore 缺省根
6
+ // `<dataRoot>/workflow-state`(zcode 宿主布局)读 workflow run state,而 pi 宿主真实
7
+ // 落盘 = JsonlRunStore 的 `<sessionDir>/workflow-state/<runId>.jsonl`——两目录生产不
8
+ // 相交 → findStateByIdSync 恒 missing → sweep 按终态补注销**活跃 run**;WorkflowRun
9
+ // GC 恒空转。修复后装配点传 resolvePiWorkflowStateDir()(execution/workflow-state-root.ts,
10
+ // 与 extensions/universal/subagent-workflow/src/session-lifecycle.ts resolveSessionDir
11
+ // 同规则重写)。
12
+ //
13
+ // 本套件用 mkdtemp 真实目录布局(非 mock fs)证明四件事:
14
+ // ① resolvePiWorkflowStateDir 探测语义两分支(sessionScopedDir 存在/不存在);
15
+ // ② FileRunStore({stateDir}) findStateByIdSync 在真实布局命中 running/终态/missing;
16
+ // ③ sweep 装配链(runPendingReconcileSweepForService,env 指向 tmp agentDir)端到端:
17
+ // running run 不补注销(修复前被误注销的事故方向)、终态 run 补注销;
18
+ // ④ idle-gc 同布局:30 天 running run 文件被终态化写回(findStateByIdSync 变 done)。
19
+ //
20
+ // 写删目标全部 mkdtempSync 自建自删(禁触真实数据目录纪律)。
21
+
22
+ import * as fs from "node:fs";
23
+ import * as os from "node:os";
24
+ import * as path from "node:path";
25
+
26
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
27
+
28
+ import { FileRunStore } from "../../orchestration/file-run-store.ts";
29
+ import { Budget } from "../../orchestration/models/budget.ts";
30
+ import { Trace } from "../../orchestration/models/trace.ts";
31
+ import { WorkflowRun } from "../../orchestration/models/workflow-run.ts";
32
+ import { startIdleGc } from "../idle-gc.ts";
33
+ import { RecordStore } from "../record-store.ts";
34
+ import { runPendingReconcileSweepForService } from "../round-supervisor/service-binding.ts";
35
+ import type { RoundSupervisorBinding } from "../round-supervisor/service-binding.ts";
36
+ import { resolvePiWorkflowStateDir } from "../workflow-state-root.ts";
37
+
38
+ const DAY_MS = 24 * 60 * 60 * 1000;
39
+ const GC_INTERVAL_MS = 60 * 60 * 1000;
40
+
41
+ let tmpDir: string;
42
+ let stopGc: (() => void) | undefined;
43
+
44
+ beforeEach(() => {
45
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-state-root-"));
46
+ });
47
+
48
+ afterEach(() => {
49
+ stopGc?.();
50
+ stopGc = undefined;
51
+ vi.unstubAllEnvs();
52
+ vi.useRealTimers();
53
+ fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
54
+ });
55
+
56
+ /** 与 session-lifecycle.resolveSessionDir 同规则的 cwd-slug(测试构造期望布局用)。 */
57
+ function slugOf(cwd: string): string {
58
+ return `--${cwd.replace(/^\//, "").replace(/\//g, "-")}--`;
59
+ }
60
+
61
+ /** 构造可持久化的 WorkflowRun(对齐 file-run-store.test.ts makeRun 模式)。 */
62
+ function makeRun(
63
+ runId: string,
64
+ opts: { status?: "running" | "done"; startedAt?: string } = {},
65
+ ): WorkflowRun {
66
+ const status = opts.status ?? "running";
67
+ return WorkflowRun.reconstruct(
68
+ runId,
69
+ {
70
+ scriptSource: "export function execute() { return 'ok'; }",
71
+ args: { topic: "demo" },
72
+ scriptName: "test-script",
73
+ scriptPath: "/fake/test.js",
74
+ parameters: { type: "object" },
75
+ budgetTokens: 1000,
76
+ },
77
+ {
78
+ status,
79
+ ...(status === "done" ? { reason: "completed" as const } : {}),
80
+ budget: new Budget({ maxTokens: 1000, usedTokens: 1, usedCost: 0, totalCallCount: 1 }),
81
+ calls: new Map(),
82
+ trace: new Trace(),
83
+ errorLogs: [],
84
+ scriptResult: undefined,
85
+ },
86
+ { startedAt: opts.startedAt ?? "2026-08-30T00:00:00.000Z" },
87
+ );
88
+ }
89
+
90
+ describe("resolvePiWorkflowStateDir 探测语义(session-lifecycle resolveSessionDir 同规则)", () => {
91
+ it("sessionScopedDir 存在 → 用 <agentDir>/sessions/<slug>/workflow-state(实例隔离布局)", () => {
92
+ const cwd = path.join(tmpDir, "proj"); // 形式 cwd(不要求真实存在)
93
+ const agentDir = path.join(tmpDir, "agent");
94
+ const sessionScopedDir = path.join(agentDir, "sessions", slugOf(cwd));
95
+ fs.mkdirSync(sessionScopedDir, { recursive: true });
96
+ expect(resolvePiWorkflowStateDir({ agentDir, cwd })).toBe(path.join(sessionScopedDir, "workflow-state"));
97
+ });
98
+
99
+ it("sessionScopedDir 不存在 → 回退 <agentDir>/workflow-state(JsonlRunStore 首写 mkdir 的根布局)", () => {
100
+ const agentDir = path.join(tmpDir, "agent");
101
+ fs.mkdirSync(agentDir, { recursive: true });
102
+ expect(resolvePiWorkflowStateDir({ agentDir, cwd: path.join(tmpDir, "fresh-proj") })).toBe(
103
+ path.join(agentDir, "workflow-state"),
104
+ );
105
+ });
106
+ });
107
+
108
+ describe("FileRunStore({stateDir}) × 真实 JsonlRunStore 布局(findStateByIdSync 读侧判据)", () => {
109
+ it("真实布局命中:running → {kind:running};done → {kind:terminal, reason};无文件 → {kind:missing}", async () => {
110
+ const stateDir = path.join(tmpDir, "sessions", slugOf("/x/y"), "workflow-state");
111
+ const store = new FileRunStore({ stateDir });
112
+ await store.save(makeRun("wf-live")); // running(首写不节流)
113
+ await store.save(makeRun("wf-done", { status: "done" }));
114
+
115
+ expect(store.findStateByIdSync("wf-live")).toEqual({ kind: "running" });
116
+ expect(store.findStateByIdSync("wf-done")).toEqual({ kind: "terminal", reason: "completed" });
117
+ expect(store.findStateByIdSync("wf-never")).toEqual({ kind: "missing" });
118
+ // 落盘路径形状与 pi 壳 JsonlRunStore 同构:<sessionDir>/workflow-state/<runId>.jsonl
119
+ expect(fs.existsSync(path.join(stateDir, "wf-live.jsonl"))).toBe(true);
120
+ });
121
+ });
122
+
123
+ describe("sweep 装配链端到端(runPendingReconcileSweepForService × 真实布局)", () => {
124
+ /**
125
+ * 装配 harness:env 指向 tmp agentDir(生产装配 resolvePiWorkflowStateDir() 无参走
126
+ * env + process.cwd()),run state 由 FileRunStore({stateDir}) 写入解析出的目录——
127
+ * 证明「生产装配点读的目录 = run state 真实落盘目录」(同源布局闭环)。
128
+ */
129
+ function setupSweep(): { agentDir: string; store: FileRunStore } {
130
+ const agentDir = path.join(tmpDir, "agent");
131
+ vi.stubEnv("PI_CODING_AGENT_DIR", agentDir);
132
+ const store = new FileRunStore({ stateDir: resolvePiWorkflowStateDir() }); // 与生产装配同参
133
+ return { agentDir, store };
134
+ }
135
+
136
+ function makeBinding(sessionFile: string, appended: Array<{ customType: string; data: unknown }>): RoundSupervisorBinding {
137
+ return {
138
+ getStore: () => new RecordStore(path.join(tmpDir, "records")),
139
+ getPi: () =>
140
+ ({
141
+ appendEntry: (type: string, data: unknown) => appended.push({ customType: type, data }),
142
+ events: { emit: vi.fn() },
143
+ sendMessage: vi.fn(),
144
+ }) as unknown as NonNullable<ReturnType<RoundSupervisorBinding["getPi"]>>,
145
+ getSessionRootId: () => "root",
146
+ getMainSessionFile: () => sessionFile,
147
+ finalizeClosed: vi.fn(),
148
+ };
149
+ }
150
+
151
+ function writeRegister(sessionFile: string, id: string): void {
152
+ fs.writeFileSync(
153
+ sessionFile,
154
+ JSON.stringify({ customType: "pending:register", data: { id, type: "workflow", name: id } }) + "\n",
155
+ "utf-8",
156
+ );
157
+ }
158
+
159
+ it("活跃 workflow run(running state 文件在盘)→ sweep 不补注销(修复前被误注销)", async () => {
160
+ const { agentDir, store } = setupSweep();
161
+ await store.save(makeRun("wf-live"));
162
+
163
+ const sessionFile = path.join(agentDir, "main-session.jsonl");
164
+ writeRegister(sessionFile, "wf-live");
165
+ const appended: Array<{ customType: string; data: unknown }> = [];
166
+ runPendingReconcileSweepForService(makeBinding(sessionFile, appended), false);
167
+ expect(appended).toHaveLength(0); // 活跃 run 不注销——事故方向的回归钉
168
+ });
169
+
170
+ it("终态 workflow run(done state 文件在盘)→ sweep 补注销(reason 取 run reason)", async () => {
171
+ const { agentDir, store } = setupSweep();
172
+ await store.save(makeRun("wf-done", { status: "done" }));
173
+
174
+ const sessionFile = path.join(agentDir, "main-session.jsonl");
175
+ writeRegister(sessionFile, "wf-done");
176
+ const appended: Array<{ customType: string; data: unknown }> = [];
177
+ runPendingReconcileSweepForService(makeBinding(sessionFile, appended), false);
178
+ expect(appended).toEqual([
179
+ { customType: "pending:unregister", data: { id: "wf-done", reason: "completed", status: "completed" } },
180
+ ]);
181
+ });
182
+ });
183
+
184
+ describe("idle-gc 同布局(WorkflowRun GC 读对根)", () => {
185
+ /**
186
+ * GC interval 用 fake timers 推进;但 gcWorkflowRuns 的 loadAll/save 是**真实 fs IO**
187
+ * (libuv 线程池回调不属被 fake 的 timer API)——推进后经 setImmediate(toFake 排除,
188
+ * 保持真实调度)反复让出事件循环排空在途 IO。
189
+ */
190
+ async function flushRealIo(): Promise<void> {
191
+ for (let i = 0; i < 50; i++) {
192
+ await new Promise<void>((resolve) => setImmediate(() => resolve()));
193
+ }
194
+ }
195
+
196
+ it("真实布局 30 天 running run → GC 终态化写回(findStateByIdSync 变 terminal/time_limited)", async () => {
197
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] });
198
+ const agentDir = path.join(tmpDir, "agent");
199
+ const cwd = path.join(tmpDir, "proj");
200
+ const sessionScopedDir = path.join(agentDir, "sessions", slugOf(cwd));
201
+ fs.mkdirSync(sessionScopedDir, { recursive: true });
202
+ const stateDir = resolvePiWorkflowStateDir({ agentDir, cwd });
203
+ const runStore = new FileRunStore({ stateDir });
204
+ await runStore.save(
205
+ makeRun("wf-stale", { startedAt: new Date(Date.now() - 31 * DAY_MS).toISOString() }),
206
+ );
207
+ expect(runStore.findStateByIdSync("wf-stale")).toEqual({ kind: "running" });
208
+
209
+ stopGc = startIdleGc(new RecordStore(path.join(tmpDir, "records")), runStore);
210
+ await vi.advanceTimersByTimeAsync(GC_INTERVAL_MS + 1);
211
+ await flushRealIo();
212
+
213
+ // 终态已落盘(同目录 append 终态快照行)——GC 读侧与落盘侧同根闭环
214
+ expect(runStore.findStateByIdSync("wf-stale")).toEqual({ kind: "terminal", reason: "time_limited" });
215
+ });
216
+
217
+ it("窗内 running run 不动(GC 判据在正确根上按 startedAt 生效)", async () => {
218
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] });
219
+ const agentDir = path.join(tmpDir, "agent");
220
+ const stateDir = resolvePiWorkflowStateDir({ agentDir, cwd: tmpDir });
221
+ const runStore = new FileRunStore({ stateDir });
222
+ await runStore.save(
223
+ makeRun("wf-fresh", { startedAt: new Date(Date.now() - 1 * DAY_MS).toISOString() }),
224
+ );
225
+
226
+ stopGc = startIdleGc(new RecordStore(path.join(tmpDir, "records")), runStore);
227
+ await vi.advanceTimersByTimeAsync(GC_INTERVAL_MS + 1);
228
+ await flushRealIo();
229
+
230
+ expect(runStore.findStateByIdSync("wf-fresh")).toEqual({ kind: "running" });
231
+ });
232
+ });
@@ -84,7 +84,7 @@ describe("worktree-git-ops", { timeout: 30_000 }, () => {
84
84
 
85
85
  afterEach(() => {
86
86
  resetCoreForTests();
87
- fs.rmSync(outerDir, { recursive: true, force: true });
87
+ fs.rmSync(outerDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
88
88
  });
89
89
 
90
90
  /**
@@ -76,7 +76,7 @@ describe("WorktreeManager 对账歧义跳过老化(PS-12 措施⑤)", { time
76
76
 
77
77
  afterEach(() => {
78
78
  process.env.TMPDIR = ORIG_TMPDIR;
79
- fs.rmSync(outerDir, { recursive: true, force: true });
79
+ fs.rmSync(outerDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
80
80
  });
81
81
 
82
82
  /** 建一个物理 worktree(绕过 WorktreeManager.create 的脏树校验,路径布局同 create)。 */
@@ -174,7 +174,7 @@ describe("WorktreeManager 对账歧义跳过老化(PS-12 措施⑤)", { time
174
174
 
175
175
  // 出现对应:s-r2 pid 死 + c2 删除 → 1 活 pid ↔ 1 残留 → 自愈补写 c1
176
176
  await waitPidDead(sleeper.pid as number);
177
- fs.rmSync(c2, { recursive: true, force: true });
177
+ fs.rmSync(c2, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
178
178
  await mgr.scan();
179
179
  expect(registry.load().map((e) => e.branch)).toEqual(["pi-sub-r1"]);
180
180
  expect(escalationCalls()).toEqual([]);
@@ -66,7 +66,7 @@ describe("WorktreeManager.scan 物理面对账(D5b)", { timeout: 30_000 }, (
66
66
 
67
67
  afterEach(() => {
68
68
  process.env.TMPDIR = ORIG_TMPDIR;
69
- fs.rmSync(outerDir, { recursive: true, force: true });
69
+ fs.rmSync(outerDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
70
70
  });
71
71
 
72
72
  /** 建一个物理 worktree(按 create() 的真实路径布局,绕过 WorktreeManager.create 的脏树校验)。 */
@@ -41,7 +41,7 @@ describe("WorktreeRegistry", () => {
41
41
  });
42
42
 
43
43
  afterEach(() => {
44
- fs.rmSync(tmpAgentDir, { recursive: true, force: true });
44
+ fs.rmSync(tmpAgentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
45
45
  });
46
46
 
47
47
  describe("add + load", () => {
@@ -20,7 +20,10 @@ import { parseResourceMeta } from "../shared/meta-parser.ts";
20
20
  import { lintAgentMeta } from "../orchestration/script-lint.ts";
21
21
  import type { AgentMeta, RoutingExample } from "../shared/resource-meta.ts";
22
22
  import type { AgentConfig } from "./model-resolver.ts";
23
- import { hasEngine, listEngines, EngineNotFoundError } from "./engine/registry.ts";
23
+ import { EngineNotFoundError, listEngines } from "./engine/registry.ts";
24
+ // [W8] 存在性校验经补扫通道(快照未命中触发一次三级补扫,W4 ensureEngineDiscovered
25
+ // 语义——宿主未接线补扫参数时与裸 hasEngine 等价)。
26
+ import { hasEngineWithRescan } from "./engine/routing.ts";
24
27
 
25
28
  const logger = getLogger("subagents");
26
29
 
@@ -134,7 +137,8 @@ function resolveAgentEngine(
134
137
  filePath: string,
135
138
  ): string | undefined {
136
139
  const engine = agentMeta?.engine ?? extractYamlField(yamlBlock, "engine");
137
- if (engine !== undefined && !hasEngine(engine)) {
140
+ // [W8] 补扫通道:快照未命中先一次三级补扫再判(装了引擎包 下次解析即可用)。
141
+ if (engine !== undefined && !hasEngineWithRescan(engine)) {
138
142
  throw new EngineNotFoundError(engine, listEngines(), filePath);
139
143
  }
140
144
  return engine;
@@ -0,0 +1,200 @@
1
+ // src/execution/collect-coordinator.ts
2
+ //
3
+ // collectCoordinator:notifyComplete 唯一路由入口——async 直通 / sync 批缓冲 / 闭合检测
4
+ // (subagent-sync-collect 设计 §3.1.4 终态数据流 / D2 隐式批 / D5 service 层缓冲)。
5
+ //
6
+ // 职责(U2):
7
+ // route(record):
8
+ // - record.collectMode !== "sync" → deps.notifyAsync(现状 notifier.notify 路径,
9
+ // 执行序列逐字节一致,G3/A5 零回归);
10
+ // - record.collectMode === "sync" → deps.toNotifyRecord 终态快照登记入缓冲(内存,
11
+ // 登记时定格——record 对象后续可变不影响快照)→ 闭合检测。
12
+ //
13
+ // 闭合判定(⛔3 已核实 U1,impl-plan §7):缓冲非空 && 无非终态 sync 成员。
14
+ // 「非终态」口径 = collectMode="sync" && 无 batchFinalized 标记 && status !== "closed"
15
+ // ——池排队成员在 store.register 时即 status="running",自动计入非终态(闭合等待它);
16
+ // batchFinalized=true 的已离场成员不阻止闭合。
17
+ //
18
+ // 跨轮续累(D2 隐式批):缓冲跨多次 route 累积不清空(分两轮派 2+1 sync → 闭合时
19
+ // 单批三成员);flush 后缓冲清空,后续 sync 成员自然开新批(语义 = "等所有 sync 待收")。
20
+ //
21
+ // [U8 拆批盲窗修复] 闭合满足 → 同宏任务去抖合批 flush(不再立即 flush):
22
+ // 盲窗机理:kickOffBackground 的 runAndFinalize(completeRecord+archive,record 从
23
+ // store.listAllActive 移除)先于 .then 链的 notifyComplete。同同步段背靠背终态时,
24
+ // 成员 A route 触发闭合检测(只扫 listAllActive 非终态)看不到「已 archive 未路由」
25
+ // 的成员 B → 立即 flush([A]);B route 再 flush([B]) → 拆两条单成员批(G1 弱化,
26
+ // 单唤醒变两次)。
27
+ // 修复:闭合首次满足改排程 flush(setTimeout(0),Node 1ms clamp——同一宏任务级窗口,
28
+ // 确定性、无可观察延迟);窗口内后续 route 落缓冲后一并 flush(背靠背成员合单批)。
29
+ // 触发时重验闭合条件:窗口内若有新 sync 成员注册 running(跨轮续累反转)→ 保留缓冲
30
+ // 不 flush,等其终态 route 重新排程(终态必经 notifyComplete,不丢、不悬挂)。
31
+ // 交互语义(授权修复轮决策):
32
+ // - E9 dispose:service.convertPendingSyncBufferToAsync 先 cancelScheduledFlush
33
+ // (取消而非同步 flush——放任触发会与逐条转 async 写账双通道并发 → 同成员双投递,
34
+ // 且触发点可能落在 notifier/store dispose 之后);取消后缓冲原样保留,E9 单通路
35
+ // 完整接管(选语义最简)。
36
+ // - E1 恢复补发:只在 session_start 编排处运行(此前 dispose 已取消挂起排程),
37
+ // 补发直走 notifier.notifyBatch 不经协调器;异常时序相撞由账本 sync-batch:<hash>
38
+ // 幂等拒绝兜底。
39
+ //
40
+ // flush 注入式(U3 已接线):deps.flushBatch 由宿主注入——U3 接 service 闭包的
41
+ //「manifest 屏障(await 落盘)→ notifier.notifyBatch 单条批投递(ledger sync-batch
42
+ // hash 写账)→ batchFinalized 落标」(见 subagent-service.ts flushBatch 闭包)。
43
+ // 测试注入 fake 断言闭合条件。flush 异步(Promise<void>)、协调器 void 调用不等待:
44
+ // 缓冲在调用前已整体移交清空,flush 内部时序(屏障 → 写账 → 落标)与失败语义归
45
+ // 实现方(与 notifier.notify 同风格)。
46
+ //
47
+ // 不变量:
48
+ // - 每个成员只登记一次:notifyComplete 调用点自带 CAS/gate 单次性(与既有 notify
49
+ // 单次性同源——cancelBackground CAS 抢锁 / kickOffBackground.then gate / watchdog
50
+ // CAS),协调器不重复去重,不改变 at-least-once 语义。
51
+ // - toNotifyRecord 返回 undefined(gate 未过/非终态)→ 静默跳过不入缓冲,与 async
52
+ // 路径的静默跳过对称——调用点 gate 语义不变,协调器不重复判定。
53
+ // - 闭合判定只敏感于「是否还有非终态 sync」:本条终态在 store 的可见性(archive 后
54
+ // 磁盘重建时序)不影响判定正确性。
55
+ // - [U8] 排程幂等:窗口内多条 route 至多一个挂起定时器;flush 只在触发时重验后执行,
56
+ // 缓冲在排程期内保持可枚举(pendingMembers 供 E9 dispose 转换读取)。
57
+
58
+ import type { BgNotifyRecord } from "./notifier.ts";
59
+ import type { ExecutionRecord } from "./types.ts";
60
+
61
+ /** 闭合判定的 record 最小视图(ExecutionRecord / SubagentRecord 的同构子集)。
62
+ *
63
+ * [U3 微调 / U5 已修] 原 deps 用 SubagentRecord——生产通路 collectRecords 经
64
+ * recordToSubagent 投影曾不含 collectMode/batchFinalized(U2 披露,U5 已补投影),
65
+ * 保留结构化最小接口 + service 接线走 store.listAllActive()(原始 ExecutionRecord
66
+ * 内存态,不经投影)——非终态 sync 成员必在内存(archive 只删终态),内存视图语义
67
+ * 完整;纯注入测试的 SubagentRecord stub 结构兼容零改动。 */
68
+ export interface CollectScanRecord {
69
+ status: string;
70
+ collectMode?: "sync";
71
+ batchFinalized?: boolean;
72
+ /** SP-5 执行态信号:true = 无活进程驱动的 running(one-shot 成功回退态)。
73
+ * [U3 补丁] ⛔3 非终态口径的必要补充——one-shot 成功完成后 record 不 archive,
74
+ * 以 running+resumable 留内存等 message 升级(真链 trace 实证):结果已定格、
75
+ * 进程已死 = 已完成待通知,不阻止闭合;否则 sync 批永不闭合。 */
76
+ resumable?: boolean;
77
+ }
78
+
79
+ /** 闭合判定的全 record 扫描上限(service 冷路径 COLD_LOOKUP_SCAN_LIMIT 同量级)。 */
80
+ export const COLLECT_SCAN_LIMIT = 1000;
81
+
82
+ /** 协调器宿主依赖(全部注入——协调器零 service/store 直依,可独立单测)。 */
83
+ export interface CollectCoordinatorDeps {
84
+ /** async 直通(现状 notifier.notify 路径,字节不变)。 */
85
+ notifyAsync(record: ExecutionRecord): void;
86
+ /** record → 通知快照(登记时定格)。undefined = gate 未过/非终态(静默跳过)。 */
87
+ toNotifyRecord(record: ExecutionRecord): BgNotifyRecord | undefined;
88
+ /** 闭合判定数据源(CollectScanRecord 最小视图,见接口注释——数据源须携带
89
+ * collectMode/batchFinalized 原始值,禁经 recordToSubagent 投影)。 */
90
+ listRecords(limit: number): CollectScanRecord[];
91
+ /** 批投递回调(合批窗口到期触发,缓冲整体移交后清空)。U3 接 service 闭包:
92
+ * manifest 屏障 → notifier.notifyBatch 写账 → batchFinalized 落标——屏障先于写账
93
+ * 是「通知可达 ⇒ 索引就位」的构造性保证,故异步;协调器 void 调用不等待其完成。 */
94
+ flushBatch(members: BgNotifyRecord[]): Promise<void>;
95
+ }
96
+
97
+ /** route 去向(测试/诊断断言用)。 */
98
+ export type CollectRouteResult =
99
+ /** 非 sync record:notifyAsync 直通(现状路径)。 */
100
+ | "async"
101
+ /** gate 未过(toNotifyRecord undefined):静默跳过,不入缓冲。 */
102
+ | "sync-skipped"
103
+ /** 入缓冲未闭合(仍有非终态 sync 成员)。 */
104
+ | "sync-buffered"
105
+ /** 闭合触发([U8] flush 已排程合批——同宏任务窗口内的后续 route 一并投递)。 */
106
+ | "sync-flushed";
107
+
108
+ export class CollectCoordinator {
109
+ /** 批缓冲(内存):已终态未 flush 的 sync 成员终态快照,跨 route 累积。
110
+ * [U8] 排程合批窗口期内保持可枚举(E9 dispose 转换依赖 pendingMembers 全量)。 */
111
+ private readonly buffer: BgNotifyRecord[] = [];
112
+
113
+ /** [U8] 挂起的合批排程定时器(undefined = 无排程)。 */
114
+ private flushTimer: ReturnType<typeof setTimeout> | undefined;
115
+
116
+ constructor(private readonly deps: CollectCoordinatorDeps) {}
117
+
118
+ /** notifyComplete 唯一路由入口。返回去向供测试/诊断断言。 */
119
+ route(record: ExecutionRecord): CollectRouteResult {
120
+ if (record.collectMode !== "sync") {
121
+ this.deps.notifyAsync(record);
122
+ return "async";
123
+ }
124
+ const snapshotRecord = this.deps.toNotifyRecord(record);
125
+ if (!snapshotRecord) return "sync-skipped";
126
+ this.buffer.push(snapshotRecord);
127
+ return this.armFlushIfClosed() ? "sync-flushed" : "sync-buffered";
128
+ }
129
+
130
+ /** 诊断/测试:当前缓冲成员数(= 已终态未 flush 的 sync 成员数,含合批窗口内成员)。 */
131
+ get pendingCount(): number {
132
+ return this.buffer.length;
133
+ }
134
+
135
+ /** 诊断/测试 + E9 dispose 转换数据源:缓冲成员快照(副本,非活引用)。 */
136
+ pendingMembers(): BgNotifyRecord[] {
137
+ return [...this.buffer];
138
+ }
139
+
140
+ /** [U8·E9 交互] 取消挂起的合批排程(缓冲原样保留——dispose 转 async 通路仍读得到
141
+ * 全部成员)。无排程时 no-op;幂等。 */
142
+ cancelScheduledFlush(): void {
143
+ if (this.flushTimer !== undefined) {
144
+ clearTimeout(this.flushTimer);
145
+ this.flushTimer = undefined;
146
+ }
147
+ }
148
+
149
+ /** 闭合判定 + 合批排程武装:缓冲非空 && 无非终态 sync 成员 → 排程 flush。
150
+ * 已有排程挂起时幂等(同窗口合批,不重复定时器)。(S6 改名:原名 closeDetected
151
+ * 读作纯检测,实带副作用——武装排程改状态,名实相符优先。) */
152
+ private armFlushIfClosed(): boolean {
153
+ if (this.buffer.length === 0) return false;
154
+ if (this.hasRunningSync()) return false;
155
+ this.scheduleFlush();
156
+ return true;
157
+ }
158
+
159
+ /** [U8] 同宏任务去抖合批排程:setTimeout(0)(Node 1ms clamp)——窗口跨度覆盖一个
160
+ * 完整事件循环轮次(当前 poll 段 I/O 回调 + 微任务链排空),足以收纳 finalize 链
161
+ * 间隙内的背靠背 route,同时无可观察延迟(毫秒级 < 任意 LLM/投递时标)。 */
162
+ private scheduleFlush(): void {
163
+ if (this.flushTimer !== undefined) return;
164
+ this.flushTimer = setTimeout(() => {
165
+ this.flushTimer = undefined;
166
+ this.flushIfClosed();
167
+ }, 0);
168
+ }
169
+
170
+ /** 合批窗口到期:重验闭合条件后整体移交 flushBatch 并清空。
171
+ * 窗口内新 sync 成员注册 running(跨轮续累反转)→ 保留缓冲不 flush,等其终态
172
+ * route 重新排程(终态必经 notifyComplete,不丢、不悬挂)。
173
+ * void 不等待 flush 的 async 屏障序列(manifest → 写账 → 落标):缓冲已 splice
174
+ * 先行整体移交,屏障 await 期间窗口内新成员重新入空缓冲开新批,互不干扰。 */
175
+ private flushIfClosed(): void {
176
+ if (this.buffer.length === 0) return;
177
+ if (this.hasRunningSync()) return;
178
+ const members = this.buffer.splice(0, this.buffer.length);
179
+ void this.deps.flushBatch(members);
180
+ }
181
+
182
+ /** 是否存在非终态 sync 成员(collectMode=sync && 无 batchFinalized && 非终态)。
183
+ * 非终态口径(⛔3 U1 已核实 + U3 resumable 补丁):status 非 closed 且非 resumable
184
+ * ——池排队/在跑成员在 store.register 时即 status="running",自动计入(闭合等待它);
185
+ * batchFinalized=true 的已离场成员不阻止闭合;running+resumable(SP-5 one-shot
186
+ * 成功回退态,进程已死结果已定格)视为已完成,不阻止闭合。 */
187
+ private hasRunningSync(): boolean {
188
+ for (const record of this.deps.listRecords(COLLECT_SCAN_LIMIT)) {
189
+ if (
190
+ record.collectMode === "sync" &&
191
+ record.batchFinalized !== true &&
192
+ record.resumable !== true &&
193
+ record.status !== "closed"
194
+ ) {
195
+ return true;
196
+ }
197
+ }
198
+ return false;
199
+ }
200
+ }
@@ -9,7 +9,11 @@ import * as path from "node:path";
9
9
 
10
10
  import { getLogger } from "../core/logger.ts";
11
11
 
12
- import type { SubagentsGlobalConfig } from "./types.ts";
12
+ import type { CollectSyncConfig, SubagentsGlobalConfig } from "./types.ts";
13
+
14
+ // 同步收集配置节类型 re-export(类型权威定义在 types.ts,避免 config → types 反向依赖成环;
15
+ // 消费方从本模块 import 与从 types.ts import 等价,U1 foundation 契约)。
16
+ export type { CollectSyncConfig };
13
17
 
14
18
  // 包内 execution 模块统一具名 logger(getLogger 缓存单例,见 notify-ledger.ts 同款)。
15
19
  const logger = getLogger("subagents");
@@ -33,8 +37,25 @@ export const DEFAULT_CONFIG: SubagentsGlobalConfig = {
33
37
  maxConcurrent: 6,
34
38
  };
35
39
 
40
+ // 注意:DEFAULT_CONFIG 刻意不含 collectSync 键(与 defaultEngine/engineRouting 同风格)——
41
+ // SW 包 package.json 的 pi.startupConfig 声明守护(startup-config-declaration.test.ts)
42
+ // 断言与 DEFAULT_CONFIG 深相等,声明面只含既有键。collectSync 缺省语义由
43
+ // DEFAULT_COLLECT_SYNC 承载,消费方以 `config.collectSync ?? DEFAULT_COLLECT_SYNC` 兜底
44
+ // (loadGlobalConfig 文件缺失/坏 JSON 路径返回 {...DEFAULT_CONFIG} 同样无键)。
45
+
36
46
  /** 默认 maxConcurrent(DEFAULT_CONFIG 的镜像,sanitize 用)。 */
37
47
  const DEFAULT_MAX_CONCURRENT = 6;
48
+
49
+ /**
50
+ * 同步收集配置节权威默认值(subagent-sync-collect 设计 §3.1.3,U1 foundation)。
51
+ * 消费方:startHandler 缺省模式解析 / U3+U4 批通知预算(flush 时热读)。
52
+ * 供守护测试断言(E5 各坏值回默认)。展开拷贝防调用方 mutate。
53
+ */
54
+ export const DEFAULT_COLLECT_SYNC: CollectSyncConfig = {
55
+ default: "async",
56
+ perItemChars: 4000,
57
+ totalChars: 24000,
58
+ };
38
59
  // ============================================================
39
60
  // 路径
40
61
  // ============================================================
@@ -133,19 +154,70 @@ function errnoCodeOf(err: unknown): string | undefined {
133
154
  function sanitizeParsedConfig(parsed: Partial<SubagentsGlobalConfig>): SubagentsGlobalConfig {
134
155
  const defaultEngine = sanitizeDefaultEngine(parsed.defaultEngine);
135
156
  const engineRouting = sanitizeEngineRouting(parsed.engineRouting);
157
+ const collectSync = sanitizeCollectSync(parsed.collectSync);
136
158
  return {
137
159
  version: parsed.version ?? DEFAULT_CONFIG.version,
138
160
  maxConcurrent: sanitizeMaxConcurrent(parsed.maxConcurrent),
139
161
  ...(defaultEngine !== undefined ? { defaultEngine } : {}),
140
162
  ...(engineRouting !== undefined ? { engineRouting } : {}),
163
+ ...(collectSync !== undefined ? { collectSync } : {}),
141
164
  };
142
165
  }
143
166
 
144
- /** maxConcurrent 校验:正整数,否则默认。 */
167
+ /** maxConcurrent 校验:正整数,否则默认(S5:委托 sanitizePositiveInt,正整数规则单点化)。 */
145
168
  function sanitizeMaxConcurrent(value: unknown): number {
146
- return typeof value === "number" && Number.isInteger(value) && value > 0
147
- ? value
148
- : DEFAULT_MAX_CONCURRENT;
169
+ return sanitizePositiveInt(value, DEFAULT_MAX_CONCURRENT);
170
+ }
171
+
172
+ /**
173
+ * collectSync 节校验(E5:负数/非枚举/坏类型回默认,不炸启动;与 maxConcurrent 同判。
174
+ * subagent-sync-collect 设计 §3.1.5 E5 + §3.1.3)。逐字段 sanitize:坏字段回该字段
175
+ * 默认,好字段透传(部分覆盖合法)。整节缺失返回 undefined(不落键,与 engineRouting
176
+ * 同风格——缺省语义由消费方读 DEFAULT_COLLECT_SYNC)。
177
+ *
178
+ * [D6 #8] 字段级坏值回默认时 warn 一条(缺省字段不 warn——部分覆盖合法):config.json
179
+ * 是用户手编文件,静默回落使「改了配置不生效」无从排查(sanitize 行为不变,只补可观测)。
180
+ *
181
+ * 消费时机:startHandler 缺省 collect 解析(config.default,新 session 生效)与
182
+ * U3/U4 批通知预算(perItemChars/totalChars,flush 时热读)。
183
+ */
184
+ export function sanitizeCollectSync(value: unknown): CollectSyncConfig | undefined {
185
+ if (typeof value !== "object" || value === null) return undefined;
186
+ const v = value as Record<string, unknown>;
187
+ const fallback = DEFAULT_COLLECT_SYNC;
188
+ // [D6 #8] 坏值字段清单(仅「显式给了坏值」计——缺省是合法部分覆盖,不 warn)。
189
+ // 值格式化不用 JSON.stringify:Symbol/BigInt 等 stringify 返回 undefined,String() 全类型安全。
190
+ const fmt = (val: unknown): string => (typeof val === "string" ? JSON.stringify(val) : String(val));
191
+ const bad: string[] = [];
192
+ if (v.default !== undefined && v.default !== "sync" && v.default !== "async") {
193
+ bad.push(`default=${fmt(v.default)}`);
194
+ }
195
+ if (v.perItemChars !== undefined && !isPositiveInt(v.perItemChars)) {
196
+ bad.push(`perItemChars=${fmt(v.perItemChars)}`);
197
+ }
198
+ if (v.totalChars !== undefined && !isPositiveInt(v.totalChars)) {
199
+ bad.push(`totalChars=${fmt(v.totalChars)}`);
200
+ }
201
+ if (bad.length > 0) {
202
+ logger.warn(
203
+ `[subagents] config collectSync invalid field(s) reverted to defaults: ${bad.join(", ")}`,
204
+ );
205
+ }
206
+ return {
207
+ default: v.default === "sync" ? "sync" : v.default === "async" ? "async" : fallback.default,
208
+ perItemChars: sanitizePositiveInt(v.perItemChars, fallback.perItemChars),
209
+ totalChars: sanitizePositiveInt(v.totalChars, fallback.totalChars),
210
+ };
211
+ }
212
+
213
+ /** 正整数校验(collectSync 预算字段用;与 sanitizeMaxConcurrent 同判)。 */
214
+ function sanitizePositiveInt(value: unknown, fallback: number): number {
215
+ return isPositiveInt(value) ? (value as number) : fallback;
216
+ }
217
+
218
+ /** 正整数判定(sanitizePositiveInt 的探测面,坏值 warn 清单复用)。 */
219
+ function isPositiveInt(value: unknown): boolean {
220
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
149
221
  }
150
222
 
151
223
  /**