@zhushanwen/subagent-core 0.4.0 → 0.6.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 (309) hide show
  1. package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
  2. package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
  3. package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
  4. package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
  5. package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
  6. package/dist/execution/engine/engines/zcode/constants.js +17 -27
  7. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  8. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  9. package/dist/execution/engine/engines/zcode/reader.js +2 -2
  10. package/dist/index.cjs +17100 -14082
  11. package/dist/index.d.cts +5336 -4033
  12. package/dist/index.d.ts +5336 -4033
  13. package/dist/index.js +17020 -14055
  14. package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
  16. package/dist.bundle/index.cjs +34933 -0
  17. package/package.json +3 -3
  18. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  19. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  20. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  21. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  22. package/src/__tests__/manifest-store.test.ts +10 -9
  23. package/src/__tests__/record-store-cache.test.ts +0 -2
  24. package/src/__tests__/record-store-index.test.ts +1 -2
  25. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  26. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  27. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  28. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  29. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  30. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  31. package/src/__tests__/session-runner.test.ts +1 -1
  32. package/src/core/__tests__/host-services.test.ts +2 -2
  33. package/src/core/__tests__/logger.test.ts +1 -1
  34. package/src/core/error-message.ts +9 -0
  35. package/src/core/host-services.ts +21 -5
  36. package/src/core/notify-ports.ts +18 -5
  37. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  38. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  39. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  40. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  41. package/src/execution/__tests__/alive-store.test.ts +1 -1
  42. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  43. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  44. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  45. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  46. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  47. package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
  48. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  49. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
  50. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  51. package/src/execution/__tests__/config.test.ts +1 -1
  52. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  53. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  54. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  55. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  56. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  57. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  58. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  59. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  60. package/src/execution/__tests__/execution-record.test.ts +217 -60
  61. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  62. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
  63. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  64. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  65. package/src/execution/__tests__/gc-timer.test.ts +56 -3
  66. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  67. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  68. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  69. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  70. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  71. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  72. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  73. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  74. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  75. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  76. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  77. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  78. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  79. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  80. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  81. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  82. package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
  83. package/src/execution/__tests__/output-collector.test.ts +117 -6
  84. package/src/execution/__tests__/pi-invocation.test.ts +36 -4
  85. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  86. package/src/execution/__tests__/record-store.test.ts +10 -9
  87. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  88. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  89. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  90. package/src/execution/__tests__/relay-env.test.ts +12 -1
  91. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  92. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  93. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  94. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  95. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  96. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  97. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  98. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  99. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  100. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  101. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  102. package/src/execution/__tests__/session-file-gc.test.ts +36 -1
  103. package/src/execution/__tests__/session-pending.test.ts +257 -14
  104. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  105. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  106. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  107. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  108. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  109. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  110. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  112. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  113. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +3 -3
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  117. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  118. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  119. package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
  120. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  122. package/src/execution/__tests__/subagent-actions-core.test.ts +48 -22
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  124. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
  125. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  126. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  127. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  128. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  129. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  130. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  131. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  132. package/src/execution/__tests__/sync-collect-recovery.test.ts +1117 -0
  133. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  134. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  135. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  136. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  137. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  138. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  139. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  140. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  141. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  142. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  143. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  144. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  145. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  146. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  147. package/src/execution/agent-registry.ts +74 -30
  148. package/src/execution/agent-result-mapper.ts +3 -0
  149. package/src/execution/cold-resurrect.ts +199 -0
  150. package/src/execution/collect-coordinator.ts +200 -0
  151. package/src/execution/config.ts +77 -5
  152. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  153. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  154. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  155. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  156. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  157. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  158. package/src/execution/engine/__tests__/common/pool-manager.test.ts +213 -53
  159. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  160. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  161. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  162. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  163. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  181. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  182. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  183. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  184. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  185. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  186. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  187. package/src/execution/engine/engines/pi/reader.ts +11 -0
  188. package/src/execution/engine/engines/pi/registration.ts +4 -2
  189. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  190. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  191. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  192. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  193. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  194. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  195. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  196. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  197. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  198. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  199. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  200. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  209. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  210. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  211. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  212. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  213. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  214. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  215. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  216. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  217. package/src/execution/engine/host-task-spec.ts +32 -36
  218. package/src/execution/engine/model-validation.ts +177 -0
  219. package/src/execution/engine/port.ts +24 -6
  220. package/src/execution/engine/routing.ts +75 -1
  221. package/src/execution/engine/types.ts +30 -103
  222. package/src/execution/execution-record.ts +229 -144
  223. package/src/execution/finalize-record.ts +141 -99
  224. package/src/execution/idle-gc.ts +28 -0
  225. package/src/execution/lifecycle-predicates.ts +1 -1
  226. package/src/execution/notifier.ts +234 -25
  227. package/src/execution/notify-host.ts +232 -0
  228. package/src/execution/record-entry.ts +16 -0
  229. package/src/execution/record-store.ts +280 -103
  230. package/src/execution/round-settlement.ts +93 -0
  231. package/src/execution/session-file-gc.ts +96 -62
  232. package/src/execution/session-reconstructor.ts +256 -143
  233. package/src/execution/sessions-index.ts +117 -47
  234. package/src/execution/settled-watchdog.ts +264 -52
  235. package/src/execution/subagent-actions-core.ts +84 -28
  236. package/src/execution/subagent-service.ts +1225 -964
  237. package/src/execution/subprocess-agent-runner.ts +144 -97
  238. package/src/execution/sync-rebuild.ts +87 -0
  239. package/src/execution/types.ts +72 -0
  240. package/src/execution/ui-request-handler-factory.ts +102 -74
  241. package/src/execution/ui-request-queue.ts +50 -19
  242. package/src/index.ts +442 -316
  243. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  244. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  245. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  246. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  247. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  248. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  249. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  250. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  251. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  252. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  253. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  254. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  255. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  256. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  257. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  258. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  259. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  260. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  261. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  262. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  263. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  264. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  265. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  266. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  267. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  268. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  269. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  270. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  271. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  272. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  273. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  274. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  275. package/src/orchestration/agent-opts-resolver.ts +1 -1
  276. package/src/orchestration/args-validator.ts +53 -31
  277. package/src/orchestration/execute-agent-call.ts +19 -73
  278. package/src/orchestration/launcher.ts +128 -81
  279. package/src/orchestration/lifecycle.ts +43 -45
  280. package/src/orchestration/models/ports.ts +4 -4
  281. package/src/orchestration/models/run-runtime.ts +2 -2
  282. package/src/orchestration/models/trace.ts +1 -1
  283. package/src/orchestration/models/types.ts +111 -22
  284. package/src/orchestration/models/workflow-run.ts +1 -1
  285. package/src/orchestration/script-lint.ts +91 -58
  286. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  287. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  288. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  289. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  290. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  291. package/src/shared/atomic-write.ts +12 -9
  292. package/src/shared/meta-parser.ts +158 -84
  293. package/src/shared/model-ref.ts +15 -2
  294. package/src/shared/resource-discovery.ts +9 -183
  295. package/src/shared/schema-jsonify.ts +1 -1
  296. package/workflows/review-fix-loop-utils.cjs +151 -89
  297. package/workflows/review-fix-loop-utils.d.cts +287 -0
  298. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  299. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  300. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  301. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  302. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  303. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  305. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  306. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  307. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  308. package/src/execution/execute-options-mapper.ts +0 -115
  309. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -5,11 +5,11 @@
