@zhushanwen/subagent-core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
  2. package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
  3. package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
  4. package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
  5. package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
  6. package/dist/execution/engine/engines/zcode/constants.js +17 -27
  7. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  8. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  9. package/dist/execution/engine/engines/zcode/reader.js +2 -2
  10. package/dist/index.cjs +17100 -14082
  11. package/dist/index.d.cts +5336 -4033
  12. package/dist/index.d.ts +5336 -4033
  13. package/dist/index.js +17020 -14055
  14. package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
  16. package/dist.bundle/index.cjs +34933 -0
  17. package/package.json +3 -3
  18. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  19. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  20. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  21. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  22. package/src/__tests__/manifest-store.test.ts +10 -9
  23. package/src/__tests__/record-store-cache.test.ts +0 -2
  24. package/src/__tests__/record-store-index.test.ts +1 -2
  25. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  26. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  27. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  28. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  29. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  30. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  31. package/src/__tests__/session-runner.test.ts +1 -1
  32. package/src/core/__tests__/host-services.test.ts +2 -2
  33. package/src/core/__tests__/logger.test.ts +1 -1
  34. package/src/core/error-message.ts +9 -0
  35. package/src/core/host-services.ts +21 -5
  36. package/src/core/notify-ports.ts +18 -5
  37. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  38. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  39. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  40. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  41. package/src/execution/__tests__/alive-store.test.ts +1 -1
  42. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  43. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  44. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  45. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  46. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  47. package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
  48. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  49. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
  50. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  51. package/src/execution/__tests__/config.test.ts +1 -1
  52. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  53. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  54. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  55. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  56. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  57. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  58. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  59. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  60. package/src/execution/__tests__/execution-record.test.ts +217 -60
  61. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  62. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
  63. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  64. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  65. package/src/execution/__tests__/gc-timer.test.ts +56 -3
  66. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  67. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  68. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  69. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  70. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  71. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  72. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  73. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  74. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  75. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  76. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  77. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  78. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  79. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  80. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  81. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  82. package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
  83. package/src/execution/__tests__/output-collector.test.ts +117 -6
  84. package/src/execution/__tests__/pi-invocation.test.ts +36 -4
  85. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  86. package/src/execution/__tests__/record-store.test.ts +10 -9
  87. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  88. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  89. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  90. package/src/execution/__tests__/relay-env.test.ts +12 -1
  91. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  92. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  93. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  94. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  95. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  96. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  97. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  98. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  99. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  100. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  101. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  102. package/src/execution/__tests__/session-file-gc.test.ts +36 -1
  103. package/src/execution/__tests__/session-pending.test.ts +257 -14
  104. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  105. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  106. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  107. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  108. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  109. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  110. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  112. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  113. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +3 -3
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  117. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  118. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  119. package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
  120. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  122. package/src/execution/__tests__/subagent-actions-core.test.ts +48 -22
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  124. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
  125. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  126. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  127. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  128. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  129. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  130. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  131. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  132. package/src/execution/__tests__/sync-collect-recovery.test.ts +1117 -0
  133. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  134. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  135. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  136. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  137. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  138. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  139. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  140. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  141. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  142. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  143. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  144. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  145. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  146. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  147. package/src/execution/agent-registry.ts +74 -30
  148. package/src/execution/agent-result-mapper.ts +3 -0
  149. package/src/execution/cold-resurrect.ts +199 -0
  150. package/src/execution/collect-coordinator.ts +200 -0
  151. package/src/execution/config.ts +77 -5
  152. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  153. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  154. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  155. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  156. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  157. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  158. package/src/execution/engine/__tests__/common/pool-manager.test.ts +213 -53
  159. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  160. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  161. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  162. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  163. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  181. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  182. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  183. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  184. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  185. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  186. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  187. package/src/execution/engine/engines/pi/reader.ts +11 -0
  188. package/src/execution/engine/engines/pi/registration.ts +4 -2
  189. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  190. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  191. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  192. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  193. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  194. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  195. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  196. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  197. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  198. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  199. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  200. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  209. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  210. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  211. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  212. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  213. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  214. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  215. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  216. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  217. package/src/execution/engine/host-task-spec.ts +32 -36
  218. package/src/execution/engine/model-validation.ts +177 -0
  219. package/src/execution/engine/port.ts +24 -6
  220. package/src/execution/engine/routing.ts +75 -1
  221. package/src/execution/engine/types.ts +30 -103
  222. package/src/execution/execution-record.ts +229 -144
  223. package/src/execution/finalize-record.ts +141 -99
  224. package/src/execution/idle-gc.ts +28 -0
  225. package/src/execution/lifecycle-predicates.ts +1 -1
  226. package/src/execution/notifier.ts +234 -25
  227. package/src/execution/notify-host.ts +232 -0
  228. package/src/execution/record-entry.ts +16 -0
  229. package/src/execution/record-store.ts +280 -103
  230. package/src/execution/round-settlement.ts +93 -0
  231. package/src/execution/session-file-gc.ts +96 -62
  232. package/src/execution/session-reconstructor.ts +256 -143
  233. package/src/execution/sessions-index.ts +117 -47
  234. package/src/execution/settled-watchdog.ts +264 -52
  235. package/src/execution/subagent-actions-core.ts +84 -28
  236. package/src/execution/subagent-service.ts +1225 -964
  237. package/src/execution/subprocess-agent-runner.ts +144 -97
  238. package/src/execution/sync-rebuild.ts +87 -0
  239. package/src/execution/types.ts +72 -0
  240. package/src/execution/ui-request-handler-factory.ts +102 -74
  241. package/src/execution/ui-request-queue.ts +50 -19
  242. package/src/index.ts +442 -316
  243. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  244. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  245. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  246. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  247. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  248. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  249. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  250. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  251. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  252. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  253. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  254. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  255. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  256. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  257. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  258. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  259. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  260. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  261. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  262. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  263. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  264. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  265. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  266. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  267. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  268. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  269. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  270. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  271. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  272. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  273. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  274. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  275. package/src/orchestration/agent-opts-resolver.ts +1 -1
  276. package/src/orchestration/args-validator.ts +53 -31
  277. package/src/orchestration/execute-agent-call.ts +19 -73
  278. package/src/orchestration/launcher.ts +128 -81
  279. package/src/orchestration/lifecycle.ts +43 -45
  280. package/src/orchestration/models/ports.ts +4 -4
  281. package/src/orchestration/models/run-runtime.ts +2 -2
  282. package/src/orchestration/models/trace.ts +1 -1
  283. package/src/orchestration/models/types.ts +111 -22
  284. package/src/orchestration/models/workflow-run.ts +1 -1
  285. package/src/orchestration/script-lint.ts +91 -58
  286. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  287. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  288. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  289. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  290. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  291. package/src/shared/atomic-write.ts +12 -9
  292. package/src/shared/meta-parser.ts +158 -84
  293. package/src/shared/model-ref.ts +15 -2
  294. package/src/shared/resource-discovery.ts +9 -183
  295. package/src/shared/schema-jsonify.ts +1 -1
  296. package/workflows/review-fix-loop-utils.cjs +151 -89
  297. package/workflows/review-fix-loop-utils.d.cts +287 -0
  298. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  299. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  300. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  301. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  302. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  303. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  305. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  306. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  307. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  308. package/src/execution/execute-options-mapper.ts +0 -115
  309. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,451 @@
