@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
@@ -58,6 +58,57 @@ function formatMessage(name: string, errors: readonly unknown[]): string {
58
58
  );
59
59
  }
60
60
 
61
+ // ── validateRunArgs 阶段 helper(按 预处理/编译 提取,主函数只留编排) ────
62
+
63
+ /** schema.properties 提取(畸形/缺失 → {},null-scan 在空 properties 上安全退化)。 */
64
+ function extractSchemaProperties(schema: Record<string, unknown>): Record<string, unknown> {
65
+ return schema.properties !== null && typeof schema.properties === "object"
66
+ ? (schema.properties as Record<string, unknown>)
67
+ : {};
68
+ }
69
+
70
+ /** 单个 property 声明是否 nullable(type 含 "null" 或 type === "null")。 */
71
+ function isNullableProp(prop: unknown): boolean {
72
+ if (prop === null || typeof prop !== "object") return false;
73
+ const propType = (prop as Record<string, unknown>).type;
74
+ return Array.isArray(propType)
75
+ ? (propType as unknown[]).includes("null")
76
+ : propType === "null";
77
+ }
78
+
79
+ /**
80
+ * null-scan:null 值视为缺失(coerceTypes 会把 null→"" 放行 required,绕过 fail-fast)。
81
+ * 只删 schema 未声明 nullable 的键——type 含 "null"(如 ["string","null"])的合法 null
82
+ * 输入保留(m3 exec-review M1 探针实证:全键删除会拒掉 nullable required 的合法值)。
83
+ */
84
+ function deleteNonNullableNullArgs(
85
+ args: Record<string, unknown>,
86
+ schema: Record<string, unknown>,
87
+ ): void {
88
+ const properties = extractSchemaProperties(schema);
89
+ for (const key of Object.keys(args)) {
90
+ if (args[key] !== null) continue;
91
+ if (!isNullableProp(properties[key])) delete args[key];
92
+ }
93
+ }
94
+
95
+ /** ajv 编译(畸形 schema → 结构化 ArgsValidationError,不泄漏原始 throw)。 */
96
+ function compileSchema(
97
+ parameters: Record<string, unknown>,
98
+ scriptName: string,
99
+ ): ReturnType<Ajv["compile"]> {
100
+ try {
101
+ return ajv.compile(parameters);
102
+ } catch (err) {
103
+ // 真畸形 schema(如 type:'not-a-type')→ 结构化 ArgsValidationError,不泄漏原始 throw
104
+ const detail = err instanceof Error ? err.message : String(err);
105
+ throw new ArgsValidationError(
106
+ scriptName,
107
+ `Workflow '${scriptName}' has an invalid parameter schema: ${detail}. Read the workflow script file (location from <available_workflows>) to inspect it.`,
108
+ );
109
+ }
110
+ }
111
+
61
112
  /**
62
113
  * 校验 spec.parameters(JSON Schema draft-07)对 spec.args 的约束。
63
114
  *
@@ -79,38 +130,9 @@ export function validateRunArgs(spec: RunSpec): void {
79
130
  );
80
131
  }
81
132
 
82
- // null-scan:null 值视为缺失(coerceTypes 会把 null→"" 放行 required,绕过 fail-fast)。
83
- // 只删 schema 未声明 nullable 的键——type 含 "null"(如 ["string","null"])的合法 null
84
- // 输入保留(m3 exec-review M1 探针实证:全键删除会拒掉 nullable required 的合法值)。
85
- const schema = parameters as Record<string, unknown>;
86
- const properties =
87
- schema.properties !== null && typeof schema.properties === "object"
88
- ? (schema.properties as Record<string, unknown>)
89
- : {};
90
- for (const key of Object.keys(args)) {
91
- if (args[key] !== null) continue;
92
- const prop = properties[key];
93
- let isNullable = false;
94
- if (prop !== null && typeof prop === "object") {
95
- const propType = (prop as Record<string, unknown>).type;
96
- isNullable = Array.isArray(propType)
97
- ? (propType as unknown[]).includes("null")
98
- : propType === "null";
99
- }
100
- if (!isNullable) delete args[key];
101
- }
133
+ deleteNonNullableNullArgs(args, parameters);
102
134
 
103
- let validate: ReturnType<Ajv["compile"]>;
104
- try {
105
- validate = ajv.compile(parameters);
106
- } catch (err) {
107
- // 真畸形 schema(如 type:'not-a-type')→ 结构化 ArgsValidationError,不泄漏原始 throw
108
- const detail = err instanceof Error ? err.message : String(err);
109
- throw new ArgsValidationError(
110
- scriptName,
111
- `Workflow '${scriptName}' has an invalid parameter schema: ${detail}. Read the workflow script file (location from <available_workflows>) to inspect it.`,
112
- );
113
- }
135
+ const validate = compileSchema(parameters, scriptName);
114
136
 
115
137
  if (!validate(args)) {
116
138
  throw new ArgsValidationError(
@@ -8,16 +8,23 @@
8
8
  * 职责:
9
9
  * - 重试:3 次 + 指数退避(BACKOFF_MS = [1000, 2000, 4000])
10
10
  * - 预算:超限不重试(直接 markDone failed)
11
- * - stale-context:不重试(直接 markDone failed)
12
- * - [MF-1] 确定性 schema 失败:不重试(直接 markDone failed)
11
+ * - stale-context(result.failureKind="stale_context"):不重试(直接 markDone failed)
12
+ * - [MF-1] 确定性 schema 失败(result.failureKind="schema_deterministic"):不重试
13
+ * (直接 markDone failed)
13
14
  * - 成功:consume usage + incrementCallCount + markDone + trace.update(completed)
14
15
  *
16
+ * [D5-③ 结构化分诊] 失败分诊读 AgentResult.failureKind 字段(产出侧唯一识别点 =
17
+ * execution/engine/engines/pi/output-collector.ts 的 classifyFailureKind,词表归属
18
+ * 见其文件头)。本模块不再扫 error 文案子串——**语义守恒(r1 MF4)**:unknown
19
+ * (含字段缺省)= 可重试,保持收敛前的默认重试语义;仅 stale_context(不重试、
20
+ * 换参重发场景由调用方编排)与 schema_deterministic 维持特判。
21
+ *
15
22
  * 关键设计:
16
23
  * - **usage 透传**:result.usage 直接交给 budget.consume,加权由 Budget 内部的权重常量
17
- * 处理(见 budget.ts)。此函数不再做 usage 形状的改写。
24
+ * 处理(见 budget.ts)。此函数不再做 usage 形状的改写。
18
25
  * - **参数显式化**:runner 直接传入(而非 ctx.getRun(runId).pool),无 runId 查找 / pool 守卫。
19
26
  * - **stale-state 检查**:signal.aborted 时早返回。WorkflowRun 状态由调用方 lifecycle
20
- * 持有,executeAgentCall 只关心单次 call 生命周期。
27
+ * 持有,executeAgentCall 只关心单次 call 生命周期。
21
28
  *
22
29
  * 层归属:Engine。零 infra 依赖(runner 是 AgentRunner port,budget/trace/call 是 Engine 模型)。
23
30
  *
@@ -41,69 +48,6 @@ const BACKOFF_EXPONENT_BASE = 2;
41
48
  /** 最大尝试次数(含首次):initial + 2 retries = 3。 */