5
5
  // D-017 时序:collectPatch → completeRecord → archive → cleanup(finalized+worktree+
6
6
  // aliveMarker+pending注销) → manifest(最后 best-effort)。
7
7
  //
8
- // [Critical #1 / PR #85] cleanup 全部在 manifest 写之前执行——manifest 是诊断辅助
9
- //(orphan recovery),写失败仅记录不阻断。旧实现 Step 2.5 throw 会跳过 Step 3 cleanup,
10
- // 导致磁盘满/权限错时 worktree 泄漏 + finalized marker 不写 + alive marker 残留 +
11
- // pending 记账错乱。现 manifest 写移到 Step 4(最后),best-effort(console.error +
12
- // appendEntry,不 throw)。
8
+ // [Critical #1 / PR #85] cleanup 全部在 manifest 写之前执行——manifest 是 sa- id 反查
9
+ // 索引(最新已知快照),非正确性依赖,写失败仅记录不阻断。旧实现 Step 2.5 throw 会跳过
10
+ // Step 3 cleanup,导致磁盘满/权限错时 worktree 泄漏 + finalized marker 不写 + alive
11
+ // marker 残留 + pending 记账错乱。现 manifest 写移到 Step 4(最后),best-effort
12
+ //(console.error + appendEntry,不 throw)。
13
13
  //
14
14
  // B9 兜底:completeRecord/archive 抛错→后续 cleanup/manifest 仍执行。
15
15
 
@@ -86,115 +86,106 @@ function findSessionFileByRecordIdentity(
86
86
  }
87
87
 
88
88
  /**
89
- * 时序收尾(D-017)。步骤 0→4 全部 best-effort 互不阻断(除 manifest 外都幂等)。
89
+ * [T1/PS-9] Step 序言:sessionFile 缺失时按 record 身份在 sessionDir 反查回填。
90
90
  *
91
- * [Critical #1] Step 3 cleanup 全部在 Step 4 manifest 之前——manifest 写失败仅 console.error +
92
- * appendEntry,不 throw 不跳过 cleanup。task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord
93
- * 补字段),manifestToSubagent 投影真实值而非硬编码空串。
91
+ * 放在一切步骤前,让 archive 投影 / Step 3 marker / Step 4 manifest 统一受益。
92
+ * RC-1(握手失败)+ LC-4(收尾反查未命中)的残余形态下,磁盘上 session 文件仍可能
93
+ * 真实存在(identity 携带 record.id)——反查命中则 tombstone/finalized sidecar 与
94
+ * removeAliveMarker 有了依据,终态原因不再丢失、alive marker 不再残留;未命中则
95
+ * 保持旧行为(best-effort 跳过)。
94
96
  */
