@zhushanwen/subagent-core 0.2.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-43ONBFZX.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-V3VHZ2VX.js +59 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
  18. package/dist/execution/engine/engines/zcode/constants.js +39 -7
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +18234 -1277
  24. package/dist/index.d.cts +5646 -673
  25. package/dist/index.d.ts +5646 -673
  26. package/dist/index.js +17828 -1277
  27. package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
  28. package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
  29. package/package.json +7 -4
  30. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  31. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  32. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  33. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  34. package/src/__tests__/manifest-store.test.ts +10 -9
  35. package/src/__tests__/record-store-cache.test.ts +0 -2
  36. package/src/__tests__/record-store-index.test.ts +1 -2
  37. package/src/__tests__/review-fix-loop-script.test.ts +93 -3
  38. package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
  39. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  40. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  41. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  42. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  43. package/src/__tests__/session-runner.test.ts +1 -1
  44. package/src/__tests__/smoke.test.ts +9 -2
  45. package/src/core/__tests__/host-services.test.ts +2 -2
  46. package/src/core/__tests__/logger.test.ts +1 -1
  47. package/src/core/error-message.ts +9 -0
  48. package/src/core/host-services.ts +21 -5
  49. package/src/core/notify-ports.ts +18 -5
  50. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  51. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  52. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  53. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  54. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  55. package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
  56. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
  57. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  58. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  59. package/src/execution/__tests__/delivery-methods.test.ts +77 -62
  60. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  61. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  62. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  63. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  64. package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
  65. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  66. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  67. package/src/execution/__tests__/execution-record.test.ts +217 -60
  68. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  69. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  70. package/src/execution/__tests__/finalize-record.test.ts +278 -16
  71. package/src/execution/__tests__/gc-timer.test.ts +57 -2
  72. package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
  73. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  74. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  75. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  76. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  77. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  78. package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
  79. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  80. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  83. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  84. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  85. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  86. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  87. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  88. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  89. package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
  90. package/src/execution/__tests__/output-collector.test.ts +117 -6
  91. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  92. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  93. package/src/execution/__tests__/record-store.test.ts +89 -5
  94. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  95. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  96. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  97. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  98. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  105. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
  106. package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
  107. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  108. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  109. package/src/execution/__tests__/session-pending.test.ts +471 -0
  110. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  111. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  112. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  113. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  114. package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
  115. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
  116. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  117. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  118. package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
  119. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  120. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  121. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  122. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  123. package/src/execution/__tests__/spawned-children.test.ts +155 -11
  124. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  125. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  126. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  127. package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
  128. package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
  129. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  130. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
  131. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
  132. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
  133. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  134. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  135. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  136. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  137. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  138. package/src/execution/__tests__/timeout-integration.test.ts +13 -12
  139. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  140. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  141. package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
  142. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  143. package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
  144. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  145. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  146. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  147. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  148. package/src/execution/agent-registry.ts +298 -30
  149. package/src/execution/agent-result-mapper.ts +3 -0
  150. package/src/execution/agents-assembly.ts +88 -0
  151. package/src/execution/cold-resurrect.ts +199 -0
  152. package/src/execution/concurrency-pool.ts +71 -9
  153. package/src/execution/dialog-queue.ts +101 -3
  154. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  155. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  156. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  158. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  159. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  160. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  161. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  162. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
  163. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
  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 +94 -3
  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/engine-discovery.ts +12 -16
  181. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  182. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  183. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  184. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
  185. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  186. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  187. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  188. package/src/execution/engine/engines/pi/reader.ts +11 -0
  189. package/src/execution/engine/engines/pi/registration.ts +4 -2
  190. package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
  191. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  192. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  193. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  194. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  195. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  197. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  198. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
  199. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  200. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  202. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  203. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  209. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  210. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
  211. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  212. package/src/execution/engine/engines/zcode/connection.ts +598 -0
  213. package/src/execution/engine/engines/zcode/constants.ts +147 -14
  214. package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
  215. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  216. package/src/execution/engine/engines/zcode/preparer.ts +22 -150
  217. package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
  218. package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
  219. package/src/execution/engine/host-task-spec.ts +32 -36
  220. package/src/execution/engine/model-validation.ts +177 -0
  221. package/src/execution/engine/port.ts +65 -7
  222. package/src/execution/engine/registry.ts +53 -0
  223. package/src/execution/engine/routing.ts +75 -1
  224. package/src/execution/engine/types.ts +30 -103
  225. package/src/execution/execution-record.ts +222 -144
  226. package/src/execution/finalize-record.ts +181 -85
  227. package/src/execution/idle-gc.ts +28 -0
  228. package/src/execution/lifecycle-manager.ts +27 -3
  229. package/src/execution/lifecycle-predicates.ts +1 -1
  230. package/src/execution/manifest-store.ts +53 -62
  231. package/src/execution/notifier.ts +10 -10
  232. package/src/execution/notify-host.ts +212 -0
  233. package/src/execution/notify-ledger.ts +117 -16
  234. package/src/execution/record-entry.ts +8 -2
  235. package/src/execution/record-store.ts +148 -88
  236. package/src/execution/round-settlement.ts +93 -0
  237. package/src/execution/session-file-gc.ts +96 -62
  238. package/src/execution/session-pending.ts +213 -62
  239. package/src/execution/session-reconstructor.ts +256 -143
  240. package/src/execution/sessions-index.ts +127 -102
  241. package/src/execution/settled-watchdog.ts +311 -0
  242. package/src/execution/subagent-actions-core.ts +686 -0
  243. package/src/execution/subagent-service.ts +1106 -907
  244. package/src/execution/subprocess-agent-runner.ts +144 -97
  245. package/src/execution/types.ts +9 -0
  246. package/src/execution/ui-request-handler-factory.ts +126 -75
  247. package/src/execution/ui-request-queue.ts +50 -19
  248. package/src/execution/worktree-git-ops.ts +397 -0
  249. package/src/execution/worktree-manager.ts +72 -10
  250. package/src/execution/worktree-registry.ts +6 -12
  251. package/src/index.ts +542 -9
  252. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  253. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
  254. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  255. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  257. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  258. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
  259. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  260. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  261. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  262. package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
  263. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  264. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  265. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  266. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  267. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  268. package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
  269. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  270. package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
  271. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  272. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  273. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  274. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  275. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  276. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
  277. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  278. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  279. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  280. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  281. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  282. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  283. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  284. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  285. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  286. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  287. package/src/orchestration/agent-opts-resolver.ts +8 -8
  288. package/src/orchestration/args-meta.ts +198 -0
  289. package/src/orchestration/args-validator.ts +53 -31
  290. package/src/orchestration/execute-agent-call.ts +25 -79
  291. package/src/orchestration/file-run-store.ts +327 -0
  292. package/src/orchestration/launcher.ts +160 -104
  293. package/src/orchestration/lifecycle.ts +371 -97
  294. package/src/orchestration/models/agent-call.ts +7 -7
  295. package/src/orchestration/models/budget.ts +5 -5
  296. package/src/orchestration/models/ports.ts +4 -4
  297. package/src/orchestration/models/run-runtime.ts +15 -15
  298. package/src/orchestration/models/trace.ts +9 -9
  299. package/src/orchestration/models/types.ts +111 -22
  300. package/src/orchestration/models/workflow-run.ts +28 -28
  301. package/src/orchestration/models/workflow-script.ts +4 -4
  302. package/src/orchestration/run-snapshot.ts +266 -0
  303. package/src/orchestration/script-generate.ts +154 -0
  304. package/src/orchestration/script-lint.ts +123 -90
  305. package/src/orchestration/worker-handle.ts +10 -10
  306. package/src/orchestration/worker-host.ts +10 -10
  307. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
  308. package/src/orchestration/worker-script-builder.ts +401 -346
  309. package/src/orchestration/workflow-files.ts +37 -11
  310. package/src/orchestration/workflow-run-summary.ts +69 -0
  311. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  312. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  313. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  314. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  315. package/src/shared/__tests__/injection-render.test.ts +518 -0
  316. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  317. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  318. package/src/shared/__tests__/resource-discovery.test.ts +261 -217
  319. package/src/shared/agent-ref.ts +143 -2
  320. package/src/shared/atomic-write.ts +323 -0
  321. package/src/shared/bounded-serialize.ts +154 -0
  322. package/src/shared/injection-render.ts +279 -0
  323. package/src/shared/meta-parser.ts +182 -68
  324. package/src/shared/model-ref.ts +15 -2
  325. package/src/shared/resource-discovery.ts +97 -204
  326. package/src/shared/resource-meta.ts +14 -0
  327. package/src/shared/schema-jsonify.ts +1 -1
  328. package/src/shared/xml-injection.ts +9 -9
  329. package/workflows/README.md +9 -9
  330. package/workflows/chain.js +4 -2
  331. package/workflows/map-reduce.js +5 -3
  332. package/workflows/parallel.js +5 -3
  333. package/workflows/review-fix-loop-utils.cjs +165 -92
  334. package/workflows/review-fix-loop-utils.d.cts +287 -0
  335. package/workflows/review-fix-loop.js +18 -7
  336. package/workflows/scatter-gather.js +4 -2
  337. package/dist/chunk-3VOERJPJ.js +0 -22
  338. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  339. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  340. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  341. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  342. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  343. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  344. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  345. package/src/execution/execute-options-mapper.ts +0 -115
  346. package/src/execution/session-runner.ts +0 -1789
  347. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -159,6 +159,21 @@ interface NegativeFileEntry {
159
159
  /** fileCache 值类型:正常条目或负缓存条目。 */
160
160
  type FileCacheValue = FileCacheEntry | NegativeFileEntry;
161
161
 
162
+ /** scanFile 单文件本轮 stat 戳集合(jsonl + 3 sidecar)。 */
163
+ interface FileStamps {
164
+ jsonl: Stamp;
165
+ cancelled: Stamp | null;
166
+ finalized: Stamp | null;
167
+ alive: Stamp | null;
168
+ }
169
+
170
+ /** sidecar payload 读取结果(索引命中与探测重建两分支共享的读点)。 */
171
+ interface SidecarPayloads {
172
+ tomb: CancelledTombstone | undefined;
173
+ aliveData: AliveMarker | undefined;
174
+ finalReason: string | undefined;
175
+ }
176
+
162
177
  /** 孤儿判定的末行读取初始窗口(常规 entry 远小于此,避免全文件读)。 */
163
178
  // eslint-disable-next-line no-magic-numbers -- 64KB = 64 * 1024 bytes 字节换算常数
164
179
  const LAST_LINE_WINDOW_BYTES = 64 * 1024;
@@ -335,6 +350,41 @@ function sameNullableStamp(a: Stamp | null, b: Stamp | null): boolean {
335
350
  return sameStamp(a, b);
336
351
  }
337
352
 
353
+ /** 缓存条目与本轮 stat 戳全同(jsonl + 3 sidecar,null 语义对齐)→ 零读取复用。 */
354
+ function isFreshCache(cached: FileCacheValue, stamps: FileStamps): boolean {
355
+ return (
356
+ sameStamp(cached.jsonl, stamps.jsonl) &&
357
+ sameNullableStamp(cached.cancelled, stamps.cancelled) &&
358
+ sameNullableStamp(cached.finalized, stamps.finalized) &&
359
+ sameNullableStamp(cached.alive, stamps.alive)
360
+ );
361
+ }
362
+
363
+ /**
364
+ * identity 三级定位——头部 64KB(首轮会话,~34%)→ 尾部 64KB(续聊场景最后一轮
365
+ * session_start 追加,~65%)→ 全文 fallback(~0.2%)。均不解析 message entries。
366
+ * size ≤ 头部读取上限时 head 读到的即全文,tail/anywhere 只会重复读同一份内容——
367
+ * 直接判负(head miss = 全文无 identity),省去同内容两连读。
368
+ */
369
+ function detectIdentity(file: string, size: number): IdentityHeaderRecon | undefined {
370
+ return size <= IDENTITY_HEAD_BYTES
371
+ ? readIdentityHeader(file)
372
+ : readIdentityHeader(file) ?? readIdentityTail(file) ?? readIdentityAnywhere(file);
373
+ }
374
+
375
+ /**
376
+ * sidecar payload 读取(读顺序:tombstone → alive marker → finalized reason)。
377
+ * tombstone/alive 是活态数据,调用方沿用每轮重读语义;finalized reason 静态数据
378
+ * 仅在 sidecar 存在时读一次(文件小,成本可忽略)。
379
+ */
380
+ function readSidecarPayloads(file: string, stamps: FileStamps): SidecarPayloads {
381
+ return {
382
+ tomb: stamps.cancelled !== null ? readCancelledTombstone(file) : undefined,
383
+ aliveData: stamps.alive !== null ? readAliveMarker(file) : undefined,
384
+ finalReason: stamps.finalized !== null ? readFinalizedReason(file) : undefined,
385
+ };
386
+ }
387
+
338
388
  /** Pi ExtensionAPI 的最小子集(仅 collectRecords 跳过损坏 manifest 时上报用)。
339
389
  * 解构为局部类型,避免与 subagent-service 的 PiLike 循环依赖。 */
340
390
  export type RecordStorePi = {
@@ -352,6 +402,12 @@ export type RecordStorePi = {
352
402
  * 读时从 sessions/*.jsonl 重建([perf] light 头部扫描 + per-file 缓存)。
353
403
  *
354
404
  * 任何 mutate → notifyChange()(仅通知监听器;磁盘缓存靠 stat 戳自校验,不清空)。
405
+ *
406
+ * record 状态查询面(U10① D6):按状态枚举 listRunning/collectRecords(statusFilter)、
407
+ * 按 id 查询 getMutable/findLightById/getFullRecord——方法签名即导出形态,本类零改动。
408
+ *
409
+ * @experimental execution 运行时面(设计 docs/design/subagent-core-sink-design.md §3.3 D6):
410
+ * 一个 minor 周期内允许签名微调,稳定后转常规 semver 承诺。
355
411
  */
356
412
  export class RecordStore {
357
413
  private readonly records = new Map<string, ExecutionRecord>();
@@ -698,11 +754,11 @@ export class RecordStore {
698
754
  ...(chatMode
699
755
  ? { resumable: true }
700
756
  : {
701
- status: "closed" as const,
702
- closedReason: "gc" as const,
703
- endedAt: Date.now(),
704
- error: "orphan recovery: no child session file (spawn interrupted or file removed externally)",
705
- }),
757
+ status: "closed" as const,
758
+ closedReason: "gc" as const,
759
+ endedAt: Date.now(),
760
+ error: "orphan recovery: no child session file (spawn interrupted or file removed externally)",
761
+ }),
706
762
  });
707
763
  }
