@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
@@ -0,0 +1,88 @@
1
+ // src/execution/agents-assembly.ts
2
+ //
3
+ // agent 装配函数(sink 设计 U2 / A6):发现 → parseAgentProfile → frontmatter name
4
+ // 去重 → 码点序,warn 口径内聚。workflow 侧 discoverWorkflows(config-loader)的
5
+ // 对称面——第三宿主仅凭该函数即可「列 agents」(G3/S5),无需复刻装配循环。
6
+ //
7
+ // 与 pi 壳装配循环(subagent-list-injector.discoverAllAgents)的等值口径:
8
+ // - 清单可见性 = IF1 严格层(profile.meta !== null)——缺 name/description 的资产
9
+ // 不进清单(注入投影「路由可见性」双轨语义,D3 定稿;宽容解析只服务执行消费面);
10
+ // - warn 口径内聚:仅「有 frontmatter 但严格校验未通过」才 warn——README 等无
11
+ // frontmatter 的 .md 静默跳过(每 turn 扫描不刷屏,pi m5 评审先例);
12
+ // - 扫描 / 遮蔽语义归 shared/resource-discovery(ADR-031),本文件零扫描逻辑。
13
+
14
+ import type { AgentEntry } from "../shared/injection-render.ts";
15
+ import { sortByCodepoint } from "../shared/injection-render.ts";
16
+ import { discoverResources, getCachedFileContent } from "../shared/resource-discovery.ts";
17
+ import type { DiscoveryRoot } from "../core/host-services.ts";
18
+ import { getLogger } from "../core/logger.ts";
19
+ import { parseAgentProfile } from "./agent-registry.ts";
20
+
21
+ const logger = getLogger("agents-assembly");
22
+
23
+ /**
24
+ * 发现并列出全部可用 agent(U2/A6 装配函数)。
25
+ *
26
+ * 流程:discoverResources(按优先级低→高,last-writer-wins)→ 逐文件
27
+ * parseAgentProfile → 仅 IF1 严格层通过(profile.meta !== null)的条目进清单 →
28
+ * 按 frontmatter name 去重(高优先级靠后覆盖,Map 后写胜)→ name 码点序输出
29
+ * (KV-cache 契约:顺序与 readdir 枚举序解耦,重建结果逐字节一致)。
30
+ *
31
+ * 抛错面(如实声明):单文件读失败仅记日志跳过;目录不存在返回空列表;但底层
32
+ * discoverResources 的不可恢复扫描错误会原样向上传播(readdir 遇权限拒绝、
33
+ * EMFILE 竞态等——resource-discovery 自述 "Throws on unrecoverable scan errors",
34
+ * Promise.all 首个 reject 即整体拒绝),调用方需自行兜底。
35
+ *
36
+ * @param workspaceRoot 项目根(findWorkspaceRoot 推导结果)
37
+ * @param hostRoots 宿主注入发现根(pi 壳 = getAgentDir 三根;无则传 []——
38
+ * user-agents/project-agents 硬编码槽仍生效,与 ScanConfig 语义一致)。
39
+ * 注意:hostRoots 之外还有四个硬编码根恒进入扫描,无法经参数关闭——
40
+ * `~/.agents/agents`、`<workspaceRoot>/.pi/agents`、
41
+ * `<workspaceRoot>/.agents/agents` 与 `XYZ_EXTENSION_PATHS`
42
+ * 环境变量展开的扩展源码路径(resource-discovery buildScanTargets
43
+ * 固定槽位,注入 hostRoots 只是增列而非替换扫描面)
44
+ */
45
+ export async function discoverAgents(
46
+ workspaceRoot: string,
47
+ hostRoots: DiscoveryRoot[],
48
+ ): Promise<AgentEntry[]> {
49
+ const resources = await discoverResources({
50
+ kind: "agents",
51
+ workspaceRoot,
52
+ hostRoots,
53
+ });
54
+
55
+ const agentMap = new Map<string, AgentEntry>();
56
+ for (const resource of resources) {
57
+ // manifest 校验失败的包整体占位(available=false),不进清单
58
+ if (!resource.available) continue;
59
+
60
+ const content = getCachedFileContent(resource.path);
61
+ if (content === null) {
62
+ // 单文件读失败不阻断整条 agent 列表装配
63
+ logger.error(`[agents-assembly] skip unreadable agent file ${resource.path}`);
64
+ continue;
65
+ }
66
+
67
+ // 宽容解析(执行消费面单点);清单可见性按严格层 meta 判定
68
+ const profile = parseAgentProfile(content, resource.path);
69
+ if (profile.meta !== null) {
70
+ agentMap.set(profile.name, {
71
+ name: profile.name,
72
+ description: profile.description,
73
+ ...(profile.when !== undefined ? { when: profile.when } : {}),
74
+ ...(profile.examples !== undefined ? { examples: profile.examples } : {}),
75
+ path: resource.path,
76
+ });
77
+ } else if (content.trimStart().startsWith("---")) {
78
+ // 仅「有 frontmatter 但严格校验未通过」才 warn——无 frontmatter 的 README 等
79
+ // 普通 .md 静默跳过(pi m5 评审先例:缺 name/description 或单条 examples 非法
80
+ // 致整体 reject 时作者需要知道,普通文档不需要)。
81
+ logger.warn(
82
+ `[agents-assembly] ${resource.path}: agent frontmatter 解析失败(IF1 校验不通过)——agent 未进清单`,
83
+ );
84
+ }
85
+ }
86
+
87
+ return sortByCodepoint([...agentMap.values()], (a) => a.name);
88
+ }
@@ -0,0 +1,199 @@
1
+ // [D4-③ 冷路径复活职责轴] message action 冷查/复活链(原 SubagentService 私有的
2
+ // findColdLookupCandidate / assertReconnectAllowed / resurrectColdRecord /
3
+ // coldLookupForAction + isReconnectableClosed 判定)整体搬移至 record-store 邻接处
4
+ // (行为逐字节等价:搬移 + 依赖注入,不重写逻辑)。变化轴:改跨重启重建 /
5
+ // 透明重生回边 / 可重连守卫语义,只改本文件;Service 的 getRecordForAction 保留
6
+ // 归属校验编排(内存未命中分支委托 coldLookupForAction)。
7
+
8
+ import * as fs from "node:fs";
9
+
10
+ import { findForeignLiveInstance, writeAliveMarker } from "./alive-store.ts";
11
+ import { createRecord, resurrectClosed } from "./execution-record.ts";
12
+ import type { StatusFilter } from "./record-store.ts";
13
+ import type { ExecutionRecord, SubagentRecord } from "./types.ts";
14
+ import { isReconnectableFinalReason, ResurrectDeniedError } from "./types.ts";
15
+
16
+ /** SP-2 冷路径按 id 查 record 的 collectRecords 扫描上限(全扫兜底的容量 cap)。
17
+ * 原定义于 subagent-service.ts,随冷查链搬移;Service.lookupRecordAnyState
18
+ * (全态查询)同样消费本常量。 */
19
+ export const COLD_LOOKUP_SCAN_LIMIT = 1000;
20
+
21
+ /** 冷查/复活的依赖注入(Service 侧供给,store 查询 + 归属上下文)。 */
22
+ export interface ColdResurrectDeps {
23
+ /** store 的 idToFile 索引直查(light 快照)。 */
24
+ findLightById: (id: string) => SubagentRecord | undefined;
25
+ /** store 的磁盘全扫(内存未命中 / 索引未热时兜底)。rootFilter 恒 undefined
26
+ * (冷查不做 root 过滤——归属校验在候选定位之后,见 coldLookupForAction)。 */
27
+ collectRecords: (limit: number, statusFilter: StatusFilter, rootFilter: undefined) => SubagentRecord[];
28
+ /** 重建 record 注册进内存 store。 */
29
+ register: (record: ExecutionRecord) => void;
30
+ /** 重建后的 transition entry 上报(live ≡ reload 等价性)。 */
31
+ reportRecordTransition: (record: ExecutionRecord) => void;
32
+ /** 当前所属根 session id(归属校验用,运行时可变——initSession 建立)。 */
33
+ getSessionRootId: () => string | null;
34
+ /** 本进程嵌套基线 recordId(直接父校验用;根进程 undefined)。 */
35
+ getBaselineRecordId: () => string | undefined;
36
+ }
37
+
38
+ /** 冷查候选定位(coldLookupForAction 步骤 1):idToFile 索引直查 running 命中,
39
+ * 未命中再全目录 collectRecords 兜底(running,或 allowReconnect 且可重连 closed)。
40
+ *
41
+ * [T5③ / PS-7b] running 候选异进程活实例守卫:冷查 running 候选(跨重启 / 内存重建)
42
+ * 此前不经任何探针直接 resurrect + resume spawn——若其 .alive marker 仍指向活着的
43
+ * 异进程实例(父进程重启后旧子进程尚存的窗口),resume 会 spawn 第二个 pi 子进程
44
+ * 写同一 session JSONL(本代码最忌惮的双写者形态,v4 A-5/P7 事故模式)。closed 候选
45
+ * 的同款守卫已在 assertReconnectAllowed(v8.5 D);本守卫闭合 running 候选的防御
46
+ * 不对称。marker 的 pid 是子进程 pi 的 pid(非父进程),本进程持有的 running record
47
+ * 恒在内存(archive 才移出),可达本冷查分支的 running 候选必然来自磁盘重建——
48
+ * 探针命中即拒绝(ResurrectDeniedError,与 closed 候选守卫同异常类型,错误含 pid
49
+ * 与恢复指引)。 */
50
+ function findColdLookupCandidate(
51
+ deps: ColdResurrectDeps,
52
+ id: string,
53
+ allowReconnect: boolean,
54
+ ): SubagentRecord | undefined {
55
+ const direct = deps.findLightById(id);
56
+ const found =
57
+ (direct?.status === "running" ? direct : undefined) ??
58
+ deps
59
+ .collectRecords(COLD_LOOKUP_SCAN_LIMIT, "all", undefined)
60
+ .find((r) => r.id === id && (r.status === "running" || (allowReconnect && isReconnectableClosed(r))));
61
+ if (found?.status === "running" && found.sessionFile) {
62
+ const foreign = findForeignLiveInstance(found.sessionFile);
63
+ if (foreign) {
64
+ throw new ResurrectDeniedError(
65
+ `subagent ${id} is currently running in another process instance (pid ${foreign.pid}, ` +
66
+ `startedAt=${new Date(foreign.startedAt).toISOString()}); resuming here would double-write ${found.sessionFile}. ` +
67
+ `Recovery: retry once that process exits; if it never exits, action:'close' this subagent, then action:'start' a fresh one.`,
68
+ );
69
+ }
70
+ }
71
+ return found;
72
+ }
73
+
74
+ /** [v8.5 D] 冷查候选过滤:closed 且死因落在可重连集。判定源 = closedReason(buildRecord
75
+ * 归一化后的对外字段:A 档真实死因直通、旧空 sidecar 兑底 disconnected——SubagentRecord
76
+ * 不暴露 raw finalizedReason);cancelled/user-close/gc 等主动关闭与自然完成死因天然不在集合内。
77
+ * 防线在集合本身而非调用点。 */
78
+ function isReconnectableClosed(r: SubagentRecord): boolean {
79
+ return isReconnectableFinalReason(r.closedReason);
80
+ }
81
+
82
+ /** 可重连守卫(coldLookupForAction 步骤 2,[v8.5 D]):先于任何状态突变与注册。
83
+ * worktree 绑定丢失 / 异进程活实例以 ResurrectDeniedError 抛出(endedMessageGuard
84
+ * 必须原样透传,不得改写为 fork-from 指引误导 agent 走已被判死的通道);拒绝时
85
+ * 内存不得残留该记录(findRecord 契约)。 */
86
+ function assertReconnectAllowed(found: SubagentRecord, id: string): void {
87
+ if (found.status !== "closed") return;
88
+ if (found.worktree === true) {
89
+ throw new ResurrectDeniedError(
90
+ `subagent ${id} cannot be transparently resumed: it was created with worktree isolation, ` +
91
+ `and its worktree checkout no longer exists after restart (resuming in place would make spawn cwd fall back to the main repo). ` +
92
+ `Recovery: action:'start' a fresh subagent (with a new worktree if isolation is still needed); ` +
93
+ `its conversation history remains intact at ${found.sessionFile}.`,
94
+ );
95
+ }
96
+ const foreign = found.sessionFile ? findForeignLiveInstance(found.sessionFile) : undefined;
97
+ if (foreign) {
98
+ throw new ResurrectDeniedError(
99
+ `subagent ${id} is not transparently resumable: its previous instance still finishing in another process ` +
100
+ `(pid ${foreign.pid}, startedAt=${new Date(foreign.startedAt).toISOString()}). ` +
101
+ `Resuming in place would double-write ${found.sessionFile}. ` +
102
+ `Recovery: retry once that process exits; if it never exits, action:'start' a fresh subagent and treat the history at ${found.sessionFile} as read-only reference.`,
103
+ );
104
+ }
105
+ }
106
+
107
+ /** 磁盘候选重建为可变 record 并 register + 上报(coldLookupForAction 步骤 4)。 */
108
+ function resurrectColdRecord(
109
+ deps: ColdResurrectDeps,
110
+ found: SubagentRecord,
111
+ id: string,
112
+ ): ExecutionRecord {
113
+ const record = createRecord(id, {
114
+ agent: found.agent,
115
+ model: found.model,
116
+ thinkingLevel: found.thinkingLevel,
117
+ mode: found.mode,
118
+ task: found.task,
119
+ slug: found.slug,
120
+ startedAt: found.startedAt,
121
+ rootSessionId: found.rootSessionId,
122
+ parentRecordId: found.parentRecordId,
123
+ depth: found.depth,
124
+ // [v4 A-3] 跨重启恢复入口——message 路径磁盘重建无条件置 chatMode=true(现状机制,
125
+ // V3 方案 A 方向兑现)。改动此处必须带 S3 回归场景(跨重启 message 续聊验证)。
126
+ // V3 SP-5 探针定案:机制已存在,本注释即定案,不再悬置。
127
+ chatMode: true,
128
+ controller: new AbortController(),
129
+ });
130
+ record.sessionFile = found.sessionFile;
131
+ record.round = found.round;
132
+ // [review round2] 跨重启 worktree 绑定丢失防护:原 record 创建时启用了 worktree 隔离
133
+ //(session entry 的 worktree 标志),但 WorktreeHandle 不可序列化、重建后恒缺失。
134
+ // 标记 hadWorktree,冷路径续轮守卫据此拒绝续聊(防 spawn cwd 静默回落主 repo 破坏
135
+ // 隔离——正是 worktree 要防的并发写冲突场景)。close 不受影响(closeChatIdle 走
136
+ // doFinalizeRecord,泄漏的 worktree 由 reaper 兜底回收)。
137
+ record.hadWorktree = found.worktree === true;
138
+ // [v8.5 D] 透明重生回边:独立函数不走 tryTransition 单向语义(closed 单向性对正常
139
+ // 执行流完整保留);准入唯一依据 = A 档 sidecar 真实死因 ∈ 可重连集。死亡语义位由
140
+ // resurrectClosed 清除;register 后立刻上报 transition entry,live/reload 视图同步
141
+ // 翻回 running(等价性由 applyEntry reducer 保证,对齐 SP-2 重建即报告先例)。
142
+ const wasClosed = found.status !== "running";
143
+ if (wasClosed) {
144
+ // [review MF-8] 磁盘终态位同步翻转:record-store buildRecord 分支 2(.finalized
145
+ // 存在 → closed)优先级高于 .alive 活态分支 3,重生若不删 sidecar,任何磁盘扫描
146
+ // (异进程 / reload / session-reader)都会把本进程内存里 running 的 record 报成
147
+ // closed/disconnected——破坏 live ≡ reload,且为跨进程二次 resurrect 开门。
148
+ // best-effort 对齐 BC-4 语义;.alive 刷新为当前进程(后续 resume spawn 会覆盖写)。
149
+ if (record.sessionFile) {
150
+ try {
151
+ fs.rmSync(`${record.sessionFile}.finalized`, { force: true });
152
+ writeAliveMarker(record.sessionFile, { pid: process.pid, id, startedAt: Date.now() });
153
+ } catch (_e) {
154
+ void _e; // best-effort:sidecar 翻转失败不阻断重生主流程
155
+ }
156
+ }
157
+ resurrectClosed(record);
158
+ }
159
+ deps.register(record);
160
+ if (wasClosed) {
161
+ deps.reportRecordTransition(record);
162
+ }
163
+ return record;
164
+ }
165
+
166
+ /** [D4-③] 冷查编排(原 Service.coldLookupForAction):getRecordForAction 内存未命中
167
+ * 分支——候选定位 → 可重连守卫 → 归属/直接父校验 → 重建注册。
168
+ * @returns 重建的 record;磁盘也无则 undefined
169
+ * @throws ResurrectDeniedError 可重连候选被 worktree/异进程活实例守卫拦截
170
+ * @throws Error parentRecordId 跨层不匹配(direct parent 错误,与外层校验同文案) */
171
+ export function coldLookupForAction(
172
+ deps: ColdResurrectDeps,
173
+ id: string,
174
+ allowReconnect: boolean,
175
+ ): ExecutionRecord | undefined {
176
+ const found = findColdLookupCandidate(deps, id, allowReconnect);
177
+ if (!found) return undefined;
178
+ // [v8.5 D] 可重连候选的守卫先于任何状态突变与注册(细节见 assertReconnectAllowed)
179
+ assertReconnectAllowed(found, id);
180
+ // [review MF-9] 归属校验先于任何持久化副作用:coldLookup 是 getRecordForAction 的
181
+ // 内存未命中分支,若先 resurrect/register/report 再由调用方抛归属错误,会在磁盘/
182
+ // 内存留下幽灵 running record + running transition entry(跨进程双 resurrect 窗口)。
183
+ // rootSessionId 不匹配 → 返回 undefined,由 getRecordForAction 抛统一「not found or
184
+ // not owned」(不区分失败形态,防跨 session 探测);parentRecordId 跨层不匹配 →
185
+ // 原样抛 direct parent 错误(与外层校验同文案,保留跨层导航指引)。
186
+ if (found.rootSessionId !== deps.getSessionRootId()) {
187
+ return undefined;
188
+ }
189
+ const baselineRecordId = deps.getBaselineRecordId();
190
+ if (found.parentRecordId !== baselineRecordId) {
191
+ throw new Error(
192
+ `subagent ${id} is owned by its direct parent; message it through that parent ` +
193
+ `(see /subagents list, parent=${found.parentRecordId ?? "(root layer)"}). [v4 A-5] cross-layer ` +
194
+ `ownership guard: this process's baseline=${baselineRecordId ?? "(root)"} is not the direct parent of ${id}; ` +
195
+ `operating here would race the owning child process's handle and double-write the session file.`,
196
+ );
197
+ }
198
+ return resurrectColdRecord(deps, found, id);
199
+ }
@@ -14,9 +14,25 @@ interface QueueEntry {
14
14
  signal?: AbortSignal;
15
15
  }