1
+ // session-channel-turn-timers.test.ts —— P0-1 U1 turn 等待两 timer 状态机单测。
2
+ //
3
+ // 设计权威源:docs/design/timeout-zcode-turn-and-settled-watchdog.md §6 D1(idle
4
+ // 主判定 + 宽上界回收兜底)/ D2(env 配置通道)/ §10 U1(lastTerminalStatus +
5
+ // lookupTurn 归因放宽)。验收映射:
6
+ // ① 事件流活跃 → idle 持续刷新不触发(活跃任务零误杀)
7
+ // ② 静默超 idle 阈值 → TurnTimeoutError(idle 形态)
8
+ // ③ 总时长超宽上界 → TurnTimeoutError(ceiling 形态,事件活跃仍回收)
9
+ // ④ 事件刷新不影响总上界倒数(fire 时刻 = 阈值精确值)
10
+ // ⑤ env 覆盖生效 + 0=关闭语义(env 与显式传参两通道)
11
+ // ⑥ 迟到 turn.terminal status 只记录不改写已落定结果(D5①/S5)
12
+ //
13
+ // 与 session-channel.test.ts(fake-appserver 子进程 + 真实 timer)互补:本文件用
14
+ // mock 连接 + fake timers 驱动纯状态机——timer 时序断言(刷新/倒数/精确 fire 点)
15
+ // 必须时钟可控才可判定。
16
+
17
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
18
+ import type { MockInstance } from "vitest";
19
+
20
+ import { getLogger } from "../../../../../core/logger.ts";
21
+ import type { AppServerConnection } from "../connection.ts";
22
+ import {
23
+ ZCODE_TURN_IDLE_TIMEOUT_ENV,
24
+ ZCODE_TURN_MAX_TIMEOUT_ENV,
25
+ ZCODE_TURN_IDLE_TIMEOUT_MS,
26
+ ZCODE_TURN_MAX_TIMEOUT_MS,
27
+ } from "../constants.ts";
28
+ import {
29
+ SessionChannel,
30
+ TurnTimeoutError,
31
+ type SessionTurnResult,
32
+ type SessionTurnOptions,
33
+ } from "../session-channel.ts";
34
+
35
+ // ============================================================
36
+ // mock 连接 + 驱动器(SessionChannel 只触 conn 的 onNotification/onClose/request/alive)
37
+ // ============================================================
38
+
39
+ interface MockChannel {
40
+ ch: SessionChannel;
41
+ /** 我方发出的请求帧流水(method 序可断言)。 */
42
+ requests: Array<{ method: string; params: unknown }>;
43
+ /** 挂起中的 session/read 应答放行器(终态后收尾窗口可控)。 */
44
+ readGates: Array<(v: unknown) => void>;
45
+ emit: (method: string, params: unknown) => void;
46
+ emitDelta: (sessionId: string | undefined, delta: string) => void;
47
+ emitFinalFrame: (sessionId: string, response: string) => void;
48
+ emitTerminal: (sessionId: string | undefined, status: string) => void;
49
+ }
50
+
51
+ let sidSeq = 0;
52
+
53
+ function makeMockChannel(opts: { gateRead?: boolean } = {}): MockChannel {
54
+ const notifications = new Map<string, (params: unknown) => void>();
55
+ const requests: Array<{ method: string; params: unknown }> = [];
56
+ const readGates: Array<(v: unknown) => void> = [];
57
+ const conn = {
58
+ alive: true,
59
+ onNotification: (method: string, h: (params: unknown) => void) => {
60
+ notifications.set(method, h);
61
+ return () => notifications.delete(method);
62
+ },
63
+ onClose: (_h: (reason: string) => void) => () => {},
64
+ request: (method: string, params: unknown) => {
65
+ requests.push({ method, params });
66
+ if (method === "session/create") {
67
+ sidSeq += 1;
68
+ return Promise.resolve({ session: { sessionId: `sess_t${sidSeq}` } });
69
+ }
70
+ if (method === "session/send") return Promise.resolve({ accepted: true });
71
+ if (method === "session/read") {
72
+ if (opts.gateRead === true) {
73
+ return new Promise((resolve) => readGates.push(resolve));
74
+ }
75
+ return Promise.resolve({ messages: [] });
76
+ }
77
+ return Promise.resolve({});
78
+ },
79
+ } as unknown as AppServerConnection;
80
+ const ch = new SessionChannel(conn);
81
+ return {
82
+ ch,
83
+ requests,
84
+ readGates,
85
+ emit: (method, params) => notifications.get(method)?.(params),
86
+ emitDelta: (sessionId, delta) =>
87
+ notifications
88
+ .get("session/event")
89
+ ?.({ sessionId, payload: { delta } }),
90
+ emitFinalFrame: (sessionId, response) =>
91
+ notifications
92
+ .get("session/event")
93
+ ?.({ sessionId, payload: { response } }),
94
+ // A.2:telemetry 帧不标会话归属——sessionId 传 undefined 即真实形态
95
+ emitTerminal: (sessionId, status) =>
96
+ notifications
97
+ .get("v4/telemetry/event")
98
+ ?.({ kind: "turn.terminal", status, sessionId }),
99
+ };
100
+ }
101
+
102
+ interface TurnTracker {
103
+ outcome: "pending" | "fulfilled" | "rejected";
104
+ result: SessionTurnResult | undefined;
105
+ error: unknown;
106
+ promise: Promise<SessionTurnResult>;
107
+ sessionId: string;
108
+ }
109
+
110
+ function startTurn(ch: SessionChannel, opts: SessionTurnOptions = {}): TurnTracker {
111
+ let sessionId = "";
112
+ const promise = ch.runTurn({ workspacePath: "/tmp/ws-timers", mode: "yolo" }, "任务", {
113
+ onSessionCreated: (sid) => {
114
+ sessionId = sid;
115
+ },
116
+ ...opts,
117
+ });
118
+ const t: TurnTracker = {
119
+ outcome: "pending",
120
+ result: undefined,
121
+ error: undefined,
122
+ promise,
123
+ sessionId: "",
124
+ };
125
+ void promise.then(
126
+ (r) => {
127
+ t.outcome = "fulfilled";
128
+ t.result = r;
129
+ },
130
+ (e) => {
131
+ t.outcome = "rejected";
132
+ t.error = e;
133
+ }
134
+ );
135
+ // sessionId 经 onSessionCreated 回填——闭包读的是 t.sessionId 快照,改为代理
136
+ Object.defineProperty(t, "sessionId", {
137
+ get: () => sessionId,
138
+ });
139
+ return t;
140
+ }
141
+
142
+ /** fake timers 下把 runTurn 的 setup 微任务链(create→subscribe→send)推到挂起点。 */
143
+ async function flushSetup(): Promise<void> {
144
+ await vi.advanceTimersByTimeAsync(0);
145
+ }
146
+
147
+ const warnLogger = getLogger("subagents");
148
+ let warnSpy: MockInstance<(msg: string, data?: unknown) => void>;
149
+
150
+ beforeEach(() => {
151
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
152
+ warnSpy = vi.spyOn(warnLogger, "warn").mockImplementation(() => {});
153
+ });
154
+
155
+ afterEach(() => {
156
+ vi.useRealTimers();
157
+ vi.restoreAllMocks();
158
+ vi.unstubAllEnvs();
159
+ });
160
+
161
+ const MIN = 60_000;
162
+
163
+ // ============================================================
164
+ // ① 事件刷新——活跃事件流 idle 永不触发
165
+ // ============================================================
166
+
167
+ describe("① idle 主判定:事件刷新(P0-1 D1)", () => {
168
+ it("短阈值:每 900ms 一个 delta(阈值 1s)连续 5 轮 → idle 不触发,终态正常 resolve", async () => {
169
+ const m = makeMockChannel();
170
+ const t = startTurn(m.ch, { idleTimeoutMs: 1_000, turnTimeoutMs: 0 }); // 关总上界隔离变量
171
+ await flushSetup();
172
+ expect(t.sessionId).not.toBe("");
173
+ for (let i = 0; i < 5; i += 1) {
174
+ await vi.advanceTimersByTimeAsync(900); // < 1s 阈值
175
+ m.emitDelta(t.sessionId, `片段${i}`); // 事件到达 → idle 重挂
176
+ expect(t.outcome).toBe("pending");
177
+ }
178
+ m.emitTerminal(undefined, "success");
179
+ await t.promise;
180
+ expect(t.outcome).toBe("fulfilled");
181
+ expect(t.result?.terminal).toEqual({ status: "success", source: "turn.terminal" });
182
+ });
183
+
184
+ it("默认阈值(30min idle / 60min 上界):每 29min 一个事件,59min 处终态 → 全程不判死", async () => {
185
+ const m = makeMockChannel();
186
+ const t = startTurn(m.ch);
187
+ await flushSetup();
188
+ await vi.advanceTimersByTimeAsync(29 * MIN);
189
+ m.emitDelta(t.sessionId, "a");
190
+ await vi.advanceTimersByTimeAsync(29 * MIN);
191
+ m.emitDelta(t.sessionId, "b");
192
+ await vi.advanceTimersByTimeAsync(1 * MIN); // 累计 59min < 60min 上界
193
+ m.emitTerminal(undefined, "success");
194
+ await t.promise;
195
+ expect(t.outcome).toBe("fulfilled");
196
+ // 累计 59min 时若 idle 不刷新早已判死——活到 59min 即刷新语义的直接证据
197
+ expect(t.result?.response).toBe("ab");
198
+ });
199
+ });
200
+
201
+ // ============================================================
202
+ // ② 静默判死——TurnTimeoutError(idle 形态)
203
+ // ============================================================
204
+
205
+ describe("② idle 静默判死(P0-1 D1)", () => {
206
+ it("整轮无事件:静默达阈值 → TurnTimeoutError kind=idle(lastEventAt=undefined)", async () => {
207
+ const m = makeMockChannel();
208
+ const t = startTurn(m.ch, { idleTimeoutMs: 1_000, turnTimeoutMs: 0 });
209
+ await flushSetup();
210
+ await vi.advanceTimersByTimeAsync(1_000);
211
+ expect(t.outcome).toBe("rejected");
212
+ const err = t.error as TurnTimeoutError;
213
+ expect(err).toBeInstanceOf(TurnTimeoutError);
214
+ expect(err.kind).toBe("idle");
215
+ expect(err.thresholdMs).toBe(1_000);
216
+ expect(err.elapsed).toBe(1_000);
217
+ expect(err.lastEventAt).toBeUndefined();
218
+ expect(err.message).toContain("连续 1000ms");
219
+ expect(err.message).toContain("终态"); // 既有消费方 /终态/ 语境兼容
220
+ expect(err.message).toContain("engine: pi");
221
+ });
222
+
223
+ it("有事件后静默:lastEventAt 记最后事件时刻,自此刻起算静默判死", async () => {
224
+ const m = makeMockChannel();
225
+ const t = startTurn(m.ch, { idleTimeoutMs: 1_000, turnTimeoutMs: 0 });
226
+ await flushSetup();
227
+ const t0 = Date.now(); // fake 时钟起点 = 挂载后基准(真实 epoch,非 0)
228
+ await vi.advanceTimersByTimeAsync(400);
229
+ m.emitDelta(t.sessionId, "最后的事件");
230
+ await vi.advanceTimersByTimeAsync(1_000); // 自最后事件起 1s(总时长 1.4s)
231
+ expect(t.outcome).toBe("rejected");
232
+ const err = t.error as TurnTimeoutError;
233
+ expect(err.kind).toBe("idle");
234
+ expect(err.elapsed).toBe(1_400);
235
+ expect(err.lastEventAt).toBe(t0 + 400);
236
+ });
237
+ });
238
+
239
+ // ============================================================
240
+ // ③④ 总上界判死——事件活跃仍回收(chatty-wedge);刷新不影响上界倒数
241
+ // ============================================================
242
+
243
+ describe("③④ 总上界回收兜底(P0-1 D1)", () => {
244
+ it("③ 事件流持续活跃:总上界到点仍判死 → TurnTimeoutError kind=ceiling + env 自救指引", async () => {
245
+ const m = makeMockChannel();
246
+ const t = startTurn(m.ch, { idleTimeoutMs: 1_000, turnTimeoutMs: 5_000 });
247
+ await flushSetup();
248
+ // 每 800ms 一个事件 ×6( idle 每次刷新,从不触发),末次事件在 4900ms
249
+ for (let i = 0; i < 6; i += 1) {
250
+ await vi.advanceTimersByTimeAsync(800);
251
+ m.emitDelta(t.sessionId, `c${i}`);
252
+ }
253
+ await vi.advanceTimersByTimeAsync(100); // 4900ms
254
+ m.emitDelta(t.sessionId, "c6");
255
+ await vi.advanceTimersByTimeAsync(200); // 5100ms > 5000ms 上界
256
+ expect(t.outcome).toBe("rejected");
257
+ const err = t.error as TurnTimeoutError;
258
+ expect(err).toBeInstanceOf(TurnTimeoutError);
259
+ expect(err.kind).toBe("ceiling"); // 不是 idle——事件流活跃,idle 从未触发
260
+ expect(err.thresholdMs).toBe(5_000);
261
+ expect(err.message).toContain("总上界");
262
+ expect(err.message).toContain("chatty-wedge");
263
+ expect(err.message).toContain("终态");
264
+ expect(err.message).toContain(ZCODE_TURN_MAX_TIMEOUT_ENV);
265
+ expect(err.message).toContain("0 关闭");
266
+ });
267
+
268
+ it("④ 事件刷新不影响总上界倒数:5 次刷新后 fire 时刻仍 = 阈值精确值", async () => {
269
+ const m = makeMockChannel();
270
+ const t = startTurn(m.ch, { idleTimeoutMs: 1_000, turnTimeoutMs: 5_000 });
271
+ await flushSetup();
272
+ const t0 = Date.now(); // fake 时钟起点 = 挂载后基准(真实 epoch,非 0)
273
+ for (let i = 0; i < 4; i += 1) {
274
+ await vi.advanceTimersByTimeAsync(800);
275
+ m.emitDelta(t.sessionId, `c${i}`);
276
+ }
277
+ await vi.advanceTimersByTimeAsync(900); // 3200+900=4100ms 处再刷一次(idle 截止 5100 > 上界 5000,避免同刻 tie)
278
+ m.emitDelta(t.sessionId, "c4");
279
+ await vi.advanceTimersByTimeAsync(1_000); // 5100ms,上界在 5000ms 已 fire
280
+ const err = t.error as TurnTimeoutError;
281
+ expect(err.kind).toBe("ceiling");
282
+ expect(err.elapsed).toBe(5_000); // 刷新只重挂 idle,总上界从挂载起固定倒数
283
+ expect(err.lastEventAt).toBe(t0 + 4_100); // 刷新被记账(事件确实到达过)
284
+ });
285
+ });
286
+
287
+ // ============================================================
288
+ // ⑤ env 覆盖生效 + 0=关闭语义(env 与显式传参两通道)
289
+ // ============================================================
290
+
291
+ describe("⑤ 配置通道(P0-1 D2)", () => {
292
+ it("env 覆盖 idle:XYZ_ZCODE_TURN_IDLE_TIMEOUT_MS=500 → 静默 500ms 判死(默认 30min 被覆盖)", async () => {
293
+ vi.stubEnv(ZCODE_TURN_IDLE_TIMEOUT_ENV, "500");
294
+ vi.stubEnv(ZCODE_TURN_MAX_TIMEOUT_ENV, "0"); // 关上界隔离变量
295
+ const m = makeMockChannel();
296
+ const t = startTurn(m.ch);
297
+ await flushSetup();
298
+ await vi.advanceTimersByTimeAsync(500);
299
+ const err = t.error as TurnTimeoutError;
300
+ expect(err).toBeInstanceOf(TurnTimeoutError);
301
+ expect(err.kind).toBe("idle");
302
+ expect(err.thresholdMs).toBe(500);
303
+ });
304
+
305
+ it("env 覆盖总上界:XYZ_ZCODE_TURN_MAX_TIMEOUT_MS=800 → 事件活跃 800ms 判死(kind=ceiling)", async () => {
306
+ vi.stubEnv(ZCODE_TURN_MAX_TIMEOUT_ENV, "800");
307
+ vi.stubEnv(ZCODE_TURN_IDLE_TIMEOUT_ENV, "600000"); // idle 10min 不干扰
308
+ const m = makeMockChannel();
309
+ const t = startTurn(m.ch);
310
+ await flushSetup();
311
+ const t0 = Date.now();
312
+ await vi.advanceTimersByTimeAsync(300);
313
+ m.emitDelta(t.sessionId, "活跃中");
314
+ await vi.advanceTimersByTimeAsync(500);
315
+ const err = t.error as TurnTimeoutError;
316
+ expect(err.kind).toBe("ceiling");
317
+ expect(err.thresholdMs).toBe(800);
318
+ expect(err.lastEventAt).toBe(t0 + 300);
319
+ });
320
+
321
+ it("env 0=关闭两 timer:静默 2 小时无任何判死 + 关闭 warn 明示后果(A10①),终态仍可 resolve", async () => {
322
+ vi.stubEnv(ZCODE_TURN_IDLE_TIMEOUT_ENV, "0");
323
+ vi.stubEnv(ZCODE_TURN_MAX_TIMEOUT_ENV, "0");
324
+ const m = makeMockChannel();
325
+ const t = startTurn(m.ch);
326
+ await flushSetup();
327
+ await vi.advanceTimersByTimeAsync(120 * MIN);
328
+ expect(t.outcome).toBe("pending"); // 无隐式判死
329
+ m.emitTerminal(undefined, "success");
330
+ await t.promise;
331
+ expect(t.outcome).toBe("fulfilled");
332
+ const warns = warnSpy.mock.calls.map((c) => String(c[0]));
333
+ expect(warns.some((w) => w.includes("idle 主判定") && w.includes("已关闭"))).toBe(true);
334
+ expect(warns.some((w) => w.includes("总上界") && w.includes("已关闭"))).toBe(true);
335
+ });
336
+
337
+ it("显式传参 0=关闭(优先级高于 env):idleTimeoutMs/turnTimeoutMs=0 压过 env 正值", async () => {
338
+ vi.stubEnv(ZCODE_TURN_IDLE_TIMEOUT_ENV, "500");
339
+ vi.stubEnv(ZCODE_TURN_MAX_TIMEOUT_ENV, "600");
340
+ const m = makeMockChannel();
341
+ const t = startTurn(m.ch, { idleTimeoutMs: 0, turnTimeoutMs: 0 });
342
+ await flushSetup();
343
+ await vi.advanceTimersByTimeAsync(2 * 60 * MIN); // 远超两个 env 值
344
+ expect(t.outcome).toBe("pending"); // 显式 0 = 关闭,env 未生效
345
+ m.emitTerminal(undefined, "success");
346
+ await t.promise;
347
+ expect(t.outcome).toBe("fulfilled");
348
+ });
349
+
350
+ it("显式传参覆盖 env:idleTimeoutMs=60000 压过 env=500,60s 判死", async () => {
351
+ vi.stubEnv(ZCODE_TURN_IDLE_TIMEOUT_ENV, "500");
352
+ vi.stubEnv(ZCODE_TURN_MAX_TIMEOUT_ENV, "0");
353
+ const m = makeMockChannel();
354
+ const t = startTurn(m.ch, { idleTimeoutMs: 60_000 });
355
+ await flushSetup();
356
+ await vi.advanceTimersByTimeAsync(600);
357
+ expect(t.outcome).toBe("pending"); // env 500 未生效——显式传参优先
358
+ await vi.advanceTimersByTimeAsync(59_400);
359
+ const err = t.error as TurnTimeoutError;
360
+ expect(err.kind).toBe("idle");
361
+ expect(err.thresholdMs).toBe(60_000);
362
+ });
363
+
364
+ it("env 非法值 warn + 回落默认:'abc' → 30min 默认阈值生效", async () => {
365
+ vi.stubEnv(ZCODE_TURN_IDLE_TIMEOUT_ENV, "abc");
366
+ vi.stubEnv(ZCODE_TURN_MAX_TIMEOUT_ENV, "0");
367
+ const m = makeMockChannel();
368
+ const t = startTurn(m.ch);
369
+ await flushSetup();
370
+ await vi.advanceTimersByTimeAsync(29 * MIN);
371
+ expect(t.outcome).toBe("pending");
372
+ await vi.advanceTimersByTimeAsync(1 * MIN);
373
+ const err = t.error as TurnTimeoutError;
374
+ expect(err.kind).toBe("idle");
375
+ expect(err.thresholdMs).toBe(ZCODE_TURN_IDLE_TIMEOUT_MS); // 默认 30min
376
+ expect(
377
+ warnSpy.mock.calls.some((c) => String(c[0]).includes("非法"))
378
+ ).toBe(true);
379
+ });
380
+
381
+ it("默认值锚定:env 未设时 idle=30min、总上界=60min(⛔P-Z0/P-Z1 标定前先验值)", () => {
382
+ expect(ZCODE_TURN_IDLE_TIMEOUT_MS).toBe(30 * MIN);
383
+ expect(ZCODE_TURN_MAX_TIMEOUT_MS).toBe(60 * MIN);
384
+ expect(ZCODE_TURN_IDLE_TIMEOUT_ENV).toBe("XYZ_ZCODE_TURN_IDLE_TIMEOUT_MS");
385
+ expect(ZCODE_TURN_MAX_TIMEOUT_ENV).toBe("XYZ_ZCODE_TURN_MAX_TIMEOUT_MS");
386
+ });
387
+ });
388
+
389
+ // ============================================================
390
+ // ⑥ 迟到 turn.terminal:只记录不改写已落定结果(D5①/S5 归因放宽)
391
+ // ============================================================
392
+
393
+ describe("⑥ 迟到权威终态只记录不改写(P0-1 D5①/S5)", () => {
394
+ it("final-frame 先落定 → 迟到 turn.terminal(status=error) 记入 lastTerminalStatus,落定结果不被改写", async () => {
395
+ const m = makeMockChannel({ gateRead: true }); // 挂起 read——制造 runTurn 收尾窗口
396
+ const t = startTurn(m.ch);
397
+ await flushSetup();
398
+ m.emitFinalFrame(t.sessionId, "全文"); // 宽松终态先落定(final-frame 恒 success)
399
+ await flushSetup();
400
+ expect(m.requests.some((r) => r.method === "session/read")).toBe(true);
401
+ m.emitTerminal(undefined, "error"); // 权威终态迟到(telemetry 无 sid——真实形态)
402
+ expect(m.readGates).toHaveLength(1);
403
+ m.readGates[0]({ messages: [] }); // 放行 read → runTurn resolve
404
+ await t.promise;
405
+ expect(t.outcome).toBe("fulfilled");
406
+ expect(t.result?.terminal).toEqual({ status: "success", source: "final-frame" }); // 不改写
407
+ expect(t.result?.lastTerminalStatus).toBe("error"); // 只记录
408
+ });
409
+
410
+ it("带 sid 的迟到 turn.terminal 同样精确归因到已落定 turn 并记录", async () => {
411
+ const m = makeMockChannel({ gateRead: true });
412
+ const t = startTurn(m.ch);
413
+ await flushSetup();
414
+ m.emitFinalFrame(t.sessionId, "全文");
415
+ await flushSetup();
416
+ m.emitTerminal(t.sessionId, "error"); // 带 sid 形态(归因放宽的精确分支)
417
+ m.readGates[0]({ messages: [] });
418
+ await t.promise;
419
+ expect(t.result?.terminal).toEqual({ status: "success", source: "final-frame" });
420
+ expect(t.result?.lastTerminalStatus).toBe("error");
421
+ });
422
+
423
+ it("turn.terminal 先到(权威先落定):terminal 与 lastTerminalStatus 同值记录", async () => {
424
+ const m = makeMockChannel();
425
+ const t = startTurn(m.ch);
426
+ await flushSetup();
427
+ m.emitTerminal(undefined, "error");
428
+ await t.promise;
429
+ expect(t.result?.terminal).toEqual({ status: "error", source: "turn.terminal" });
430
+ expect(t.result?.lastTerminalStatus).toBe("error");
431
+ });
432
+
433
+ it("多在途宁丢勿错不回退:一落定一在途时迟到 terminal 归因到在途 turn,落定者不记录", async () => {
434
+ const m = makeMockChannel({ gateRead: true });
435
+ const t1 = startTurn(m.ch);
436
+ await flushSetup();
437
+ m.emitFinalFrame(t1.sessionId, "全文一"); // t1 落定,read 挂起(在册窗口)
438
+ const t2 = startTurn(m.ch);
439
+ await flushSetup();
440
+ m.emitTerminal(undefined, "success"); // 无 sid:唯一在途 = t2 → 归因 t2
441
+ await flushSetup(); // t2 settle 后的 read 请求在微任务里才发出——先推到位再放行
442
+ expect(m.readGates).toHaveLength(2);
443
+ m.readGates[0]({ messages: [] }); // 放行 t1 的 read
444
+ m.readGates[1]({ messages: [] }); // 放行 t2 的 read(gateRead 对两个 turn 都生效)
445
+ await t1.promise;
446
+ await t2.promise;
447
+ expect(t1.result?.terminal).toEqual({ status: "success", source: "final-frame" });
448
+ expect(t1.result?.lastTerminalStatus).toBeUndefined(); // 多在途宁丢勿错
449
+ expect(t2.result?.terminal).toEqual({ status: "success", source: "turn.terminal" });
450
+ });
451
+ });
@@ -25,6 +25,7 @@ import { ZCODE_APPSERVER_GOLDEN } from "../golden-sample.ts";
25
25
  import {
26
26
  SessionChannel,
27
27
  SUBSCRIBE_DELIVERY_KIND,
28
+ TurnTimeoutError,
28
29
  extractAssistantText,
29
30
  extractCreatedSessionId,
30
31
  extractReadUsage,
@@ -137,10 +138,14 @@ function makeChannel(overrides: ScenarioOverrides = {}): ChannelHandle {
137
138
  : {}),
138
139
  };