708
764
 
@@ -740,9 +796,19 @@ export class RecordStore {
740
796
  this.indexHigherVersion = false;
741
797
  }
742
798
 
743
- /** /resume /fork /new 后复活(dispose 的逆操作)。 */
799
+ /**
800
+ * /resume /fork /new 后复活(dispose 的逆操作)。
801
+ *
802
+ * [PS-10/T6④] 同步复位 orphanJudged 防重缓存:resumable 形态(IO-error 保守分支 /
803
+ * chatMode 分流)没有 .finalized sidecar 锚,重判资格完全由本缓存承载——dispose 时
804
+ * 有 clear(session 结束),但 revive 此前不复位,导致「同进程内曾经的 IO 失败记录
805
+ * 永久停留 resumable」,与本文件 recoverOrphanRecords 注释承诺的「IO 恢复后重开可重判」
806
+ * 不符。/new 复活正是「重开」语义:IO 已恢复的记录下次 recoverOrphanRecords 重新判定
807
+ * 收敛终态;仍不可读的记录重判再落一次 resumable entry(幂等,末条语义不变)。
808
+ */
744
809
  revive(): void {
745
810
  this._disposed = false;
811
+ this.orphanJudged.clear();
746
812
  }
747
813
 
748
814
  // ── 内部 ──────────────────────────────────────────────────