16
16
 
17
+ /**
18
+ * 排队策略(D7/U4):release 时从等待队列放行哪个条目。策略差异是宿主声明的
19
+ * 有意决策(pi=priority / zsw=strict-fifo),保留为参数而非消灭(见 sink 设计
20
+ * subagent-core-sink-design.md §3.3 D7)。
21
+ *
22
+ * - `"priority"`(缺省):priority 值最小(0=最高)者优先,同优先级按入队序(FIFO)。
23
+ * 与 pi 既有行为等值。
24
+ * - `"strict-fifo"`:忽略 priority,严格按入队顺序(seq)放行。纯 FIFO 语义,
25
+ * 供 zsw 侧等需要「先到先得、不许插队」语义的宿主消费。
26
+ */
27
+ export type QueuePolicy = "priority" | "strict-fifo";
28
+
17
29
  /** 并发池接口(可注入,便于测试 mock)。 */
18
30
  export interface ConcurrencyPool {
19
- /** 按优先级排队获取槽位(0=最高)。可选 effectiveMaxConcurrent 覆盖实例级默认配额。可选 AbortSignal 在 abort 时 reject 排队条目。 */
31
+ /**
32
+ * 排队获取槽位(priority 0=最高;"strict-fifo" 策略下该值仅记录不参与出队选择,
33
+ * 见 createConcurrencyPool)。可选 effectiveMaxConcurrent 覆盖实例级默认配额。
34
+ * 可选 AbortSignal 在 abort 时 reject 排队条目。
35
+ */
20
36
  acquire(priority: number, effectiveMaxConcurrent?: number, signal?: AbortSignal): Promise<void>;
21
37
  /** 归还槽位。必须无条件执行(finally)。 */
22
38
  release(): void;
@@ -27,27 +43,40 @@ export interface ConcurrencyPool {
27
43
  }
28
44
 
29
45
  /**
30
- * 默认实现:maxConcurrent 槽位 + 优先级队列。
46
+ * 默认实现:maxConcurrent 槽位 + 可策略化排队队列。
31
47
  *
32
48
  * acquire(priority, effectiveMaxConcurrent?):
33
49
  * effective = effectiveMaxConcurrent ?? maxConcurrent
34
50
  * active < effective → active++, resolve
35
- * 否则 → 入队 { priority, resolve, seq }, 队列按 priority 升序 + seq FIFO
51
+ * 否则 → 入队 { priority, resolve, seq }
36
52
  *
37
53
  * release():
38
- * queue 非空 → 出队最高优先级 resolve(active 不变)
54
+ * queue 非空 → queuePolicy 选一个条目出队 resolve(active 不变):
55
+ * - "priority":priority 升序 + 同优先级 seq FIFO
56
+ * - "strict-fifo":纯 seq FIFO(priority 仅记录)
39
57
  * queue 空 → active--(防下溢)
58
+ *
59
+ * 已知契约边界(审查 S-2 登记):分层配额(effectiveMaxConcurrent)仅在
60
+ * acquire 时点判定;release 授予排队条目时不复查其 effective——即活跃数
61
+ * 仍 ≥ 条目 effective 时,单次释放也可能授予该条目(pi 运行时既有行为,
62
+ * concurrency-pool.test T-A2 锚定)。需要严格分层隔离的宿主应在条目侧
63
+ * 自行保证,勿依赖 release 时点复查。
64
+ *
65
+ * 类保留内部消费(subagent-service 深路径);对外导出面走 createConcurrencyPool
66
+ * 工厂(对象参数 + 策略枚举,不暴露本类名与位置参数构造——D7/U4 裁决)。
40
67
  */
41
68
  export class DefaultConcurrencyPool implements ConcurrencyPool {
42
69
  private _active = 0;
43
70
  private readonly queue: QueueEntry[] = [];
44
71
  private seqCounter = 0;
72
+ private readonly queuePolicy: QueuePolicy;
45
73
 
46
74
  /** 下限 1——maxConcurrent=0 会让 acquire 永久排队死锁(C3 修复)。 */
47
75
  readonly maxConcurrent: number;
48
76
 
49
- constructor(maxConcurrent: number) {
77
+ constructor(maxConcurrent: number, queuePolicy: QueuePolicy = "priority") {
50
78
  this.maxConcurrent = Math.max(1, maxConcurrent);
79
+ this.queuePolicy = queuePolicy;
51
80
  }
52
81
 
53
82
  acquire(priority: number, effectiveMaxConcurrent?: number, signal?: AbortSignal): Promise<void> {
@@ -86,14 +115,24 @@ export class DefaultConcurrencyPool implements ConcurrencyPool {
86
115
  });
87
116
  }
88
117
 
118
+ /**
119
+ * 出队候选比较:cur 是否优于 best(release 时选谁获得释放的槽位)。
120
+ * 策略单一分派点——新增策略只需在此分支,acquire/abort/clamp 逻辑策略无关。
121
+ */
122
+ private isBetterCandidate(cur: QueueEntry, best: QueueEntry): boolean {
123
+ if (this.queuePolicy === "strict-fifo") {
124
+ // 纯 FIFO:seq 单调递增,先入队者 seq 必更小——priority 完全不参与
125
+ return cur.seq < best.seq;
126
+ }
127
+ // priority:取优先级最高(priority 最小)的;同优先级 FIFO(seq 最小)
128
+ return cur.priority < best.priority || (cur.priority === best.priority && cur.seq < best.seq);
129
+ }
130
+
89
131
  release(): void {
90
132
  if (this.queue.length > 0) {
91
- // 取优先级最高(priority 最小)的;同优先级 FIFO(seq 最小)
92
133
  let bestIdx = 0;
93
134
  for (let i = 1; i < this.queue.length; i++) {
94
- const cur = this.queue[i];
95
- const best = this.queue[bestIdx];
96
- if (cur.priority < best.priority || (cur.priority === best.priority && cur.seq < best.seq)) {
135
+ if (this.isBetterCandidate(this.queue[i], this.queue[bestIdx])) {
97
136
  bestIdx = i;
98
137
  }
99
138
  }
@@ -114,3 +153,26 @@ export class DefaultConcurrencyPool implements ConcurrencyPool {
114
153
  return this._active;
115
154
  }
116
155
  }
156
+
157
+ /** createConcurrencyPool 选项(对象参数构造——导出面禁止位置参数歧义,D7/U4)。 */
158
+ export interface CreateConcurrencyPoolOptions {
159
+ /** 实例级最大并发配额。0/负数 clamp 到 1(防 acquire 永久排队死锁,C3 修复语义)。 */
160
+ maxConcurrent: number;
161
+ /**
162
+ * 排队策略(见 QueuePolicy)。缺省 `"priority"`——与 pi 既有消费
163
+ * (`new DefaultConcurrencyPool(n)`)行为逐点等值,缺省即零回归。
164
+ */
165
+ queuePolicy?: QueuePolicy;
166
+ }
167
+
168
+ /**
169
+ * 并发池工厂(D7/U4 导出面):宿主经此创建并发池,无需感知实现类。
170
+ *
171
+ * 返回 ConcurrencyPool 接口而非 DefaultConcurrencyPool——barrel 导出面只认
172
+ * 「对象参数 + 策略枚举」,实现类可内部替换,策略差异(pi=priority /
173
+ * zsw=strict-fifo)显式化为参数而非两份复刻实现(深度分层公式与下限常量单源,
174
+ * 公式见类注释与 slots 消费方)。
175
+ */
176
+ export function createConcurrencyPool(options: CreateConcurrencyPoolOptions): ConcurrencyPool {
177
+ return new DefaultConcurrencyPool(options.maxConcurrent, options.queuePolicy ?? "priority");
178
+ }
@@ -20,8 +20,70 @@
20
20
  // 直接调 handler(见 ui-request-handler-factory.ts),不经过本队列。enqueue 内仍防御性兼容
21
21
  // fire-and-forget(万一调用方未判):直接调 handler 返回,不入队串行。调用方不应依赖此防御。
22
22
 
23
+ import { getLogger } from "../core/logger.ts";
24
+ import { MAX_TIMER_DELAY_MS } from "../shared/timer-delay.ts";
25
+
23
26
  import { isDialogMethod } from "./ui-interaction-model.ts";
24
27
 
28
+ const logger = getLogger("subagents");
29
+
30
+ // ── dialog 超时上界(LC-3 / T2⑦,设计 docs/design/subagent-core-unbounded-wait-audit.md)──
31
+
32
+ /** 分钟/秒/毫秒换算常数(与 lifecycle-manager.ts 同款命名)。 */
33
+ const SECONDS_PER_MINUTE = 60;
34
+ const MS_PER_SECOND = 1000;
35
+
36
+ /** dialog 默认超时上界(30 分钟):请求方未传 timeout 时的回收层兜底。
37
+ *
38
+ * 30 分钟是裁决值(产品语义:ask_user 挂起 30 分钟无响应视为放弃),不随 P-T2
39
+ * (keep-alive 分布,等后代)标定——那是无关总体。请求方需要更长等待时在协议
40
+ * request 上显式传 timeout(毫秒,pi ExtensionUIDialogOptions.timeout 同义)覆盖。
41
+ *
42
+ * 「等用户无限久」改为默认有界是有意的行为变更:LC-3 的两种形态(host UI promise
43
+ * 挂死 / 用户永不回答)原都会把 L2 processing 永久占死 → 所有子进程 ask_user 全局
44
+ * 死锁 + 该 child L1 队列 head-of-line 阻塞。 */
45
+ const DEFAULT_DIALOG_TIMEOUT_MINUTES = 30;
46
+ export const DEFAULT_DIALOG_TIMEOUT_MS =
47
+ DEFAULT_DIALOG_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
48
+
49
+ /**
50
+ * 解析一个 dialog 项的队列级上界:请求方传了合法正数 timeout 用之,否则挂默认上界。
51
+ * 非法值(<=0 / NaN / ±Infinity)回落默认——不因脏参数把上界拆掉(拆掉即回到 LC-3 无界)。
52
+ *
53
+ * 这里 clamp 到 MAX_TIMER_DELAY_MS 而非 fail-fast(timer-delay.ts 的 assertSafeTimerDelay
54
+ * 惯例是配置入口 fail-fast):timeout 是 per-request 运行时传值,fail-fast 会把整个
55
+ * dialog 处理路径炸成 cancelled(伤害正常请求);超大 timeout 的调用方意图是「近乎不限时」,
56
+ * clamp 到 2^31-1(约 24.8 天)是该意图在 setTimeout 域内的安全语义近似——不 clamp 会被
57
+ * Node 塌缩为 1ms 立即触发(语义反转:刚入队就超时)。
58
+ */
59
+ function resolveDialogTimeoutMs(timeout: number | undefined): number {
60
+ const resolved = isValidDialogTimeout(timeout) ? timeout : DEFAULT_DIALOG_TIMEOUT_MS;
61
+ return Math.min(resolved, MAX_TIMER_DELAY_MS);
62
+ }
63
+
64
+ /**
65
+ * dialog req.timeout 合法域判定(单一权威源,A2-3):合法正数(finite 且 > 0)才参与
66
+ * 超时语义——队列层据此决定上界取值(非法回落 DEFAULT_DIALOG_TIMEOUT_MS),
67
+ * factory 层据此决定是否透传 SDK 第三参(非法不透传,数组为空,由队列层默认上界兜底)。
68
+ * 两处共用本函数,禁止各写一份判定(此前两处不一致:SDK 层把 0/负数/NaN 原样透传,
69
+ * 队列层判非法回落默认)。
70
+ */
71
+ export function isValidDialogTimeout(timeout: number | undefined): timeout is number {
72
+ return typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0;
73
+ }
74
+
75
+ /** 超时 settle 的日志消息(恢复指引 + 已等待时长,父进程日志是完整错误消息的承载面——
76
+ * pi 协议 RpcExtensionUIResponse 是封闭联合(value/confirmed/cancelled),错误文本
77
+ * 无法进 stdin 响应体,子 agent 侧收到的是 cancelled 语义)。 */
78
+ function dialogTimeoutLogMessage(req: UiRequest, waitedMs: number): string {
79
+ return (
80
+ `[subagents] dialog timed out after ${waitedMs}ms without response ` +
81
+ `(id=${req.id}, method=${req.method}) — settled as cancelled. ` +
82
+ "Recovery: the requesting agent can continue and re-issue the question (重新发起提问); " +
83
+ "to wait longer, pass an explicit timeout (ms) on the request."
84
+ );
85
+ }
86
+
25
87
  // ── 类型定义(本模块是 UiRequest/UiResponse/UiRequestHandler 的规范来源) ──
26
88
  // session-runner.ts 和 ui-channels.ts 复用这些类型(session-runner 再导出供测试 import)。
27
89
 
@@ -118,6 +180,11 @@ interface QueueItem {
118
180
  childPid: number | undefined;
119
181
  /** 是否已 settle(防 handler 完成 / rejectChildDialogs 重复 resolve)。 */
120
182
  settled: boolean;
183
+ /** 队列级超时 timer 句柄(processNext 使 item 成为 current 时挂上)。
184
+ * settleItem 统一 clearTimeout——handler 永挂时 processNext 的 await 永不 resume,
185
+ * 其内联 clearTimeout 不可达,reject/rejectAll 抢先 settle 的路径只能靠这里清理
186
+ * (A2-2:否则 timer armed 到期触发虚假「dialog timed out」warn + 句柄滞留)。 */
187
+ timeoutTimer?: ReturnType<typeof setTimeout>;
121
188
  }
122
189
 
123
190
  /**
@@ -137,6 +204,10 @@ interface QueueItem {
137
204
  * - FIFO 串行:前一个 handler settle 后才处理下一个
138
205
  * - SR-4:rejectChildDialogs(child) 把该 child 的 pending 全部 resolve 为 {cancelled:true}
139
206
  * - handler 抛错兜底:catch → {cancelled:true} → 继续下一个(队列不卡死)
207
+ * - 超时上界(LC-3/T2⑦):每个 dialog 项必有上界——req.timeout 显式传值优先,
208
+ * 未传挂 DEFAULT_DIALOG_TIMEOUT_MS(30min,裁决值);到点 settle {cancelled:true}
209
+ * 并 warn(恢复指引见 dialogTimeoutLogMessage),L2 processing 释放、队列继续推进。
210
+ * 「等用户无限久」改为默认有界是有意的行为变更。
140
211
  * - 调用方约定只对 dialog 类调 enqueue;fire-and-forget 由调用方直接调 handler 不入队
141
212
  *(enqueue 内仍防御性兼容 fire-and-forget,但不保证行为)
142
213
  *
@@ -201,18 +272,26 @@ export class DialogGlobalQueue {
201
272
  }
202
273
 
203
274
  /**
204
- * settle 一个 item(幂等)。handler 完成 / rejectChildDialogs / rejectAll 都通过本方法,
205
- * settled 标志保证只 settle 一次(防竞争)。
275
+ * settle 一个 item(幂等)。handler 完成 / rejectChildDialogs / rejectAll / 超时 timer
276
+ * 都通过本方法,settled 标志保证只 settle 一次(防竞争)。
206
277
  *
207
278
  * #19 单一推进点:本方法 settle Promise + 清状态后,**唯一**调 processNext 推进队列。
208
279
  * processNext 尾部不再调 processNext(旧代码双重推进,虽靠 processing 标志幂等,但语义混乱)。
209
280
  * 为什么推进必须在 settleItem 而非 processNext 尾部:rejectChildDialogs 取消一个永不 settle
210
281
  * 的 current(handler 等用户输入卡死)时,processNext 的 `await item.handler` 永不 resume,
211
282
  * 尾部不会执行;只有 settleItem 里的 processNext 才能打破死锁,推进下一个。
283
+ *
284
+ * [A2-2] 超时 timer 清理也统一收口在这里:任何路径抢先 settle(reject/rejectAll/超时
285
+ * 自身)都必须撤下 armed timer——handler 永挂时 processNext 内联的 clearTimeout 不可达,
286
+ * 不在这里清则 timer 到期触发虚假「dialog timed out」warn 且句柄滞留至超时点。
212
287
  */
213
288
  private settleItem(item: QueueItem, resp: UiResponse): void {
214
289
  if (item.settled) return;
215
290
  item.settled = true;
291
+ if (item.timeoutTimer) {
292
+ clearTimeout(item.timeoutTimer);
293
+ item.timeoutTimer = undefined;
294
+ }
216
295
  item.resolve(resp);
217
296
  // 若是正在处理的项,清空 current/processing 并推进队列(唯一推进点)。
218
297
  // 非当前项(队列中被 reject)只 settle Promise,不影响 current/推进。
@@ -263,6 +342,14 @@ export class DialogGlobalQueue {
263
342
  *
264
343
  * handler 抛错兜底(TC-E4 case 3):catch → settle {cancelled:true} → 继续。
265
344
  * 不能让一个失败卡死队列(processing 永远 true)。
345
+ *
346
+ * LC-3/T2⑦ 超时上界:`await item.handler` 原本无上界——host UI promise 挂死或用户
347
+ * 永不回答时 processing 恒 true(全局 dialog 死锁)。现在每项挂队列级 timer:
348
+ * req.timeout(请求方显式传值)优先,未传/非法挂 DEFAULT_DIALOG_TIMEOUT_MS。
349
+ * 到点 settle {cancelled:true}(完整错误消息落父进程日志,含恢复指引与等待时长),
350
+ * settleItem 的 settled 标志保证与 handler 完成 / rejectChildDialogs 三方竞态下
351
+ * 恰 settle 一次。timer 回调闭包捕获 item(非读 this.current):迟到触发时
352
+ * settled 标志已置位,直接 noop,不误伤后继项。
266
353
  */
267
354
  private async processNext(): Promise<void> {
268
355
  if (this.processing) return;
@@ -270,12 +357,23 @@ export class DialogGlobalQueue {
270
357
  this.processing = true;
271
358
  const item = this.queue.shift()!;
272
359
  this.current = item;
360
+ const timeoutMs = resolveDialogTimeoutMs(item.req.timeout);
361
+ const timer = setTimeout(() => {
362
+ // [A2-2] 已被抢先 settle(正常 settle 时 timer 已被 settleItem 清除,这里是防御性
363
+ // 兜底):静默返回,不发虚假「dialog timed out」warn、不重复 settle。
364
+ if (item.settled) return;
365
+ logger.warn(dialogTimeoutLogMessage(item.req, timeoutMs));
366
+ this.settleItem(item, { cancelled: true });
367
+ }, timeoutMs);
368
+ item.timeoutTimer = timer;
273
369
  try {
274
370
  const resp = await item.handler(item.req);
275
- // handler 完成:settle(若已被 rejectChildDialogs 抢先 settle 则 noop)。
371
+ clearTimeout(timer);
372
+ // handler 完成:settle(若已被超时 timer / rejectChildDialogs 抢先 settle 则 noop)。
276
373
  // settleItem 内会清 current/processing 并推进队列(#19 单一推进点)。
277
374
  this.settleItem(item, resp);
278
375
  } catch {
376
+ clearTimeout(timer);
279
377
  // handler 抛错兜底:回 cancelled,不向上抛(队列不能卡死)
280
378
  this.settleItem(item, { cancelled: true });
281
379
  }