139
140
  writeFileSync(scenarioFile, JSON.stringify(scenario));
141
+ // buildAppServerEnv 2026-09 共享宿主 HOME 改版后只收单参 baseEnv(不再覆写 HOME);
142
+ // 旧两参形态 (homeDir, extraEnv) 的第一参被展开成字符索引对象——PATH 进不了子 env,
143
+ // spawn("node") 无 PATH 可查 → ENOENT。此处传 env 对象本体(与 zcode-engine-*
144
+ // 引擎测试的 deps.processEnv 同模式)。
140
145
  const conn = new AppServerConnection({
141
146
  cliPath: FAKE_CLI,
142
147
  cwd: workspacePath,
143
- env: buildAppServerEnv(join(TMP, `home-${connSeq}`), {
148
+ env: buildAppServerEnv({
144
149
  PATH: process.env.PATH ?? "",
145
150
  FAKE_STATE_FILE: stateFile,
146
151
  FAKE_SESSION_SCENARIO: scenarioFile,
@@ -222,7 +227,7 @@ describe("A.2 帧序列逐字断言", () => {
222
227
  expect(frames).toHaveLength(1);
223
228
  // 帧级键集 {id, method, params}——无 jsonrpc、无未知键
224
229
  expect(Object.keys(frames[0]).sort()).toEqual(["id", "method", "params"]);
225
- const params = frames[0].params;
230
+ const params = frames[0].params as Record<string, { workspaceKey?: string; workspacePath?: string; [k: string]: unknown }>;
226
231
  expect(Object.keys(params).sort()).toEqual([
227
232
  "mode",
228
233
  "persistence",
@@ -258,7 +263,7 @@ describe("A.2 帧序列逐字断言", () => {
258
263
  },
259
264
  "做点什么"
260
265
  );
261
- const params = sentFrames(stateFile, "session/create")[0].params;
266
+ const params = sentFrames(stateFile, "session/create")[0].params as Record<string, { workspaceKey?: string; workspacePath?: string; [k: string]: unknown }>;
262
267
  expect(Object.keys(params).sort()).toEqual([
263
268
  "mode",
264
269
  "model",
@@ -568,16 +573,23 @@ describe("close(D4 用后即毁)", () => {
568
573
  expect(sentFrames(stateFile, "session/close")).toHaveLength(1);
569
574
  }, 10_000);
570
575
 
571
- it("终态超时(turn.terminal 与收尾帧均未达)→ reject 超时语境且 close 仍被调用", async () => {
576
+ it("终态超时(turn.terminal 与收尾帧均未达)→ TurnTimeoutError(ceiling 形态,P0-1 两 timer 语义)且 close 仍被调用", async () => {
572
577
  const onlyRunning = [ZCODE_APPSERVER_GOLDEN.pushStream[0]]; // 仅 state.updated,无终态
573
578
  const { ch, stateFile, workspacePath } = makeChannel({
574
579
  replaceSendPushes: onlyRunning,
575
580
  });
576
- await expect(
577
- ch.runTurn({ workspacePath, mode: "yolo" }, "做点什么", {
578
- turnTimeoutMs: 250,
581
+ const err = await ch
582
+ .runTurn({ workspacePath, mode: "yolo" }, "做点什么", {
583
+ turnTimeoutMs: 250, // P0-1 语义收窄:显式总上界(不再是固定墙钟预算)
579
584
  })
580
- ).rejects.toThrow(/终态/);
585
+ .then(
586
+ () => {
587
+ throw new Error("should reject");
588
+ },
589
+ (e: unknown) => e
590
+ );
591
+ expect(err).toBeInstanceOf(TurnTimeoutError);
592
+ expect((err as TurnTimeoutError).kind).toBe("ceiling");
581
593
  expect(sentFrames(stateFile, "session/close")).toHaveLength(1);
582
594
  }, 10_000);
583
595
  });