@@ -849,18 +915,15 @@ export class RecordStore {
849
915
  this.fileCache.delete(file);
850
916
  return null;
851
917
  }
852
- const cancelled = statStamp(`${file}.cancelled`);
853
- const finalized = statStamp(`${file}.finalized`);
854
- const alive = statStamp(`${file}.alive`);
918
+ const stamps: FileStamps = {
919
+ jsonl,
920
+ cancelled: statStamp(`${file}.cancelled`),
921
+ finalized: statStamp(`${file}.finalized`),
922
+ alive: statStamp(`${file}.alive`),
923
+ };
855
924
 
856
925
  const cached = this.fileCache.get(file);
857
- if (
858
- cached !== undefined &&
859
- sameStamp(cached.jsonl, jsonl) &&
860
- sameNullableStamp(cached.cancelled, cancelled) &&
861
- sameNullableStamp(cached.finalized, finalized) &&
862
- sameNullableStamp(cached.alive, alive)
863
- ) {
926
+ if (cached !== undefined && isFreshCache(cached, stamps)) {
864
927
  if (cached.negative) return null; // 负缓存命中:确认无 identity,零读取跳过
865
928
  // pid 探活结果不落盘(进程死亡无 IO)——分支 3 的 running 项每扫重查,
866
929
  // 保留原语义(旧实现每次 collectRecords 都重新 isProcessAlive)。
@@ -869,83 +932,54 @@ export class RecordStore {
869
932
  }
870
933
 
871
934
  // [perf L-1] 磁盘索引查询(首扫惰性装载,miss/空索引时 get 恒 undefined = 无索引)。
872
- // 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。sidecar payload(tombstone/
873
- // alive)是活态数据,沿用探测分支的每轮重读语义;finalized reason 静态数据仅在
874
- // sidecar 存在时读一次(文件小,成本可忽略)。
875
- if (this.indexEntries !== null) {
876
- const hit = this.indexEntries.get(path.basename(file));
877
- if (hit !== undefined && hit.mtimeMs === jsonl.mtimeMs && hit.size === jsonl.size) {
878
- if (hit.negative === true) {
879
- // 负条目命中:「确认无 identity」跨实例持久,零探测跳过(与下方负缓存同款形态)。
880
- this.fileCache.set(file, { negative: true, jsonl, cancelled, finalized, alive });
881
- return null;
882
- }
883
- const tomb = cancelled !== null ? readCancelledTombstone(file) : undefined;
884
- const aliveData = alive !== null ? readAliveMarker(file) : undefined;
885
- const finalReason = finalized !== null ? readFinalizedReason(file) : undefined;
886
- const entry: FileCacheEntry = {
887
- light: RecordStore.buildRecord(
888
- { ...hit, forkDepth: undefined, sessionFile: file },
889
- { tomb, finalized: finalized !== null, finalizedReason: finalReason, alive: aliveData, jsonlMtimeMs: jsonl.mtimeMs, now },
890
- ),
891
- full: undefined,
892
- jsonl,
893
- cancelled,
894
- finalized,
895
- alive,
896
- tomb,
897
- aliveData,
898
- finalReason,
899
- };
900
- this.fileCache.set(file, entry);
901
- this.idToFile.set(hit.id, file);
902
- return entry;
903
- }
904
- }
935
+ // 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。undefined = 未命中
936
+ // (落到下方探测),null = 负条目命中(零探测跳过)。
937
+ const fromIndex = this.buildEntryFromIndex(file, stamps, now);
938
+ if (fromIndex !== undefined) return fromIndex;
905
939
 
906
940
  // [perf L-1] 索引 miss/戳不匹配落到原三级探测:本轮探测结果必须进索引(含负探测)。
907
941
  // 覆盖两种形态:首扫(映像已装载但 miss/不匹配)与后续轮次(映像已释放,凡进重建分支必是戳变化)。
908
942
  this.indexDirty = true;
909
943
 
910
- // 重建:identity 三级定位——头部 64KB(首轮会话,~34%)→ 尾部 64KB(续聊场景
911
- // 最后一轮 session_start 追加,~65%)→ 全文 fallback(~0.2%)。均不解析 message entries。
912
- // size ≤ 头部读取上限时 head 读到的即全文,tail/anywhere 只会重复读同一份内容——
913
- // 直接判负(head miss = 全文无 identity),省去同内容两连读。
914
- const header =
915
- jsonl.size <= IDENTITY_HEAD_BYTES
916
- ? readIdentityHeader(file)
917
- : readIdentityHeader(file) ?? readIdentityTail(file) ?? readIdentityAnywhere(file);
944
+ const header = detectIdentity(file, jsonl.size);
918
945
  if (!header) {
919
946
  // 负缓存:确认无 identity。后续扫描 stat 命中直接跳过;戳变化(文件补写)自动重试。
920
- this.fileCache.set(file, { negative: true, jsonl, cancelled, finalized, alive });
947
+ this.fileCache.set(file, { negative: true, ...stamps });
921
948
  return null;
922
949
  }
923
- const tomb = cancelled !== null ? readCancelledTombstone(file) : undefined;
924
- const aliveData = alive !== null ? readAliveMarker(file) : undefined;
925
- const finalReason = finalized !== null ? readFinalizedReason(file) : undefined;
926
- const entry: FileCacheEntry = {
927
- light: RecordStore.buildRecord(header, {
928
- tomb,
929
- finalized: finalized !== null,
930
- finalizedReason: finalReason,
931
- alive: aliveData,
932
- jsonlMtimeMs: jsonl.mtimeMs,
933
- now,
934
- }),
935
- full: undefined,
936
- jsonl,
937
- cancelled,
938
- finalized,
939
- alive,
940
- tomb,
941
- aliveData,
942
- finalReason,
943
- };
950
+ const payloads = readSidecarPayloads(file, stamps);
951
+ const entry = RecordStore.buildFileCacheEntry(header, file, stamps, payloads, now);
944
952
  this.fileCache.set(file, entry);
945
953
  this.idToFile.set(header.id, file);
946
954
  return entry;
947
955
  }
948
956
 
957
+ /**
958
+ * [perf L-1] 磁盘索引查询(首扫惰性装载,miss/空索引时 get 恒 undefined = 无索引)。
959
+ * 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。sidecar payload(tombstone/
960
+ * alive)是活态数据,沿用探测分支的每轮重读语义;finalized reason 静态数据仅在
961
+ * sidecar 存在时读一次(文件小,成本可忽略)。
962
+ *
963
+ * 返回 undefined = 索引未命中/戳不匹配(调用方落到原三级探测);null = 负条目命中
964
+ * (「确认无 identity」跨实例持久,零探测跳过,与内存负缓存同款形态)。
965
+ */
966
+ private buildEntryFromIndex(file: string, stamps: FileStamps, now: number): FileCacheEntry | null | undefined {
967
+ if (this.indexEntries === null) return undefined;
968
+ const hit = this.indexEntries.get(path.basename(file));
969
+ if (hit === undefined || hit.mtimeMs !== stamps.jsonl.mtimeMs || hit.size !== stamps.jsonl.size) {
970
+ return undefined;
971
+ }
972
+ if (hit.negative === true) {
973
+ this.fileCache.set(file, { negative: true, ...stamps });
974
+ return null;
975
+ }
976
+ const payloads = readSidecarPayloads(file, stamps);
977
+ const entry = RecordStore.buildFileCacheEntry({ ...hit, forkDepth: undefined, sessionFile: file }, file, stamps, payloads, now);
978
+ this.fileCache.set(file, entry);
979
+ this.idToFile.set(hit.id, file);
980
+ return entry;
981
+ }
982
+
949
983
  /**
950
984
  * [perf L-1] 扫描尾索引落盘(节流):释放映像 → dirty/高版本/60s 节流窗三重门 →
951
985
  * fire-and-forget saveIndex(fileCache 全量投影)。写决策与发起在同步栈(collectRecords
@@ -971,7 +1005,10 @@ export class RecordStore {
971
1005
  })
972
1006
  .catch((err: unknown) => {
973
1007
  this.indexDirty = true; // 失败恢复 dirty,下轮过窗重试
974
- logger.debug("[subagents] sessions-index write failed", {
1008
+ // [PS-14/T7③] warn:索引反复写失败(权限/磁盘满)曾仅 debug 级,排障时
1009
+ // 无线索。失败会跨轮重试(dirty 恢复),warn 每次过窗写失败都会出现——
1010
+ // 正是「反复写失败需要可见」的信号面。
1011
+ logger.warn("[subagents] sessions-index write failed", {
975
1012
  detail: { dir: encDir, error: err instanceof Error ? err.message : String(err) },
976
1013
  });
977
1014
  });
@@ -1070,6 +1107,34 @@ export class RecordStore {
1070
1107
  entry.light.externalInstance = live ? marker : undefined;
1071
1108
  }
1072
1109
 
1110
+ /** identity 基底 + sidecar 状态矩阵 → 缓存条目(索引命中与探测重建两分支的公共装配点)。 */
1111
+ private static buildFileCacheEntry(
1112
+ base: IdentityHeaderRecon,
1113
+ file: string,
1114
+ stamps: FileStamps,
1115
+ payloads: SidecarPayloads,
1116
+ now: number,
1117
+ ): FileCacheEntry {
1118
+ return {
1119
+ light: RecordStore.buildRecord(base, {
1120
+ tomb: payloads.tomb,
1121
+ finalized: stamps.finalized !== null,
1122
+ finalizedReason: payloads.finalReason,
1123
+ alive: payloads.aliveData,
1124
+ jsonlMtimeMs: stamps.jsonl.mtimeMs,
1125
+ now,
1126
+ }),
1127
+ full: undefined,
1128
+ jsonl: stamps.jsonl,
1129
+ cancelled: stamps.cancelled,
1130
+ finalized: stamps.finalized,
1131
+ alive: stamps.alive,
1132
+ tomb: payloads.tomb,
1133
+ aliveData: payloads.aliveData,
1134
+ finalReason: payloads.finalReason,
1135
+ };
1136
+ }
1137
+
1073
1138
  /** identity 基底(头部 light 或全量 recon)+ 四分支 sidecar 状态矩阵 → SubagentRecord。 */
1074
1139
  private static buildRecord(
1075
1140
  base: IdentityHeaderRecon | ReconstructedRecord,
@@ -1171,14 +1236,9 @@ export class RecordStore {
1171
1236
  return rec;
1172
1237
  }
1173
1238
 
1174
- /** 从缓存与索引移除某文件(文件删除时;负缓存条目无 id,仅删缓存项)。 */
1175
- private dropFileCache(file: string): void {
1176
- const entry = this.fileCache.get(file);
1177
- if (entry) {
1178
- if (!entry.negative) this.idToFile.delete(entry.light.id);
1179
- this.fileCache.delete(file);
1180
- }
1181
- }
1239
+ // [PS-15/T7⑤] 「从缓存与索引移除单文件」的旧私有方法已整体删除:全仓无调用方
1240
+ // 的死代码(设计 §4.3 PS-15 实锤,顺手清理,无行为影响)。「文件删除时移除缓存」
1241
+ // 的职责实际由 reconstructAll 的消失文件修剪路径承担。
1182
1242
 
1183
1243
  /** 排序比较器:status priority(running<failed<cancelled<done)+ startedAt desc。 */
1184
1244
  private static compareRecords(a: SubagentRecord, b: SubagentRecord): number {
@@ -0,0 +1,93 @@
1
+ // [D4-② 轮次结算职责轴] chatMode 轮次完成的业务闭包(原 SubagentService
2
+ // buildSessionRunnerContext 内的 onRoundSettled 回调 ~95 行区域)整体搬移至此
3
+ // (行为逐字节等价:搬移 + 依赖注入,不重写逻辑)。变化轴:改轮次增量派生 /
4
+ // notify 时序 / base 推进 / closeAfterRound 消费语义,只改本文件;Service 经
5
+ // buildSessionRunnerContext 注入三个依赖回调(notify / 持久化上报 / close 兑现)。
6
+
7
+ import { getLogger } from "../core/logger.ts";
8
+
9
+ import { getFullTextFrom, nextRoundBaseTurnIndex } from "./execution-record.ts";
10
+ import type { ExecutionRecord } from "./types.ts";
11
+
12
+ const logger = getLogger("subagents");
13
+
14
+ /** 轮次结算的依赖注入(Service 侧供给)。 */
15
+ export interface RoundSettlementDeps {
16
+ /** 本轮增量的通知投递(Service.notifyComplete——经 notifyHost)。 */
17
+ notifyComplete: (record: ExecutionRecord) => void;
18
+ /** 轮终迁移持久化上报(store.reportRecordTransition——live ≡ reload 等价性)。 */
19
+ reportRecordTransition: (record: ExecutionRecord) => void;
20
+ /** closeAfterRound 兑现(Service.closeAfterRoundSettled——chatMode 优雅关闭的终态化)。 */
21
+ closeAfterRoundSettled: (record: ExecutionRecord) => Promise<void>;
22
+ }
23
+
24
+ /** 构造 onRoundSettled 回调(session-runner agent_settled 时调用)。
25
+ *
26
+ * [V2 决策 2] chatMode 首轮闭环:session-runner 已 arm idle timer,
27
+ * isIdle=true 让 notify 守卫放行(时序:armIdleTimer → onRoundSettled)。
28
+ * 回调体原为 subagent-service.ts buildSessionRunnerContext 的内联闭包,D4-② 拆出。 */
29
+ export function createRoundSettler(deps: RoundSettlementDeps): (record: ExecutionRecord) => void {
30
+ return (record) => {
31
+ // v4 B-1:status 保持 running(旧 idle 折入 running);session-runner 已 arm idle timer,
32
+ // isIdle=true 让 notify 守卫放行(时序:armIdleTimer → onRoundSettled,见 session-runner.ts:670)。
33
+ // round 可能初始 undefined(与 notifier.ts `record.round ?? 0` 兜底一致),
34
+ // 首轮 0+1=1。round 是 notifier dedup key 的组成部分,递增后同 id 下一轮不被 60s dedup 吞。
35
+ record.round = (record.round ?? 0) + 1;
36
+ // [N2][增量] 轮次回复写点(增量语义):roundText 自 roundBaseTurnIndex 起派生本轮增量
37
+ //(undefined 视为 0——首轮增量 = 全量,与改造前首轮逐字节一致)。成功轮次的 MF-2 原写点
38
+ //(doFinalizeRoundToIdle)不可达——agent_settled 恒 arm idle timer → runAndFinalize 恒
39
+ // early return。写入 record.result 后再 notify;本轮无非空增量且无 lastError(纯工具轮 /
40
+ // interrupt 抢占轮 / 模型空回复)时固定占位 "(no output this round)"(D5:增量语义下沿用
41
+ // 旧 record.result = 上一轮增量 → 本轮通知正文 = 上一轮内容,父 agent 误读为原样重复回复;
42
+ // lastError 兜底保留让失败轮通知可读)。后续 closeAfterRoundSettled 的合成 result 读
43
+ // record.result,同样携带本轮增量。
44
+ const roundText = getFullTextFrom(record, record.roundBaseTurnIndex ?? 0);
45
+ record.result = roundText ||
46
+ (record.lastError ? `round did not complete: ${record.lastError}` : "(no output this round)");
47
+ // 先送达本轮增量(notify),再推进 base / 消费 closeAfterRound——终态通知由
48
+ // closeAfterRoundSettled / closeChatIdle 的 notifyClosed 显式发出(dedup 身份为裸 id,
49
+ // 与本次 round notify 的 id:round key 区分),保证「本轮增量 + 终态通知」都送达;
50
+ // 轮次收尾 .then 的冷路径 notifyComplete 仍与本次 round notify 同 key 被 60s
51
+ // dedup 吞(不构成第三条)。
52
+ //
53
+ // 幂等性(覆盖面如实限定):同步路径 at-least-once——notifyComplete(同步 void)抛错时
54
+ // 推进/消费被跳过 → base 不推进 → 增量未消费,下轮 roundText 必含本轮文本(重发载体为
55
+ // 后续轮次增量拼接)。轮次收尾 .then 的冷路径 notifyComplete 不构成重发通道
56
+ // (notifier dedup.set 与 pending.splice 均先于 sendMessage,同 key `${id}:${round}`——
57
+ // round 已递增——60s 窗内重入被吞)。异步 flush 窗口不保证:合并 timer armed(其他 busy
58
+ // background 在场)或 isIdle 退避期间 notify 的『成功』只是入队,实际 sendMessage 发生在
59
+ // base 推进之后的异步时机;该窗口进程崩溃或 sendMessage 失败 → 丢失不可重发(现状全量
60
+ // 重发的次轮自愈在增量语义下消失),wave1 期恢复通道仅父 agent 经 /subagents 详情读取
61
+ // record.result,wave2 指针行落地后补全。反序(先推进后 notify)在同步路径 notify 失败时
62
+ // 静默丢增量且无任何重算机会,故 notify 后推进是定案。重复发送由 notifier dedup key
63
+ // 60s 窗界定。
64
+ deps.notifyComplete(record);
65
+ // R1 观测哨(不变式违反形态):推进前检查末 turn 未闭合且 text 非空——pi 现序下不可达
66
+ //(带 usage 的 message_end 恒先于 turn_end,settle 时 turn 全闭合,见 types.ts
67
+ // roundBaseTurnIndex 注释的行号锚定),ES1 单测自造事件序列锁不住 pi 层变化;pi 升级若
68
+ // 改变 turn_end/agent_end 时序,此哨兵留痕(该形态下公式仍把文本计入本轮,不丢数据)。
69
+ const lastTurn = record.turns[record.turns.length - 1];
70
+ if (lastTurn !== undefined && !lastTurn.closed && lastTurn.text.length > 0) {
71
+ logger.warn(
72
+ `[subagents] round settle with unclosed non-empty turn (record=${record.id}, turnIndex=${record.turns.length - 1}) — pi turn_end/agent_end ordering may have changed`,
73
+ );
74
+ }
75
+ // [增量] base 推进(notify 之后):下一轮增量从本轮边界起。滞后空 turn 不计入边界
76
+ //(防御分支,留在下一轮增量内防丢文本——nextRoundBaseTurnIndex 注释)。
77
+ record.roundBaseTurnIndex = nextRoundBaseTurnIndex(record);
78
+ // 轮终迁移持久化(residual-fixes U3 补全):热路径轮终不经 doFinalizeRoundToIdle
79
+ //(agent_settled 恒 arm idle timer → runAndFinalize 恒 early return,MF-2 原写点
80
+ // 不可达)——不 appendEntry 则 runtime/W18 派生缓存不失效,renderer 停留在
81
+ // register 快照(无 result),chat 等续聊的 waiting 形态显示不出来、spinner
82
+ // 卡死。显式上报:entry 携带本轮 result/round/chatMode(§5.4:result 有值 +
83
+ // chatMode=true → waiting)。closeAfterRound 的终态 entry 在此后追加,序不变。
84
+ deps.reportRecordTransition(record);
85
+ // [M5] closeAfterRound 消费点:chatMode 每轮完成的统一汇聚点(热路径轮不经
86
+ // runAndFinalize CAS 分支——agent_settled 恒 arm idle timer → runAndFinalize 恒
87
+ // early return,旧消费点对 chatMode 不可达,标志置了无人消费、tool 谎报 closed:true)。
88
+ if (record.closeAfterRound) {
89
+ record.closeAfterRound = undefined;
90
+ void deps.closeAfterRoundSettled(record);
91
+ }
92
+ };
93
+ }
@@ -2,11 +2,19 @@
2
2
  //
3
3
  // 概率性清理过期 subagent session 文件(TTL 30 天)。
4
4
  // session_start 时调用,best-effort(失败不影响启动)。
5
+ //
6
+ // [D8 池引用计数接线] 同概率门内追加引擎池 TTL 兜底(cleanupExpiredPoolRefs):
7
+ // journal / refs.json 条目按同 30 天 mtime 回收。record 主数据(主 session 文件里
8
+ // 的 subagent-record entry)由 pi 侧管理,其删除对 core 无触发点——done record 的
9
+ // journal 没有精确回收锚,只能靠此兜底;workflow 域(taskId 占位无 record 生命周期
10
+ // 锚)同样依赖它(D8 分域口径「journal 依赖 30 天 TTL 自然回收」的落地)。
5
11
 
6
12
  import * as fs from "node:fs";
7
13
  import * as path from "node:path";
8
14
 
9
15
  import { isProcessAlive, readAliveMarker } from "./alive-store.ts";
16
+ import { getEngineDataDir } from "./engine/common/data-dir.ts";
17
+ import { cleanupExpiredPoolRefs } from "./engine/common/pool-manager.ts";
10
18
 
11
19
  /** 30 天 TTL(毫秒)。 */
12
20
  const TTL_DAYS = 30;
@@ -29,21 +37,105 @@ const SUBAGENTS_DIR = "subagents";
29
37
  * 1. 概率性触发(CLEANUP_PROBABILITY)
30
38
  * 2. 递归扫描 <agentDir>/subagents 下所有 .jsonl 文件
31
39
  * 3. mtime 超 TTL → unlink
40
+ * 4. [D8] 引擎池 TTL 兜底:journal / refs 条目超龄回收(见文件头注释)
32
41
  */
33
42
  export function maybeCleanupExpiredSessionFiles(agentDir: string, cwd: string): void {
34
43
  void cwd;
35
44
  try {
36
45
  if (Math.random() >= CLEANUP_PROBABILITY) return;
37
46
  const dir = path.join(agentDir, SUBAGENTS_DIR);
38
- if (!fs.existsSync(dir)) return;
39
- const now = Date.now();
40
- walkAndClean(dir, now);
47
+ if (fs.existsSync(dir)) {
48
+ walkAndClean(dir, Date.now());
49
+ }
50
+ cleanupEnginePoolsBestEffort();
41
51
  } catch (_e) {
42
52
  // best-effort:任何异常不阻断 session_start
43
53
  void _e;
44
54
  }
45
55
  }
46
56
 
57
+ /** 引擎池 TTL 兜底(dataDir 解析失败 = 未配置宿主服务,best-effort 吞掉)。 */
58
+ function cleanupEnginePoolsBestEffort(): void {
59
+ try {
60
+ cleanupExpiredPoolRefs(getEngineDataDir(), TTL_MS);
61
+ } catch (_e) {
62
+ void _e;
63
+ }
64
+ }
65
+
66
+ /** 孤儿 sidecar 名判定:.cancelled/.finalized/.alive,以及 [MF#2] patch 回传
67
+ * (<branch>.patch 按 branch 命名,与 .jsonl basename 无关联,删除 .jsonl 时无法同删;
68
+ * 作为孤儿按 TTL 清理,避免永久堆积)。 */
69
+ function isOrphanSidecarName(name: string): boolean {
70
+ return (
71
+ name.endsWith(".cancelled") ||
72
+ name.endsWith(".finalized") ||
73
+ name.endsWith(".alive") ||
74
+ name.endsWith(".patch")
75
+ );
76
+ }
77
+
78
+ /** stat 超 TTL 即 unlink(manifest .json 与孤儿 sidecar 共用原语)。失败静默(文件可能已被删除)。 */
79
+ function unlinkIfExpired(full: string, now: number): void {
80
+ try {
81
+ const stat = fs.statSync(full);
82
+ if (now - stat.mtimeMs > TTL_MS) {
83
+ fs.unlinkSync(full);
84
+ }
85
+ } catch (_e) {
86
+ // 文件可能已被删除,忽略
87
+ void _e;
88
+ }
89
+ }
90
+
91
+ /** .jsonl 超 TTL 清理:.alive 探活保护(活进程的 session 不删,D-024 安全网)+
92
+ * 同名 sidecar 一起清理。失败静默(文件可能已被删除)。 */
93
+ function cleanExpiredJsonl(full: string, now: number): void {
94
+ try {
95
+ const stat = fs.statSync(full);
96
+ if (now - stat.mtimeMs > TTL_MS) {
97
+ // .alive 探活:活进程的 session 不删(D-024 安全网)。
98
+ const aliveMarker = readAliveMarker(full);
99
+ if (aliveMarker !== undefined && isProcessAlive(aliveMarker.pid)) {
100
+ return; // 活进程 → 跳过,不清理
101
+ }
102
+ fs.unlinkSync(full);
103
+ // 同名 sidecar 一起清理。
104
+ for (const ext of [".cancelled", ".finalized", ".alive"]) {
105
+ try { fs.unlinkSync(`${full}${ext}`); } catch (_e) { void _e; /* sidecar 可能不存在 */ }
106
+ }
107
+ }
108
+ } catch (_e) {
109
+ // 文件可能已被删除,忽略
110
+ void _e;
111
+ }
112
+ }
113
+
114
+ /** 单条 dirent 分发。分支判断顺序与 fs 副作用时序保持与原实现一致:
115
+ * 目录递归 →(records 内)manifest .json → .jsonl → 孤儿 sidecar。 */
116
+ function cleanDirent(dir: string, entry: fs.Dirent, now: number, allowManifestJson: boolean): void {
117
+ const full = path.join(dir, entry.name);
118
+ if (entry.isDirectory()) {
119
+ // 只在进入名为 records 的子目录时打开 manifest .json 清理。
120
+ // records 在 <enc>/records/ 下递归自动覆盖;其他位置(如 subagents/worktrees.json)
121
+ // 不能匹配 .json——否则会误删 worktree reaper 依赖的状态文件。
122
+ walkAndClean(full, now, entry.name === "records");
123
+ } else if (
124
+ allowManifestJson &&
125
+ entry.name.endsWith(".json") &&
126
+ // 跳过 .tmp.:recoverTmpFiles(session_start)同步处理 tmp,GC 不重复。
127
+ // 不校验内容——30 天 mtime 已是强 orphan 信号,扩展名 + 文件名足够。
128
+ !entry.name.includes(".tmp.")
129
+ ) {
130
+ unlinkIfExpired(full, now);
131
+ } else if (entry.name.endsWith(".jsonl")) {
132
+ cleanExpiredJsonl(full, now);
133
+ } else if (isOrphanSidecarName(entry.name)) {
134
+ // 孤儿 sidecar(兄弟 .jsonl 已被外部删除):按同 TTL 清理。
135
+ unlinkIfExpired(full, now);
136
+ }
137
+ }
138
+
47
139
  /** 递归扫描目录,unlink 超 TTL 的 .jsonl 文件及其 .cancelled sidecar。
48
140
  * [F2] 进入名为 records 的子目录时,额外清理超 TTL 的 manifest .json(跳过 .tmp.——
49
141
  * recoverTmpFiles 同步处理)。allowManifestJson 仅由父调用按目录名开启,其他位置
@@ -57,64 +149,6 @@ function walkAndClean(dir: string, now: number, allowManifestJson = false): void
57
149
  return;
58
150
  }
59
151
  for (const entry of entries) {
60
- const full = path.join(dir, entry.name);
61
- if (entry.isDirectory()) {
62
- // 只在进入名为 records 的子目录时打开 manifest .json 清理。
63
- // records 在 <enc>/records/ 下递归自动覆盖;其他位置(如 subagents/worktrees.json)
64
- // 不能匹配 .json——否则会误删 worktree reaper 依赖的状态文件。
65
- walkAndClean(full, now, entry.name === "records");
66
- } else if (
67
- allowManifestJson &&
68
- entry.name.endsWith(".json") &&
69
- // 跳过 .tmp.:recoverTmpFiles(session_start)同步处理 tmp,GC 不重复。
70
- // 不校验内容——30 天 mtime 已是强 orphan 信号,扩展名 + 文件名足够。
71
- !entry.name.includes(".tmp.")
72
- ) {
73
- try {
74
- const stat = fs.statSync(full);
75
- if (now - stat.mtimeMs > TTL_MS) {
76
- fs.unlinkSync(full);
77
- }
78
- } catch (_e) {
79
- // 文件可能已被删除,忽略
80
- void _e;
81
- }
82
- } else if (entry.name.endsWith(".jsonl")) {
83
- try {
84
- const stat = fs.statSync(full);
85
- if (now - stat.mtimeMs > TTL_MS) {
86
- // .alive 探活:活进程的 session 不删(D-024 安全网)。
87
- const aliveMarker = readAliveMarker(full);
88
- if (aliveMarker !== undefined && isProcessAlive(aliveMarker.pid)) {
89
- continue; // 活进程 → 跳过,不清理
90
- }
91
- fs.unlinkSync(full);
92
- // 同名 sidecar 一起清理。
93
- for (const ext of [".cancelled", ".finalized", ".alive"]) {
94
- try { fs.unlinkSync(`${full}${ext}`); } catch (_e) { void _e; /* sidecar 可能不存在 */ }
95
- }
96
- }
97
- } catch (_e) {
98
- // 文件可能已被删除,忽略
99
- void _e;
100
- }
101
- } else if (
102
- entry.name.endsWith(".cancelled") ||
103
- entry.name.endsWith(".finalized") ||
104
- entry.name.endsWith(".alive") ||
105
- // [MF#2] patch 回传(<branch>.patch)按 branch 命名,与 .jsonl basename 无关联,
106
- // 删除 .jsonl 时无法同删;作为孤儿按 TTL 清理,避免永久堆积。
107
- entry.name.endsWith(".patch")
108
- ) {
109
- // 孤儿 sidecar(兄弟 .jsonl 已被外部删除):按同 TTL 清理。
110
- try {
111
- const stat = fs.statSync(full);
112
- if (now - stat.mtimeMs > TTL_MS) {
113
- fs.unlinkSync(full);
114
- }
115
- } catch (_e) {
116
- void _e;
117
- }
118
- }
152
+ cleanDirent(dir, entry, now, allowManifestJson);
119
153
  }
120
154
  }