95
- export async function doFinalizeRecord(
96
- deps: FinalizeDeps,
97
- record: ExecutionRecord,
98
- result: AgentResult,
99
- status: "closed",
100
- closedReason?: ClosedReason,
101
- ): Promise<void> {
102
- // [T1/PS-9] sessionFile 缺失时 sessionDir 反查(放在一切步骤前,让 archive 投影 /
103
- // Step 3 marker / Step 4 manifest 统一受益)。RC-1(握手失败)+ LC-4(收尾反查未命中)
104
- // 的残余形态下,磁盘上 session 文件仍可能真实存在(identity 携带 record.id)——
105
- // 反查命中则 tombstone/finalized sidecar 与 removeAliveMarker 有了依据,终态原因
106
- // 不再丢失、alive marker 不再残留;未命中则保持旧行为(best-effort 跳过)。
107
- if (!record.sessionFile && deps.sessionDir) {
108
- const resolved = findSessionFileByRecordIdentity(deps.sessionDir, record.id);
109
- if (resolved) {
110
- record.sessionFile = resolved;
111
- logger.warn(
112
- `[subagent] finalizeRecord: sessionFile was missing, resolved via sessionDir identity lookup: ${resolved}`,
113
- );
114
- }
97
+ function resolveMissingSessionFile(deps: FinalizeDeps, record: ExecutionRecord): void {
98
+ if (record.sessionFile || !deps.sessionDir) return;
99
+ const resolved = findSessionFileByRecordIdentity(deps.sessionDir, record.id);
100
+ if (resolved) {
101
+ record.sessionFile = resolved;
102
+ logger.warn(
103
+ `[subagent] finalizeRecord: sessionFile was missing, resolved via sessionDir identity lookup: ${resolved}`,
104
+ );
115
105
  }
106
+ }
116
107
 
117
- // ── Step 0: collectPatch(best-effort)──
118
- // [MF#3] patchFile 写到 worktree 之外(sessionsDir/<branch>.patch),避免被 cleanup 删除;
119
- // 路径回填 record.patchFile,供调用方(tool result / /subagents list)应用。
120
- if (record.worktreeHandle) {
121
- try {
122
- const sessionsDir = getSubagentSessionDir(
123
- deps.modelService.getAgentDir(),
124
- record.worktreeHandle.mainCwd,
125
- );
126
- fs.mkdirSync(sessionsDir, { recursive: true });
127
- const patchFile = path.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
128
- const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
129
- if (patch.written) record.patchFile = patchFile;
130
- } catch (pe: unknown) {
131
- bestEffort(pe, "collectPatch (finalizeRecord Step0)");
132
- }
108
+ /**
109
+ * Step 0: collectPatch(best-effort,仅 worktree 绑定时执行)。
110
+ * [MF#3] patchFile 写到 worktree 之外(sessionsDir/<branch>.patch),避免被 cleanup 删除;
111
+ * 路径回填 record.patchFile,供调用方(tool result / /subagents list)应用。
112
+ */
113
+ async function collectPatchIfWorktree(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
114
+ if (!record.worktreeHandle) return;
115
+ try {
116
+ const sessionsDir = getSubagentSessionDir(
117
+ deps.modelService.getAgentDir(),
118
+ record.worktreeHandle.mainCwd,
119
+ );
120
+ fs.mkdirSync(sessionsDir, { recursive: true });
121
+ const patchFile = path.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
122
+ const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
123
+ if (patch.written) record.patchFile = patchFile;
124
+ } catch (pe: unknown) {
125
+ bestEffort(pe, "collectPatch (finalizeRecord Step0)");
133
126
  }
127
+ }
134
128
 
135
- // ── Step 1: completeRecord(B9: 抛错→后续仍执行)──
129
+ /**
130
+ * Step 3a: finalized/tombstone sidecar(best-effort 幂等,仅 sessionFile 存在时执行)。
131
+ * MF-1 fix / v4 B-1: cancelled(closedReason='cancelled')写 tombstone 而非 finalized,
132
+ * 防重建丢失 cancelled;其余 reason 写 finalized sidecar(真实 reason 进 sidecar 内容,
133
+ * 磁盘重建用它还原 closedReason,不再一律硬编码 gc)。
134
+ */
135
+ function writeFinalizedOrTombstone(record: ExecutionRecord, closedReason: ClosedReason | undefined): void {
136
+ if (!record.sessionFile) return;
136
137
  try {
137
- completeRecord(record, result, status, closedReason);
138
+ if (closedReason === "cancelled") {
139
+ writeCancelledTombstone(record.sessionFile, {
140
+ id: record.id,
141
+ status: "cancelled",
142
+ agent: record.agent,
143
+ startedAt: record.startedAt,
144
+ endedAt: record.endedAt ?? Date.now(),
145
+ });
146
+ } else {
147
+ writeFinalized(record.sessionFile, closedReason);
148
+ }
138
149
  } catch (err) {
139
- bestEffort(err, "completeRecord (finalizeRecord B9)", "error");
150
+ bestEffort(err, "writeFinalized/tombstone (finalizeRecord Step3)");
140
151
  }
152
+ }
141
153
 
142
- // ── Step 2: archive(B9: 抛错→后续仍执行)──
154
+ /**
155
+ * Step 3b: worktree cleanup(best-effort 幂等,仅 worktree 绑定时执行)。
156
+ * [Critical] 绝不能因 manifest 写失败而跳过(否则 worktree 泄漏)。
157
+ */
158
+ async function cleanupWorktreeIfBound(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
159
+ if (!record.worktreeHandle) return;
143
160
  try {
144
- deps.store.archive(record);
161
+ await deps.worktreeManager.cleanup(record.worktreeHandle);
145
162
  } catch (err) {
146
- bestEffort(err, "store.archive (finalizeRecord B9)", "error");
163
+ bestEffort(err, "worktree cleanup (finalizeRecord Step3)");
147
164
  }
165
+ }
148
166
 
149
- // ── Step 3: finalized + cleanup + aliveMarker + pending注销(全部先执行,幂等)──
150
- // [Critical] 清理必须在 manifest 写入之前:worktree cleanup / finalized marker / aliveMarker
151
- // 都是幂等且不可跳过的副作用。绝不能因 manifest 写失败而跳过 worktree cleanup
152
- // (否则 worktree 泄漏)。各件独立 try/catch,互不阻断。
153
- if (record.sessionFile) {
154
- try {
155
- // MF-1 fix / v4 B-1: cancelled(closedReason='cancelled')写 tombstone 而非 finalized,防重建丢失 cancelled
156
- if (closedReason === "cancelled") {
157
- writeCancelledTombstone(record.sessionFile, {
158
- id: record.id,
159
- status: "cancelled",
160
- agent: record.agent,
161
- startedAt: record.startedAt,
162
- endedAt: record.endedAt ?? Date.now(),
163
- });
164
- } else {
165
- // [v8.5 A2] 真实 reason 写入 sidecar 内容(旧格式是空文件):磁盘重建时用它
166
- // 还原 closedReason,不再一律硬编码 gc。cancelled 之外的全部原因(gc /
167
- // user-close / parent-*)都经此路径持久化。
168
- writeFinalized(record.sessionFile, closedReason);
169
- }
170
- } catch (err) {
171
- bestEffort(err, "writeFinalized/tombstone (finalizeRecord Step3)");
172
- }
173
- }
174
- if (record.worktreeHandle) {
175
- try {
176
- await deps.worktreeManager.cleanup(record.worktreeHandle);
177
- } catch (err) {
178
- bestEffort(err, "worktree cleanup (finalizeRecord Step3)");
179
- }
180
- }
181
- if (record.sessionFile) {
182
- try {
183
- removeAliveMarker(record.sessionFile);
184
- } catch (err) {
185
- bestEffort(err, "removeAliveMarker (finalizeRecord Step3)");
186
- }
167
+ /** Step 3c: .alive marker(best-effort 幂等,仅 sessionFile 存在时执行)。 */
168
+ function removeAliveMarkerIfPresent(record: ExecutionRecord): void {
169
+ if (!record.sessionFile) return;
170
+ try {
171
+ removeAliveMarker(record.sessionFile);
172
+ } catch (err) {
173
+ bestEffort(err, "removeAliveMarker (finalizeRecord Step3)");
187
174
  }
175
+ }
188
176
 
189
- // pending-notifications:终态注销(只记 registry 状态,通知由 BgNotifier 发)
190
- deps.emitUnregister(record.id, status);
191
-
192
- // ── Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)──
193
- // [Critical #1] manifest 是诊断辅助(orphan recovery),不是正确性依赖。写失败时
194
- // 仅记录(console.error + appendEntry),绝不让 manifest 写失败跳过上面的 worktree
195
- // cleanup 或抛出打断 finalize 链。旧实现 Step 2.5 throw 会跳过 Step 3 cleanup。
196
- // task/slug/model ExecutionRecord 抓取(配合 ManifestRecord 补字段),
197
- // manifestToSubagent 投影时用真实值而非硬编码空串。
177
+ /**
178
+ * Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)。
179
+ *
180
+ * [Critical #1] manifest 是 sa- id 反查索引(最新已知快照),不是正确性依赖。本步骤
181
+ * 写终态快照;sync 批成功成员不经此处——落标出口(subagent-service
182
+ * appendBatchFinalizedEntry)已先行补写,message upgrade 到终态时被本步骤原子
183
+ * 覆盖。写失败时仅记录(console.error + appendEntry),绝不让 manifest 写失败
184
+ * 跳过 Step 3 cleanup 或抛出打断 finalize 链。旧实现 Step 2.5 throw 会
185
+ * 跳过 Step 3 cleanup。task/slug/model 从 ExecutionRecord 抓取(配合
186
+ * ManifestRecord 补字段),manifestToSubagent 投影时用真实值而非硬编码空串。
187
+ */
188
+ async function writeManifestBestEffort(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
198
189
  try {
199
190
  await deps.manifestStore.writeManifest({
200
191
  id: record.id,
@@ -220,6 +211,55 @@ export async function doFinalizeRecord(
220
211
  }
221
212
  }
222
213
 
214
+ /**
215
+ * 时序收尾(D-017)。步骤 0→4 全部 best-effort 互不阻断(除 manifest 外都幂等)。
216
+ *
217
+ * [Critical #1] Step 3 cleanup 全部在 Step 4 manifest 之前——manifest 写失败仅 console.error +
218
+ * appendEntry,不 throw 不跳过 cleanup。task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord
219
+ * 补字段),manifestToSubagent 投影真实值而非硬编码空串。
220
+ */
221
+ export async function doFinalizeRecord(
222
+ deps: FinalizeDeps,
223
+ record: ExecutionRecord,
224
+ result: AgentResult,
225
+ status: "closed",
226
+ closedReason?: ClosedReason,
227
+ ): Promise<void> {
228
+ // [T1/PS-9] sessionFile 缺失时 sessionDir 反查(序言,先于一切步骤)。
229
+ resolveMissingSessionFile(deps, record);
230
+
231
+ // ── Step 0: collectPatch(best-effort)──
232
+ await collectPatchIfWorktree(deps, record);
233
+
234
+ // ── Step 1: completeRecord(B9: 抛错→后续仍执行)──
235
+ try {
236
+ completeRecord(record, result, status, closedReason);
237
+ } catch (err) {
238
+ bestEffort(err, "completeRecord (finalizeRecord B9)", "error");
239
+ }
240
+
241
+ // ── Step 2: archive(B9: 抛错→后续仍执行)──
242
+ try {
243
+ deps.store.archive(record);
244
+ } catch (err) {
245
+ bestEffort(err, "store.archive (finalizeRecord B9)", "error");
246
+ }
247
+
248
+ // ── Step 3: finalized + cleanup + aliveMarker(全部先执行,幂等)──
249
+ // [Critical] 清理必须在 manifest 写入之前:worktree cleanup / finalized marker / aliveMarker
250
+ // 都是幂等且不可跳过的副作用。绝不能因 manifest 写失败而跳过 worktree cleanup
251
+ // (否则 worktree 泄漏)。各件独立 try/catch,互不阻断。
252
+ writeFinalizedOrTombstone(record, closedReason);
253
+ await cleanupWorktreeIfBound(deps, record);
254
+ removeAliveMarkerIfPresent(record);
255
+
256
+ // pending-notifications:终态注销(只记 registry 状态,通知由 BgNotifier 发)
257
+ deps.emitUnregister(record.id, status);
258
+
259
+ // ── Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)──
260
+ await writeManifestBestEffort(deps, record);
261
+ }
262
+
223
263
  /**
224
264
  * 对话模式轮次完成收尾:record 进 idle 态(非终态化,等待续聊)。
225
265
  *
@@ -227,7 +267,9 @@ export async function doFinalizeRecord(
227
267
  * - 不调 completeRecord(record 不冻结,保留 turns[] 等运行时状态供续聊累积)
228
268
  * - 不调 store.archive(record 留内存,getMutable 可查、list 可见)
229
269
  * - 不 cleanup worktree(保留对话模式工作目录)
230
- * - 不写 manifest(idle 非终态,manifest 是终态诊断辅助)
270
+ * - 不写 manifest:轮终回 running-resumable 非终态化,本方法无终态快照可写——
271
+ * manifest 是 sa- id 反查索引(最新已知快照),sync 批成员的先行快照由落标出口
272
+ * 补写(subagent-service appendBatchFinalizedEntry),此处不重复
231
273
  * - 删 .alive marker(进程已 SIGTERM 回收,不再是活进程)
232
274
  * - emitUnregister(进程已死,从 pending 活跃后代差集移除;record 留内存不 archive)
233
275
  *
@@ -4,9 +4,12 @@
4
4
  * 启动幂等由调用方(service.startGcTimer)守卫;返回 stop 函数供 dispose 清理。
5
5
  */
6
6
  import { getLogger } from "../core/logger.ts";
7
+ import { getEngineDataDir } from "./engine/common/data-dir.ts";
8
+ import { releasePoolRef } from "./engine/common/pool-manager.ts";
7
9
  import { bestEffort } from "./best-effort.ts";
8
10
  import { isResumable } from "./lifecycle-predicates.ts";
9
11
  import type { RecordStore } from "./record-store.ts";
12
+ import type { ExecutionRecord } from "./types.ts";
10
13
 
11
14
  const logger = getLogger("subagents");
12
15
 
@@ -24,6 +27,15 @@ const MS_PER_DAY = 24 * 60 * 60 * 1000;
24
27
  * 启动 idle record GC 定时器,返回 stop 函数(清理 interval;幂等)。
25
28
  * 每个扫描周期:对 store 内全部 active record 中 resumable 且带 idleSince 的,
26
29
  * 超过 IDLE_TTL_MS 的归档(archive 单条失败不阻断其余,bestEffort 留痕)。
30
+ *
31
+ * [D8 池引用计数接线] 归档时同步释放该 record 的引擎池引用(releasePoolRef:
32
+ * journal 跟随 record 删除 + refs 移除 + 归零删池内原生状态)。锚点选在 30 天 TTL
33
+ * 而非 dispose/archive 类时机,依据:idle record 30 天后引擎侧不会再有活动(archive
34
+ * 后 message 续聊走 fork-from/新 start,不触原引擎),且与 pi 域 session 文件 30 天
35
+ * TTL(session-file-gc 同期删①级读源)形成两引擎对称衰减;disposeAllRecords/close
36
+ * 类时机 record 数据仍保留(可 resurrect / GUI 历史重建可见),journal(②级数据源)
37
+ * 不能删——那是 record 主数据死亡(主 session 文件被 pi 侧删除,core 无触发点)才有
38
+ * 的处置,由 cleanupExpiredPoolRefs 的 TTL 兜底覆盖。
27
39
  */
28
40
  export function startIdleGc(store: RecordStore): () => void {
29
41
  const timer = setInterval(() => {
@@ -38,6 +50,7 @@ export function startIdleGc(store: RecordStore): () => void {
38
50
  } catch (err) {
39
51
  bestEffort(err, `GC archive record ${record.id}`);
40
52
  }
53
+ releaseEnginePoolRef(record);
41
54
  }
42
55
  }
43
56
  }
@@ -45,3 +58,18 @@ export function startIdleGc(store: RecordStore): () => void {
45
58
  timer.unref?.();
46
59
  return () => clearInterval(timer);
47
60
  }
61
+
62
+ /**
63
+ * 释放 record 的引擎池引用(best-effort)。无 engineHandle(存量 record / 非引擎
64
+ * 路径)时 no-op;engine 缺省投影 'pi'(与读侧存量 entry 零迁移口径一致)。
65
+ */
66
+ function releaseEnginePoolRef(record: ExecutionRecord): void {
67
+ const poolKey = record.engineHandle?.poolKey;
68
+ if (poolKey === undefined) return;
69
+ const engineId = record.engine ?? "pi";
70
+ try {
71
+ releasePoolRef(getEngineDataDir(), engineId, poolKey, record.id);
72
+ } catch (err) {
73
+ bestEffort(err, `GC release pool ref for record ${record.id}`);
74
+ }
75
+ }
@@ -20,7 +20,7 @@
20
20
  // 故 isIdle 在这两个检查点恒为 true——notify 守卫与 early-return 判据可靠。
21
21
 
22
22
  import { hasIdleTimer } from "./lifecycle-manager.ts";
23
- import { getChildByRecord } from "./session-runner.ts";
23
+ import { getChildByRecord } from "./engine/engines/pi/session-runner.ts";
24
24
  import type { ExecutionRecord } from "./types.ts";
25
25
 
26
26
  /**
@@ -1,6 +1,7 @@
1
1
  // src/execution/notifier.ts
2
2
  //
3
- // Background 完成回注主对话。sync 不用(调用方还在 await,结果直接返回)。
3
+ // Background 完成回注主对话。collect:"sync" 的批通知不经单条 notify() 生命周期,
4
+ // 由 collect-coordinator 合批后调本文件 notifyBatch 投递(成员结果不直接返回调用方)。
4
5
  //
5
6
  // 职责(U2 后):
6
7
  // - buildLlmContent:格式化通知文案(本文件唯一逻辑职责)
@@ -9,6 +10,7 @@
9
10
  // notify-ledger.ts);未装配时退回投递内核路径(createDelivery 经通知域窄端口
10
11
  // NotifyDomainPorts 注入——core 依赖闭包不含 session-delivery,见 core/notify-ports.ts;
11
12
  // 合并窗口 / 去重 / 退避 / flush 委托内核,旧装配 / 无 ledger 测试兼容)
13
+ // - notifyBatch / buildBatchLlmContent / computeBatchBudget:sync 批通知三件套(U3/U4)
12
14
  //
13
15
  // 投递通道(D5 单通道化):ledger 路径经 courier 在 settled 边沿直达
14
16
  // pi.sendMessage({triggerTurn:true});内核路径的 port.send 同样只传
@@ -17,6 +19,8 @@
17
19
  // 设计 D5 实测证伪);busy 场景由 ledger(settled 边沿 + isIdle 二次复查)或内核
18
20
  // settled 订阅驱动在空闲边沿投递。
19
21
 
22
+ import { createHash } from "node:crypto";
23
+
20
24
  import { getLogger } from "../core/logger.ts";
21
25
  import { getNotifyDomainPorts, type DeliveryHandle, type DeliveryPort } from "../core/notify-ports.ts";
22
26
 
@@ -104,6 +108,169 @@ export interface NotifierHost {
104
108
  onAgentSettled?(handler: () => void): void;
105
109
  }
106
110
 
111
+ /** sync 批通知幂等键前缀(subagent-sync-collect 设计 §3.1.3 批身份)。 */
112
+ const BATCH_NOTIFY_ID_PREFIX = "sync-batch:";
113
+
114
+ /**
115
+ * sync 批通知 notifyId:`sync-batch:<sha1(sorted member ids)>`。
116
+ *
117
+ * 成员集身份(D2 隐式批):字典序排序后 sha1——同成员集不同登记顺序同 hash(重启
118
+ * 重建批 / E1 补发凭此与账本原条目对齐,幂等去重);不同成员集必异 hash(跨批不互吞)。
119
+ * 成员 id 为 sa-xxx 格式无分隔歧义,join(",") 仅作边界防御。
120
+ */
121
+ export function buildBatchNotifyId(memberIds: readonly string[]): string {
122
+ const digest = createHash("sha1")
123
+ .update([...memberIds].sort().join(","))
124
+ .digest("hex");
125
+ return `${BATCH_NOTIFY_ID_PREFIX}${digest}`;
126
+ }
127
+
128
+ // ─── U4 预算截断 + 指针(设计 §3.1.2 两段式预算,确定性统一收紧)───
129
+
130
+ /** sync 批预算参数(config collectSync 节语义)。U4 以设计默认值锁规格;
131
+ * config 热读(flush 时读值)需 service 传参接线,不在本函数内自行读 config。 */
132
+ export interface BatchBudgetParams {
133
+ /** 单条目正文上限(第一段截断)。 */
134
+ perItemChars: number;
135
+ /** 全批正文中量预算(第二段收紧触发阈值)。 */
136
+ totalChars: number;
137
+ }
138
+
139
+ /** 设计默认预算(§3.1.3 config 示例值)。 */
140
+ const DEFAULT_BATCH_BUDGET: BatchBudgetParams = { perItemChars: 4000, totalChars: 24000 };
141
+
142
+ /** 预算计划(computeBatchBudget 输出,纯数据)。 */
143
+ export interface BatchBudgetPlan {
144
+ /** ② 总量再压缩是否触发(Σ per-item 截断后正文 > totalChars)。 */
145
+ tightened: boolean;
146
+ /** 每条目正文有效上限:未收紧 = perItemChars(兼作第一段截断上限);纯清单退化 = 0。 */
147
+ effectivePerItem: number;
148
+ /** 纯清单退化(floor(totalChars/n) < 200 → 条目只剩头行 + 指针行,正文 0 字符)。 */
149
+ listOnly: boolean;
150
+ }
151
+
152
+ /** 绝对下限(设计 §3.1.2):预算折算低于它时放弃正文,条目退化为「头行 + 指针行」。 */
153
+ const LIST_ONLY_FLOOR = 200;
154
+
155
+ /**
156
+ * 两段式批预算计划(纯函数,规格测试 collect-budget.test.ts 锁定)。
157
+ *
158
+ * ① per-item:每条目正文 ≤ perItemChars;② 总量:Σ(截断后) > totalChars 时
159
+ * effectivePerItem = clamp(floor(totalChars / n), 200, perItemChars) 统一收紧
160
+ * (n = 成员数)。刻意放弃按剩余预算的瀑布分配(非确定、依赖条目顺序),
161
+ * 统一收紧换确定性;收紧后 Σ ≤ n × floor(totalChars/n) ≤ totalChars 恒回到预算内。
162
+ * 未触发收紧时 effectivePerItem = perItemChars(同时承担第一段截断上限)。
163
+ */
164
+ export function computeBatchBudget(
165
+ bodyLengths: readonly number[],
166
+ perItemChars: number,
167
+ totalChars: number,
168
+ ): BatchBudgetPlan {
169
+ const n = bodyLengths.length;
170
+ const sumAfterPerItem = bodyLengths.reduce((sum, len) => sum + Math.min(len, perItemChars), 0);
171
+ if (n === 0 || sumAfterPerItem <= totalChars) {
172
+ return { tightened: false, effectivePerItem: perItemChars, listOnly: false };
173
+ }
174
+ const tight = Math.floor(totalChars / n);
175
+ if (tight < LIST_ONLY_FLOOR) {
176
+ return { tightened: true, effectivePerItem: 0, listOnly: true };
177
+ }
178
+ return { tightened: true, effectivePerItem: Math.min(tight, perItemChars), listOnly: false };
179
+ }
180
+
181
+ /** 千位分隔(对齐设计样例 11,234;手写正则零 ICU 依赖)。 */
182
+ function formatChars(n: number): string {
183
+ return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
184
+ }
185
+
186
+ /**
187
+ * session_read result action 的默认单条字符上限(adversarial-review-fixes §3.4 C3)。
188
+ * 对齐 extensions/universal/session-reader/src/result-action.ts 的 RESULT_DEFAULT_LIMIT
189
+ * (8000)——extension 与 runtime 包无共享常量处、反向 import 会建立 subagent-core 对
190
+ * extension 的依赖,故本地常量 + 注释锚定;两侧改动须同步。
191
+ */
192
+ const SESSION_READ_DEFAULT_LIMIT = 8000;
193
+
194
+ /** 截断指针行(设计 §3.1.2 样例):主 agent 据此按需 session_read 取回全文。
195
+ * [S8 code-simplify 口径注] 本函数的 X(total - kept)= **丢弃**字符数;而
196
+ * session-reader result-action.ts formatResultTruncation 同模板的 X = **保留**
197
+ * 字符数(limit)——同模板异语义,统一字符串 = 行为变更,勿顺手改口径。
198
+ * [C3 limit 随附] perItemLimit = 该成员实际截断预算(effectivePerItem):预算超过
199
+ * session_read 默认 limit(8000) 时附 "limit":N——模型照抄指针行 JSON 调用才能取到
200
+ * ≥ 默认值的有效正文(否则默认 8000 反而少于通知里已见的 kept,触发第二层截断);
201
+ * ≤8000 不附(默认已覆盖,避免噪音)。 */
202
+ function buildTruncationPointer(id: string, kept: number, total: number, perItemLimit: number): string {
203
+ const limitPart = perItemLimit > SESSION_READ_DEFAULT_LIMIT ? `,"limit":${perItemLimit}` : "";
204
+ return `[truncated ${formatChars(total - kept)} of ${formatChars(total)} chars — full result: session_read {"action":"result","session":"${id}"${limitPart}}]`;
205
+ }
206
+
207
+ /** closed 成员 outcome 兑底物化(与下方 notify() 投影边界同款);running 原样透传。 */
208
+ function withMaterializedOutcome(record: BgNotifyRecord): BgNotifyRecord {
209
+ return record.status === "closed"
210
+ ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error) }
211
+ : record;
212
+ }
213
+
214
+ /**
215
+ * sync 批通知文案(U3 批形态 + U4 两段式预算截断)。
216
+ *
217
+ * 形态(设计 §3.1.1 交互样例):批头行 `Subagent batch completed: N finished,
218
+ * M failed, K cancelled.` + 各成员条目(buildLlmContent 同语义)以 "\n\n---\n\"
219
+ * \n" join(分隔符对齐 ledger mergeItems / 内核合批——TUI/LLM 两消费方同构)。
220
+ * 批头计数口径 = 成员 outcome 三态(completed→finished);closed 成员 outcome 缺省
221
+ * 时按单一权威 deriveOutcome 兑底(与 notify() 投影边界同款,幂等无害)。
222
+ *
223
+ * [U4 预算截断] 仅对展示结果正文的条目(closed+completed / running)生效;
224
+ * failed/cancelled 条目无 result 可取回(指针无意义)不计正文预算、原样输出。
225
+ * totalChars 口径(§3.1.2):仅计条目正文(截断后)之和,批头/头行/指针行/
226
+ * 分隔符等包装开销为有界常量不入预算。未触预算时输出与 U3 基线逐字节一致。
227
+ * budget 缺省 = 设计默认值;config 热读接线由 flush 调用方传入。
228
+ */
229
+ export function buildBatchLlmContent(
230
+ records: readonly BgNotifyRecord[],
231
+ budget: BatchBudgetParams = DEFAULT_BATCH_BUDGET,
232
+ ): string {
233
+ // 入口一次物化(S1,code-simplify):closed 成员 outcome 兑底仅此一处,bodies/计数/
234
+ // 条目全部读物化结果——「单一权威 deriveOutcome」从注释承诺变构造事实。幂等纯函数,
235
+ // 与原四处各自推导逐字节等价;running 原样透传零拷贝。
236
+ const mat = records.map(withMaterializedOutcome);
237
+ let finished = 0;
238
+ let failed = 0;
239
+ let cancelled = 0;
240
+ // 各条目参与预算的正文(§3.1.2 口径);null = 该条目不展示 result 正文。
241
+ const bodies = mat.map((record): string | null => {
242
+ if (record.status === "closed" && record.outcome !== "completed") return null;
243
+ return record.result ?? "(empty)";
244
+ });
245
+ const plan = computeBatchBudget(
246
+ bodies.map((body) => body?.length ?? 0),
247
+ budget.perItemChars,
248
+ budget.totalChars,
249
+ );
250
+ const items = mat.map((record, i) => {
251
+ if (record.status === "closed") {
252
+ const outcome = record.outcome;
253
+ if (outcome === "completed") finished += 1;
254
+ else if (outcome === "failed") failed += 1;
255
+ else if (outcome === "cancelled") cancelled += 1;
256
+ }
257
+ const body = bodies[i];
258
+ if (body === null || body.length <= plan.effectivePerItem) {
259
+ // 未触预算(含 failed/cancelled 条目):U3 基线字节不变。
260
+ return buildLlmContent(record);
261
+ }
262
+ // 截断:正文保留前 limit 字符 + 省略号,尾接指针行(kept=0 纯清单时省略号一并省略)。
263
+ const kept = body.slice(0, plan.effectivePerItem);
264
+ const truncated = buildLlmContent({ ...record, result: plan.listOnly ? "" : `${kept}…` });
265
+ // 纯清单时 result 置空会在 buildLlmContent 内残留头行尾换行("Result:\n" + ""),
266
+ // 收掉该空行让条目严格为「头行 + 指针行」两行形态(设计 §3.1.2 纯清单退化)。
267
+ const stripped = plan.listOnly ? truncated.replace(/\n$/, "") : truncated;
268
+ return `${stripped}\n${buildTruncationPointer(record.id, kept.length, body.length, plan.effectivePerItem)}`;
269
+ });
270
+ const header = `Subagent batch completed: ${finished} finished, ${failed} failed, ${cancelled} cancelled.`;
271
+ return [header, ...items].join("\n\n---\n\n");
272
+ }
273
+
107
274
  /**
108
275
  * 将 BgNotifyRecord 格式化为 LLM 可读的 notification content。
109
276
  *
@@ -164,6 +331,13 @@ function buildLlmContent(record: BgNotifyRecord): string {
164
331
  export interface BgNotifier {
165
332
  /** 入队一条完成通知(去重 + 合批窗口合并)。dispose 后短路。 */
166
333
  notify(record: BgNotifyRecord): void;
334
+ /**
335
+ * sync 批通知(subagent-sync-collect U3):成员集 → 单条批投递。幂等键
336
+ * `sync-batch:<sha1(sorted ids)>`,走与 notify 同一写账→settled 边沿投递链(单
337
+ * entry)。返回 false = 幂等拒绝(同成员集批已在账/已销账)或空批 / dispose 后——
338
+ * 调用方(service flushBatch 接线)据此决定 batchFinalized 落标。
339
+ */
340
+ notifyBatch(records: readonly BgNotifyRecord[], budget?: BatchBudgetParams): boolean;
167
341
  /** 立即 flush(session_shutdown 调用,防丢失)。 */
168
342
  flushPendingNotifications(): void;
169
343
  /** session 结束:清队列,dispose 内核 handle。 */
@@ -292,6 +466,37 @@ export function createNotifier(host: NotifierHost): BgNotifier {
292
466
  };
293
467
  let handle: DeliveryHandle = createHandle();
294
468
 
469
+ /** 四步投递链尾部共用段(S2,code-simplify):notify 单条与 notifyBatch 逐行同构
470
+ * 的收尾——原两份手写拷贝使注释宣称的「同一四步链」无结构强制,现单点实现。
471
+ * ledger 在 → ①写账(拒绝 = false,幂等去重:同 notifyId 已在账/已销账)→
472
+ * ②attemptDeliver(settled 边沿 + isIdle 二次复查,③④销账/重放在 ledger 内);
473
+ * ledger 缺(旧装配 / 部分测试,含 jiti 单例分裂的失效形态)→ 内核路径降级 +
474
+ * 降级留痕 warn(C-ext-06 配套,不改变向后兼容行为)。返回 false 仅 notifyBatch
475
+ * 消费(调用方跳过落标);notify 忽略(fire-and-forget)。闭包读 let handle:
476
+ * revive 重建后自然指向新 handle。 */
477
+ function deliverViaLedgerOrKernel(notifyId: string, content: string, details: object): boolean {
478
+ const ledger = getBoundNotifyLedger();
479
+ if (ledger) {
480
+ if (!ledger.record(notifyId, content, details)) return false;
481
+ ledger.attemptDeliver();
482
+ return true;
483
+ }
484
+ notifyLogger.warn("notify ledger not bound, falling back to delivery kernel path (at-most-once)", {
485
+ notifyId,
486
+ });
487
+ handle.send({
488
+ payload: {
489
+ kind: "custom",
490
+ customType: NOTIFY_CUSTOM_TYPE,
491
+ content,
492
+ display: true,
493
+ details,
494
+ },
495
+ dedupeKey: notifyId,
496
+ });
497
+ return true;
498
+ }
499
+
295
500
  return {
296
501
  notify(record: BgNotifyRecord): void {
297
502
  if (disposed) return;
@@ -314,31 +519,35 @@ export function createNotifier(host: NotifierHost): BgNotifier {
314
519
  // 投递尝试)→ ②courier 边沿投递(settled 边沿 + 120s 看门狗 + isIdle 二次
315
520
  // 复查)→ ③回执销账 / ④重放幂等均在 ledger 内。record 返回 false = 同
316
521
  // notifyId 已在账或已销账(幂等去重,含重启恢复后的已送达账号零重发)。
317
- const ledger = getBoundNotifyLedger();
318
- if (ledger) {
319
- if (!ledger.record(notifyId, content, payload)) return;
320
- ledger.attemptDeliver();
321
- return;
322
- }
522
+ // ledger 装配时内核路径降级 + 降级留痕(C-ext-06 配套,见 helper 注释)。
523
+ // 单条通知为 fire-and-forget,返回值忽略。
524
+ deliverViaLedgerOrKernel(notifyId, content, payload);
525
+ },
323
526
 
324
- // ledger 装配(旧装配 / 部分测试):内核路径——合批窗口 / settled 边沿 /
325
- // dedupe(按 notifyId,key 规则与旧 dedupeKey 一致:id id:round)不变。
326
- // [C-ext-06 配套] 降级留痕:bind 缺失(含 jiti 单例分裂致跨模块读不到绑定的
327
- // 失效形态)本是无声分岔,U2 at-least-once 在此退化为内核路径——warn 一条
328
- // 供诊断检索,不改变向后兼容行为。
329
- notifyLogger.warn("notify ledger not bound, falling back to delivery kernel path (at-most-once)", {
330
- notifyId,
331
- });
332
- handle.send({
333
- payload: {
334
- kind: "custom",
335
- customType: NOTIFY_CUSTOM_TYPE,
336
- content,
337
- display: true,
338
- details: payload,
339
- },
340
- dedupeKey: notifyId,
341
- });
527
+ notifyBatch(records: readonly BgNotifyRecord[], budget?: BatchBudgetParams): boolean {
528
+ if (disposed || records.length === 0) return false;
529
+
530
+ // 投影边界物化(与 notify 同款):closed 成员补 outcome——批头计数与成员条目
531
+ // 单一来源。消源自入参浅拷贝,不改写协调器缓冲持有的快照对象。
532
+ const payloads = records.map((record) =>
533
+ record.status === "closed"
534
+ ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error) }
535
+ : { ...record },
536
+ );
537
+ const batchNotifyId = buildBatchNotifyId(payloads.map((p) => p.id));
538
+ // budget(可选)= service flushBatch/E1 补发接线传入的 config 热读值(U4 deviation #8
539
+ // 由 U5 接线);undefined → buildBatchLlmContent 参数缺省 = 设计默认值(4000/24000)。
540
+ // 账本重放走写入时已定格的 content,预算在写账时刻生效(“flush 时热读”语义)。
541
+ const content = buildBatchLlmContent(payloads, budget);
542
+ // details 形态 = ledger mergeItems 批量分支({batch:true, items}——bg-notify-render
543
+ // extractBatch 已支持,⛔1 核对)+ 顶层 notifyId:collectDeliveredNotifyIds 的回执
544
+ // 匹配键认 details.notifyId / details.items[].notifyId,批身份键必须顶层可达,
545
+ // 否则批 entry 永不销账、重投至放弃。顶层 notifyId 键对 extractBatch 透明(多键无感)。
546
+ const details = { batch: true as const, notifyId: batchNotifyId, items: payloads };
547
+
548
+ // 与 notify 同一四步链(deliverViaLedgerOrKernel,S2 提取):返回 false = 同成
549
+ // 员集批已在账(E1 重建重发 / 重复 flush)→ 调用方跳过落标。
550
+ return deliverViaLedgerOrKernel(batchNotifyId, content, details);
342
551
  },
343
552
 
344
553
  flushPendingNotifications(): void {