@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,782 @@
1
+ // zcode-engine-appserver.test.ts —— 引擎接线测试(2026-09 起单一 app-server 形态):
2
+ // 全部跑 __fixtures__/fake-appserver.mjs 子进程(scenario 注入),绝不 spawn 真
3
+ // zcode.cjs。覆盖:事件时序前移(text_delta 流式 + 终态 message_end/turn_end +
4
+ // resolve 严格晚于末事件——不变量 2)/ usage 映射(与 parser.mapZcodeUsage 同源)/
5
+ // per-session model 透传 / 共享宿主 HOME(poolKey 恒 'shared'、HOME 正向透传、
6
+ // argv=app-server --cwd engineDataDir、无池 config/lockfile/pidfile)/
7
+ // onHandleReady 回调时点(create 应答后)与 onPoolResolved 时点(prepare 期,
8
+ // 先于首事件)/ abort 链 D3(stop 帧先发 → stop 优雅生效不杀进程 / stop 无效
9
+ // grace 耗尽 → killChain 连坐)/ 连接复用(跨任务 boot 1)/ dispose(幂等 /
10
+ // close 帧先于 SIGTERM / dispose 后 run 重建——不变量 4)/ 多会话并发不串线
11
+ // (RA3 地基,FAKE_STAMP_SESSION)/ -32603 Model config missing 错误归类 /
12
+ // capabilities 断言(eventGranularity 翻转无下游破坏的生产零消费方守卫)。
13
+
14
+ import * as fs from "node:fs";
15
+ import * as os from "node:os";
16
+ import * as path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
20
+
21
+ import type { EngineRunResult, RunContext } from "../../../port.ts";
22
+ import type { AgentEvent } from "../../../types.ts";
23
+ import type { AgentCallOpts } from "../../../../../orchestration/models/types.ts";
24
+ import { getLogger } from "../../../../../core/logger.ts";
25
+ import { ZCODE_SHARED_POOL_KEY } from "../constants.ts";
26
+ import { ZCODE_APPSERVER_GOLDEN } from "../golden-sample.ts";
27
+ import { AppServerConnection } from "../connection.ts";
28
+ import { SessionChannel } from "../session-channel.ts";
29
+ import { ZcodeEngine, type ZcodeEngineDeps } from "../zcode-engine.ts";
30
+
31
+ const FAKE_CLI = fileURLToPath(new URL("./__fixtures__/fake-appserver.mjs", import.meta.url));
32
+ const PROVIDER = "test-provider";
33
+
34
+ /** 宿主 zcode 会话 db 期望值的独立展开(①级读取钥匙)——刻意不 import 实现的
35
+ * hostZcodeDbPath(自指断言:实现改错时断言仍绿),路径知识独立于实现复述。 */
36
+ function expectedHostDbPath(): string {
37
+ return path.resolve(os.homedir(), ".zcode", "cli", "db", "db.sqlite");
38
+ }
39
+
40
+ /** 与 zcode-engine.ts 同一 facade 单例引用(spy 它的方法即拦截引擎的 warn 出声)。 */
41
+ const subagentsLogger = getLogger("subagents");
42
+
43
+ const GOLDEN_SESSION_ID = "sess_golden_r3_01";
44
+ const GOLDEN_FULL_TEXT = "你好,任务完成";
45
+
46
+ interface RpcErrorSpec {
47
+ code: number;
48
+ message: string;
49
+ data?: unknown;
50
+ }
51
+
52
+ interface EngineHandle_ {
53
+ engine: ZcodeEngine;
54
+ stateFile: string;
55
+ workspace: string;
56
+ }
57
+
58
+ /** run 返回形态别名(abort 用例的中止终态断言面)。 */
59
+ type EngineRunResult_ = EngineRunResult;
60
+
61
+ const engines: ZcodeEngine[] = [];
62
+ let seq = 0;
63
+ let tmpRoot: string;
64
+ let dataDir: string;
65
+ let v2Path: string;
66
+
67
+ function writeJson(p: string, v: unknown): void {
68
+ fs.mkdirSync(path.dirname(p), { recursive: true });
69
+ fs.writeFileSync(p, JSON.stringify(v, null, 2));
70
+ }
71
+
72
+ beforeEach(() => {
73
+ tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "zcode-eng-appserver-"));
74
+ dataDir = path.join(tmpRoot, "data");
75
+ v2Path = path.join(tmpRoot, "v2.json");
76
+ writeJson(v2Path, {
77
+ provider: { [PROVIDER]: { options: { apiKey: "k", baseURL: "https://t.example" }, models: { m1: {} } } },
78
+ });
79
+ });
80
+
81
+ afterEach(async () => {
82
+ for (const engine of engines.splice(0)) await engine.dispose().catch(() => undefined);
83
+ fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
84
+ });
85
+
86
+ interface ScenarioOverrides {
87
+ dropTurnTerminal?: boolean;
88
+ replaceSendPushes?: string[];
89
+ createError?: RpcErrorSpec;
90
+ /** send 应答 error 帧(运行中失败注入——-32004 会话 id 留痕用例)。 */
91
+ sendError?: RpcErrorSpec;
92
+ stopBehavior?: "terminal" | "none";
93
+ stampSession?: boolean;
94
+ /** 覆盖 read 应答(read 是全文权威来源——schema 用例须与收尾帧一致)。 */
95
+ readResult?: unknown;
96
+ /** 多会话用例:不覆盖 createResult/readResult(fake 缺省每会话独立 sess_<id> + read {messages:[]})。 */
97
+ distinctSessionIds?: boolean;
98
+ }
99
+
100
+ /** 建一个连到 fake 的引擎(scenario 文件随 env 固化进子进程;每测试独立 state 文件)。 */
101
+ function makeEngine(overrides: ScenarioOverrides = {}): EngineHandle_ {
102
+ seq += 1;
103
+ const stateFile = path.join(tmpRoot, `state-${seq}.jsonl`);
104
+ const scenarioFile = path.join(tmpRoot, `scenario-${seq}.json`);
105
+ const workspace = path.join(tmpRoot, `ws-${seq}`);
106
+ const pushes = (
107
+ overrides.replaceSendPushes ?? [
108
+ ...ZCODE_APPSERVER_GOLDEN.pushStream,
109
+ ...(overrides.dropTurnTerminal ? [] : [ZCODE_APPSERVER_GOLDEN.terminal[0]]),
110
+ ZCODE_APPSERVER_GOLDEN.terminal[1],
111
+ ]
112
+ ).map((l) => (typeof l === "string" ? (JSON.parse(l) as Record<string, unknown>) : l));
113
+ writeJson(scenarioFile, {
114
+ ...(overrides.distinctSessionIds === true
115
+ ? {}
116
+ : {
117
+ createResult: JSON.parse(ZCODE_APPSERVER_GOLDEN.createResponse),
118
+ readResult: overrides.readResult ?? JSON.parse(ZCODE_APPSERVER_GOLDEN.readResponse),
119
+ }),
120
+ sendPushes: pushes,
121
+ ...(overrides.stopBehavior !== undefined ? { stopBehavior: overrides.stopBehavior } : {}),
122
+ ...(overrides.createError !== undefined ? { createError: overrides.createError } : {}),
123
+ ...(overrides.sendError !== undefined ? { sendError: overrides.sendError } : {}),
124
+ });
125
+ const deps: ZcodeEngineDeps = {
126
+ engineDataDir: () => dataDir,
127
+ cliPath: FAKE_CLI,
128
+ sources: { v2ConfigPath: v2Path },
129
+ processEnv: {
130
+ PATH: process.env.PATH ?? "",
131
+ HOME: "/fake-host-home",
132
+ FAKE_STATE_FILE: stateFile,
133
+ FAKE_SESSION_SCENARIO: scenarioFile,
134
+ ...(overrides.stampSession === true ? { FAKE_STAMP_SESSION: "1" } : {}),
135
+ },
136
+ };
137
+ const engine = new ZcodeEngine(deps);
138
+ engines.push(engine);
139
+ return { engine, stateFile, workspace };
140
+ }
141
+
142
+ // ── 流水读取 helpers(与 session-channel.test.ts 同款) ──
143
+
144
+ interface StateEvent {
145
+ seq: number;
146
+ ev: string;
147
+ [key: string]: unknown;
148
+ }
149
+
150
+ function readState(file: string): StateEvent[] {
151
+ try {
152
+ return fs
153
+ .readFileSync(file, "utf8")
154
+ .trim()
155
+ .split("\n")
156
+ .filter(Boolean)
157
+ .map((l) => JSON.parse(l) as StateEvent);
158
+ } catch {
159
+ return [];
160
+ }
161
+ }
162
+
163
+ function isRecord(v: unknown): v is Record<string, unknown> {
164
+ return typeof v === "object" && v !== null && !Array.isArray(v);
165
+ }
166
+
167
+ function sentFrames(stateFile: string, method: string): Array<{ id: number; params: Record<string, unknown> }> {
168
+ return readState(stateFile)
169
+ .map((e) => e.frame)
170
+ .filter(
171
+ (f): f is { id: number; params: Record<string, unknown> } =>
172
+ isRecord(f) && f.method === method && isRecord(f.params) && typeof f.id === "number",
173
+ );
174
+ }
175
+
176
+ function sentMethods(stateFile: string): string[] {
177
+ return readState(stateFile)
178
+ .map((e) => e.frame)
179
+ .filter((f): f is Record<string, unknown> => isRecord(f) && typeof f.method === "string")
180
+ .map((f) => f.method as string);
181
+ }
182
+
183
+ function bootCount(stateFile: string): number {
184
+ return readState(stateFile).filter((e) => e.ev === "boot").length;
185
+ }
186
+
187
+ function bootEnv(stateFile: string): Record<string, unknown> {
188
+ const env = readState(stateFile).find((e) => e.ev === "env");
189
+ return isRecord(env) ? env : {};
190
+ }
191
+
192
+ function makeTask(overrides?: Partial<AgentCallOpts>): AgentCallOpts {
193
+ return { prompt: "做点什么", description: "s", model: `${PROVIDER}/m1`, ...overrides };
194
+ }
195
+
196
+ function makeCtx(overrides?: Partial<RunContext>): RunContext {
197
+ return { taskId: "sa-appserver", poolKey: "", ...overrides };
198
+ }
199
+
200
+ // ============================================================
201
+ // 事件时序前移 + usage 映射 + 句柄回调时点(不变量 2/3)
202
+ // ============================================================
203
+
204
+ describe("事件流与回调时点(缺省 appserver 路径)", () => {
205
+ it("text_delta 逐个实时流出 + 终态 message_end(usage)/turn_end + resolve 严格晚于末事件", async () => {
206
+ const { engine, workspace } = makeEngine();
207
+ const order: string[] = [];
208
+ const events: AgentEvent[] = [];
209
+ const { handle, outcome } = await engine.run(
210
+ makeTask({ cwd: workspace }),
211
+ makeCtx({
212
+ onEvent: (e) => {
213
+ events.push(e);
214
+ order.push(e.type);
215
+ },
216
+ }),
217
+ );
218
+ order.push("resolve");
219
+ expect(order).toEqual([
220
+ "text_delta",
221
+ "text_delta",
222
+ "text_delta",
223
+ "message_end",
224
+ "turn_end",
225
+ "resolve",
226
+ ]);
227
+ // text_delta 逐字(golden pushStream 的三条 session/event delta)
228
+ expect(events[0]).toEqual({ type: "text_delta", delta: "你好" });
229
+ expect(events[1]).toEqual({ type: "text_delta", delta: "," });
230
+ expect(events[2]).toEqual({ type: "text_delta", delta: "任务完成" });
231
+ // message_end.usage:收尾帧 usage 经 parser.mapZcodeUsage 同源映射
232
+ expect(events[3]).toEqual({
233
+ type: "message_end",
234
+ usage: { input: 12599, output: 17, cacheRead: 0, cacheWrite: 0 },
235
+ });
236
+ expect(events[4]).toEqual({ type: "turn_end" });
237
+
238
+ // outcome:content == 全文;usage(orchestration 版,mapZcodeOutcomeUsage 同源)
239
+ expect(outcome.error).toBeUndefined();
240
+ expect(outcome.content).toBe(GOLDEN_FULL_TEXT);
241
+ expect(outcome.sessionId).toBe(GOLDEN_SESSION_ID);
242
+ expect(outcome.usage).toEqual({ input: 12599, output: 17, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 1 });
243
+ expect(outcome.exitCode).toBe(0);
244
+
245
+ // handle 锚定:poolKey 恒 'shared',sessionRef.dbPath = 宿主 HOME 绝对路径
246
+ // (期望值独立展开,见 expectedHostDbPath 注释——不调实现函数)
247
+ expect(handle.data.poolKey).toBe(ZCODE_SHARED_POOL_KEY);
248
+ expect(handle.data.sessionRef).toEqual({ sessionId: GOLDEN_SESSION_ID, dbPath: expectedHostDbPath() });
249
+ expect(String(handle.data.sessionRef["dbPath"]).startsWith("/")).toBe(true);
250
+ }, 15_000);
251
+
252
+ it("回调时点:onPoolResolved(prepare 期)先于 onHandleReady(create 应答后)先于首事件", async () => {
253
+ const { engine, workspace } = makeEngine();
254
+ const order: string[] = [];
255
+ await engine.run(
256
+ makeTask({ cwd: workspace }),
257
+ makeCtx({
258
+ onPoolResolved: (k) => order.push(`pool:${k}`),
259
+ onHandleReady: (partial) => order.push(`handle:${partial.poolKey}:${partial.sessionRef["sessionId"]}`),
260
+ onEvent: (e) => {
261
+ if (e.type === "text_delta" && order[order.length - 1] !== "event") order.push("event");
262
+ },
263
+ }),
264
+ );
265
+ expect(order[0]).toBe(`pool:${ZCODE_SHARED_POOL_KEY}`);
266
+ expect(order[1]).toBe(`handle:${ZCODE_SHARED_POOL_KEY}:${GOLDEN_SESSION_ID}`);
267
+ expect(order[2]).toBe("event");
268
+ }, 15_000);
269
+
270
+ it("共享宿主 HOME:HOME 正向透传、遥测关、argv=app-server --cwd engineDataDir、无池 config/lockfile/pidfile、onChildSpawned 零调用", async () => {
271
+ const { engine, stateFile, workspace } = makeEngine();
272
+ const onChildSpawned = vi.fn();
273
+ const { handle } = await engine.run(makeTask({ cwd: workspace }), makeCtx({ onChildSpawned }));
274
+ expect(handle.data.poolKey).toBe(ZCODE_SHARED_POOL_KEY);
275
+ // fake 侧 boot env:base env 携带的宿主 HOME 值原样透传(buildAppServerEnv 不覆写
276
+ // HOME 键——launcher 同进程 import() 也不改 env)、遥测 false、统一嵌套标记
277
+ const env = bootEnv(stateFile);
278
+ expect(env["home"]).toBe("/fake-host-home");
279
+ expect(env["telemetry"]).toBe("false");
280
+ expect(env["unifiedNested"]).toBe("1");
281
+ const boot = readState(stateFile).find((e) => e.ev === "boot");
282
+ expect(boot?.["argv"]).toEqual(["app-server", "--cwd", dataDir]);
283
+ // D6:app-server 常驻进程是引擎自管资源(dispose 收割),不进宿主终止链——
284
+ // run 全程 onChildSpawned 不被调用(宿主 killAll 不误杀共享进程)
285
+ expect(onChildSpawned).not.toHaveBeenCalled();
286
+ // 池化时代的产物全部不存在:engines/zcode/ 只含 fs 拦截 launcher,无池
287
+ // config/lockfile/pidfile/journal 目录
288
+ const engineDir = fs.readdirSync(path.join(dataDir, "engines", "zcode"));
289
+ expect(engineDir).toEqual(["appserver-launcher.cjs"]);
290
+ }, 15_000);
291
+
292
+ it("maxTurns(pi 专属)→ 引擎内不再拒绝([D3-④] 能力拒绝上提宿主 capability-gate,防双轨拦截复活)", async () => {
293
+ // [D3-④ 合并注] capability 拒绝唯一实现 = common/capability-gate(两调用点 = chat 域
294
+ // executeViaEngine 同步段 + SAR run 前,拒绝语义 engine_capability_unsupported 由
295
+ // capability-gate.test.ts 承载)。引擎域 run() 不做 shape 检查——本用例反转回归面:
296
+ // maxTurns 直传引擎不得再被拒(曾因引擎内残留拦截与 gate 形成双轨)。
297
+ const { engine, stateFile, workspace } = makeEngine();
298
+ const r = await engine.run(makeTask({ cwd: workspace, maxTurns: 10 }), makeCtx());
299
+ expect(r.outcome.error).toBeUndefined();
300
+ expect(r.outcome.content).toBe(GOLDEN_FULL_TEXT);
301
+ // 正常走 app-server 流程(create×1——maxTurns 对 zcode 无通道,静默不透传)
302
+ expect(sentFrames(stateFile, "session/create")).toHaveLength(1);
303
+ }, 10_000);
304
+
305
+ it("per-session model:create 帧 model={providerId,modelId}(task.model 拆分)+ toolDenylist 透传", async () => {
306
+ const { engine, stateFile, workspace } = makeEngine();
307
+ await engine.run(makeTask({ cwd: workspace, denyTools: ["bash", ""] }), makeCtx());
308
+ const create = sentFrames(stateFile, "session/create")[0];
309
+ expect(create.params["model"]).toEqual({ providerId: PROVIDER, modelId: "m1" });
310
+ expect(create.params["toolDenylist"]).toEqual(["bash"]); // 空串过滤
311
+ expect(create.params["workspace"]).toMatchObject({ workspacePath: workspace });
312
+ expect(create.params["mode"]).toBe("yolo");
313
+ }, 15_000);
314
+
315
+ it("thinkingLevel → create 帧 thoughtLevel 透传(F15a:task 声明映射协议通道)", async () => {
316
+ const { engine, stateFile, workspace } = makeEngine();
317
+ await engine.run(makeTask({ cwd: workspace, thinkingLevel: "high" }), makeCtx());
318
+ const create = sentFrames(stateFile, "session/create")[0];
319
+ expect(create.params["thoughtLevel"]).toBe("high");
320
+ }, 15_000);
321
+
322
+ it("thinkingLevel 未传/空白 → create 帧无 thoughtLevel 键(A.2 ① strict 对象不设空键,防 -32602)", async () => {
323
+ const { engine, stateFile, workspace } = makeEngine();
324
+ await engine.run(makeTask({ cwd: workspace, thinkingLevel: " " }), makeCtx());
325
+ const create = sentFrames(stateFile, "session/create")[0];
326
+ expect("thoughtLevel" in create.params).toBe(false);
327
+ }, 15_000);
328
+
329
+ it("thinkingLevel=medium(非常见档位)→ warn 一行提示且 thoughtLevel 键仍透传(RX2-F1:提示不拦截)", async () => {
330
+ const { engine, stateFile, workspace } = makeEngine();
331
+ const warns: string[] = [];
332
+ const warnSpy = vi.spyOn(subagentsLogger, "warn").mockImplementation(((msg: string) => {
333
+ warns.push(msg);
334
+ }) as typeof subagentsLogger.warn);
335
+ try {
336
+ await engine.run(makeTask({ cwd: workspace, thinkingLevel: "medium" }), makeCtx());
337
+ } finally {
338
+ warnSpy.mockRestore();
339
+ }
340
+ // 提示恰一行;措辞是「若不支持将被忽略/回落」的或然警告(core 不做权威值域断言)
341
+ const hits = warns.filter((m) => m.includes("thoughtLevel"));
342
+ expect(hits).toHaveLength(1);
343
+ expect(hits[0]).toContain("medium");
344
+ expect(hits[0]).toContain("将被忽略/回落");
345
+ expect(hits[0]).toContain("low/high/max");
346
+ // 透传不拦截:create 帧仍带原值(off/minimal/medium/xhigh 全放行)
347
+ const create = sentFrames(stateFile, "session/create")[0];
348
+ expect(create.params["thoughtLevel"]).toBe("medium");
349
+ }, 15_000);
350
+
351
+ it("thinkingLevel=high(常见档位)→ 零 thoughtLevel 提示(RX2-F1:常见档位不出声)", async () => {
352
+ const { engine, workspace } = makeEngine();
353
+ const warns: string[] = [];
354
+ const warnSpy = vi.spyOn(subagentsLogger, "warn").mockImplementation(((msg: string) => {
355
+ warns.push(msg);
356
+ }) as typeof subagentsLogger.warn);
357
+ try {
358
+ await engine.run(makeTask({ cwd: workspace, thinkingLevel: "high" }), makeCtx());
359
+ } finally {
360
+ warnSpy.mockRestore();
361
+ }
362
+ expect(warns.filter((m) => m.includes("thoughtLevel"))).toHaveLength(0);
363
+ }, 15_000);
364
+
365
+ it("task.model 缺省 + ctx.ctxModel 存在 → 出声留痕(F16b:ctxModel 是 pi 链路兜底,zcode 落自身缺省链)", async () => {
366
+ // v2 config 补缺省模型 provider 条目——ZCODE_FALLBACK_DEFAULT_MODEL 的解析链才可校验通过
367
+ writeJson(v2Path, {
368
+ provider: {
369
+ [PROVIDER]: { options: { apiKey: "k", baseURL: "https://t.example" }, models: { m1: {} } },
370
+ "builtin:bigmodel-coding-plan": { options: { apiKey: "k" }, models: { "GLM-5.3": {} } },
371
+ },
372
+ });
373
+ const { engine, workspace } = makeEngine();
374
+ const warns: Array<{ msg: string; data: unknown }> = [];
375
+ const warnSpy = vi.spyOn(subagentsLogger, "warn").mockImplementation(((msg: string, data: unknown) => {
376
+ warns.push({ msg, data });
377
+ }) as typeof subagentsLogger.warn);
378
+ try {
379
+ const { outcome } = await engine.run(
380
+ makeTask({ cwd: workspace, model: undefined }),
381
+ makeCtx({ ctxModel: { id: "main-model", name: "Main", provider: "p", reasoning: false } }),
382
+ );
383
+ expect(outcome.error).toBeUndefined(); // 信号不阻断任务
384
+ const hit = warns.find((w) => w.msg.includes("ctxModel"));
385
+ expect(hit).toBeDefined();
386
+ expect(hit?.msg).toContain("main-model");
387
+ expect(hit?.msg).toContain("builtin:bigmodel-coding-plan/GLM-5.3");
388
+ } finally {
389
+ warnSpy.mockRestore();
390
+ }
391
+ }, 15_000);
392
+
393
+ it("显式 task.model 或 ctx 无 ctxModel → 零 ctxModel 信号(F16b:只在「ctx 有模型但被忽略」时出声)", async () => {
394
+ // 第二段 run(model 缺省)走引擎缺省链——v2 config 需含 ZCODE_FALLBACK_DEFAULT_MODEL 的 provider
395
+ writeJson(v2Path, {
396
+ provider: {
397
+ [PROVIDER]: { options: { apiKey: "k", baseURL: "https://t.example" }, models: { m1: {} } },
398
+ "builtin:bigmodel-coding-plan": { options: { apiKey: "k" }, models: { "GLM-5.3": {} } },
399
+ },
400
+ });
401
+ const { engine, workspace } = makeEngine();
402
+ const warns: string[] = [];
403
+ const warnSpy = vi.spyOn(subagentsLogger, "warn").mockImplementation(((msg: string) => {
404
+ warns.push(msg);
405
+ }) as typeof subagentsLogger.warn);
406
+ try {
407
+ // 显式 task.model + ctxModel:走正常解析链,不出声
408
+ await engine.run(makeTask({ cwd: workspace }), makeCtx({ ctxModel: { id: "main-model", name: "Main", provider: "p", reasoning: false } }));
409
+ // model 缺省 + ctx 无 ctxModel:预期缺省链,不出声
410
+ await engine.run(makeTask({ cwd: workspace, model: undefined }), makeCtx());
411
+ expect(warns.filter((m) => m.includes("ctxModel"))).toHaveLength(0);
412
+ } finally {
413
+ warnSpy.mockRestore();
414
+ }
415
+ }, 15_000);
416
+
417
+ it("schema 任务:appserver 路径同接 schema 仿真(合法 JSON → parsedOutput)", async () => {
418
+ const finalText = '{"verdict":"ok"}';
419
+ const { engine, workspace } = makeEngine({
420
+ replaceSendPushes: buildJsonPushes(finalText),
421
+ // read 是全文权威来源——须与收尾帧一致(否则 read 兜底覆盖掉 JSON 全文)
422
+ readResult: { messages: [{ info: { role: "assistant" }, parts: [{ type: "text", text: finalText }] }] },
423
+ });
424
+ const { outcome } = await engine.run(
425
+ makeTask({ cwd: workspace, schema: { type: "object", properties: { verdict: { type: "string" } }, required: ["verdict"] } }),
426
+ makeCtx(),
427
+ );
428
+ expect(outcome.error).toBeUndefined();
429
+ expect(outcome.parsedOutput).toEqual({ verdict: "ok" });
430
+ }, 15_000);
431
+
432
+ it("schema 失败重试一轮:两会话两 send(重试轮独立 create),usage 两轮累计", async () => {
433
+ const { engine, stateFile, workspace } = makeEngine({
434
+ replaceSendPushes: buildJsonPushes("I think everything is fine."),
435
+ });
436
+ // 重试轮仍返回非 JSON(scenario 固化)→ 两轮均失败 → schema_emulation_failed
437
+ const { outcome } = await engine.run(
438
+ makeTask({ cwd: workspace, schema: { type: "object", properties: { verdict: { type: "string" } }, required: ["verdict"] } }),
439
+ makeCtx(),
440
+ );
441
+ expect(outcome.error).toContain("schema_emulation_failed");
442
+ expect(sentFrames(stateFile, "session/create")).toHaveLength(2);
443
+ expect(sentFrames(stateFile, "session/send")).toHaveLength(2);
444
+ // 重试 prompt 带强化指令
445
+ const sends = sentFrames(stateFile, "session/send");
446
+ expect(String(sends[1]?.params["content"])).toContain("Retry: Structured Output Failed");
447
+ }, 15_000);
448
+
449
+ it("-32603 'Model config is missing' → engine_credential_missing 归类(outcome 不 reject)", async () => {
450
+ const { engine, workspace } = makeEngine({
451
+ createError: { code: -32603, message: "Model config is missing" },
452
+ });
453
+ const { outcome } = await engine.run(makeTask({ cwd: workspace }), makeCtx());
454
+ expect(outcome.error).toContain("engine_credential_missing");
455
+ expect(outcome.error).toContain("Model config is missing");
456
+ }, 15_000);
457
+
458
+ it("create 漂移错误(-32602)→ run-failed outcome 透传 code(R5 降级链地基)", async () => {
459
+ const { engine, workspace } = makeEngine({
460
+ createError: { code: -32602, message: "Invalid params", data: [{ path: ["model"] }] },
461
+ });
462
+ const { outcome } = await engine.run(makeTask({ cwd: workspace }), makeCtx());
463
+ expect(outcome.error).toContain("engine_run_failed");
464
+ expect(outcome.error).toContain("-32602");
465
+ }, 15_000);
466
+
467
+ it("-32004 运行中失败 → outcome/handle/错误文案携带会话 id(错误规格表:按任务失败上报含会话 id)", async () => {
468
+ // create 成功(golden 会话)后 send 阶段失败——会话已建立,失败上报必须留痕 id
469
+ const { engine, workspace } = makeEngine({
470
+ sendError: { code: -32004, message: "Session is not active" },
471
+ });
472
+ const { handle, outcome } = await engine.run(makeTask({ cwd: workspace }), makeCtx());
473
+ expect(outcome.error).toContain("engine_run_failed");
474
+ expect(outcome.error).toContain("-32004");
475
+ expect(outcome.error).toContain(GOLDEN_SESSION_ID);
476
+ expect(outcome.sessionId).toBe(GOLDEN_SESSION_ID);
477
+ expect(handle.data.sessionRef).toEqual({ sessionId: GOLDEN_SESSION_ID, dbPath: expectedHostDbPath() });
478
+ }, 15_000);
479
+ });
480
+
481
+ // ============================================================
482
+ // abort 链(D3:stop → grace → killChain;stop 成功不 kill)
483
+ // ============================================================
484
+
485
+ describe("abort 链(D3)", () => {
486
+ /** 无终态推送的挂起场景(仅 state.updated——turn 永不自然落定;scenario 每引擎
487
+ * 固化,故「后续任务」同样挂起——复用形态断言用再次 abort 收口,不 await 自然终态)。 */
488
+ const hangPushes = [ZCODE_APPSERVER_GOLDEN.pushStream[0]];
489
+
490
+ /** 挂起场景下发起第 N 轮并推进到该轮 create+send 已达(轮次按 create 帧计数——
491
+ * 全局 send 计数会被上一轮命中,waitFor 会假通过)。 */
492
+ async function startHangRun(
493
+ engine: ZcodeEngine,
494
+ workspace: string,
495
+ stateFile: string,
496
+ taskId: string,
497
+ ): Promise<{ run: Promise<EngineRunResult_>; controller: AbortController }> {
498
+ const controller = new AbortController();
499
+ const before = sentFrames(stateFile, "session/create").length;
500
+ const run = engine.run(makeTask({ cwd: workspace }), makeCtx({ taskId, signal: controller.signal }));
501
+ await vi.waitFor(
502
+ () => expect(sentFrames(stateFile, "session/create").length).toBeGreaterThan(before),
503
+ { timeout: 5_000 },
504
+ );
505
+ await vi.waitFor(
506
+ () => expect(sentFrames(stateFile, "session/send").length).toBeGreaterThan(before),
507
+ { timeout: 5_000 },
508
+ );
509
+ return { run, controller };
510
+ }
511
+
512
+ it("stop 优雅生效:stop 帧先发(send 之后)→ 终态在 grace 内到达 → 不杀共享进程(第二轮零重启)", async () => {
513
+ // stopBehavior 缺省 'terminal':fake 收到 stop 即推该会话终态
514
+ const { engine, stateFile, workspace } = makeEngine({ replaceSendPushes: [...hangPushes] });
515
+ // 第一轮:挂起 → abort → stop 推终态 → 优雅收口
516
+ const first = await startHangRun(engine, workspace, stateFile, "sa-a1");
517
+ first.controller.abort();
518
+ const r1 = await first.run;
519
+ expect(r1.outcome.exitCode).toBeNull();
520
+ expect(r1.outcome.error).toContain("session/stop");
521
+ // 帧序:send 之后紧跟 stop(stop 帧先于任何杀链动作)
522
+ const methods = sentMethods(stateFile);
523
+ expect(methods.indexOf("session/stop")).toBeGreaterThan(methods.indexOf("session/send"));
524
+ // 不杀共享进程:同引擎第二轮(同挂起场景)再 abort 一次——进程若被杀必 boot 2,
525
+ // 存活则 boot 计数仍 1(常驻进程跨任务复用的机制性证据)
526
+ const second = await startHangRun(engine, workspace, stateFile, "sa-a2");
527
+ second.controller.abort();
528
+ const r2 = await second.run;
529
+ expect(r2.outcome.exitCode).toBeNull();
530
+ expect(bootCount(stateFile)).toBe(1);
531
+ }, 30_000);
532
+
533
+ it("stop 无效(stopBehavior none + 无终态)→ grace 耗尽 → killChain 收割共享进程 → 崩溃路径收口 + 重建", async () => {
534
+ const { engine, stateFile, workspace } = makeEngine({
535
+ replaceSendPushes: [...hangPushes],
536
+ stopBehavior: "none",
537
+ });
538
+ const first = await startHangRun(engine, workspace, stateFile, "sa-k1");
539
+ first.controller.abort();
540
+ const r1 = await first.run;
541
+ expect(r1.outcome.exitCode).toBeNull();
542
+ expect(r1.outcome.error).toContain("session/stop");
543
+ // killChain 生效:第二轮自动重建(boot 2——进程死后重建路径,不变量 4 同源)
544
+ const second = await startHangRun(engine, workspace, stateFile, "sa-k2");
545
+ second.controller.abort();
546
+ const r2 = await second.run;
547
+ expect(r2.outcome.exitCode).toBeNull();
548
+ expect(bootCount(stateFile)).toBe(2);
549
+ }, 40_000);
550
+
551
+ it("pre-aborted signal:短路返回中止终态——不建会话不发任何帧(防误杀共享进程);onPoolResolved 先于 error 事件(不变量 3)", async () => {
552
+ const { engine, stateFile, workspace } = makeEngine();
553
+ const controller = new AbortController();
554
+ controller.abort();
555
+ const order: string[] = [];
556
+ const { handle, outcome } = await engine.run(
557
+ makeTask({ cwd: workspace }),
558
+ makeCtx({
559
+ signal: controller.signal,
560
+ onPoolResolved: (k) => order.push(`pool:${k}`),
561
+ onEvent: (e) => order.push(`event:${e.type}`),
562
+ }),
563
+ );
564
+ expect(outcome.exitCode).toBeNull();
565
+ expect(outcome.error).toContain("session/stop");
566
+ expect(handle.data.sessionRef["sessionId"]).toBeUndefined();
567
+ // 不变量 3:poolKey 声明先于首个事件(error)——journal writer 重定向先于落盘,
568
+ // 落盘池与 handle.poolKey 同值同源(短路分支不满足即落 shared 占位池漂移)
569
+ expect(order).toEqual([`pool:${ZCODE_SHARED_POOL_KEY}`, "event:error"]);
570
+ expect(handle.data.poolKey).toBe(ZCODE_SHARED_POOL_KEY);
571
+ expect(readState(stateFile)).toHaveLength(0); // 连惰性启动都没触发
572
+ }, 10_000);
573
+
574
+ it("宿主超时 abort(reason 标记)→ engine_timeout 公共合成终态(对齐点④,appserver 同语义)", async () => {
575
+ const { engine, workspace } = makeEngine({ replaceSendPushes: [...hangPushes], stopBehavior: "none" });
576
+ const controller = new AbortController();
577
+ controller.abort("agent-call-timeout");
578
+ const { outcome } = await engine.run(makeTask({ cwd: workspace }), makeCtx({ signal: controller.signal }));
579
+ expect(outcome.exitCode).toBeNull();
580
+ expect(outcome.error).toContain("engine_timeout");
581
+ }, 10_000);
582
+ });
583
+
584
+ // ============================================================
585
+ // 连接复用(共享宿主 HOME:无凭据刷新机制——登录态轮换后常驻连接用旧凭据,
586
+ // 引擎进程重启才生效;跨任务连接复用本身仍是 D1 不变量)
587
+ // ============================================================
588
+
589
+ describe("连接复用", () => {
590
+ it("第二任务复用常驻连接(boot 1),两会话独立 create", async () => {
591
+ const { engine, stateFile, workspace } = makeEngine();
592
+ await engine.run(makeTask({ cwd: workspace }), makeCtx());
593
+ await engine.run(makeTask({ cwd: workspace }), makeCtx());
594
+ expect(bootCount(stateFile)).toBe(1);
595
+ expect(sentFrames(stateFile, "session/create")).toHaveLength(2);
596
+ }, 20_000);
597
+ });
598
+
599
+ // ============================================================
600
+ // dispose(D6 主体:幂等 / close 帧先于 SIGTERM / dispose 后 run 重建)
601
+ // ============================================================
602
+
603
+ describe("dispose", () => {
604
+ it("在途会话:close 帧发出(fake 收到且记入流水)→ 进程收割 → 挂起任务走崩溃路径收口", async () => {
605
+ const { engine, stateFile, workspace } = makeEngine({
606
+ replaceSendPushes: [ZCODE_APPSERVER_GOLDEN.pushStream[0]], // 挂起不落定
607
+ });
608
+ const runPromise = engine.run(makeTask({ cwd: workspace }), makeCtx());
609
+ await vi.waitFor(() => expect(sentFrames(stateFile, "session/send")).toHaveLength(1), { timeout: 5_000 });
610
+ await engine.dispose();
611
+ const { outcome } = await runPromise;
612
+ // 挂起任务被崩溃路径收割(连接死亡 → failAllTurns → run-failed outcome)
613
+ expect(outcome.error).toContain("engine_run_failed");
614
+ // close 帧确实发出(fake 必须活着才能记流水——SIGTERM 先发则该帧大概率丢失,
615
+ // 帧在流水里即证明 close 先于进程死亡送达)
616
+ const closeFrames = sentFrames(stateFile, "session/close");
617
+ expect(closeFrames).toHaveLength(1);
618
+ expect(closeFrames[0]?.params["sessionId"]).toBe(GOLDEN_SESSION_ID);
619
+ // 帧序:close 在 send 之后(在途会话的收尾)
620
+ const methods = sentMethods(stateFile);
621
+ expect(methods.indexOf("session/close")).toBeGreaterThan(methods.indexOf("session/send"));
622
+ // 预算说明:waitFor ≤5s + killChain grace 5s + fake SIGTERM 100ms 排空窗口,套件
623
+ // 并发负载下 20s 偶发超限(正常完成 ~10s),放宽到 30s 防环境性 flake
624
+ }, 30_000);
625
+
626
+ it("幂等:连续两次 dispose 第二次零副作用;无运行时时 dispose 是 no-op", async () => {
627
+ const { engine } = makeEngine();
628
+ await engine.dispose(); // 从未 run——no-op
629
+ const { engine: e2, stateFile, workspace } = makeEngine();
630
+ await e2.run(makeTask({ cwd: workspace }), makeCtx());
631
+ await e2.dispose();
632
+ const boots = bootCount(stateFile);
633
+ await e2.dispose();
634
+ expect(bootCount(stateFile)).toBe(boots); // 无新 boot、无重复杀
635
+ }, 15_000);
636
+
637
+ it("dispose 后首个 run 自动重建(与进程死后重建同路径——不变量 4)", async () => {
638
+ const { engine, stateFile, workspace } = makeEngine();
639
+ await engine.run(makeTask({ cwd: workspace }), makeCtx());
640
+ expect(bootCount(stateFile)).toBe(1);
641
+ await engine.dispose();
642
+ const second = await engine.run(makeTask({ cwd: workspace }), makeCtx());
643
+ expect(second.outcome.error).toBeUndefined();
644
+ expect(second.outcome.content).toBe(GOLDEN_FULL_TEXT);
645
+ expect(bootCount(stateFile)).toBe(2);
646
+ }, 15_000);
647
+
648
+ it("窄竞态守卫:进程已死(child=null)但 activeSessions 残留时,dispose 不经 post 惰性拉起新进程(D6 防泄漏)", async () => {
649
+ // 真实微拍窗口(进程死亡收割与 activeSessions 清理之间的 microtask)不可从公开
650
+ // 面同步注入——白盒构造同形态快照:runtime 的 conn 无活进程、activeSessions 非空。
651
+ // 守卫缺失时 dispose 循环 post("session/close") 会 ensureStarted 惰性 spawn 一代新
652
+ // 进程再被同次 dispose 杀掉(onSpawned 同步触发);守卫生效时永不 spawn。
653
+ // 观测面用 conn.onSpawned(spawnGeneration 内同步调用)而非 fake 侧 boot 计数:
654
+ // post 后紧接 SIGTERM,新进程来不及写 boot 即死,写盘时序不可靠。
655
+ const { engine } = makeEngine();
656
+ let spawned = false;
657
+ const conn = new AppServerConnection({
658
+ cliPath: FAKE_CLI,
659
+ cwd: tmpRoot,
660
+ env: {
661
+ PATH: process.env.PATH ?? "",
662
+ FAKE_STATE_FILE: path.join(tmpRoot, "state-dispose-guard.jsonl"),
663
+ FAKE_SESSION_SCENARIO: path.join(tmpRoot, "scenario-dispose-guard.json"),
664
+ },
665
+ stderrLogPath: path.join(dataDir, "logs", "dispose-guard-stderr.log"),
666
+ onSpawned: () => {
667
+ spawned = true;
668
+ },
669
+ });
670
+ const ghostRt = {
671
+ conn,
672
+ channel: new SessionChannel(conn),
673
+ activeSessions: new Set<string>(["sess_ghost"]),
674
+ };
675
+ (engine as unknown as { appserverRuntime: unknown }).appserverRuntime = ghostRt;
676
+ expect(conn.alive).toBe(false); // 快照前置:无活进程
677
+ await engine.dispose();
678
+ expect(spawned).toBe(false); // 守卫生效:close 帧循环跳过,零惰性 spawn
679
+ }, 15_000);
680
+ });
681
+
682
+ // ============================================================
683
+ // 多会话并发(RA3 双会话地基:FAKE_STAMP_SESSION——推送按会话归因)
684
+ // ============================================================
685
+
686
+ describe("多会话并发(单常驻进程双会话不串线)", () => {
687
+ it("并发两 runTurn:各自 sessionId/事件流/结果独立,boot 计数 1(同进程共享)", async () => {
688
+ // distinctSessionIds:fake 缺省 create 应答每会话独立 sess_<请求id>;stamp 后
689
+ // 推送按目标会话归因(telemetry 帧补 sid——并发下不再依赖「唯一在途」兜底)
690
+ const { engine, stateFile, workspace } = makeEngine({ stampSession: true, distinctSessionIds: true });
691
+ const eventsA: AgentEvent[] = [];
692
+ const eventsB: AgentEvent[] = [];
693
+ const [a, b] = await Promise.all([
694
+ engine.run(makeTask({ cwd: workspace }), makeCtx({ taskId: "sa-a", onEvent: (e) => eventsA.push(e) })),
695
+ engine.run(makeTask({ cwd: workspace }), makeCtx({ taskId: "sa-b", onEvent: (e) => eventsB.push(e) })),
696
+ ]);
697
+ expect(a.outcome.error).toBeUndefined();
698
+ expect(b.outcome.error).toBeUndefined();
699
+ expect(a.outcome.content).toBe(GOLDEN_FULL_TEXT);
700
+ expect(b.outcome.content).toBe(GOLDEN_FULL_TEXT);
701
+ // 两会话 sessionId 独立(fake 缺省 sess_<请求id>)——不串线
702
+ expect(a.outcome.sessionId).toMatch(/^sess_\d+$/);
703
+ expect(b.outcome.sessionId).toMatch(/^sess_\d+$/);
704
+ expect(a.outcome.sessionId).not.toBe(b.outcome.sessionId);
705
+ // 各自的 delta 流完整(三条全到,无串扰丢失)
706
+ expect(eventsA.filter((e) => e.type === "text_delta")).toHaveLength(3);
707
+ expect(eventsB.filter((e) => e.type === "text_delta")).toHaveLength(3);
708
+ // 两会话的 handle 各归各
709
+ expect(a.handle.data.sessionRef["sessionId"]).toBe(a.outcome.sessionId);
710
+ expect(b.handle.data.sessionRef["sessionId"]).toBe(b.outcome.sessionId);
711
+ const creates = sentFrames(stateFile, "session/create");
712
+ expect(creates).toHaveLength(2);
713
+ // 两会话的 send/subscribe/read/close 各自指向自己的 sessionId(通道帧不串线)
714
+ const sendSids = sentFrames(stateFile, "session/send").map((f) => f.params["sessionId"]);
715
+ expect(new Set(sendSids).size).toBe(2);
716
+ expect(sendSids.sort()).toEqual([a.outcome.sessionId, b.outcome.sessionId].sort());
717
+ expect(bootCount(stateFile)).toBe(1);
718
+ }, 20_000);
719
+ });
720
+
721
+ // ============================================================
722
+ // capabilities(D5)+ eventGranularity 生产零消费方守卫
723
+ // ============================================================
724
+
725
+ describe("capabilities(D5:仅 eventGranularity 变)", () => {
726
+ it("单一 app-server 形态声明 eventGranularity='stream',其余能力位与设计声明一致", () => {
727
+ const { engine } = makeEngine();
728
+ expect(engine.capabilities()).toEqual({
729
+ schemaEnforcement: "emulated",
730
+ steer: "unsupported",
731
+ conversation: "unsupported",
732
+ personaInjection: "prompt",
733
+ eventGranularity: "stream",
734
+ sandbox: "none",
735
+ sessionRead: "full",
736
+ resume: "cold",
737
+ interrupt: "kill-only",
738
+ permissionMode: "native",
739
+ maxTurns: false,
740
+ });
741
+ });
742
+
743
+ it("eventGranularity 生产零消费方守卫:src 内(测试除外)无任何能力位读取访问(翻转无下游破坏)", () => {
744
+ const srcRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../../..");
745
+ const consumerAccess = /\.(capabilities\(\)|caps|capabilities)\.eventGranularity\b/g;
746
+ const offenders: string[] = [];
747
+ const walk = (dir: string): void => {
748
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
749
+ const full = path.join(dir, entry.name);
750
+ if (entry.isDirectory()) {
751
+ if (entry.name === "__tests__" || entry.name === "node_modules" || entry.name === "dist") continue;
752
+ walk(full);
753
+ } else if (entry.name.endsWith(".ts")) {
754
+ const text = fs.readFileSync(full, "utf8");
755
+ consumerAccess.lastIndex = 0;
756
+ if (consumerAccess.test(text)) offenders.push(path.relative(srcRoot, full));
757
+ }
758
+ }
759
+ };
760
+ walk(srcRoot);
761
+ expect(offenders).toEqual([]);
762
+ });
763
+ });
764
+
765
+ // ── 内部 helper ──
766
+
767
+ /** 造一组「delta 流 + 收尾帧 + read 应答」的 JSON 输出推送(schema 仿真用例)。 */
768
+ function buildJsonPushes(finalText: string): string[] {
769
+ return [
770
+ ZCODE_APPSERVER_GOLDEN.pushStream[0],
771
+ ZCODE_APPSERVER_GOLDEN.pushStream[1],
772
+ ZCODE_APPSERVER_GOLDEN.pushStream[2],
773
+ JSON.stringify({
774
+ method: "v4/telemetry/event",
775
+ params: { kind: "turn.terminal", status: "success" },
776
+ }),
777
+ JSON.stringify({
778
+ method: "session/event",
779
+ params: { sessionId: GOLDEN_SESSION_ID, payload: { response: finalText } },
780
+ }),
781
+ ];
782
+ }