42
49
  const MAX_ATTEMPTS = 3;
43
50
 
44
- /**
45
- * Stale context 检测模式(P1-5;W4b 对齐 pi 0.84.x 真实文案)。
46
- *
47
- * pi session context 被 compact/cancel 时报告的模式。这种情况下重试无意义——
48
- * 同样的 call 会再次失败。直接 markDone failed 终止单次调用。
49
- *
50
- * W4b:原 "stale context"/"stalecontext" 与 pi 真实文案零匹配(真实文案为
51
- * "This extension ctx is stale after session replacement or reload. ..."——
52
- * runner.ts:544(dist runner.js:352),词序是 "ctx is stale" 而非 "stale context"),stale 分诊对
53
- * 真实文案失效。现对齐:
54
- * - "ctx is stale":真实文案核心子串(词序修正)
55
- * - "stale after session replacement":scheduler 已验证 marker(runtime.ts
56
- * STALE_CTX_MARKER,同文案锚定)
57
- * - "context canceled"/"aborted":保留——abort 族错误同样不重试(signal.aborted
58
- * 分支的先行分诊,防边界竞态漏网),删除会放宽重试语义。
59
- */
60
- export const STALE_CONTEXT_PATTERNS = [
61
- "ctx is stale",
62
- "stale after session replacement",
63
- "context canceled",
64
- "aborted",
65
- ] as const;
66
-
67
- /**
68
- * 判断错误信息是否表示 stale/canceled pi session context。
69
- * 命中时不重试——重试只会再次失败(P1-5)。
70
- */
71
- export function isStaleContextErrorMsg(msg: string | undefined): boolean {
72
- if (!msg) return false;
73
- const lower = msg.toLowerCase();
74
- return STALE_CONTEXT_PATTERNS.some((p) => lower.includes(p));
75
- }
76
-
77
- /**
78
- * [MF-1] 确定性 schema 失败标记(error 文本前缀,产出方 = output-collector 的
79
- * describeMissingParsedOutput)。
80
- *
81
- * 标记 SSOT 放本模块(与 STALE_CONTEXT_PATTERNS 同布局:orchestration 持表、
82
- * execution 值引用;反向引用会形成 execute-agent-call → output-collector →
83
- * execute-agent-call 运行时循环)。
84
- *
85
- * 标记词逐字核对不命中 STALE_CONTEXT_PATTERNS 任一 pattern 与
86
- * isStaleContextErrorMsg 的子串匹配(否则归因 error 被误诊 stale-context,
87
- * 虽然同样不重试但归因语义被污染;output-collector.test 有交叉锁定)。
88
- *
89
- * 三态可重试性矩阵(F-1 归因):
90
- * | 归因态 | 带本标记 | 可重试性 | 理由 |
91
- * |----------------------------|---------|---------|------|
92
- * | ① 从未调用 SO tool | 是 | 不可重试 | 缺 extension 是环境确定性(C1 安装盲区),同环境重试必同结果 |
93
- * | ② SO 调用 isError(gate 终止/不可满足 schema) | 是 | 不可重试 | 同 schema 重试必同结果(第五轮实测:3 attempts/4 子进程/235s 纯烧钱) |
94
- * | ③ 调用过但无 details | 否 | 可重试 | 可能瞬态(details 提取/序列化异常),保留既有重试语义 |
95
- */
96
- export const DETERMINISTIC_SCHEMA_FAILURE_PREFIX = "Structured output failed deterministically:";
97
-
98
- /**
99
- * [MF-1] 判断错误信息是否为确定性 schema 失败(命中标记前缀)。
100
- * 命中时不重试——同 schema 重试必同结果(矩阵见 DETERMINISTIC_SCHEMA_FAILURE_PREFIX)。
101
- */
102
- export function isDeterministicSchemaFailureMsg(msg: string | undefined): boolean {
103
- if (!msg) return false;
104
- return msg.includes(DETERMINISTIC_SCHEMA_FAILURE_PREFIX);
105
- }
106
-
107
51
  // ── 内部 helper ──────────────────────────────────────────────
108
52
 
109
53
  /**
@@ -124,7 +68,7 @@ function backoffDelay(retryIndex: number): number {
124
68
  * rebuild 竞态窗口中,重跑 dispatch 已 append 同 stepIndex 新节点,旧代际
125
69
  * finalize 的 update 会命中新节点,TUI/中间快照短暂可见错误终态。正确性论证:
126
70
  * 运行期 calls Map 写点仅 discardInFlightCalls 的 delete 与 dispatchAgentCall 的
127
- * set 两族(error-recovery.ts isOrphanedCall 文档注释既定),故实例不等 ⟺ 本
71
+ * set 两族(worker-message-pump.ts isOrphanedCall 文档注释既定),故实例不等 ⟺ 本
128
72
  * finalize 属于被丢弃/被替换的旧代际——与 dispatch 层 .then/.catch 守卫
129
73
  * (S7-second 修复,8353f6b60)同一判定语义,本守卫只是把它前移到 trace.update
130
74
  * 之前。markDone 与 sessionId/sessionFile 同步保留(markDone 在孤儿实例上无害,
@@ -203,44 +147,46 @@ export async function executeAgentCall(
203
147
 
204
148
  const result = await runner.run(call.opts, signal, onEvent, stream);
205
149
 
206
- // 累加 usage(加权由 budget.consume 内部按权重常量处理,见 budget.ts)
150
+ // 累加 usage(加权由 budget.consume 内部按权重常量处理,见 budget.ts)
207
151
  if (result.usage) {
208
152
  budget.consume(result.usage);
209
153
  }
210
154
 
211
- // stale-context:不重试(P1-5)
212
- if (result.error !== undefined && isStaleContextErrorMsg(result.error)) {
155
+ // stale-context(D5-③ 结构化分诊:读 failureKind 字段,词表识别在产出侧
156
+ // output-collector):不重试(P1-5)
157
+ if (result.error !== undefined && result.failureKind === "stale_context") {
213
158
  finalizeCall(call, result, trace, isOrphaned);
214
159
  budget.incrementCallCount();
215
160
  return;
216
161
  }
217
162
 
218
- // [MF-1] 确定性 schema 失败:不重试(gate 终止/不可满足 schema 同 schema 重试必同
219
- // 结果——重试纯烧钱;三态可重试性矩阵见 DETERMINISTIC_SCHEMA_FAILURE_PREFIX)
220
- if (result.error !== undefined && isDeterministicSchemaFailureMsg(result.error)) {
163
+ // [MF-1] 确定性 schema 失败(failureKind="schema_deterministic"):不重试
164
+ // (gate 终止/不可满足 schema 同 schema 重试必同结果——重试纯烧钱;三态矩阵
165
+ // 见产出侧 output-collector 的确定性失败标记注释)
166
+ if (result.error !== undefined && result.failureKind === "schema_deterministic") {
221
167
  finalizeCall(call, result, trace, isOrphaned);
222
168
  budget.incrementCallCount();
223
169
  return;
224
170
  }
225
171
 
226
- // signal 已 abort:调用方终止,不重试(避免无意义的递归)
172
+ // signal 已 abort:调用方终止,不重试(避免无意义的递归)
227
173
  if (signal.aborted) {
228
174
  finalizeCall(call, result, trace, isOrphaned);
229
175
  budget.incrementCallCount();
230
176
  return;
231
177
  }
232
178
 
233
- // 预算超限:不重试(重试只会突破预算且无意义)
179
+ // 预算超限:不重试(重试只会突破预算且无意义)
234
180
  if (result.error !== undefined && budget.isExceeded()) {
235
181
  finalizeCall(call, result, trace, isOrphaned);
236
182
  budget.incrementCallCount();
237
183
  return;
238
184
  }
239
185
 
240
- // 可重试失败:退避后递归
186
+ // 可重试失败:退避后递归
241
187
  if (result.error !== undefined && call.attempts < MAX_ATTEMPTS) {
242
188
  await delay(backoffDelay(call.attempts));
243
- // 退避期间 signal 可能 abort
189
+ // 退避期间 signal 可能 abort
244
190
  if (signal.aborted) {
245
191
  finalizeCall(call, result, trace, isOrphaned);
246
192
  budget.incrementCallCount();
@@ -250,7 +196,7 @@ export async function executeAgentCall(
250
196
  return;
251
197
  }
252
198
 
253
- // 终态(成功或达到重试上限的失败)
199
+ // 终态(成功或达到重试上限的失败)
254
200
  finalizeCall(call, result, trace, isOrphaned);
255
201
  budget.incrementCallCount();
256
202
  }
@@ -0,0 +1,327 @@
1
+ // src/orchestration/file-run-store.ts
2
+ //
3
+ // RunStore port 的通用文件实现(D2 设计件——zsw 回接 host-surface 单元)。
4
+ //
5
+ // 为什么需要它:pi 壳的 JsonlRunStore 深耦合 pi session(appendEntry /
6
+ // sessionManager,经 pi SDK 落盘 session JSONL),zcode 侧宿主没有这两个设施,
7
+ // 无法复用。RunStore port 早在 ports.ts 定义却只有 pi 一份 Infra 实现——本文件
8
+ // 补上「宿主无关」的第二份实现,双宿主的 workflow state 持久化从此同源(消灭
9
+ // 失败模式 B:行为不一致各自修)。
10
+ //
11
+ // 落盘布局:<dataRoot>/workflow-state/<runId>.jsonl(D2 规定,与 pi 壳
12
+ // <sessionDir>/workflow-state/<runId>.jsonl 同名分量、锚点不同:pi 锚 session,
13
+ // 本实现锚宿主数据根——zcode 宿主无 session dir 概念,daemon 重启后按 dataRoot
14
+ // 重水合孤儿 run)。
15
+ //
16
+ // dataRoot 通道选型:直接走 getHostServices().dataRoot()(core/host-services.ts),
17
+ // 不用 getEngineDataDir(engine/common/data-dir.ts)——后者是引擎 journal/隔离池
18
+ // 通道,带 XYZ_AGENT_DATA_DIR env 优先 + warn-once 语义(xyz-agent 宿主注入专用);
19
+ // workflow run 快照是宿主编排状态,语义归属宿主数据根本身,宿主 configureCore
20
+ // 注入什么就落什么,不引入第二条 env 覆盖链。
21
+
22
+ import { appendFile, mkdir, readdir, readFile, stat, unlink } from "node:fs/promises";
23
+ import { join } from "node:path";
24
+
25
+ import { getHostServices } from "../core/host-services.ts";
26
+ import { getLogger } from "../core/logger.ts";
27
+ import type { RunStore } from "./models/ports.ts";
28
+ import { WorkflowRun } from "./models/workflow-run.ts";
29
+ import { SNAPSHOT_VERSION, fromRunSnapshot, toRunSnapshot } from "./run-snapshot.ts";
30
+
31
+ const logger = getLogger("file-run-store");
32
+
33
+ /** run 状态目录名(<dataRoot> 下的固定分量)。 */
34
+ const STATE_DIR_NAME = "workflow-state";
35
+
36
+ // ── 磁盘保留(C1,语义对齐 pi jsonl-run-store mtime 裁剪) ─────────
37
+
38
+ /** run state 文件名 glob:runId 形如 `wf-<ts>-<rand>`(lifecycle.ts 生成),只删命中者。
39
+ * 同目录可能存在的非 state 文件永不碰(对齐 pi STATE_FILE_GLOB)。 */
40
+ const STATE_FILE_GLOB = /^wf-.*\.jsonl$/;
41
+
42
+ /**
43
+ * 磁盘保留默认上限(OR-5 跨 run 保留修复):envName 通道在 env 未设/空时生效。
44
+ *
45
+ * OR-5 将「STATE_MAX_RUNS opt-in 默认关」(无界累积)改为默认开:跨 run state
46
+ * 文件按 mtime 裁剪到本上限。取值 50 是无真实 run 体积分布数据下的保守值
47
+ * (设计 §11-4:标定待 S-A 验收后复核)——偏大不碍事(有界即达标),偏小会
48
+ * 误删仍被引用的 run 缓存,故取保守端。env 显式设置(有效正数)优先于本值;
49
+ * 显式非法值是 opt-out 通道(不清理,见 pruneStateFilesBeyondCap)。
50
+ */
51
+ export const DEFAULT_STATE_MAX_RUNS = 50;
52
+
53
+ // ── save 节流(OR-5 单 run 快照 O(n²) 主修) ──────────────────
54
+
55
+ /**
56
+ * 同一 run 两次快照落盘的最小间隔(ms)。OR-5 单 run O(n²) 主修参数:现状每
57
+ * 次 save 都 append 全量快照(快照体积 O(calls) × save 次数 O(calls)),节流后
58
+ * 落盘次数有界为 ceil(run 时长 / 本间隔)(§11-4 量级推演见 impl-plan 偏差登记:
59
+ * 100-call run 从 ~200 次落盘 / ~50MB 降到 ~17 次 / ~8MB,增量 append diff 需
60
+ * 改造两宿主共享 codec(基线+delta 行 + loadAll 重放 + 版本兼容),收益不抵
61
+ * 复杂度,节流即终案)。取值对齐 jsonl-run-store 去抖同款考量:agent-call 间隔
62
+ * 秒级,60s 窗口把快照次数压到与「分钟级 run 时长」同量级,又不让崩溃窗口
63
+ * (未落盘的 running 尾部丢失,等价崩溃链由恢复路径收编)超出分钟级。
64
+ */
65
+ export const DEFAULT_SAVE_MIN_INTERVAL_MS = 60_000;
66
+
67
+ /** FileRunStore 构造参数(全部可选;缺省即生产形态)。 */
68
+ export interface FileRunStoreOptions {
69
+ /**
70
+ * save 节流最小间隔(ms);0 = 禁用节流(每次 save 都落盘)。缺省
71
+ * {@link DEFAULT_SAVE_MIN_INTERVAL_MS}。测试经此注入小窗口(fake timers 推进)。
72
+ */
73
+ saveMinIntervalMs?: number;
74
+ }
75
+
76
+ /** Node fs 错误 code 判定(ENOENT = 路径不存在,并发删除场景;对齐 pi isEnoentError)。 */
77
+ function isEnoentError(err: unknown): boolean {
78
+ return typeof err === "object" && err !== null && "code" in err &&
79
+ (err as { code?: unknown }).code === "ENOENT";
80
+ }
81
+
82
+ // ── 快照形状 / 序列化 / 重水合 ────────────────────────────────
83
+ //
84
+ // 投影与版本衔接语义收敛于 ./run-snapshot.ts 单源 codec(下沉收口 D4/U8):
85
+ // 本 store 只保留 IO 策略(append-only + 从尾向头取最后有效行)。版本衔接的
86
+ // 宿主侧职责(D4 裁决②③,见 parseLine):「缺 v 宽容读」预处理与「版本不
87
+ // 匹配 warn 可见性」在此实现——不内聚进 codec,保 pi 侧「v1 存量静默跳过」
88
+ // 语义不被宽容化误读。
89
+
90
+ // ── FileRunStore ────────────────────────────────────────────
91
+
92
+ /**
93
+ * RunStore port 的宿主无关文件实现(port 见 models/ports.ts)。
94
+ *
95
+ * - save:append-only + 节流——快照行仍全量(崩溃时旧快照仍在,loadAll 取最后
96
+ * 一条有效行恢复到最后一致状态),但同一 running run 两次落盘有最小间隔
97
+ * (OR-5 ⑥a:节流前每次状态变更都 append 全量快照,快照体积 O(calls) ×
98
+ * save 次数 O(calls) = 单 run 磁盘 O(n²);节流参数与语义见 save 注释)。
99
+ * - loadAll:扫 <dataRoot>/workflow-state/*.jsonl,每文件从尾向头取第一条形状
100
+ * 有效的快照行;损坏行(JSON.parse 失败 / 形状校验不过 / 版本不匹配)跳过并
101
+ * warn——单行损坏不拖垮整个 run 的恢复(与 pi 壳 kill-9 恢复同容忍度)。
102
+ * 版本衔接(快照 codec 归 run-snapshot.ts 单源,D4):存量无 v 行按当前版本
103
+ * 宽容读、写入恒补 v、v 不匹配跳过 + warn(三裁决明细见 parseLine 注释)。
104
+ * - stateFilePath:纯路径计算(<dataRoot>/workflow-state/<runId>.jsonl),不建目录。
105
+ *
106
+ * 未 configureCore 即 save/loadAll 会抛 core_host_not_configured(dataRoot 端口
107
+ * 语义,host-services.ts §3.4)——宿主壳必须在初始化最早期注入。
108
+ */
109
+ export class FileRunStore implements RunStore {
110
+ /** run 状态目录绝对路径(dataRoot 每次现取——宿主覆盖配置即刻生效,对齐
111
+ * data-dir.ts「不缓存路径防测试/宿主切换读到旧值」先例)。 */
112
+ private stateDir(): string {
113
+ return join(getHostServices().dataRoot(), STATE_DIR_NAME);
114
+ }
115
+
116
+ /** save 节流最小间隔(ms),0 = 禁用。 */
117
+ private readonly saveMinIntervalMs: number;
118
+ /**
119
+ * per-runId 上次实际落盘时刻(节流判据)。终态落盘成功即删(终态后 runId 不再
120
+ * save);残留条目只出现在「running 中 run 消失(崩溃/宿主弃用)」场景,单条
121
+ * ~100B 可忽略(对齐 jsonl-run-store chains「每 runId 残留 settled Promise」
122
+ * 的取舍先例)。时间源 Date.now()(fake timers 下可推进,测试友好)。
123
+ */
124
+ private readonly lastSavedAt = new Map<string, number>();
125
+
126
+ constructor(opts?: FileRunStoreOptions) {
127
+ this.saveMinIntervalMs = Math.max(0, opts?.saveMinIntervalMs ?? DEFAULT_SAVE_MIN_INTERVAL_MS);
128
+ }
129
+
130
+ stateFilePath(runId: string): string {
131
+ return join(this.stateDir(), `${runId}.jsonl`);
132
+ }
133
+
134
+ /**
135
+ * 快照落盘(OR-5 ⑥a 节流后):
136
+ * - 首写(该 runId 尚无落盘记录)永不节流——保证新 run 至少一条快照,
137
+ * loadAll 重水合可发现;
138
+ * - 终态(status 非 running)永不节流——最终状态必落盘,末行即终态快照;
139
+ * - running 中间态距上次落盘不足 {@link saveMinIntervalMs} → 跳过本次 append
140
+ * (状态仍在调用方内存 runs Map,下次落盘带全量最新快照;本文件最后一条
141
+ * 快照因此最多落后真实状态一个节流窗口——崩溃语义与 jsonl-run-store 去抖
142
+ * 同源:未落盘的 running 尾部丢失,等价崩溃链由恢复路径收编)。
143
+ *
144
+ * 节流判据在落盘成功后才更新(IO 失败不吞下一次重试机会)。
145
+ */
146
+ async save(run: WorkflowRun): Promise<void> {
147
+ const isTerminal = run.state.status !== "running";
148
+ const now = Date.now();
149
+ const last = this.lastSavedAt.get(run.runId);
150
+ if (!isTerminal && last !== undefined && now - last < this.saveMinIntervalMs) {
151
+ return; // 节流窗口内:跳过本次全量快照 append
152
+ }
153
+ // mkdir recursive 每次 save 前执行:幂等零成本(目录已存在时仅一次 stat),
154
+ // 且免「构造时预建」——构造时建会在宿主尚未 configureCore 的窗口抛错。
155
+ await mkdir(this.stateDir(), { recursive: true });
156
+ // toRunSnapshot 补 v 字段(D4 裁决②写入侧)+ strip live 落盘
157
+ const line = JSON.stringify(toRunSnapshot(run));
158
+ await appendFile(this.stateFilePath(run.runId), line + "\n", "utf8");
159
+ if (isTerminal) {
160
+ this.lastSavedAt.delete(run.runId);
161
+ } else {
162
+ this.lastSavedAt.set(run.runId, now);
163
+ }
164
+ }
165
+
166
+ async loadAll(): Promise<WorkflowRun[]> {
167
+ let files: string[];
168
+ try {
169
+ files = await readdir(this.stateDir());
170
+ } catch {
171
+ // 目录不存在 = 从未持久化过(首启/干净环境),空集是正常态不是错误。
172
+ return [];
173
+ }
174
+
175
+ const runs: WorkflowRun[] = [];
176
+ for (const file of files) {
177
+ if (!file.endsWith(".jsonl")) continue;
178
+ const run = await this.loadLatestValidLine(join(this.stateDir(), file), file);
179
+ if (run) runs.push(run);
180
+ }
181
+ return runs;
182
+ }
183
+
184
+ /** 单文件从尾向头取第一条有效快照行;整文件无有效行返回 undefined(warn)。 */
185
+ private async loadLatestValidLine(absPath: string, display: string): Promise<WorkflowRun | undefined> {
186
+ let content: string;
187
+ try {
188
+ content = await readFile(absPath, "utf8");
189
+ } catch (err) {
190
+ const msg = err instanceof Error ? err.message : String(err);
191
+ logger.warn(`[file-run-store] skip unreadable state file ${display}: ${msg}`);
192
+ return undefined;
193
+ }
194
+
195
+ const lines = content.split("\n");
196
+ for (let i = lines.length - 1; i >= 0; i--) {
197
+ const line = lines[i].trim();
198
+ if (line === "") continue; // 尾部空行(末行 \n 产物)静默跳过
199
+ const run = this.parseLine(line, display, i);
200
+ if (run) return run;
201
+ // 损坏行 warn 后继续向前找——最后一条「有效」行可能早于文件尾部(半行写入崩溃)
202
+ }
203
+ logger.warn(`[file-run-store] no valid snapshot line in ${display} (empty or all corrupted)`);
204
+ return undefined;
205
+ }
206
+
207
+ /**
208
+ * 单行解析 + 版本衔接预处理(D4 裁决②③,宿主侧职责)+ 形状校验;损坏
209
+ * warn 并返回 undefined。
210
+ *
211
+ * - 缺 v 字段(core 存量行)→ 就地补当前版本再进 codec(「缺版本 = 当前
212
+ * 版本」宽容读,不做自动迁移——写回时经 toRunSnapshot 自然补 v 完成渐进
213
+ * 收敛);预处理留在 store 层而非 codec,保 pi 侧「v1 存量静默跳过」语义
214
+ * 不被宽容化误读(D4 裁决②归属裁决)。
215
+ * - v 存在但不匹配(未知更高版本/降级写入)→ 跳过 + warn(补可见性,对齐
216
+ * pi 静默跳过语义;字符串版本无大小序,不引入比较逻辑——D4 裁决③)。
217
+ * 此处版本判断仅为 warn 可见性,数据防线仍是 codec 内 guard(双保险,
218
+ * pi 切换 codec 后共享同一防线)。
219
+ */
220
+ private parseLine(line: string, display: string, lineNo: number): WorkflowRun | undefined {
221
+ let parsed: unknown;
222
+ try {
223
+ parsed = JSON.parse(line);
224
+ } catch (err) {
225
+ const msg = err instanceof Error ? err.message : String(err);
226
+ logger.warn(`[file-run-store] skip corrupted line ${display}:${lineNo}: ${msg}`);
227
+ return undefined;
228
+ }
229
+ if (parsed !== null && typeof parsed === "object") {
230
+ const rec = parsed as { v?: unknown };
231
+ if (rec.v === undefined) {
232
+ rec.v = SNAPSHOT_VERSION;
233
+ } else if (rec.v !== SNAPSHOT_VERSION) {
234
+ logger.warn(
235
+ `[file-run-store] skip snapshot with unsupported version ${display}:${lineNo}: v=${JSON.stringify(rec.v)} (this build only reads v=${JSON.stringify(SNAPSHOT_VERSION)}; the run line is skipped). To recover: upgrade @zhushanwen/subagent-core, or migrate/delete this state file if its runs are no longer needed`,
236
+ );
237
+ return undefined;
238
+ }
239
+ }
240
+ const run = fromRunSnapshot(parsed);
241
+ if (run === undefined) {
242
+ logger.warn(`[file-run-store] skip malformed snapshot ${display}:${lineNo} (shape validation failed)`);
243
+ return undefined;
244
+ }
245
+ return run;
246
+ }
247
+
248
+ /**
249
+ * 把 workflow-state 目录裁剪到上限个最新 state 文件(mtime 升序删最旧,C1)。
250
+ *
251
+ * 语义对齐 pi jsonl-run-store.pruneStateFilesBeyondCap(逐段同构):
252
+ * - 只删本目录内命中 {@link STATE_FILE_GLOB} 的文件;任何失败都不抛(清理是
253
+ * 旁路维护,不能拖垮持久化主链路):readdir 失败静默放弃本轮(ENOENT =
254
+ * 从未持久化,正常态),单个 unlink 失败(非 ENOENT)warn 留证后继续删
255
+ * 其余——ENOENT 视为并发删除竞态下的已达成目标,不告警;
256
+ * - stat 全集取 mtime,allSettled 部分降级——单文件 stat 失败(并发删除
257
+ * ENOENT 等)静默跳过该文件,不阻断本轮裁剪。
258
+ *
259
+ * 上限解析(envName 通道,OR-5 ⑥b 默认开;显式非法值 opt-out 对齐 pi 解析风格):
260
+ * - `envName` 提供 → env 通道:`process.env[envName]` 未设/空 → 按默认上限
261
+ * {@link DEFAULT_STATE_MAX_RUNS} 裁剪(**默认开**——OR-5 修复前的 opt-in
262
+ * 「默认关」正是跨 run 无界累积缺陷本身);设了有限正数 → 上限 = env 值
263
+ * (env 值即上限);设了非法值(非有限数/≤0)→ 不清理(显式 opt-out 通道:
264
+ * 用户意图不明时不动磁盘——对齐本方法 readdir/stat 失败一律放弃的保守哲学,
265
+ * 宿主如需自管保留可设足够大的正数值);
266
+ * - `envName` 缺省 → 无 env 通道,直接按 `max` 参数裁剪(上限 = max,调用方
267
+ * 自管启用时机)。
268
+ *
269
+ * 本方法只做磁盘裁剪,不动内存 runs Map(内存侧淘汰归
270
+ * lifecycle.evictDoneRunsBeyondCap,两域独立)。
271
+ *
272
+ * @param max 上限(envName 缺省时生效;env 通道启用时被 env 值覆盖)
273
+ * @param envName opt-in 开关 + 上限覆盖 env 变量名(可选;pi 先例
274
+ * `XYZ_SUBAGENT_STATE_MAX_RUNS`)
275
+ */
276
+ async pruneStateFilesBeyondCap(max: number, envName?: string): Promise<void> {
277
+ let cap = max;
278
+ if (envName !== undefined) {
279
+ // 未设/空 → 默认开(OR-5 ⑥b:DEFAULT_STATE_MAX_RUNS);非法/≤0 → 不清理
280
+ // (显式 opt-out 通道,见方法注释);有效正数 → env 值覆盖
281
+ const raw = process.env[envName];
282
+ if (raw === undefined || raw === "") {
283
+ cap = DEFAULT_STATE_MAX_RUNS;
284
+ } else {
285
+ const parsed = Number(raw);
286
+ if (!Number.isFinite(parsed) || parsed <= 0) return;
287
+ cap = parsed;
288
+ }
289
+ }
290
+
291
+ const stateDir = this.stateDir();
292
+ let names: string[];
293
+ try {
294
+ names = await readdir(stateDir);
295
+ } catch (err) {
296
+ if (!isEnoentError(err)) {
297
+ const reason = err instanceof Error ? err.message : String(err);
298
+ logger.warn(`[file-run-store] state retention: readdir ${stateDir} failed: ${reason}`);
299
+ }
300
+ return;
301
+ }
302
+ const stateFiles = names.filter((n) => STATE_FILE_GLOB.test(n)).sort();
303
+ if (stateFiles.length <= cap) return;
304
+
305
+ // stat 全集取 mtime;allSettled 部分降级(单文件失败静默跳过,不阻断本轮)
306
+ const settled = await Promise.allSettled(
307
+ stateFiles.map(async (name) => {
308
+ const full = join(stateDir, name);
309
+ return { full, mtimeMs: (await stat(full)).mtimeMs };
310
+ }),
311
+ );
312
+ const byMtimeAsc = settled
313
+ .flatMap((r) => (r.status === "fulfilled" ? [r.value] : []))
314
+ .sort((a, b) => a.mtimeMs - b.mtimeMs);
315
+ const victims = byMtimeAsc.slice(0, byMtimeAsc.length - cap);
316
+ for (const victim of victims) {
317
+ try {
318
+ await unlink(victim.full);
319
+ logger.debug(`[file-run-store] state retention: pruned ${victim.full}`);
320
+ } catch (err) {
321
+ if (isEnoentError(err)) continue; // 并发删除已达成目标
322
+ const reason = err instanceof Error ? err.message : String(err);
323
+ logger.warn(`[file-run-store] state retention: failed to delete ${victim.full}: ${reason}`);
324
+ }
325
+ }
326
+ }
327
+ }