@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
@@ -1,41 +1,43 @@
1
1
  // src/execution/engine/engines/zcode/zcode-engine.ts
2
2
  //
3
- // ZcodeEngine(P3 R4 双模式):zcode CLI EnginePort 实现。设计权威源:
4
- // docs/architecture/subagent-engine-abstraction.md D10(MVP 引擎集)/ §3.3.4(reviewer@zcode
5
- // 物理数据流)/ §3.3.5(run 错误语义三条);docs/design/zcode-engine-appserver-resident.md
6
- // §3.3 D1(每引擎实例一条连接)/ D3(abort 链)/ D4(会话自包含)/ D5(capabilities
7
- // 升级序)/ D6(停机面 + 孤儿自愈)/ D7(常驻 HOME)/ §3.4 不变量 1-4。
3
+ // ZcodeEngine:zcode EnginePort 实现(2026-09 起单一 app-server 形态)。
4
+ // 设计权威源:docs/architecture/subagent-engine-abstraction.md D10 / §3.3.4 /
5
+ // §3.3.5docs/design/zcode-engine-appserver-resident.md §3.3 D1(每引擎实例一条
6
+ // 连接)/ D3(abort 链)/ D4(会话自包含)/ D5(capabilities)/ D6(停机面)。
8
7
  //
9
- // 双模式分派(R4;D2 降级链的骨架):
10
- // - appserver(缺省):惰性常驻连接(connection.ts + session-channel.ts)上的
11
- // create→subscribe→send→事件流→终态→read→close;per-session model 经 create
12
- // 参数透传;常驻 HOME = engines/zcode/home-appserver(D7 全量语义见 appserver-home.ts);
13
- // 缺省路径带 R5 降级链门控:协议冒烟探针(appserver-probe.ts,结论与 CLI mtime
14
- // 绑定)失败 本任务起 spawn 直走;首任务命中漂移类 RPC 错误(-32601/-32602)
15
- // 本任务降级 spawn 重跑一次 + 后续任务直走 spawn(标志内存化);
16
- // - spawn(XYZ_ZCODE_MODE=spawn 定向 / 降级目标):原单轮路径**零行为改动**
17
- // 保留(runViaSpawn——launcher/parser 四件套原链路)。
8
+ // 2026-09 breaking 重构(用户拍板,理由与代价见设计文档修订节):
9
+ // - **删除 CLI spawn 降级链**(原 XYZ_ZCODE_MODE=spawn 定向 / probe 冒烟门控 /
10
+ // protocol-drift 首败降级):zcode 无公开契约,协议漂移不再降级保底,直接报
11
+ // 可操作错误(提示核对版本 / 重启 / 改用 engine: pi)。
12
+ // - **删除 HOME 池化,共享宿主 HOME**:spawn env 不覆写 HOME,app-server 共享
13
+ // 宿主 ~/.zcode/(会话 db GUI 共写同一 SQLite,WAL 并发安全;凭据经
14
+ // appserver-launcher fs 拦截注入——cli config 读取重定向为「真实文件 + v2
15
+ // provider」合并,同 id v2 优先,机制与漂移面见该文件头注;已接受代价:
16
+ // GUI 会话列表可见 headless 会话、登录态轮换后常驻连接需引擎进程重启才用
17
+ // 新凭据)。HOME 依赖副作用(如 pnpm store 路径随 HOME 翻转)随之消失。
18
+ // - journal 分组 key 固定 'shared'(与 pi 引擎 PI_POOL_KEY 同构):journal 落
19
+ // engineDataDir/engines/zcode/shared/journal-<taskId>.jsonl;handle.dbPath
20
+ // 为绝对路径(宿主 ~/.zcode/cli/db/db.sqlite)。
18
21
  //
19
- // 职责编排(四件套的消费方):
20
- // preparer(隔离 HOME + 凭据 config)→ launcher(argv/env/spawn/杀链)
21
- // parser(stdout 收集 + 终 JSON + coarse 事件合成)→ reader(read 第①级 sqlite)。
22
- //
23
- // run 错误语义(设计 §3.3.5,两模式共用):
24
- // ① prepare 期错误(credential_missing / model_not_available / prompt_too_large /
25
- // capability 拒绝)在进程创建前 reject,不产生 handle;
22
+ // run 错误语义(设计 §3.3.5):
23
+ // prepare 期错误(credential_missing / model_not_available)在进程创建前
24
+ // reject,不产生 handle;
26
25
  // ② 运行中失败不 reject——合成 engine_run_failed outcome + 正常 handle 返回
27
26
  // (record 必须收尾);
28
- // ③ abort:appserver 走 D3 链(session/stop → grace → killChain 连坐共享进程);
29
- // spawn 走公共杀链(SIGTERM→grace→SIGKILL)——终态同为 exitCode=null + 杀链标记。
27
+ // ③ abort:D3 链(session/stop → grace → killChain 连坐共享进程)——终态
28
+ // exitCode=null + 杀链标记。
29
+ // [D3-④] capability 拒绝(fork/conversation/maxTurns/worktree)不再在本引擎:
30
+ // 上提到宿主调用前预检(common/capability-gate,两调用点 = chat 域
31
+ // executeViaEngine 同步段 + SAR run 前)——拒绝语义不变
32
+ // (engine_capability_unsupported + 无进程创建)。
30
33
  //
31
- // schema 仿真接线(D4 emulated 侧):common/schema-emulation.ts——spawn 前拼 prompt
32
- // 仿真段、终态后三级容错提取 + ajv 校验、失败强化重试一次、仍失败报
33
- // schema_emulation_failed(appserver 路径同接线:prompt 组装共享 buildPrompt,
34
- // 校验/重试编排共享 run 级重试轮)。read 第②级 journal 降级已接线(对齐点①:
35
- // common/journal-replay 复用 live reducer)。
34
+ // schema 仿真接线(D4 emulated 侧):common/schema-emulation.ts——prompt 拼仿真段、
35
+ // 终态后三级容错提取 + ajv 校验、失败强化重试一次、仍失败报 schema_emulation_failed。
36
+ // read 第②级 journal 降级已接线(common/journal-replay 复用 live reducer)。
36
37
 
37
38
  import { execFile } from "node:child_process";
38
39
  import * as fs from "node:fs";
40
+ import * as os from "node:os";
39
41
  import * as path from "node:path";
40
42
 
41
43
  import { getLogger } from "../../../../core/logger.ts";
@@ -45,14 +47,13 @@ import {
45
47
  extractAndValidateStructuredOutput,
46
48
  } from "../../common/schema-emulation.ts";
47
49
  import { HOST_TIMEOUT_ABORT_REASON, synthesizeTimeoutOutcome } from "../../common/kill-chain.ts";
48
- import { applyPersona } from "../../common/persona-router.ts";
49
50
  import { engineTimeoutDetail } from "../../common/errors.ts";
50
51
  import { replayJournalToSessionView } from "../../common/journal-replay.ts";
52
+ import type { AgentCallOpts } from "../../../../orchestration/models/types.ts";
51
53
  import type { EnginePort, EngineRunResult, RunContext } from "../../port.ts";
52
54
  import type {
53
55
  AgentEvent,
54
56
  AgentOutcome,
55
- AgentTaskSpec,
56
57
  EngineCapabilities,
57
58
  EngineHandle,
58
59
  InteractAction,
@@ -64,57 +65,42 @@ import { resolvePoolDir } from "../../paths.ts";
64
65
  import {
65
66
  ZCODE_ADAPTER_VERSION,
66
67
  ZCODE_APPSERVER_ABORT_GRACE_MS,
67
- ZCODE_APPSERVER_DRIFT_RPC_CODES,
68
68
  ZCODE_APPSERVER_ERR_BUSY_SESSION,
69
69
  ZCODE_APPSERVER_ERR_MODEL_CONFIG_MISSING,
70
70
  ZCODE_APPSERVER_HARVEST_GRACE_MS,
71
- ZCODE_APPSERVER_PIDFILE_NAME,
72
- ZCODE_APPSERVER_POOL_KEY,
73
71
  ZCODE_APPSERVER_STOP_TIMEOUT_MS,
74
72
  ZCODE_CLI_DEFAULT_PATH,
75
73
  ZCODE_ENGINE_ID,
76
74
  ZCODE_ERROR_TAIL_CHARS,
75
+ ZCODE_HOST_DB_SUFFIX,
77
76
  ZCODE_KILL_GRACE_MS,
78
- ZCODE_MODE_ENV_VAR,
79
- ZCODE_POOL_DB_RELATIVE_PATH,
77
+ ZCODE_SHARED_POOL_KEY,
78
+ ZCODE_TURN_MAX_TIMEOUT_ENV,
79
+ parseZcodeTurnTimeoutEnv,
80
80
  } from "./constants.ts";
81
- import { ZCODE_GOLDEN_STDOUT } from "./golden-sample.ts";
82
- import {
83
- buildZcodeArgv,
84
- buildZcodeEnv,
85
- assertZcodeArgvBudget,
86
- launchZcodeProcess,
87
- type ZcodeLaunchedProcess,
88
- } from "./launcher.ts";
89
81
  import {
90
- buildRunFailedMessage,
91
- collectZcodeOutput,
92
82
  mapZcodeOutcomeUsage,
93
83
  mapZcodeUsage,
94
- parseZcodeTerminal,
95
84
  synthesizeCoarseEvents,
96
- type ZcodeCollectedOutput,
97
85
  type ZcodeTerminalPayload,
98
86
  } from "./parser.ts";
99
87
  import {
100
- acquireAppServerHome,
101
- bootstrapAppServerConfig,
102
- isLockHeldByUs,
103
- startLockHeartbeat,
104
- writeAppServerPidFile,
105
- type AppServerHomeHandle,
106
- } from "./appserver-home.ts";
107
- import {
88
+ defaultV2ConfigPath,
108
89
  listZcodeModels,
109
- prepareZcodeHome,
110
90
  resolveZcodeModelRef,
111
91
  splitZcodeModelRef,
112
92
  type ZcodeSourcePaths,
113
93
  } from "./preparer.ts";
94
+ import { ensureAppServerLauncher } from "./appserver-launcher.ts";
114
95
  import { readZcodeSessionView } from "./reader.ts";
115
- import { runAppServerSmokeProbe } from "./appserver-probe.ts";
116
96
  import { AppServerConnection, buildAppServerEnv, isAppServerRpcError } from "./connection.ts";
117
- import { SessionChannel, type SessionCreateParams, type SessionTurnResult } from "./session-channel.ts";
97
+ import {
98
+ SessionChannel,
99
+ TurnTimeoutError,
100
+ type SessionCreateParams,
101
+ type SessionTurnResult,
102
+ } from "./session-channel.ts";
103
+ import { toErrorMessage } from "../../../../core/error-message.ts";
118
104
 
119
105
  const logger = getLogger("subagents");
120
106
 
@@ -128,30 +114,16 @@ const PROBE_VERSION_TIMEOUT_MS = 15_000;
128
114
  */
129
115
  const COMMON_THOUGHT_LEVELS: readonly string[] = ["low", "high", "max"];
130
116
 
131
- // ============================================================
132
- // [R4/R5] 执行模式分派(D2:定向不探不降;缺省探 + 降)
133
- // ============================================================
134
-
135
- export type ZcodeRunMode = "appserver" | "spawn";
136
-
137
- /** [R5] 三态:undefined = 未定向(缺省路径,探 + 降);定向值原样。 */
138
- export type ZcodeModePin = ZcodeRunMode | undefined;
139
-
140
- /**
141
- * [R5 D2④] 定向判定:`XYZ_ZCODE_MODE=appserver|spawn` 是**定向**(不探不降——失败
142
- * 直接上报,不给定向者换通道);undefined = 缺省(probe 门控 + 首败降级)。
143
- * deps.processEnv 可注入(测试钉扎三态)。
144
- */
145
- export function pinnedZcodeMode(env: NodeJS.ProcessEnv = process.env): ZcodeModePin {
146
- const v = env[ZCODE_MODE_ENV_VAR];
147
- return v === "appserver" || v === "spawn" ? v : undefined;
117
+ /** 宿主 HOME 下 zcode 会话 db 的绝对路径(共享 HOME 形态的 read/handle 锚点)。 */
118
+ export function hostZcodeDbPath(): string {
119
+ return path.join(os.homedir(), ...ZCODE_HOST_DB_SUFFIX);
148
120
  }
149
121
 
150
122
  /** ZcodeEngine 构造依赖(全部可注入——测试不依赖真机 CLI/真凭据)。 */
151
123
  export interface ZcodeEngineDeps {
152
124
  /**
153
- * 引擎数据目录(池根:<dir>/engines/zcode/<poolKey>/)。来源通道(宿主 dataDir)
154
- * 由并行任务/W3 解决——本引擎只消费,见 registration.ts 缺省解析。
125
+ * 引擎数据目录(journal 分组根 <dir>/engines/zcode/shared/ 与 stderr 日志落点)。
126
+ * 来源通道(宿主 dataDir)见 registration.ts 缺省解析。
155
127
  */
156
128
  engineDataDir: () => string;
157
129
  /** zcode CLI 路径;缺省 ZCODE_CLI_DEFAULT_PATH。 */
@@ -160,35 +132,18 @@ export interface ZcodeEngineDeps {
160
132
  sources?: ZcodeSourcePaths;
161
133
  /** 版本探测执行器(probe check "version";测试注入 fake 防真实子进程)。 */
162
134
  probeVersion?: (cliPath: string) => Promise<string | undefined>;
163
- /** spawn 执行器(测试注入 fake 进程)。 */
164
- launch?: (opts: { cliPath: string; args: string[]; env: NodeJS.ProcessEnv }) => ZcodeLaunchedProcess;
165
- /** env 基底(测试注入;缺省 process.env——模式分派与 app-server env 组装都经它)。 */
135
+ /** env 基底(测试注入;缺省 process.env——app-server env 组装经它)。 */
166
136
  processEnv?: NodeJS.ProcessEnv;
167
- /**
168
- * [R5 D8] 协议冒烟探针总预算(缺省 ZCODE_APPSERVER_PROBE_BUDGET_MS = 10s;测试
169
- * 注入短预算验证超时降级路径,不真等 10s)。
170
- */
171
- probeBudgetMs?: number;
172
137
  }
173
138
 
174
- /** 常驻运行时(每引擎实例一份;dispose/凭据刷新/池变更时整件丢弃重建)。 */
139
+ /** 常驻运行时(每引擎实例一份;dispose 时整件丢弃)。 */
175
140
  interface AppServerRuntime {
176
141
  conn: AppServerConnection;
177
142
  channel: SessionChannel;
178
- homePoolKey: string;
179
- homeDir: string;
180
143
  /** 在途会话登记(dispose 时 fire session/close 的目标集;settle 后移除)。 */
181
144
  activeSessions: Set<string>;
182
145
  }
183
146
 
184
- /** 已持有的常驻 HOME 记账(锁心跳归属;dispose 不释放——锁随宿主进程存活)。 */
185
- interface AppServerHomeState {
186
- poolKey: string;
187
- homeDir: string;
188
- lockPath: string;
189
- stopHeartbeat: () => void;
190
- }
191
-
192
147
  /** zcode 引擎适配器。 */
193
148
  export class ZcodeEngine implements EnginePort {
194
149
  readonly id = ZCODE_ENGINE_ID;
@@ -196,30 +151,20 @@ export class ZcodeEngine implements EnginePort {
196
151
  private readonly deps: ZcodeEngineDeps;
197
152
  private probeCache: ProbeReport | undefined;
198
153
  private appserverRuntime: AppServerRuntime | undefined;
199
- private homeState: AppServerHomeState | undefined;
200
- /** 并发任务的首次锁获取在途 promise(重入守卫,见 ensureAppServerHome)。 */
201
- private homeAcquireInFlight: Promise<AppServerHomeHandle> | undefined;
202
154
  /**
203
- * [R5 D2③] 探针结论(与 CLI 文件 mtime 绑定的内存缓存):mtime 未变不重探;
204
- * zcode 升级(mtime 变化)后首个任务前重探。不落盘——进程重启后重探重建。
155
+ * [P0-1 U4] 引擎停机标志(dispose 置位,不重置——dispose 后首个 run 走重建路径
156
+ * 不受影响):瞬时重试判定据此排除 dispose 收割引发的崩溃形态——停机后的重试轮
157
+ * 会经 ensureAppServerRuntime 惰性重建进程(复活已停机引擎),违背 dispose 防泄漏
158
+ * 语义。
205
159
  */
206
- private smokeConclusion: { cliMtimeMs: number; ok: boolean; detail: string } | undefined;
207
- /**
208
- * [R5 D2②] 漂移降级标志(内存化,不落盘):首任务运行中命中 -32601/-32602 后置
209
- * true,本进程后续任务直走 spawn;进程重启后经探针门控重建(重探通过则恢复
210
- * app-server)。
211
- */
212
- private driftDegraded = false;
160
+ private disposed = false;
213
161
 
214
162
  constructor(deps: ZcodeEngineDeps) {
215
163
  this.deps = deps;
216
164
  }
217
165
 
218
166
  /**
219
- * zcode 链路实际接通的能力(D3 链路口径;R4 D5 升级序:eventGranularity
220
- * coarse→stream——链路先行〔session/event payload.delta → text_delta 实时流出,
221
- * turn.terminal → turn_end,收尾帧 usage → message_end.usage〕,其余能力位维持
222
- * 现值。声明升级必须先改链路再改声明(C4 原则)。
167
+ * zcode 链路实际接通的能力(D3 链路口径。声明升级必须先改链路再改声明(C4 原则)。
223
168
  */
224
169
  capabilities(): EngineCapabilities {
225
170
  return {
@@ -227,12 +172,11 @@ export class ZcodeEngine implements EnginePort {
227
172
  schemaEnforcement: "emulated",
228
173
  // send-while-running 恒 -32010 硬错误(旧实测)——app-server 常驻化不改变此判据
229
174
  steer: "unsupported",
230
- // 无同进程 idle 复用(D4:每任务自包含 create→run→close);--resume 是冷启动
175
+ // 无同进程 idle 复用(D4:每任务自包含 create→run→close
231
176
  conversation: "unsupported",
232
177
  // 无 --append-system-prompt flag(实测拒收)——persona 只能拼进 prompt
233
178
  personaInjection: "prompt",
234
- // app-server 推送流实时流出(R4);spawn 降级路径退化为终态两事件(D2 声明不降级
235
- // ——降级是任务级兜底非能力级,record 留痕降级事实)
179
+ // app-server 推送流实时流出(session/event payload.delta → text_delta)
236
180
  eventGranularity: "stream",
237
181
  // 首期未接 worktree 隔离(公共层 worktree-manager 接入后升 emulated)
238
182
  sandbox: "none",
@@ -245,10 +189,12 @@ export class ZcodeEngine implements EnginePort {
245
189
  interrupt: "kill-only",
246
190
  // --mode build/edit/plan/yolo 原生权限档位
247
191
  permissionMode: "native",
192
+ // [D3-④] 无 turn_end 语义,轮数上限不可兑现(预检 gate 据此同步拒绝)
193
+ maxTurns: false,
248
194
  };
249
195
  }
250
196
 
251
- /** 探针(D7):二进制存在 + 版本解析 + golden 样本干跑回归(zsub 式逆向契约引擎必做)。 */
197
+ /** 探针(D7):二进制存在 + 版本解析(zcode 无公开契约,版本漂移的入口信号)。 */
252
198
  async probe(opts?: { force?: boolean }): Promise<ProbeReport> {
253
199
  if (!opts?.force && this.probeCache) return this.probeCache;
254
200
 
@@ -265,9 +211,6 @@ export class ZcodeEngine implements EnginePort {
265
211
  engineVersion = version.engineVersion;
266
212
  }
267
213
 
268
- // check 3:golden 样本干跑回归(parser 对实录样本解析——格式漂移入口拦截)
269
- checks.push(this.probeGoldenCheck());
270
-
271
214
  const ok = checks.every((c) => c.ok);
272
215
  const report: ProbeReport = {
273
216
  ok,
@@ -306,144 +249,51 @@ export class ZcodeEngine implements EnginePort {
306
249
  };
307
250
  }
308
251
 
309
- /** check 3:golden 样本干跑(parser 对实录样本解析——stdout 格式漂移的入口拦截)。 */
310
- private probeGoldenCheck(): ProbeReport["checks"][number] {
311
- const golden = parseZcodeTerminal(ZCODE_GOLDEN_STDOUT);
312
- const goldenOk = golden.ok && golden.payload.sessionId !== undefined && golden.payload.usage !== undefined;
313
- return {
314
- name: "golden-regression",
315
- ok: goldenOk,
316
- detail: goldenOk
317
- ? "parser 对 0.16.5 实录样本回归通过(sessionId/response/usage 形状完整)"
318
- : `解析实录样本失败:${golden.ok ? "字段缺失(sessionId/usage)" : golden.reason}`,
319
- };
320
- }
321
-
322
252
  /** 探针失败的恢复指引(§3.3.3 终态四:版本确认命令 + 探针重跑 + 调研文档路径)。 */
323
253
  private probeFailureRecovery(cliPath: string): NonNullable<ProbeReport["error"]> {
324
254
  return {
325
255
  code: "engine_probe_failed",
326
256
  recovery:
327
257
  `Run \`node ${cliPath} --version\` 确认 zcode CLI 可用且版本未漂移,然后重跑探针(重新初始化引擎或 probe({force:true}))。` +
328
- `若 stdout 格式已变:把新样本补录进 golden 库(__tests__/__fixtures__/zcode-golden-spawn.json golden-sample.ts)并更新 parser。` +
258
+ `若 app-server 协议已漂移(RPC 错误),重启 ZCode 或固定 zcode 版本后重试。` +
329
259
  `参照 docs/research/agent-engine-zcode.md。`,
330
260
  };
331
261
  }
332
262
 
333
- /**
334
- * D1 主语义 + [R5] D2 降级链四步:
335
- * ① 定向(XYZ_ZCODE_MODE=appserver|spawn):不探不降——定向者要的就是这条通道,
336
- * 失败直接上报(spawn 兜底原路径 / appserver 直连);
337
- * ② 缺省 + 已漂移降级(内存标志):后续任务直走 spawn(record 标注降级事实);
338
- * ③ 缺省 + 探针门控(结论与 CLI mtime 绑定):探针失败 → 本任务起直接 spawn;
339
- * ④ 缺省 + 探针通过但首任务命中漂移类 RPC 错误(-32601/-32602)→ 本任务降级
340
- * spawn 重跑一次(同一任务,结果标注降级)+ 后续任务直走 spawn。
341
- */
342
- async run(task: AgentTaskSpec, ctx: RunContext): Promise<EngineRunResult> {
343
- this.rejectUnsupportedTaskShapes(task);
344
- const pin = pinnedZcodeMode(this.deps.processEnv ?? process.env);
345
- if (pin === "appserver") return (await this.runViaAppServer(task, ctx)).result;
346
- if (pin === "spawn") return this.runViaSpawn(task, ctx);
347
-
348
- // 缺省:probe 门控 + 首败降级(D2)
349
- if (this.driftDegraded) {
350
- return this.runViaSpawn(task, ctx, {
351
- degradedReason:
352
- "protocol-drift(app-server 首任务命中 -32601/-32602 漂移类错误,本进程后续任务直走 spawn;进程重启后重探重建)",
353
- });
354
- }
355
- // pre-aborted 短路:不触发探针(不 spawn 探针进程),由常驻路径内部合成中止终态
356
- if (ctx.signal?.aborted !== true) {
357
- const gate = await this.appServerProbeGate(task);
358
- if (!gate.ok) {
359
- return this.runViaSpawn(task, ctx, {
360
- degradedReason: `probe-failed(协议冒烟探针未通过:${gate.detail};CLI mtime 变化后首个任务前重探)`,
361
- });
362
- }
363
- }
364
- const { result, driftCode } = await this.runViaAppServer(task, ctx);
365
- if (driftCode === undefined) return result;
366
- // D2② 首败降级:漂移错误 → 标志内存化 + 同一任务 spawn 重跑一次(结果标注)。
367
- // [RX2-F3] skipCtxModelWarn:appserver 首跑已出声过 ctxModel 忽略留痕,同任务
368
- // spawn 重跑再 warn 一遍是噪音(探针场景同款自我要求)——重跑侧跳过。
369
- this.driftDegraded = true;
370
- logger.warn(
371
- `[zcode-engine] app-server 漂移类错误(RPC code ${driftCode})——本任务降级 spawn 重跑,后续任务直走 spawn`,
372
- { taskId: ctx.taskId },
373
- );
374
- return this.runViaSpawn(task, ctx, {
375
- degradedReason: `protocol-drift(首任务 app-server 命中 RPC code ${driftCode},已降级 spawn 重跑本任务;后续任务直走 spawn)`,
376
- skipCtxModelWarn: true,
377
- });
378
- }
379
-
380
- /**
381
- * [R5 D8] 探针门控:CLI 文件 mtime 与结论绑定——mtime 未变命中缓存不重探;变化
382
- * (zcode 升级)或首次 → 独立短命连接上跑协议冒烟(appserver-probe.ts;必须用已
383
- * 引导的常驻 HOME——D7 教训「先 bootstrap 再 probe 否则永远误降级」)。CLI 不存在
384
- * 按探针失败处理(spawn 路径自身还有 binary 检查兜底)。
385
- */
386
- private async appServerProbeGate(task: AgentTaskSpec): Promise<{ ok: boolean; detail: string }> {
387
- const cliPath = this.deps.cliPath ?? ZCODE_CLI_DEFAULT_PATH;
388
- let cliMtimeMs: number;
389
- try {
390
- cliMtimeMs = fs.statSync(cliPath).mtimeMs;
391
- } catch {
392
- return { ok: false, detail: `zcode CLI 不存在:${cliPath}` };
393
- }
394
- if (this.smokeConclusion !== undefined && this.smokeConclusion.cliMtimeMs === cliMtimeMs) {
395
- return this.smokeConclusion; // mtime 未变不重探(D2③)
396
- }
397
- const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
398
- const home = await this.ensureAppServerHome(modelRef);
399
- const r = await runAppServerSmokeProbe({
400
- cliPath,
401
- homeDir: home.homeDir,
402
- baseEnv: this.deps.processEnv ?? process.env,
403
- stderrLogPath: path.join(this.deps.engineDataDir(), "logs", "zcode-appserver-probe-stderr.log"),
404
- ...(this.deps.probeBudgetMs !== undefined ? { budgetMs: this.deps.probeBudgetMs } : {}),
405
- });
406
- this.smokeConclusion = { cliMtimeMs, ok: r.ok, detail: r.detail };
407
- logger.debug("[zcode-engine] appserver smoke probe", { ok: r.ok, detail: r.detail, cliMtimeMs });
408
- return this.smokeConclusion;
263
+ /** D1 主语义:唯一通道 = app-server 常驻连接(spawn 降级链已删除)。 */
264
+ async run(task: AgentCallOpts, ctx: RunContext): Promise<EngineRunResult> {
265
+ // [D3-④] fork/conversation/maxTurns 的能力拒绝已上提到宿主调用前预检
266
+ //(common/capability-gate,capabilities.maxTurns 扩位承载)——引擎内不再做
267
+ // shape 检查(拦截逻辑单点化,重演双轨根因的形态被删除)。
268
+ return this.runViaAppServer(task, ctx);
409
269
  }
410
270
 
411
271
  // ============================================================
412
- // [R4] app-server 常驻路径(D1/D3/D4/D7
272
+ // app-server 常驻路径(D1/D3/D4)
413
273
  // ============================================================
414
274
 
415
275
  /**
416
- * 常驻路径主编排:常驻 HOME(锁/派生/孤儿回收/凭据刷新——appserver-home D7 全量)→
417
- * 惰性连接 + runTurn(事件时序前移:text_delta 流式、终态后 message_end/turn_end)→
418
- * schema 仿真重试(与 spawn 同编排)→ outcome/handle。poolKey 静态常量,
419
- * onPoolResolved 在 prepare 期、onHandleReady 在 create 应答后(§3.4 不变量 3)。
420
- *
421
- * [R5] 返回附带 driftCode:末轮 attempt 以漂移类 RPC 错误(-32601/-32602)收场时
422
- * 给出 code(run 编排降级 spawn 重跑);其余终态(成功/中止/非漂移失败)为
423
- * undefined——-32004/-32010/-32603 按错误规格表各自上报,不降级。
276
+ * 常驻路径主编排:模型解析(v2 单源校验)→ 惰性连接 + runTurn(事件时序前移:
277
+ * text_delta 流式、终态后 message_end/turn_end)→ schema 仿真重试 → outcome/handle。
278
+ * poolKey 固定 'shared'(共享宿主 HOME,无池),onPoolResolved prepare 期、
279
+ * onHandleReady 在 create 应答后(§3.4 不变量 3)。
424
280
  */
425
- private async runViaAppServer(
426
- task: AgentTaskSpec,
427
- ctx: RunContext,
428
- ): Promise<{ result: EngineRunResult; driftCode: number | undefined }> {
281
+ private async runViaAppServer(task: AgentCallOpts, ctx: RunContext): Promise<EngineRunResult> {
429
282
  const startedAt = Date.now();
430
283
  // pre-aborted 短路:取消先于启动——不创建会话、不触发连接惰性启动(防误杀共享
431
- // 进程殃及在途任务;spawn 路径无此形态因每任务独占进程)
284
+ // 进程殃及在途任务)
432
285
  if (ctx.signal?.aborted === true) {
433
286
  return this.abortedAppServerRun(task, ctx, startedAt);
434
287
  }
435
288
 
436
- // ① prepare 期:模型解析(provider 体系校验——错误语义与 spawn 同为进程创建前
437
- // reject)+ 常驻 HOME(锁判定/派生/pidfile 孤儿回收/config 内容 hash 刷新)
289
+ // ① prepare 期:模型解析(provider 体系校验——错误语义为进程创建前 reject)
438
290
  const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
439
291
  this.warnIgnoredCtxModel(task, ctx, modelRef);
440
292
  // [RX2-F1] 非常见档位出声一行(不拦截透传);放主编排而非 attemptAppServerTurn——
441
293
  // schema 重试轮会二次进 attempt,warn 只应随任务出声一次
442
294
  this.warnThoughtLevelUncommon(task, ctx);
443
- const home = await this.ensureAppServerHome(modelRef);
444
- // 对齐点③(不变量 3):poolKey 在 prepare 期声明(静态常量或派生目录名),
445
- // 早于连接建立与首个事件
446
- ctx.onPoolResolved?.(home.poolKey);
295
+ // 对齐点③(不变量 3):poolKey prepare 期声明,早于连接建立与首个事件
296
+ ctx.onPoolResolved?.(ZCODE_SHARED_POOL_KEY);
447
297
 
448
298
  const cwd = task.cwd ?? process.cwd();
449
299
  const schema = isPlainObject(task.schema) ? task.schema : undefined;
@@ -451,25 +301,17 @@ export class ZcodeEngine implements EnginePort {
451
301
 
452
302
  // ② 首轮执行 + schema 仿真重试(重试语义与不变量注释见 runAppServerAttemptsWithRetry)
453
303
  const usageAcc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false };
454
- const final = await this.runAppServerAttemptsWithRetry(task, ctx, home, modelRef, cwd, basePrompt, schema, usageAcc);
304
+ const final = await this.runAppServerAttemptsWithRetry(task, ctx, modelRef, cwd, basePrompt, schema, usageAcc);
455
305
 
456
306
  const outcome = this.finalizeOutcome(task, ctx, final, usageAcc, startedAt);
457
- return { result: { handle: this.appServerHandle(home.poolKey, outcome), outcome }, driftCode: driftCodeOf(final) };
307
+ return { handle: this.appServerHandle(outcome), outcome };
458
308
  }
459
309
 
460
- /** pre-aborted 短路收口(常驻路径专用):合成中止 outcome + 池锚定 handle。 */
461
- private abortedAppServerRun(
462
- task: AgentTaskSpec,
463
- ctx: RunContext,
464
- startedAt: number,
465
- ): { result: EngineRunResult; driftCode: number | undefined } {
466
- // poolKey 锚定:已持有 HOME(含派生场景)用其真实 poolKey;首次即 abort 尚无
467
- // HOME 可用,退回静态常量(此刻确实无派生事实可锚定)
468
- const poolKey = this.homeState?.poolKey ?? ZCODE_APPSERVER_POOL_KEY;
310
+ /** pre-aborted 短路收口:合成中止 outcome + 'shared' 锚定 handle。 */
311
+ private abortedAppServerRun(task: AgentCallOpts, ctx: RunContext, startedAt: number): EngineRunResult {
469
312
  // 对齐点③(不变量 3):onPoolResolved 必须先于首个事件 emit——本分支
470
- // finalizeOutcome 经 applyAbortedOutcome emit error 事件,缺本调用会把事件落
471
- // shared 占位池,与 handle.poolKey 漂移(契约同正常路径 prepare 期声明)
472
- ctx.onPoolResolved?.(poolKey);
313
+ // finalizeOutcome 经 applyAbortedOutcome emit error 事件
314
+ ctx.onPoolResolved?.(ZCODE_SHARED_POOL_KEY);
473
315
  const outcome = this.finalizeOutcome(
474
316
  task,
475
317
  ctx,
@@ -477,48 +319,95 @@ export class ZcodeEngine implements EnginePort {
477
319
  { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false },
478
320
  startedAt,
479
321
  );
480
- return { result: { handle: this.appServerHandle(poolKey, outcome), outcome }, driftCode: undefined };
322
+ return { handle: this.appServerHandle(outcome), outcome };
481
323
  }
482
324
 
483
325
  /**
484
- * 首轮执行 + schema 仿真重试编排(常驻路径):schema 任务校验失败时重试一次(强化
485
- * JSON 输出指令——与 spawn/structured-output 的重试语义对齐)。重试轮是独立会话的
486
- * 独立 LLM 调用:token 计入 outcome.usage 总量;事件面 text_delta 按实际流出(含
487
- * 失败轮——journal 记录真实流水),message_end/turn_end 只在最终轮终态后合成
488
- * (不变量 2/5)。
326
+ * 首轮执行 + 双重试编排(常驻路径):
327
+ * - **schema 仿真重试**(既有语义):parsed 但校验失败时重试一次(强化 JSON 输出
328
+ * 指令——与 structured-output 的重试语义对齐)。
329
+ * - **瞬时失败自动重试一次**([P0-1 U4/D6]):末次 attempt 为 timeout 类(idle/
330
+ * ceiling)或连接崩溃类失败且非用户 abort → 用新会话重跑一次(attempt 本就每次
331
+ * 新建会话)。重试轮 prompt 用 basePrompt 原样重跑(失败形态非 schema),文案补
332
+ * 「已自动重试一次」句(retried 标记仅对真实发生的重试生效)。
333
+ *
334
+ * 两次重试一次封顶各自独立(D6 被否①:多次重试/指数退避不做——重跑一轮=整任务
335
+ * 重算,一次封顶)。组合序:瞬时重试在前、schema 重试在后——瞬时重试轮 parsed 且
336
+ * 校验失败时仍进 schema 重试(末次 attempt 语义,schema 重试编排保持现状不动)。
337
+ *
338
+ * 重试轮是独立会话的独立 LLM 调用:token 计入 outcome.usage 总量;事件面
339
+ * text_delta 按实际流出(含失败轮——journal 记录真实流水),message_end/turn_end
340
+ * 只在最终轮终态后合成(不变量 2/5)。
489
341
  */
490
342
  private async runAppServerAttemptsWithRetry(
491
- task: AgentTaskSpec,
343
+ task: AgentCallOpts,
492
344
  ctx: RunContext,
493
- home: AppServerHomeHandle,
494
345
  modelRef: string,
495
346
  cwd: string,
496
347
  basePrompt: string,
497
- schema: object | undefined,
348
+ schema: JsonSchemaObject | undefined,
498
349
  usageAcc: { input: number; output: number; cacheRead: number; cacheWrite: number; has: boolean },
499
350
  ): Promise<AttemptResult> {
500
- let final = await this.attemptAppServerTurn(task, ctx, home, modelRef, cwd, basePrompt);
351
+ const attemptStartedAt = Date.now();
352
+ let final = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt);
501
353
  accumulateUsage(usageAcc, final);
354
+ // [P0-1 U4/D6] 瞬时失败自动重试一次:判据 = run-failed 且 transient 形态标记
355
+ // (类型化,不经字符串反推;RPC 错误/status=error 终态等有应答的精确归类形态
356
+ // 构造处即无 transient——含协议漂移类,漂移不再降级 spawn、直接报错)+ 非用户
357
+ // 已取消 + 非引擎停机(dispose 收割引发的崩溃不重试——停机后惰性重建 = 复活
358
+ // 进程,违背 dispose 防泄漏语义)。
359
+ // 预算继承(P-Z4):显式总上界预算下重试轮上界 = 剩余(总 − 已耗尽),剩余不足
360
+ // 最小下限不重试直接终态化;重试轮启动即 journal 出声(D6:重试事实记入 journal)。
361
+ if (
362
+ final.kind === "run-failed" &&
363
+ final.transient !== undefined &&
364
+ ctx.signal?.aborted !== true &&
365
+ !this.disposed
366
+ ) {
367
+ const budget = resolveTransientRetryBudget(explicitTurnBudgetMs(), Date.now() - attemptStartedAt);
368
+ if (budget.state === "depleted") {
369
+ logger.warn(
370
+ `[zcode-engine] 末次 attempt 瞬时失败(${final.transient})——显式总上界预算剩余不足 ${ZCODE_TURN_RETRY_MIN_BUDGET_MS}ms,不重试直接终态化(预算继承:重试不重置总预算)`,
371
+ );
372
+ } else {
373
+ logger.warn(
374
+ `[zcode-engine] 末次 attempt 瞬时失败(${final.transient})——止损链已终局,新会话自动重试一次` +
375
+ (budget.state === "inherit"
376
+ ? `(预算继承:重试轮总上界=剩余 ${budget.remainingMs}ms,不重置总预算)`
377
+ : "(无显式总上界预算,重试轮走 env/默认上界)"),
378
+ );
379
+ const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt, {
380
+ // 预算继承传递点(D2 内部传参面):显式预算 → 剩余值;无显式预算 → 缺省
381
+ // (channel 侧走同一 env/默认,与首轮行为一致)
382
+ ...(budget.state === "inherit" ? { turnTimeoutMs: budget.remainingMs } : {}),
383
+ // 重试事实进文案:「已自动重试一次」句仅对真实发生的重试生效(未重试形态
384
+ // 不含——与行为一致,§5.2 F-1/F-4)
385
+ retried: true,
386
+ });
387
+ accumulateUsage(usageAcc, retry);
388
+ final = retry;
389
+ }
390
+ }
502
391
  if (final.kind === "parsed" && final.schemaResult !== undefined && !final.schemaResult.ok && schema !== undefined) {
503
392
  const retryPrompt = appendSchemaRetryDirective(basePrompt, final.schemaResult.error);
504
- const retry = await this.attemptAppServerTurn(task, ctx, home, modelRef, cwd, retryPrompt);
393
+ const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, retryPrompt);
505
394
  accumulateUsage(usageAcc, retry);
506
395
  final = retry;
507
396
  }
508
397
  return final;
509
398
  }
510
399
 
511
- /** 常驻路径的 handle 合成(poolKey = 常驻 HOME 实际目录名——锚定不变量载体)。 */
512
- private appServerHandle(poolKey: string, outcome: AgentOutcome): EngineHandle {
400
+ /** 常驻路径的 handle 合成(poolKey 固定 'shared';dbPath = 宿主 HOME 绝对路径)。 */
401
+ private appServerHandle(outcome: AgentOutcome): EngineHandle {
513
402
  return {
514
403
  data: {
515
404
  v: 1,
516
405
  engineId: ZCODE_ENGINE_ID,
517
406
  sessionRef: {
518
- dbPath: ZCODE_POOL_DB_RELATIVE_PATH,
407
+ dbPath: hostZcodeDbPath(),
519
408
  ...(outcome.sessionId !== undefined ? { sessionId: outcome.sessionId } : {}),
520
409
  },
521
- poolKey,
410
+ poolKey: ZCODE_SHARED_POOL_KEY,
522
411
  ...(this.probeCache?.engineVersion !== undefined && this.probeCache.engineVersion !== ""
523
412
  ? { engineVersion: this.probeCache.engineVersion }
524
413
  : {}),
@@ -529,17 +418,21 @@ export class ZcodeEngine implements EnginePort {
529
418
 
530
419
  /**
531
420
  * 单轮常驻执行:runTurn 组合面 + D3 abort 链 + 事件前移(text_delta 实时流出;
532
- * 终态数据经 read 兜底收口后才 resolve——不变量 1/2)。产出三态与 spawn 同构。
421
+ * 终态数据经 read 兜底收口后才 resolve——不变量 1/2)。
422
+ *
423
+ * @param opts turnTimeoutMs:显式总上界传参面(D2 内部传参点)——U4/D6 预算继承
424
+ * 向重试轮传剩余值;缺省不传(channel 走 env→默认,首轮行为)。
425
+ * retried:瞬时重试轮标记——失败文案补「已自动重试一次」句(F-1/F-4)。
533
426
  */
534
427
  private async attemptAppServerTurn(
535
- task: AgentTaskSpec,
428
+ task: AgentCallOpts,
536
429
  ctx: RunContext,
537
- home: AppServerHomeHandle,
538
430
  modelRef: string,
539
431
  cwd: string,
540
432
  prompt: string,
433
+ opts: { turnTimeoutMs?: number; retried?: boolean } = {},
541
434
  ): Promise<AttemptResult> {
542
- const rt = this.ensureAppServerRuntime(home);
435
+ const rt = this.ensureAppServerRuntime();
543
436
  const { providerId, modelId } = splitZcodeModelRef(modelRef);
544
437
  const createParams = buildAppServerCreateParams(task, providerId, modelId, cwd);
545
438
 
@@ -557,17 +450,22 @@ export class ZcodeEngine implements EnginePort {
557
450
  signalSessionCreated?.();
558
451
  // §3.4 不变量 3:create 应答后立即回填(早于 subscribe/send/终态/run resolve)
559
452
  ctx.onHandleReady?.({
560
- sessionRef: { dbPath: ZCODE_POOL_DB_RELATIVE_PATH, sessionId },
561
- poolKey: home.poolKey,
453
+ sessionRef: { dbPath: hostZcodeDbPath(), sessionId },
454
+ poolKey: ZCODE_SHARED_POOL_KEY,
562
455
  });
563
456
  },
457
+ // 显式总上界传参(D2/U4:缺省缺席——channel 侧 resolveTurnTimerMs 走 env→默认)
458
+ ...(opts.turnTimeoutMs !== undefined ? { turnTimeoutMs: opts.turnTimeoutMs } : {}),
564
459
  });
565
460
 
566
461
  // D3 abort 链:signal abort → ① session/stop {sessionId} ② grace 窗口确认终态
567
462
  // ③ stop 失败/超时 → killChain 杀共享进程(接受连坐——协议已不可信)→ 在途
568
463
  // 其他任务走崩溃路径。capabilities.interrupt 维持 kill-only 不升级(C4)。
464
+ // [P0-1 U2] 用户取消入口 = escalateOn:"turn-settled"(grace 窗口确认 turn 落定,
465
+ // 现状语义零改动);channel 超时判死(TurnTimeoutError)走 catch 分流的
466
+ // escalateOn:"stop-outcome" 入口(stop 应答三态裁决)。
569
467
  const onAbort = (): void => {
570
- void this.appServerAbortChain(rt, turn, () => currentSessionId, sessionCreated);
468
+ void this.appServerAbortChain(rt, turn, () => currentSessionId, sessionCreated, { escalateOn: "turn-settled" });
571
469
  };
572
470
  if (ctx.signal !== undefined) {
573
471
  if (ctx.signal.aborted) onAbort();
@@ -577,12 +475,37 @@ export class ZcodeEngine implements EnginePort {
577
475
  try {
578
476
  const r = await turn;
579
477
  // stop 优雅生效(终态在 grace 内到达):宿主已取消——按中止终态收口(record
580
- // 终态迁移由编排层 CAS 决定,engine 侧保持与 spawn abort 同构语义)
478
+ // 终态迁移由编排层 CAS 决定)
581
479
  if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
582
480
  return parsedAppServerAttempt(task, r);
583
481
  } catch (err) {
584
482
  if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
585
- return failedAppServerAttempt(err, home, currentSessionId);
483
+ // [P0-1 U2] 超时入口(D3 v1.1):turn 已被 channel 判死 reject——升级判据不能
484
+ // 再挂在 turn 落定上(race 恒真,killChain 结构性不可达的 v1 击穿点),改以
485
+ // stop 应答三态裁决。await 链终局(非 fire-and-forget):outcome 止损文案与
486
+ // 重试时序(D6,u-z4)都依赖链终局信号——止损完成前不合成终态。
487
+ if (err instanceof TurnTimeoutError) {
488
+ const stopPath = await this.appServerAbortChain(
489
+ rt,
490
+ turn,
491
+ () => currentSessionId,
492
+ sessionCreated,
493
+ { escalateOn: "stop-outcome" },
494
+ );
495
+ // [P0-1 U4] timeout 类(idle/ceiling 都算)是 D6 明文的可重试形态——结构化
496
+ // 标记(TurnTimeoutError 类型化判据,不经字符串匹配,D4 同精神)
497
+ return timeoutAppServerAttempt(err, currentSessionId, stopPath, { retried: opts.retried });
498
+ }
499
+ // [P0-1 U4] 连接崩溃收割形态(failAllTurns 的错误,D6 第二可重试形态)判据:
500
+ // 非 RPC error(服务端无明确应答——有应答即精确错误归类,非瞬时崩溃面)且
501
+ // conn 不存活。时序可靠性:catch 时刻紧随 onClose 收割,连接重建仅由
502
+ // conn.request 惰性触发——本链路中 runTurn finally 的 closeSession 对死连接
503
+ // 短路(channel 侧 !alive 守卫)、stop 只属 abort/超时入口(前者已被
504
+ // signal.aborted 短路、后者走上一分支)——此刻无 request 可重建,判据可靠。
505
+ if (!isAppServerRpcError(err) && !rt.conn.alive) {
506
+ return failedAppServerAttempt(err, currentSessionId, { retried: opts.retried, transient: "conn-closed" });
507
+ }
508
+ return failedAppServerAttempt(err, currentSessionId, { retried: opts.retried });
586
509
  } finally {
587
510
  if (currentSessionId !== undefined) rt.activeSessions.delete(currentSessionId);
588
511
  if (ctx.signal !== undefined) ctx.signal.removeEventListener("abort", onAbort);
@@ -590,166 +513,157 @@ export class ZcodeEngine implements EnginePort {
590
513
  }
591
514
 
592
515
  /**
593
- * D3 abort 链执行体(fire-and-forget——与 turn promise 并行推进):
594
- * stop 帧(超时 ZCODE_APPSERVER_STOP_TIMEOUT_MS)→ grace 窗口内 turn 落定即止
595
- * (不杀共享进程)→ 超时 killChain(conn.shutdown 全序:SIGTERM→grace→SIGKILL)。
596
- * turn 的最终落定由 attempt 主路径 await 收口,本链不直接产出终态。abort 与
597
- * create 竞态(signal 先到、session 未建立):等会话建立(带上限)再发 stop——
598
- * 否则 stop 永远发不出,直接连坐杀共享进程。
516
+ * killChain 后等待连接 finalize 实际完成(child 置空 + onClose 广播)再宣告链终局:
517
+ * shutdown resolve `exit` 事件,而 finalize 挂 `close`(stdio 排空)——两者之间的
518
+ * 事件窗口内 conn.child 仍非 null,紧接的下一任务 request 会复用垂死进程(写入成功
519
+ * 但必败,走崩溃路径)而非触发重建。与 shutdownRuntimeAndDisposeChannel
520
+ * HARVEST_GRACE 同款 race 形态(close 永不到达不挂死)。超时入口的 await 链终局
521
+ * 语义(D6 重试时序依据)因此是「进程收割确认完成」而非「SIGTERM 已发出」。
522
+ */
523
+ private async awaitConnFinalized(rt: AppServerRuntime): Promise<void> {
524
+ if (!rt.conn.alive) return;
525
+ await new Promise<void>((resolve) => {
526
+ const timer = setTimeout(() => finish(), ZCODE_APPSERVER_HARVEST_GRACE_MS);
527
+ if (typeof timer.unref === "function") timer.unref();
528
+ const off = rt.conn.onClose(() => finish());
529
+ function finish(): void {
530
+ clearTimeout(timer);
531
+ off(); // Set.delete 幂等——并发触发无副作用
532
+ resolve();
533
+ }
534
+ });
535
+ }
536
+
537
+ /**
538
+ * D3 abort 链执行体(双入口分岔,P0-1 U2 参数化——设计
539
+ * timeout-zcode-turn-and-settled-watchdog.md §6 D3 v1.1):
540
+ * - **用户取消入口**(`escalateOn: "turn-settled"`,现状语义零改动;fire-and-forget——
541
+ * 与 turn promise 并行推进):stop 帧(超时 ZCODE_APPSERVER_STOP_TIMEOUT_MS)→
542
+ * grace 窗口内 turn 落定即止(不杀共享进程)→ 超窗 killChain(conn.shutdown 全序:
543
+ * SIGTERM→grace→SIGKILL)。turn 的最终落定由 attempt 主路径 await 收口,本链不
544
+ * 直接产出终态。abort 与 create 竞态(signal 先到、session 未建立):等会话建立
545
+ * (带上限)再发 stop——否则 stop 永远发不出,直接连坐杀共享进程。
546
+ * - **超时入口**(`escalateOn: "stop-outcome"`,channel 判死后由 catch await 链终局):
547
+ * turn 已 reject,对它 race 恒真不可用(v1 击穿点)——升级判据改挂在 **stop 应答
548
+ * 三态**:①成功应答 → 服务端接受停 turn,止损确认,链终止;②协议性 error 应答
549
+ * (有 error 帧即控制面活的证据,多因 runTurn finally 的 closeSession 先行关会话,
550
+ * 健康形态竞态)→ 链终止**不升级**(止损由 close 回收 + 服务端自治承担;把
551
+ * 「stop 报错」一律升级会误杀健康共享进程并连坐并发任务);③超时/写入失败/进程
552
+ * 死等连接级失败(控制面死)→ killChain 升级。判据实现依据:error 应答帧 reject
553
+ * 携带 number code(isAppServerRpcError);连接级失败是无 code 的新 Error
554
+ * (connection.request 三态 reject 形态)。返回值即止损路径(超时入口的 outcome
555
+ * 文案素材——D3 强制可观测面)。
599
556
  */
600
557
  private async appServerAbortChain(
601
558
  rt: AppServerRuntime,
602
559
  turn: Promise<SessionTurnResult>,
603
560
  getSessionId: () => string | undefined,
604
561
  sessionCreated: Promise<void>,
605
- ): Promise<void> {
562
+ entry: { escalateOn: "turn-settled" | "stop-outcome" },
563
+ ): Promise<AbortChainStopPath> {
564
+ const graceRaceThenKill = async (): Promise<AbortChainStopPath> => {
565
+ const settled = await Promise.race([
566
+ turn.then(
567
+ () => true,
568
+ () => true,
569
+ ),
570
+ delayResolved(ZCODE_APPSERVER_ABORT_GRACE_MS, false),
571
+ ]);
572
+ if (settled) return "settled-in-grace"; // stop 生效:终态在 grace 窗口内到达,共享进程不杀
573
+ logger.warn(
574
+ `[zcode-engine] abort grace 窗口内未见终态——killChain 收割共享进程(接受连坐,在途任务走崩溃路径)`,
575
+ );
576
+ await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
577
+ await this.awaitConnFinalized(rt);
578
+ return "escalated-kill";
579
+ };
580
+
606
581
  let sessionId = getSessionId();
607
582
  if (sessionId === undefined) {
608
583
  await Promise.race([sessionCreated, delayResolved(ZCODE_APPSERVER_STOP_TIMEOUT_MS, undefined)]);
609
584
  sessionId = getSessionId();
585
+ if (sessionId === undefined) {
586
+ // 会话始终未建立:超时入口实际不可达(TurnTimeoutError 只能发生在 openTurn
587
+ // 挂 timer 后,彼时 create 已成功),防御分支——无会话即无在途任务可止损;
588
+ // 用户取消入口保持既有语义:跳过 stop,grace race 兜底(create 竞态挂死形态)
589
+ if (entry.escalateOn === "stop-outcome") return "no-session";
590
+ return graceRaceThenKill();
591
+ }
592
+ }
593
+ // [u-z2 修复轮] alive 守卫(与 closeSession 的 `!conn.alive` return 同款防御,
594
+ // 对称补齐):进程在 turn 判死/abort 与本链发 stop 之间 finalize 完成的微窗口内,
595
+ // request 首行 ensureStarted 会惰性 spawn 新一代进程再写 stop 帧——凭空拉起无人
596
+ // 使用的进程,且 stop-outcome 入口会拿到新进程的「成功应答」误报止损确认。
597
+ // 不 alive = 连接级失败形态(进程已死即已收割):超时入口直接落杀链终局(等同
598
+ // 三态的连接级失败分支);用户取消入口跳过 stop 落回 grace race(turn 已被
599
+ // failAllTurns 收割则立即 settled,语义零变化)。
600
+ if (!rt.conn.alive) {
601
+ if (entry.escalateOn === "stop-outcome") return "stop-unreachable-killed";
602
+ return graceRaceThenKill();
610
603
  }
611
- if (sessionId !== undefined) {
612
- try {
613
- await rt.conn.request("session/stop", { sessionId }, { timeoutMs: ZCODE_APPSERVER_STOP_TIMEOUT_MS });
614
- } catch (err) {
615
- logger.debug(
616
- `[zcode-engine] session/stop 失败(${errMessage(err)})——grace 后走 killChain 兜底`,
604
+ try {
605
+ await rt.conn.request("session/stop", { sessionId }, { timeoutMs: ZCODE_APPSERVER_STOP_TIMEOUT_MS });
606
+ } catch (err) {
607
+ if (entry.escalateOn === "stop-outcome") {
608
+ if (isAppServerRpcError(err)) {
609
+ // 协议性 error:控制面活、会话已被回收(健康形态竞态)——不升级
610
+ logger.debug(
611
+ `[zcode-engine] session/stop 报协议性 error(${errMessage(err)})——会话已回收,控制面存活,不升级杀链`,
612
+ );
613
+ return "stop-rejected";
614
+ }
615
+ // ③ 连接级失败(请求超时/写入失败/进程死):控制面死,只有杀进程能止损
616
+ logger.warn(
617
+ `[zcode-engine] session/stop 无应答(${errMessage(err)})——升级 killChain 收割共享进程(超时入口,接受连坐)`,
617
618
  );
619
+ await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
620
+ await this.awaitConnFinalized(rt);
621
+ return "stop-unreachable-killed";
618
622
  }
623
+ logger.debug(
624
+ `[zcode-engine] session/stop 失败(${errMessage(err)})——grace 后走 killChain 兜底`,
625
+ );
619
626
  }
620
- const settled = await Promise.race([
621
- turn.then(
622
- () => true,
623
- () => true,
624
- ),
625
- delayResolved(ZCODE_APPSERVER_ABORT_GRACE_MS, false),
626
- ]);
627
- if (settled) return; // stop 生效:终态在 grace 窗口内到达,共享进程不杀
628
- logger.warn(
629
- `[zcode-engine] abort grace 窗口内未见终态——killChain 收割共享进程(接受连坐,在途任务走崩溃路径)`,
630
- );
631
- await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
627
+ if (entry.escalateOn === "stop-outcome") return "stop-acked"; // ① 成功应答:止损确认,链终止
628
+ return graceRaceThenKill();
632
629
  }
633
630
 
634
- // ── 常驻 HOME 与运行时管理(D1/D6/D7)──────────────────────
631
+ // ── 常驻运行时管理(D1/D6)──────────────────────
635
632
 
636
633
  /**
637
- * 每任务的常驻 HOME 保障:已持有(lockfile.pid=本进程)→ 只做凭据刷新比对
638
- * (config 内容 hash;不一致重写 + 重建连接——在途任务走崩溃路径,换取凭据变更
639
- * 下一任务生效);未持有(首任务/锁被夺)→ acquireAppServerHome 全量(锁判定/
640
- * 派生/接管 + pidfile 孤儿回收/引导)+ 启动锁心跳。
634
+ * 惰性获取常驻运行时(D1:每引擎实例一条连接,全任务共享;连接自身的崩溃重建在
635
+ * connection 层内部完成——同一条代码路径,§3.4 不变量 4)。常驻进程不进宿主
636
+ * spawnedChildren、不调 onChildSpawned(D6——生命周期归 dispose)。进程级 --cwd
637
+ * 用引擎数据目录(连接跨任务共享的中性位置,工作区由 create
638
+ * workspace.workspacePath 按任务传递——D10 基线不预设任务级进程 cwd)。
639
+ * spawn 经 fs 拦截 wrapper(appserver-launcher:cli config 读取重定向为
640
+ * 「真实文件 + v2 provider 注入」——CLI 形态 app-server 在共享宿主 HOME 下的
641
+ * 唯一凭据供数通路,机制与漂移面见该文件头注)。
641
642
  */
642
- private async ensureAppServerHome(modelRef: string): Promise<AppServerHomeHandle> {
643
+ private ensureAppServerRuntime(): AppServerRuntime {
644
+ if (this.appserverRuntime !== undefined) return this.appserverRuntime;
645
+ const cliPath = this.deps.cliPath ?? ZCODE_CLI_DEFAULT_PATH;
643
646
  const engineDataDir = this.deps.engineDataDir();
644
- // 并发任务重入守卫:首任务在途的锁获取只跑一次(同进程并发 run 各自走判定循环
645
- // 会把「自己的活锁」误判成他人活持有 派生 -2 → poolKey 漂移触发 teardown 杀
646
- // 活连接)。在途完成后落回已持有路径(含凭据刷新比对)。
647
- if (this.homeAcquireInFlight !== undefined) {
648
- try {
649
- await this.homeAcquireInFlight;
650
- } catch (err) {
651
- // 共享失败不击穿并发任务:首任务 acquire 失败(如凭据缺失)后 homeState 未落,
652
- // 落到下方自行重走 acquire 一次——成功则本任务正常推进,仍失败则报本任务自身
653
- // 的错误语义(而非继承首任务的异常实例)
654
- logger.debug(
655
- `[zcode-engine] 并发等待的首任务 home acquire 失败(${errMessage(err)})——自行重走 acquire`,
656
- );
657
- }
658
- }
659
- if (this.homeState !== undefined && isLockHeldByUs(this.homeState.lockPath)) {
660
- const boot = bootstrapAppServerConfig({
661
- homeDir: this.homeState.homeDir,
662
- modelRef,
663
- sources: this.deps.sources,
664
- });
665
- if (boot.wroteConfig) this.teardownAppServerRuntime("credential-refresh");
666
- return {
667
- poolKey: this.homeState.poolKey,
668
- homeDir: this.homeState.homeDir,
669
- lockPath: this.homeState.lockPath,
670
- tookOver: false,
671
- orphanReap: "not-applicable",
672
- ...boot,
673
- };
674
- }
675
- const acquire = (async () => {
676
- const fresh = await acquireAppServerHome({ engineDataDir, modelRef, sources: this.deps.sources });
677
- if (this.homeState !== undefined) this.homeState.stopHeartbeat();
678
- this.homeState = {
679
- poolKey: fresh.poolKey,
680
- homeDir: fresh.homeDir,
681
- lockPath: fresh.lockPath,
682
- stopHeartbeat: startLockHeartbeat(fresh.lockPath),
683
- };
684
- logger.debug("[zcode-engine] appserver home acquired", {
685
- poolKey: fresh.poolKey,
686
- tookOver: fresh.tookOver,
687
- orphanReap: fresh.orphanReap,
688
- wroteConfig: fresh.wroteConfig,
689
- providers: fresh.providerIds.length,
690
- });
691
- if (fresh.wroteConfig || (this.appserverRuntime !== undefined && this.appserverRuntime.homePoolKey !== fresh.poolKey)) {
692
- this.teardownAppServerRuntime(fresh.wroteConfig ? "credential-refresh" : "home-pool-changed");
693
- }
694
- return fresh;
695
- })();
696
- this.homeAcquireInFlight = acquire;
697
- try {
698
- return await acquire;
699
- } finally {
700
- this.homeAcquireInFlight = undefined;
701
- }
702
- }
703
-
704
- /**
705
- * 惰性获取常驻运行时(D1:每引擎实例一条连接,全任务共享)。池 key 未变直接复用
706
- * (连接自身的崩溃重建在 connection 层内部完成——同一条代码路径,§3.4 不变量 4);
707
- * 池变更(派生目录名变化)→ 旧运行时整件丢弃(shutdown fire)+ 新建。常驻进程
708
- **不进**宿主 spawnedChildren、不调 onChildSpawned(D6——生命周期归 dispose)。
709
- */
710
- private ensureAppServerRuntime(home: AppServerHomeHandle): AppServerRuntime {
711
- if (this.appserverRuntime !== undefined && this.appserverRuntime.homePoolKey === home.poolKey) {
712
- return this.appserverRuntime;
713
- }
714
- if (this.appserverRuntime !== undefined) this.teardownAppServerRuntime("home-pool-changed");
647
+ const launcherScript = ensureAppServerLauncher(engineDataDir);
648
+ const env = buildAppServerEnv(this.deps.processEnv ?? process.env);
649
+ env.ZCODE_ENG_CLI_PATH = cliPath;
650
+ env.ZCODE_ENG_V2_CONFIG = this.deps.sources?.v2ConfigPath ?? defaultV2ConfigPath();
715
651
  const conn = new AppServerConnection({
716
- cliPath: this.deps.cliPath ?? ZCODE_CLI_DEFAULT_PATH,
717
- // 进程级 --cwd 用稳定 HOME(连接跨任务共享,工作区由 create 的
718
- // workspace.workspacePath 按任务传递——D10 基线不预设任务级进程 cwd)
719
- cwd: home.homeDir,
720
- env: buildAppServerEnv(home.homeDir, this.deps.processEnv ?? process.env),
721
- stderrLogPath: path.join(this.deps.engineDataDir(), "logs", "zcode-appserver-stderr.log"),
722
- // 每代进程 spawn 后写 pidfile(D6③ 孤儿回收的数据源;崩溃重建的代同样覆盖写)
723
- onSpawned: (child) => {
724
- void writeAppServerPidFile(home.homeDir, child.pid ?? -1).catch((err: unknown) => {
725
- logger.debug(`[zcode-engine] pidfile 写入失败(best-effort): ${errMessage(err)}`);
726
- });
727
- },
652
+ cliPath,
653
+ cwd: engineDataDir,
654
+ env,
655
+ launcherScript,
656
+ stderrLogPath: path.join(engineDataDir, "logs", "zcode-appserver-stderr.log"),
728
657
  });
729
658
  const rt: AppServerRuntime = {
730
659
  conn,
731
660
  channel: new SessionChannel(conn),
732
- homePoolKey: home.poolKey,
733
- homeDir: home.homeDir,
734
661
  activeSessions: new Set<string>(),
735
662
  };
736
663
  this.appserverRuntime = rt;
737
664
  return rt;
738
665
  }
739
666
 
740
- /** 丢弃当前常驻运行时(凭据刷新/池变更):shutdown fire(killChain 全序),在途任务走崩溃路径。 */
741
- private teardownAppServerRuntime(reason: string): void {
742
- const rt = this.appserverRuntime;
743
- if (rt === undefined) return;
744
- this.appserverRuntime = undefined;
745
- // 退订必须在崩溃收割**之后**:channel 的 onClose 收割(failAllTurns)是在途 turn
746
- // 的快速失败通道,先退订会让它们挂到 turnTimeoutMs(300s)预算耗尽
747
- void this.shutdownRuntimeAndDisposeChannel(rt).catch((err: unknown) => {
748
- logger.debug(`[zcode-engine] 常驻连接关闭失败(${reason},best-effort): ${errMessage(err)}`);
749
- });
750
- logger.debug(`[zcode-engine] appserver runtime torn down (${reason})`, { poolKey: rt.homePoolKey });
751
- }
752
-
753
667
  /**
754
668
  * [R5 修复 R4 既有竞态] shutdown → 等崩溃收割实际发生 → channel 退订。killChain 在
755
669
  * `exit` 事件 resolve,而连接 finalize(onClose → channel 的 failAllTurns)挂
@@ -757,6 +671,12 @@ export class ZcodeEngine implements EnginePort {
757
671
  * turn 会错过收割、挂到 turnTimeoutMs(300s)。退订前等 onClose 触发(本方法先于
758
672
  * shutdown 订阅;channel 的订阅在构造期更早——其 failAllTurns 先于本 promise
759
673
  * resolve 执行);ZCODE_APPSERVER_HARVEST_GRACE_MS 兜底防 `close` 永不到达时挂死。
674
+ * [P0-1 U5/D7] grace race 输掉(close 迟到/永不到达——stdio 被孙进程持有排空不
675
+ * 尽等病态形态)时,channel.dispose() 内置的 dispose 收割(failAllTurns 先于退订,
676
+ * SessionChannel.dispose)兜底在途 turn——退化终点从「挂满 turn 自身 idle/总上界
677
+ * 预算」收敛为「grace 窗口内明确失败」(设计 §3.4 退化路径闭合);race 窗口与
678
+ * awaitConnFinalized 同源同量级(ZCODE_APPSERVER_HARVEST_GRACE_MS)。正常 close
679
+ * 先到时 onClose 收割先行,dispose 收割幂等 no-op(零回归)。
760
680
  */
761
681
  private async shutdownRuntimeAndDisposeChannel(rt: AppServerRuntime): Promise<void> {
762
682
  const harvested = new Promise<void>((resolve) => {
@@ -766,20 +686,23 @@ export class ZcodeEngine implements EnginePort {
766
686
  });
767
687
  });
768
688
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
689
+ // close 未在 grace 内到达 → 输掉 race → channel.dispose() 的内置收割兜底
769
690
  await Promise.race([harvested, delayResolved(ZCODE_APPSERVER_HARVEST_GRACE_MS, undefined)]);
770
691
  rt.channel.dispose();
771
692
  }
772
693
 
773
694
  /**
774
- * [R1 D6/R4 主体] 引擎停机面:①fire 全部在途会话的 session/close 帧(不等待
695
+ * [R1 D6 主体] 引擎停机面:①fire 全部在途会话的 session/close 帧(不等待
775
696
  * 应答——D6① 顺序规定:close 帧必须先于 SIGTERM,否则对面来不及处理即被杀)→
776
697
  * ②同步 SIGTERM(conn.shutdown 调用内 killChain 前缀同步执行——同步面在返回
777
698
  * Promise 前完成)→ ③grace → SIGKILL(异步面,Promise resolve 于进程退出)。
778
699
  * 幂等:运行时字段取走即置空,二次调用零副作用;dispose 后首个 run 经
779
700
  * ensureAppServerRuntime 自动重建(与崩溃重建同一代码路径,不变量 4)。
780
- * 锁不释放(随宿主进程存活——活宿主持有语义;进程死锁自然无主可接管)。
781
701
  */
782
702
  async dispose(): Promise<void> {
703
+ // [P0-1 U4] 停机标志先行:在途任务的瞬时重试判定据此短路(dispose 收割引发的
704
+ // 崩溃不再触发重试轮——重试轮会经惰性重建复活已停机引擎)
705
+ this.disposed = true;
783
706
  const rt = this.appserverRuntime;
784
707
  if (rt === undefined) return;
785
708
  this.appserverRuntime = undefined;
@@ -787,8 +710,8 @@ export class ZcodeEngine implements EnginePort {
787
710
  // 必须先于 SIGTERM,否则对面来不及处理即被杀)。进程已死(child=null,如崩溃
788
711
  // 收割与 activeSessions 清理之间的微拍)则整体跳过:post 会经 ensureStarted 惰性
789
712
  // 拉起新进程再被同次 dispose 杀掉——无意义的 spawn+kill 循环(D6 dispose=防泄漏
790
- // 语义不制造新进程),且对面进程已不在,close 帧也没有送达对象。同步循环内
791
- // alive 不会中途翻转(进程退出是异步事件,本轮循环不可重入)
713
+ // 语义不制造新进程)。同步循环内 alive 不会中途翻转(进程退出是异步事件,本轮
714
+ // 循环不可重入)
792
715
  if (rt.conn.alive) {
793
716
  for (const sessionId of [...rt.activeSessions]) {
794
717
  rt.conn.post("session/close", { sessionId });
@@ -799,127 +722,11 @@ export class ZcodeEngine implements EnginePort {
799
722
  //竞态的修复体,见 shutdownRuntimeAndDisposeChannel——先退订会让在途 turn 挂到
800
723
  //turnTimeoutMs)
801
724
  await this.shutdownRuntimeAndDisposeChannel(rt);
802
- // 进程已死:pidfile 成为陈旧记录,清掉(防后续 pid 复用误判)
803
- try {
804
- fs.rmSync(path.join(rt.homeDir, ZCODE_APPSERVER_PIDFILE_NAME), { force: true });
805
- } catch (err) {
806
- logger.debug(`[zcode-engine] dispose 后 pidfile 清理失败(best-effort): ${errMessage(err)}`);
807
- }
808
- }
809
-
810
- // ============================================================
811
- // spawn 单轮路径(D2 兜底——R4 起仅 XYZ_ZCODE_MODE=spawn 定向 / R5 降级可达)
812
- // ============================================================
813
-
814
- /**
815
- * spawn 路径主编排(原 run 主体,行为零改动):preparer → launcher → parser → 仿真重试 → outcome/handle。
816
- * [R5] degrade 参数:降级链落点(探针失败 / 漂移首败重跑 / 降级后直走)——结果经
817
- * outcome.engineFallback 标注「degraded: spawn + 原因」(D9① 留痕面复用,record
818
- * 同步投影;capabilities 声明不降级——D2 降级是任务级兜底非能力级)。
819
- * [RX2-F3] degrade.skipCtxModelWarn(内部标志):漂移首败重跑场景置 true——该任务
820
- * 的 appserver 首跑已输出过 ctxModel 忽略留痕,spawn 重跑侧跳过防同 taskId 双份
821
- * 相同 warn;warnEffortUnsupportedBySpawn 不受此标志影响(降级重跑时最终结果出自
822
- * spawn,其出声合理,保持现状)。探针失败/降级直走两个落点不置位——任务此前未走
823
- * 过 appserver,spawn 侧的 warn 是首次出声。
824
- */
825
- private async runViaSpawn(
826
- task: AgentTaskSpec,
827
- ctx: RunContext,
828
- degrade?: { degradedReason: string; skipCtxModelWarn?: boolean },
829
- ): Promise<EngineRunResult> {
830
- const startedAt = Date.now();
831
- this.rejectUnsupportedTaskShapes(task);
832
- this.warnEffortUnsupportedBySpawn(task, ctx);
833
-
834
- // ① prepare 期:模型解析(provider 体系校验)+ 隔离 HOME 池引导(凭据 + model.main)
835
- const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
836
- if (degrade?.skipCtxModelWarn !== true) this.warnIgnoredCtxModel(task, ctx, modelRef);
837
- const prepared = prepareZcodeHome({
838
- engineDataDir: this.deps.engineDataDir(),
839
- modelRef,
840
- sources: this.deps.sources,
841
- });
842
- // 对齐点③:把实际池 key 声明给宿主(journal writer 重定向到 handle.poolKey 同源
843
- // 的路径——单一权威,宿主不再两边推导);须在首个事件 emit 前(终态后合成,天然满足)
844
- ctx.onPoolResolved?.(prepared.poolKey);
845
- logger.debug("[zcode-engine] isolated home prepared", {
846
- poolKey: prepared.poolKey,
847
- wroteConfig: prepared.wroteConfig,
848
- modelRef,
849
- taskId: ctx.taskId,
850
- });
851
-
852
- const cwd = task.cwd ?? process.cwd();
853
- const schema = isPlainObject(task.schema) ? task.schema : undefined;
854
- const basePrompt = this.buildPrompt(task, schema);
855
-
856
- // ②-④ 首轮执行 + schema 仿真重试(重试语义与不变量注释见 runSpawnAttemptsWithRetry)
857
- const usageAcc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false };
858
- const final = await this.runSpawnAttemptsWithRetry(task, ctx, prepared, cwd, basePrompt, schema, usageAcc);
859
-
860
- const outcome = this.finalizeOutcome(task, ctx, final, usageAcc, startedAt);
861
-
862
- const handle: EngineHandle = this.buildSpawnEngineHandle(prepared, outcome);
863
- if (degrade !== undefined) {
864
- // 降级标注(engineFallback 留痕面合并语义见 applyDegradeFallback)
865
- applyDegradeFallback(outcome, degrade.degradedReason);
866
- }
867
- return { handle, outcome };
868
- }
869
-
870
- /**
871
- * 首轮执行 + schema 仿真重试编排(spawn 路径):schema 任务校验失败时重试一次(强化
872
- * JSON 输出指令——与 structured-output 的重试语义对齐,设计 §3.3.3
873
- * schema_emulation_failed 行)。重试轮产生的新 session 是独立 LLM 调用:token 计入
874
- * outcome.usage 总量,事件只在最终轮终态后一次性合成(不变量 5:事件 emit 完成先于
875
- * run resolve)。
876
- */
877
- private async runSpawnAttemptsWithRetry(
878
- task: AgentTaskSpec,
879
- ctx: RunContext,
880
- prepared: ReturnType<typeof prepareZcodeHome>,
881
- cwd: string,
882
- basePrompt: string,
883
- schema: object | undefined,
884
- usageAcc: { input: number; output: number; cacheRead: number; cacheWrite: number; has: boolean },
885
- ): Promise<AttemptResult> {
886
- let final = await this.attemptOnce(task, ctx, prepared, cwd, basePrompt);
887
- accumulateUsage(usageAcc, final);
888
- if (final.kind === "parsed" && final.schemaResult !== undefined && !final.schemaResult.ok && schema !== undefined) {
889
- const retryPrompt = appendSchemaRetryDirective(basePrompt, final.schemaResult.error);
890
- const retry = await this.attemptOnce(task, ctx, prepared, cwd, retryPrompt);
891
- accumulateUsage(usageAcc, retry);
892
- final = retry;
893
- }
894
- return final;
895
- }
896
-
897
- /** spawn 路径的 handle 合成(poolKey = 隔离池目录名;探针版本可留痕)。 */
898
- private buildSpawnEngineHandle(
899
- prepared: ReturnType<typeof prepareZcodeHome>,
900
- outcome: AgentOutcome,
901
- ): EngineHandle {
902
- return {
903
- data: {
904
- v: 1,
905
- engineId: ZCODE_ENGINE_ID,
906
- sessionRef: {
907
- // 相对池目录自描述(设计 §3.3.6)——read 时经 resolvePoolDir + 此相对路径重定位
908
- dbPath: ZCODE_POOL_DB_RELATIVE_PATH,
909
- ...(outcome.sessionId !== undefined ? { sessionId: outcome.sessionId } : {}),
910
- },
911
- poolKey: prepared.poolKey,
912
- ...(this.probeCache?.engineVersion !== undefined && this.probeCache.engineVersion !== ""
913
- ? { engineVersion: this.probeCache.engineVersion }
914
- : {}),
915
- adapterVersion: ZCODE_ADAPTER_VERSION,
916
- },
917
- };
918
725
  }
919
726
 
920
727
  /** 终态合成(extension-conventions 函数 80 行上限,从 run 提取):aborted / run-failed / parsed 三分支。 */
921
728
  private finalizeOutcome(
922
- task: AgentTaskSpec,
729
+ task: AgentCallOpts,
923
730
  ctx: RunContext,
924
731
  final: AttemptResult,
925
732
  usageAcc: { input: number; output: number; cacheRead: number; cacheWrite: number; has: boolean },
@@ -951,30 +758,29 @@ export class ZcodeEngine implements EnginePort {
951
758
  /** abort 合成终态:exitCode=null(record 正常收尾,不留僵尸)。 */
952
759
  private applyAbortedOutcome(
953
760
  outcome: AgentOutcome,
954
- task: AgentTaskSpec,
761
+ task: AgentCallOpts,
955
762
  ctx: RunContext,
956
763
  final: Extract<AttemptResult, { kind: "aborted" }>,
957
764
  emit: (event: AgentEvent) => void,
958
765
  ): void {
959
766
  outcome.exitCode = null;
960
767
  // 对齐点④:宿主超时(mergeTimeoutSignal 的 timeout abort)统一走公共合成终态
961
- // (common/kill-chain.synthesizeTimeoutOutcome——engine_timeout 文案 SSOT:stdout
962
- // 尾部 + 「可用 engine: pi 重跑」建议);用户主动 cancel 维持 engine_run_failed
963
- // 中止标记(非超时语义,不冒充超时)。?? 兜底是类型收窄(合成器恒写 error)。
964
- // appserver 路径经 abortMessage 描述 D3 链形态(与 spawn 杀链文案分立)
768
+ // (common/kill-chain.synthesizeTimeoutOutcome——engine_timeout 文案 SSOT + 「可用
769
+ // engine: pi 重跑」建议);用户主动 cancel 维持 engine_run_failed 中止标记(非超时
770
+ // 语义,不冒充超时)。?? 兜底是类型收窄(合成器恒写 error)。
965
771
  outcome.error = isHostTimeoutAbort(ctx)
966
772
  ? synthesizeTimeoutOutcome(task, final.output.stdoutText, ZCODE_ENGINE_ID).error ??
967
773
  engineTimeoutDetail(final.output.stdoutText)
968
774
  : final.abortMessage ??
969
- `engine_run_failed: zcode 任务被中止(杀链 SIGTERM→${ZCODE_KILL_GRACE_MS}ms→SIGKILL,宿主合成终态)。` +
970
- `stdout 尾部: ${final.output.stdoutText.slice(-ZCODE_ERROR_TAIL_CHARS)}`;
775
+ `engine_run_failed: zcode 任务被中止(app-server abort 链收口,宿主合成终态)。` +
776
+ `输出尾部: ${final.output.stdoutText.slice(-ZCODE_ERROR_TAIL_CHARS)}`;
971
777
  emit({ type: "error", message: outcome.error });
972
778
  }
973
779
 
974
780
  /**
975
- * run-failed 合成终态:错误信息由 buildRunFailedMessage 产出(已含恢复指引)直接透传;
976
- * appserver 路径附带的会话 id 落 outcome.sessionId(错误规格表 -32004 行「含会话 id」
977
- * ——appServerHandle 据此写 handle.sessionRef,run-failed 不再恒缺)。
781
+ * run-failed 合成终态:错误信息由 buildAppServerRunFailedMessage 产出(已含恢复
782
+ * 指引)直接透传;附带的会话 id 落 outcome.sessionId(错误规格表 -32004 行「含会话
783
+ * id」——appServerHandle 据此写 handle.sessionRef,run-failed 不再恒缺)。
978
784
  */
979
785
  private applyRunFailedOutcome(
980
786
  outcome: AgentOutcome,
@@ -1028,97 +834,6 @@ export class ZcodeEngine implements EnginePort {
1028
834
  for (const ev of synthesizeCoarseEvents(payload.response, payload.usage)) emit(ev);
1029
835
  }
1030
836
 
1031
- /**
1032
- * 单轮执行(launch → collect → parse → schema 校验)。产出三态之一给 run 编排:
1033
- * aborted(我方杀链)/ run-failed(非零退出或解析失败)/ parsed(含 schema 校验结果)。
1034
- */
1035
- private async attemptOnce(
1036
- task: AgentTaskSpec,
1037
- ctx: RunContext,
1038
- prepared: ReturnType<typeof prepareZcodeHome>,
1039
- cwd: string,
1040
- prompt: string,
1041
- ): Promise<AttemptResult> {
1042
- // ② argv 组装 + 字节估算(超限抛 prompt_too_large——进程创建前;公共预算权威)
1043
- const args = buildZcodeArgv({ cwd, prompt, denyTools: task.denyTools });
1044
- const cliPath = this.deps.cliPath ?? ZCODE_CLI_DEFAULT_PATH;
1045
- assertZcodeArgvBudget("node", cliPath, args);
1046
-
1047
- // ③ launch:spawn 单轮(stdin ignore;HOME=池目录;嵌套标记经公共 nesting-guard)
1048
- const launch = this.deps.launch ?? launchZcodeProcess;
1049
- const env = buildZcodeEnv(prepared.homeDir, this.deps.processEnv ?? process.env);
1050
- let proc: ZcodeLaunchedProcess;
1051
- try {
1052
- proc = launch({ cliPath, args, env });
1053
- } catch (err) {
1054
- // spawn 同步失败(node 缺失等):进程未创建,按 prepare 期语义 reject
1055
- throw new Error(
1056
- `[engine_run_failed] 无法启动 zcode CLI(${cliPath}):${err instanceof Error ? err.message : String(err)}。` +
1057
- `恢复指引:确认 node 在 PATH 且 ${cliPath} 存在(probe 可探测),或改用 engine: pi。`,
1058
- );
1059
- }
1060
-
1061
- // [U0 D10] 终止链路径①:spawn 成功后同步注册子进程句柄进宿主 spawnedChildren
1062
- // 记账(port.ts 契约「spawn 成功后同步回调」)——cancelBackground SIGTERM /
1063
- // dispose killAll 收割兜底对引擎 record 生效,防 controller 丢失/竞态场景下孤儿进程
1064
- ctx.onChildSpawned?.(proc.child);
1065
-
1066
- // abort 分级(D1):zcode 无原生中断(interrupt: kill-only),AbortSignal 直通
1067
- // SIGTERM → grace → SIGKILL 杀链,终态由宿主合成
1068
- const onAbort = (): void => {
1069
- void proc.abort(ZCODE_KILL_GRACE_MS);
1070
- };
1071
- if (ctx.signal !== undefined) {
1072
- if (ctx.signal.aborted) onAbort();
1073
- else ctx.signal.addEventListener("abort", onAbort, { once: true });
1074
- }
1075
-
1076
- // ④ parser:有界收集 + 终 JSON
1077
- const output = await collectZcodeOutput(proc);
1078
- if (ctx.signal !== undefined) ctx.signal.removeEventListener("abort", onAbort);
1079
-
1080
- if (proc.killedByUs()) return { kind: "aborted", output };
1081
- if (output.exitCode !== 0) {
1082
- return {
1083
- kind: "run-failed",
1084
- output,
1085
- message: buildRunFailedMessage({
1086
- cliPath,
1087
- exitCode: output.exitCode,
1088
- stdoutTail: output.stdoutText,
1089
- stderrTail: output.stderrTail,
1090
- modelRef: prepared.modelRef,
1091
- configPath: prepared.configPath,
1092
- }),
1093
- };
1094
- }
1095
- const terminal = parseZcodeTerminal(output.stdoutText);
1096
- if (!terminal.ok) {
1097
- return {
1098
- kind: "run-failed",
1099
- output,
1100
- message: buildRunFailedMessage({
1101
- cliPath,
1102
- exitCode: output.exitCode,
1103
- stdoutTail: output.stdoutText,
1104
- stderrTail: output.stderrTail,
1105
- parseReason: terminal.reason,
1106
- modelRef: prepared.modelRef,
1107
- configPath: prepared.configPath,
1108
- }),
1109
- };
1110
- }
1111
- const schema = isPlainObject(task.schema) ? task.schema : undefined;
1112
- return {
1113
- kind: "parsed",
1114
- output,
1115
- payload: terminal.payload,
1116
- ...(schema !== undefined
1117
- ? { schemaResult: extractAndValidateStructuredOutput(terminal.payload.response, schema) }
1118
- : {}),
1119
- };
1120
- }
1121
-
1122
837
  /**
1123
838
  * D1 可选面:zcode 首期不支持 conversation(capabilities 声明)——同步拒绝、
1124
839
  * 不创建进程,文案给可操作建议(A11)。
@@ -1129,22 +844,36 @@ export class ZcodeEngine implements EnginePort {
1129
844
  code: "engine_capability_unsupported",
1130
845
  message:
1131
846
  "zcode 引擎不支持 conversation 交互控制面(capabilities.conversation = 'unsupported'," +
1132
- "spawn 单轮模式无同进程 idle 复用)。恢复指引:改用单次 subagent 调用重新派发任务," +
847
+ "每任务自包含会话,无同进程 idle 复用)。恢复指引:改用单次 subagent 调用重新派发任务," +
1133
848
  "或使用 engine: 'pi'(chatMode idle 复用,支持 message/close/cancel)。",
1134
849
  };
1135
850
  }
1136
851
 
1137
- /**
1138
- * D6 read 三级降级:①sqlite 原生读取 → ②宿主 event journal 重放(对齐点①接线:
1139
- * replayJournalToSessionView 复用 live reducer,重放等价性见 §3.3.6)→ ③outcome-only。
1140
- * sessionId 缺失(解析失败的 run 无法在共享池 db 内定位 session)跳过①级;②级
1141
- * 依赖 handle.journalPath(宿主 run 后回填)。
1142
- */
1143
852
  /** [U7] 模型可发现性:v2 桌面登录态聚合(带凭据 provider × models),失败安全返回清单本身可能为空。 */
1144
853
  listModels(): Array<{ id: string; name?: string }> {
1145
854
  return listZcodeModels(this.deps.sources);
1146
855
  }
1147
856
 
857
+ /**
858
+ * [u-h2 D2-2] 派发同步期 model 校验:委托 resolveZcodeModelRef(与 run prepare 期
859
+ * 同一函数——canonicalRef 归一化、短名缺省 provider、凭据与清单校验单一权威,无双实现)。
860
+ * modelRef undefined = 返回引擎缺省模型 canonical 全名(ZCODE_FALLBACK_DEFAULT_MODEL,
861
+ * D2-1 ctxModel 不透传的承接面)。校验失败原样抛 ZcodePrepareError,由编排层
862
+ * (engine/model-validation.ts)包装成「引擎与模型不配套」文案。
863
+ */
864
+ validateModel(modelRef: string | undefined): { canonicalRef: string } {
865
+ return { canonicalRef: resolveZcodeModelRef(modelRef, this.deps.sources) };
866
+ }
867
+
868
+ /**
869
+ * D6 read 三级降级:①sqlite 原生读取 → ②宿主 event journal 重放(对齐点①接线:
870
+ * replayJournalToSessionView 复用 live reducer,重放等价性见 §3.3.6)→ ③outcome-only。
871
+ * sessionId 缺失(解析失败的 run 无法定位 session)跳过①级;②级依赖
872
+ * handle.journalPath(宿主 run 后回填)。dbPath:新 handle 恒绝对路径(宿主
873
+ * ~/.zcode/cli/db/db.sqlite,tier1 精确匹配白名单见方法体);旧 records(池时代)
874
+ * 的相对路径仍按 poolKey 锚定解析(read 兼容旧数据,池目录不存在时自然落②级
875
+ * journal 降级)。
876
+ */
1148
877
  async read(handle: EngineHandle): Promise<SessionView> {
1149
878
  if (handle.data.engineId !== ZCODE_ENGINE_ID) {
1150
879
  return { engineId: ZCODE_ENGINE_ID, turns: [], source: "outcome-only" };
@@ -1152,18 +881,33 @@ export class ZcodeEngine implements EnginePort {
1152
881
  const sessionId = handle.data.sessionRef["sessionId"];
1153
882
  const dbPathRaw = handle.data.sessionRef["dbPath"];
1154
883
  if (typeof sessionId === "string" && typeof dbPathRaw === "string") {
1155
- // 相对路径锚定池目录(handle.poolKey 自描述);绝对路径(未来形态)直用
1156
- const dbPath = path.isAbsolute(dbPathRaw)
1157
- ? dbPathRaw
1158
- : path.join(resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, handle.data.poolKey), dbPathRaw);
1159
- try {
1160
- return await readZcodeSessionView(dbPath, sessionId);
1161
- } catch (err) {
1162
- logger.warn("[zcode-engine] native session read failed, degrade to journal replay", {
1163
- dbPath,
1164
- sessionId,
1165
- reason: err instanceof Error ? err.message : String(err),
1166
- });
884
+ // 绝对路径 tier1 白名单(与 runtime subagent-engine-history 同判):handle/
885
+ // record 来自 append-only JSONL(不可信面),仅放行宿主真实 db 的精确匹配,
886
+ // 其余绝对路径拒绝 ①级 sqlite 读取、降 journal 重放——防任意文件读
887
+ let dbPath: string | undefined;
888
+ if (path.isAbsolute(dbPathRaw)) {
889
+ if (dbPathRaw === hostZcodeDbPath()) dbPath = dbPathRaw;
890
+ else {
891
+ logger.warn("[zcode-engine] record dbPath 非宿主 db 绝对路径,拒绝 ①级读取降 journal", {
892
+ dbPath: dbPathRaw,
893
+ });
894
+ }
895
+ } else {
896
+ dbPath = path.join(
897
+ resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, handle.data.poolKey),
898
+ dbPathRaw,
899
+ );
900
+ }
901
+ if (dbPath !== undefined) {
902
+ try {
903
+ return await readZcodeSessionView(dbPath, sessionId);
904
+ } catch (err) {
905
+ logger.warn("[zcode-engine] native session read failed, degrade to journal replay", {
906
+ dbPath,
907
+ sessionId,
908
+ reason: toErrorMessage(err),
909
+ });
910
+ }
1167
911
  }
1168
912
  }
1169
913
  // ②级:journal 重放(journalPath 缺省 / 文件不存在 / 无事件 → undefined 落③级)
@@ -1175,66 +919,20 @@ export class ZcodeEngine implements EnginePort {
1175
919
  // ── 内部 ──
1176
920
 
1177
921
  /**
1178
- * prepare 期的能力拒绝(进程创建前):fork pi 专属(AgentTaskSpec.fork 契约:
1179
- * 其他引擎按 capabilities 拒绝);conversation 是 interact 控制面的 task 标志,
1180
- * zcode 无此面(A11:同步拒绝 + 可操作建议,无进程创建);maxTurns 是 pi 引擎
1181
- * 专属(turn limiter + spawn watchdog 估算依赖 pi 的 turn_end 事件流)——zcode
1182
- * turn_end 语义,静默丢弃会造成「传了上限却失控」的假象,显式拒绝(U4,
1183
- * fork 模式)。
1184
- */
1185
- private rejectUnsupportedTaskShapes(task: AgentTaskSpec): void {
1186
- if (task.fork === true) {
1187
- throw new ZcodeTaskShapeError(
1188
- "engine_capability_unsupported",
1189
- "zcode 引擎不支持 fork(pi 专属会话分叉语义)。恢复指引:去掉 fork 参数重派,或使用 engine: 'pi'。",
1190
- );
1191
- }
1192
- if (task.conversation === true) {
1193
- throw new ZcodeTaskShapeError(
1194
- "engine_capability_unsupported",
1195
- "zcode 引擎不支持 conversation 模式(spawn 单轮,无同进程 idle 复用)。" +
1196
- "恢复指引:改用单次调用(去掉 conversation),或使用 engine: 'pi'。",
1197
- );
1198
- }
1199
- if (task.maxTurns !== undefined) {
1200
- throw new ZcodeTaskShapeError(
1201
- "engine_capability_unsupported",
1202
- "zcode 引擎不支持 maxTurns(pi 引擎专属 turn limiter;zcode 无 turn_end 语义,无法兑现轮数上限)。" +
1203
- "恢复指引:去掉 maxTurns 参数重派,或使用 engine: 'pi'。",
1204
- );
1205
- }
1206
- }
1207
-
1208
- /**
1209
- * [F15b] spawn 路径的 effort 丢弃信号:spawn CLI 无 thoughtLevel 类 flag(协议
1210
- * 通道是 appserver 路径专属),effort 只能丢弃——但静默丢弃会让调用方误以为推理
1211
- * 档位已生效,故出声留痕(引擎现成信号风格:logger.warn,同漂移降级先例)。
1212
- * 诊断语义:effort 是可忽略档位(降档不改变任务正确性),warn 留痕而非硬拒绝
1213
- * (与 maxTurns「传了上限却失控」的假象不同质性)。
922
+ * [RX2-F1] appserver 路径的非常见档位提示:thinkingLevel thoughtLevel 恒等透传
923
+ * (F15a),全 7 档放行不拦截——但部分档位(off/minimal/medium/xhigh 等)不在部分
924
+ * 模型的合法值域内(如 GLM-5.3 仅接受 low/high/max),app-server 侧对不支持的档位
925
+ * warn-skip(会话照常但档位静默失效),调用方无从察觉。此处仅对
926
+ * COMMON_THOUGHT_LEVELS 之外的档位出声一行提示(措辞是「若不支持将被忽略/回落」的
927
+ * 或然警告,非无效断言);是否真不支持由目标模型决定,core 不做权威校验(引擎层
928
+ * 不掌握各模型值域)。
1214
929
  */
1215
- private warnEffortUnsupportedBySpawn(task: AgentTaskSpec, ctx: RunContext): void {
1216
- const effort = task.effort?.trim();
1217
- if (effort === undefined || effort === "") return;
1218
- logger.warn(
1219
- `[zcode-engine] effort=${effort} 被忽略:zcode spawn 不支持 thoughtLevel 通道(CLI 无对应 flag),任务按引擎缺省推理档位执行;需要 effort 请走 appserver 模式`,
1220
- { taskId: ctx.taskId },
1221
- );
1222
- }
1223
-
1224
- /**
1225
- * [RX2-F1] appserver 路径的非常见档位提示:effort → thoughtLevel 恒等透传(F15a),
1226
- * 全 7 档放行不拦截——但部分档位(off/minimal/medium/xhigh 等)不在部分模型的合法
1227
- * 值域内(如 GLM-5.3 仅接受 low/high/max),app-server 侧对不支持的档位 warn-skip
1228
- * (会话照常但档位静默失效),调用方无从察觉。此处仅对 COMMON_THOUGHT_LEVELS 之外
1229
- * 的档位出声一行提示(措辞是「若不支持将被忽略/回落」的或然警告,非无效断言);
1230
- * 是否真不支持由目标模型决定,core 不做权威校验(引擎层不掌握各模型值域)。
1231
- */
1232
- private warnThoughtLevelUncommon(task: AgentTaskSpec, ctx: RunContext): void {
1233
- const thoughtLevel = task.effort?.trim();
930
+ private warnThoughtLevelUncommon(task: AgentCallOpts, ctx: RunContext): void {
931
+ const thoughtLevel = task.thinkingLevel?.trim();
1234
932
  if (thoughtLevel === undefined || thoughtLevel === "") return;
1235
933
  if (COMMON_THOUGHT_LEVELS.includes(thoughtLevel)) return;
1236
934
  logger.warn(
1237
- `[zcode-engine] effort=${thoughtLevel} 已透传为 thoughtLevel(非常见档位):若目标模型不支持该档位将被忽略/回落到模型缺省推理档位(常见档位:${COMMON_THOUGHT_LEVELS.join("/")});档位是否生效以模型实际行为为准`,
935
+ `[zcode-engine] thinkingLevel=${thoughtLevel} 已透传为 thoughtLevel(非常见档位):若目标模型不支持该档位将被忽略/回落到模型缺省推理档位(常见档位:${COMMON_THOUGHT_LEVELS.join("/")});档位是否生效以模型实际行为为准`,
1238
936
  { taskId: ctx.taskId },
1239
937
  );
1240
938
  }
@@ -1246,12 +944,9 @@ export class ZcodeEngine implements EnginePort {
1246
944
  * ctxModel。「调用方给了 ctxModel 但 task.model 未显式指定」时出声一行,说明
1247
945
  * 实际落引擎缺省模型(含实际 model id)——防静默降档无据可查。只在「ctx 有模型
1248
946
  * 但被忽略」场景输出:显式 task.model 走正常解析链、ctx 本就无模型属预期缺省,
1249
- * 均不出声(避免噪音)。探针期(appServerProbeGate)不调用——同一任务的正式
1250
- * run 链路必经此处,双份输出是噪音。[RX2-F3] 漂移首败的 spawn 重跑同理由调用方
1251
- * 带 degrade.skipCtxModelWarn 跳过——appserver 首跑已出声过,同任务双份相同 warn
1252
- * 是噪音(与探针场景同一自我要求)。
947
+ * 均不出声(避免噪音)。
1253
948
  */
1254
- private warnIgnoredCtxModel(task: AgentTaskSpec, ctx: RunContext, modelRef: string): void {
949
+ private warnIgnoredCtxModel(task: AgentCallOpts, ctx: RunContext, modelRef: string): void {
1255
950
  if (ctx.ctxModel === undefined) return;
1256
951
  const requested = task.model?.trim();
1257
952
  if (requested !== undefined && requested !== "") return;
@@ -1264,19 +959,13 @@ export class ZcodeEngine implements EnginePort {
1264
959
 
1265
960
  /**
1266
961
  * persona 拼接后的完整 prompt(personaInjection: 'prompt'——zcode 无 flag 通道):
1267
- * persona 段经 common/persona-router.applyPersona 按 capabilities 路由产出
1268
- * (agentRef/skillPath 引用行 + appendSystemPrompt 正文统一拼装,S5 接线——替换
1269
- * 原手拼 appendSystemPrompt 段,skillPath/agentRef 不再丢弃),task 正文居中,
1270
- * schema 仿真段尾置(common/schema-emulation 公共层产出,D4 emulated 侧——zcode
1271
- * 无 native schema 通道)。
962
+ * appendSystemPrompt 段在前(人设/约束语境——D6 合流后 persona≡skillPath+
963
+ * appendSystemPrompt 平铺,由上游解析进 appendSystemPrompt),task 正文居中,
964
+ * schema 仿真段尾置(common/schema-emulation 公共层产出)。
1272
965
  */
1273
- private buildPrompt(task: AgentTaskSpec, schema: object | undefined): string {
1274
- const segments: string[] = [];
1275
- if (task.persona !== undefined) {
1276
- const routed = applyPersona(task.persona, this.capabilities());
1277
- if (routed.promptSegment !== "") segments.push(routed.promptSegment);
1278
- }
1279
- segments.push(task.task);
966
+ private buildPrompt(task: AgentCallOpts, schema: JsonSchemaObject | undefined): string {
967
+ const segments: string[] = [...(task.appendSystemPrompt ?? [])];
968
+ segments.push(task.prompt);
1280
969
  if (schema !== undefined) segments.push(buildSchemaEmulationSegment(schema));
1281
970
  return segments.join("\n\n");
1282
971
  }
@@ -1284,40 +973,100 @@ export class ZcodeEngine implements EnginePort {
1284
973
 
1285
974
  // ── 模块级辅助(run 的重试编排件) ──
1286
975
 
976
+ /** app-server 路径的合成 output 形态(stdoutText 恒空——失败素材在 message 内)。 */
977
+ interface AttemptOutput {
978
+ exitCode: number | null;
979
+ stdoutText: string;
980
+ stderrTail: string;
981
+ }
982
+
1287
983
  /**
1288
- * attempt 的三态产物(两模式共用;run 按序编排重试与终态合成)。appserver 路径的
1289
- * output 为合成形态(stdoutText 恒空——其失败素材在 message/abortMessage 内)。
984
+ * attempt 的三态产物(run 按序编排重试与终态合成)。
1290
985
  */
1291
986
  type AttemptResult =
1292
- | { kind: "aborted"; output: ZcodeCollectedOutput; abortMessage?: string }
987
+ | { kind: "aborted"; output: AttemptOutput; abortMessage?: string }
1293
988
  | {
1294
989
  kind: "run-failed";
1295
- output: ZcodeCollectedOutput;
990
+ output: AttemptOutput;
1296
991
  message: string;
1297
- rpcCode?: number;
1298
992
  /** appserver 路径失败时已建立的会话 id(错误规格表 -32004 行:按任务失败上报含会话 id)。 */
1299
993
  sessionId?: string;
994
+ /**
995
+ * [P0-1 U4/D6] 瞬时失败形态标记(重试判定判据——类型化字段,不经字符串
996
+ * 反推):timeout = channel 判死(TurnTimeoutError,idle/ceiling 都算——D6
997
+ * 明文两类均可重试);conn-closed = 连接崩溃收割(failAllTurns 形态,判据 =
998
+ * 非 RPC error 且 conn 不存活——catch 时刻重建仅由 conn.request 惰性触发,
999
+ * 此前无 request,判据可靠)。缺席 = 非瞬时形态(RPC 错误/status=error 终态/
1000
+ * send 未送达等),不参与重试(D6 被否③:status='error' 终态 v1 不重试)。
1001
+ */
1002
+ transient?: "timeout" | "conn-closed";
1300
1003
  }
1301
1004
  | {
1302
1005
  kind: "parsed";
1303
- output: ZcodeCollectedOutput;
1006
+ output: AttemptOutput;
1304
1007
  payload: ZcodeTerminalPayload;
1305
1008
  schemaResult?: { ok: true; parsed: unknown } | { ok: false; error: string; tail: string };
1306
1009
  };
1307
1010
 
1308
- /** Record 形状 guard(task.schema 的运行时窄化——Record<string, unknown> 不满足 ajv 的 object 入参)。 */
1309
- function isPlainObject(v: unknown): v is object {
1011
+ /**
1012
+ * [P0-1 U4/D6] 瞬时失败自动重试的最小剩余预算下限(ms):显式总上界预算下,剩余
1013
+ * 低于此值不再重试直接终态化(D6「剩余不足一个最小下限(如 5min)」——重跑一轮
1014
+ * 整任务的最小耗时估计,剩余更小的重试注定再被上界回收,白烧一轮 token)。
1015
+ * 单消费方(本文件重试编排),故为模块常量不进 constants.ts(跨文件共享才上移)。
1016
+ */
1017
+ export const ZCODE_TURN_RETRY_MIN_BUDGET_MS = 300_000;
1018
+
1019
+ /** resolveTransientRetryBudget 的判定结果(可判别联合——inherit 分支剩余值必有)。 */
1020
+ export type ZcodeTurnRetryBudget =
1021
+ | { state: "inherit"; remainingMs: number }
1022
+ | { state: "depleted" }
1023
+ | { state: "unbounded" };
1024
+
1025
+ /**
1026
+ * [P0-1 U4/D6 预算继承] 重试轮预算判定(纯函数,P-Z4 探针「显式预算下重试轮不
1027
+ * 重置总预算」的数学本体——剩余 = 总预算 − 已耗尽):显式总上界预算存在时重试轮
1028
+ * 上界收窄为剩余(不重置),剩余不足最小下限则不重试;非显式(env 未设/非法/
1029
+ * ≤0 显式关闭)为 unbounded——无「总预算」可言,重试轮走 env/默认全新上界(与
1030
+ * 首轮同源,行为一致),不受预算门禁。
1031
+ */
1032
+ export function resolveTransientRetryBudget(
1033
+ totalBudgetMs: number | undefined,
1034
+ consumedMs: number,
1035
+ ): ZcodeTurnRetryBudget {
1036
+ if (totalBudgetMs === undefined) return { state: "unbounded" };
1037
+ const remainingMs = totalBudgetMs - consumedMs;
1038
+ return remainingMs >= ZCODE_TURN_RETRY_MIN_BUDGET_MS
1039
+ ? { state: "inherit", remainingMs }
1040
+ : { state: "depleted" };
1041
+ }
1042
+
1043
+ /**
1044
+ * 显式总上界预算读取(P-Z4 门禁的「显式」判定——D6「显式设置了 turnTimeoutMs
1045
+ * (env 或内部传参)」在引擎侧的唯一来源是 env;引擎内部传参点只用于向重试轮传
1046
+ * 剩余值):env 设置为正数 → 显式预算;未设/非法(走默认)与 ≤0(显式关闭上界)
1047
+ * 均非显式预算(undefined → unbounded)。读 process.env 直连(与 session-channel
1048
+ * 的 resolveTurnTimerMs 同源同通道,vi.stubEnv 可测——D2 env 通道一致性)。
1049
+ */
1050
+ function explicitTurnBudgetMs(): number | undefined {
1051
+ const parsed = parseZcodeTurnTimeoutEnv(process.env[ZCODE_TURN_MAX_TIMEOUT_ENV]);
1052
+ return parsed.state === "valid" && parsed.ms > 0 ? parsed.ms : undefined;
1053
+ }
1054
+
1055
+ /**
1056
+ * task.schema 的最小 JSON Schema 形状(S13:替代裸 object——序列化边界上表达
1057
+ * 「ajv 可消费的 schema 对象」;具体关键字(type/properties/required…)由
1058
+ * schema-emulation 层解释,此处只约束对象形态)。
1059
+ */
1060
+ type JsonSchemaObject = Readonly<Record<string, unknown>>
1061
+
1062
+ /** Record 形状 guard(task.schema 的运行时窄化——JsonSchemaObject 不满足 ajv 的 object 入参)。 */
1063
+ function isPlainObject(v: unknown): v is JsonSchemaObject {
1310
1064
  return typeof v === "object" && v !== null && !Array.isArray(v);
1311
1065
  }
1312
1066
 
1313
1067
  /** 错误/日志出声用的 message 提取(非 Error 值不抛二次异常)。 */
1314
1068
  function errMessage(err: unknown): string {
1315
- return err instanceof Error ? err.message : String(err);
1316
- }
1317
-
1318
- /** [R5 D2] 漂移类 RPC code 判据(错误规格表第 1 行:-32601 方法不存在 / -32602 参数变形)。 */
1319
- function isDriftRpcCode(code: number): boolean {
1320
- return (ZCODE_APPSERVER_DRIFT_RPC_CODES as readonly number[]).includes(code);
1069
+ return toErrorMessage(err);
1321
1070
  }
1322
1071
 
1323
1072
  /** ms 后 resolve 指定值(abort 链 grace 窗口的 race 材料;unref 不阻塞进程退出)。 */
@@ -1329,11 +1078,11 @@ function delayResolved<T>(ms: number, value: T): Promise<T> {
1329
1078
  }
1330
1079
 
1331
1080
  /** appserver 路径的合成 output(无 stdout 可收集——exitCode 语义:0=正常轮;null=中止/连接级失败)。 */
1332
- function syntheticAppServerOutput(exitCode: number | null): ZcodeCollectedOutput {
1081
+ function syntheticAppServerOutput(exitCode: number | null): AttemptOutput {
1333
1082
  return { exitCode, stdoutText: "", stderrTail: "" };
1334
1083
  }
1335
1084
 
1336
- /** appserver 路径的 aborted 三态(abortMessage 描述 D3 链形态——与 spawn 杀链文案分立)。 */
1085
+ /** appserver 路径的 aborted 三态(abortMessage 描述 D3 链形态)。 */
1337
1086
  function abortedAppServerAttempt(ctx: RunContext): AttemptResult {
1338
1087
  void ctx;
1339
1088
  return {
@@ -1345,7 +1094,7 @@ function abortedAppServerAttempt(ctx: RunContext): AttemptResult {
1345
1094
  };
1346
1095
  }
1347
1096
 
1348
- /** runTurn 终态 → parser 载荷形态(usage 映射与 spawn 路径同源:parser.mapZcodeUsage 一族)。 */
1097
+ /** runTurn 终态 → 载荷形态(usage 映射:parser.mapZcodeUsage 一族)。 */
1349
1098
  function turnResultToPayload(r: SessionTurnResult): ZcodeTerminalPayload {
1350
1099
  return {
1351
1100
  response: r.response,
@@ -1359,20 +1108,24 @@ function turnResultToPayload(r: SessionTurnResult): ZcodeTerminalPayload {
1359
1108
 
1360
1109
  /**
1361
1110
  * appserver 路径运行中失败的结构化文案(错误规格表):-32603 "Model config is
1362
- * missing" → engine_credential_missing(与 prepare 期同码——错误定位常驻 HOME config);
1111
+ * missing" → engine_credential_missing(共享宿主 HOME——凭据在 ZCode 桌面端管理);
1363
1112
  * -32010 → 单会话一任务是结构保证(busy 不排队不打断),文案引导附带 sessionId/state
1364
- * 流水报告;其余(连接崩溃/会话失败/漂移类透传——漂移降级归 R5)→ engine_run_failed +
1365
- * 恢复指引。sessionId 已建立时随文案透出(错误规格表 -32004 行「按任务失败上报含会话 id」)。
1113
+ * 流水报告;其余(连接崩溃/会话失败/协议漂移)→ engine_run_failed + 恢复指引
1114
+ * (漂移不再降级 spawn——直接报错)。sessionId 已建立时随文案透出。
1115
+ *
1116
+ * retried([P0-1 U4] §5.2 F-4「已重试 1 次」):仅在瞬时重试真实发生后为 true——
1117
+ * 兜底行恢复指引补「已自动重试一次仍失败」句(未重试形态不含,与行为一致);专属
1118
+ * 归类行(credential/busy)有独立恢复指引,不掺重试事实(错误规格表行的归类语义优先)。
1366
1119
  */
1367
- function buildAppServerRunFailedMessage(err: unknown, home: AppServerHomeHandle, sessionId?: string): string {
1120
+ function buildAppServerRunFailedMessage(err: unknown, sessionId?: string, retried = false): string {
1368
1121
  if (
1369
1122
  isAppServerRpcError(err) &&
1370
1123
  err.code === ZCODE_APPSERVER_ERR_MODEL_CONFIG_MISSING &&
1371
1124
  /Model config is missing/.test(err.message)
1372
1125
  ) {
1373
1126
  return (
1374
- `engine_credential_missing: app-server 报 "Model config is missing"(常驻 HOME ${home.homeDir} config.json ` +
1375
- `无可用模型配置)。恢复指引:在 ZCode 桌面端登录并配置 provider 凭据后重跑本任务(引擎将在下任务重写常驻 config 并重建连接)。`
1127
+ `engine_credential_missing: app-server 报 "Model config is missing"(宿主 HOME 的 zcode 配置无可用模型)。` +
1128
+ `恢复指引:在 ZCode 桌面端登录并配置 provider 凭据后重跑本任务(常驻连接在引擎进程重启后生效新凭据)。`
1376
1129
  );
1377
1130
  }
1378
1131
  if (isAppServerRpcError(err) && err.code === ZCODE_APPSERVER_ERR_BUSY_SESSION) {
@@ -1384,9 +1137,13 @@ function buildAppServerRunFailedMessage(err: unknown, home: AppServerHomeHandle,
1384
1137
  }
1385
1138
  const code = isAppServerRpcError(err) && err.code !== undefined ? `(code ${err.code})` : "";
1386
1139
  const sid = sessionId !== undefined ? `(会话 ${sessionId})` : "";
1140
+ const retryNote = retried
1141
+ ? `恢复指引:直接重跑本任务(瞬时故障已自动重试一次仍失败;重试用的是崩溃后自动重建的新会话)。`
1142
+ : `恢复指引:直接重跑本任务(连接崩溃后自动重建进程);`;
1387
1143
  return (
1388
1144
  `engine_run_failed: app-server 会话执行失败${code}${sid}: ${errMessage(err).slice(-ZCODE_ERROR_TAIL_CHARS)}。` +
1389
- `恢复指引:直接重跑本任务(连接崩溃后自动重建进程);若持续失败,跑 probe 核对协议漂移(R5 降级链)或改用 engine: pi。`
1145
+ `${retryNote}若持续失败(疑似 zcode 升级后协议漂移——` +
1146
+ `-32601/-32602 类错误),重启 ZCode 或固定 zcode 版本后重试,或改用 engine: pi。`
1390
1147
  );
1391
1148
  }
1392
1149
 
@@ -1415,15 +1172,15 @@ function appendSchemaRetryDirective(basePrompt: string, validationError: string)
1415
1172
 
1416
1173
  /** create 参数组装(A.2 ① strict 键集:空白 thoughtLevel / 空 deny 清单不设键)。 */
1417
1174
  function buildAppServerCreateParams(
1418
- task: AgentTaskSpec,
1175
+ task: AgentCallOpts,
1419
1176
  providerId: string,
1420
1177
  modelId: string,
1421
1178
  cwd: string,
1422
1179
  ): SessionCreateParams {
1423
1180
  const denyTools = (task.denyTools ?? []).filter((t) => typeof t === "string" && t.trim() !== "");
1424
- // effort → thoughtLevel(A.2 ① 键集内):空白串归一为不设键——strict 对象下
1181
+ // thinkingLevel → thoughtLevel(A.2 ① 键集内):空白串归一为不设键——strict 对象下
1425
1182
  // 空值键位无语义且防 -32602 变形拒收(与 denyTools 空清单不设键同款纪律)
1426
- const thoughtLevel = task.effort?.trim();
1183
+ const thoughtLevel = task.thinkingLevel?.trim();
1427
1184
  return {
1428
1185
  workspacePath: cwd,
1429
1186
  mode: "yolo",
@@ -1435,8 +1192,42 @@ function buildAppServerCreateParams(
1435
1192
  };
1436
1193
  }
1437
1194
 
1438
- /** appserver 轮成功收口的 parsed 三态(read 兜底后的 response + schema 校验)。 */
1439
- function parsedAppServerAttempt(task: AgentTaskSpec, r: SessionTurnResult): AttemptResult {
1195
+ /**
1196
+ * 权威终态 status 提取(P0-1 U3/D5② + ⛔P-Z2 降级路径约束「只消费
1197
+ * source="turn.terminal" 的 status」):turn.terminal 到达(先到/迟到)时 u-z1 的
1198
+ * lastTerminalStatus 必有记录(channel 无条件先记再 settle/不改写落定);缺席说明
1199
+ * 终态仅由 final-frame 宽松判定落定(恒 settle success,不可信)——无权威 status
1200
+ * 可消费,不据此判失败。
1201
+ */
1202
+ function authoritativeTerminalStatus(r: SessionTurnResult): string | undefined {
1203
+ if (r.lastTerminalStatus !== undefined) return r.lastTerminalStatus;
1204
+ return r.terminal.source === "turn.terminal" ? r.terminal.status : undefined;
1205
+ }
1206
+
1207
+ /**
1208
+ * 失败终态判据(⛔P-Z2 门修正):真实 status 枚举 = ["success","interrupted",
1209
+ * "failed"](app-server dist schema f.enum 实证,**无 "error"**——v1 判据
1210
+ * `=== "error"` 对真实 failed 终态漏分流即假成功,本修复轮根修)。裁决:
1211
+ * - "failed" → run-failed(模型/服务端真实失败——§5.2 F-3);
1212
+ * - "interrupted" → 不分流(用户中断,不属引擎失败——随宿主 abort 主路径收口,
1213
+ * 引擎侧不抢先把它终态化为失败);
1214
+ * - "error" → 保留为容错分支(非真实枚举,防协议漂移/旧版本形态再滑入假成功;
1215
+ * 假成功代价 >> 误报失败代价,取并集防御)。
1216
+ */
1217
+ function isFailedTerminalStatus(status: string | undefined): boolean {
1218
+ return status === "failed" || status === "error";
1219
+ }
1220
+
1221
+ /**
1222
+ * [P0-1 U3/D5②] appserver 轮成功收口的 parsed 三态(read 兜底后的 response + schema
1223
+ * 校验)。失败终态(isFailedTerminalStatus,"interrupted" 不在其中——不误判失败)
1224
+ * 先分流(§3.2 缺陷 B 不再假成功;schema 校验对失败形态无意义——失败终态的
1225
+ * response 是错误尾部,非结构化输出候选)。
1226
+ */
1227
+ function parsedAppServerAttempt(task: AgentCallOpts, r: SessionTurnResult): AttemptResult {
1228
+ if (isFailedTerminalStatus(authoritativeTerminalStatus(r))) {
1229
+ return failedTerminalAppServerAttempt(r);
1230
+ }
1440
1231
  const schema = isPlainObject(task.schema) ? task.schema : undefined;
1441
1232
  return {
1442
1233
  kind: "parsed",
@@ -1448,19 +1239,54 @@ function parsedAppServerAttempt(task: AgentTaskSpec, r: SessionTurnResult): Atte
1448
1239
  };
1449
1240
  }
1450
1241
 
1451
- /** appserver 轮失败收口的 run-failed 三态(结构化文案 + RPC code 透传 + 会话 id 留痕)。 */
1242
+ /**
1243
+ * [P0-1 U3/D5② + ⛔P-Z2 门修正] failed 终态的 run-failed 合成(§5.2 F-3 文案):
1244
+ * exitCode=null 异常终态、sessionId 留痕同 failedAppServerAttempt。错误详情优先级:
1245
+ * terminal 帧 errorCode/errorMessage(⛔P-Z2 实证——真实 failed 终态的错误详情只在
1246
+ * terminal 帧,read/delta 携带不了)> read 兜底/delta 聚合尾部(F-3 原文案,降级为
1247
+ * 兜底)> 「无返回内容」(P-Z2 降级形态:final-frame 先到且 read 无错误信息——不
1248
+ * 伪造错误详情,覆盖面收窄但不假成功)。
1249
+ */
1250
+ function failedTerminalAppServerAttempt(r: SessionTurnResult): AttemptResult {
1251
+ const status = authoritativeTerminalStatus(r);
1252
+ const detail = r.lastTerminalError;
1253
+ const detailParts: string[] = [];
1254
+ if (detail?.code !== undefined) detailParts.push(`errorCode: ${detail.code}`);
1255
+ if (detail?.message !== undefined) detailParts.push(detail.message);
1256
+ // 错误详情优先级(⛔P-Z2):terminal 帧详情 > read 兜底/delta 聚合尾部 > 无返回内容
1257
+ let body: string;
1258
+ if (detailParts.length > 0) {
1259
+ body = `服务端错误:${detailParts.join(":")}。`;
1260
+ } else {
1261
+ const tail = r.response.trim();
1262
+ body =
1263
+ tail !== ""
1264
+ ? `服务端返回尾部:${tail.slice(-ZCODE_ERROR_TAIL_CHARS)}。`
1265
+ : "服务端无返回内容(read 兜底/delta 聚合均为空)。";
1266
+ }
1267
+ return {
1268
+ kind: "run-failed",
1269
+ output: syntheticAppServerOutput(null),
1270
+ message:
1271
+ `engine_run_failed: app-server 终态 status=${status}(会话 ${r.sessionId})。${body}\n` +
1272
+ `👉 恢复指引:错误内容来自模型/服务端;直接重跑,若持续出现核对 ZCode 桌面端凭据与模型配置(engine_credential_missing 同族排查)。`,
1273
+ sessionId: r.sessionId,
1274
+ };
1275
+ }
1276
+
1277
+ /** appserver 轮失败收口的 run-failed 三态(结构化文案 + 会话 id 留痕)。 */
1452
1278
  function failedAppServerAttempt(
1453
1279
  err: unknown,
1454
- home: AppServerHomeHandle,
1455
1280
  currentSessionId: string | undefined,
1281
+ opts: { retried?: boolean; transient?: "timeout" | "conn-closed" } = {},
1456
1282
  ): AttemptResult {
1457
1283
  return {
1458
1284
  kind: "run-failed",
1459
1285
  output: syntheticAppServerOutput(null),
1460
- message: buildAppServerRunFailedMessage(err, home, currentSessionId),
1461
- // [R5] RPC code 透传给 run 编排(-32601/-32602 漂移降级判据;连接级/超时类
1462
- // 错误无 code 不参与降级)
1463
- ...(isAppServerRpcError(err) && err.code !== undefined ? { rpcCode: err.code } : {}),
1286
+ message: buildAppServerRunFailedMessage(err, currentSessionId, opts.retried === true),
1287
+ // [P0-1 U4/D6] 瞬时失败形态标记(重试判定判据——timeout 形态走
1288
+ // timeoutAppServerAttempt,此处只承载 conn-closed)
1289
+ ...(opts.transient !== undefined ? { transient: opts.transient } : {}),
1464
1290
  // 错误规格表 -32004 行「按任务失败上报(含会话 id)」:create 成功后运行中失败
1465
1291
  // (-32004/-32010 等)时留痕会话 id——经 applyRunFailedOutcome 落 outcome.sessionId
1466
1292
  // 与 handle.sessionRef(create 阶段失败无会话,缺省不带)
@@ -1468,24 +1294,103 @@ function failedAppServerAttempt(
1468
1294
  };
1469
1295
  }
1470
1296
 
1471
- /** [R5] 末轮 attempt 的漂移类 RPC code 提取(-32601/-32602 → run 编排降级判据;其余 undefined)。 */
1472
- function driftCodeOf(final: AttemptResult): number | undefined {
1473
- return final.kind === "run-failed" && final.rpcCode !== undefined && isDriftRpcCode(final.rpcCode)
1474
- ? final.rpcCode
1475
- : undefined;
1476
- }
1297
+ /**
1298
+ * D3 abort 链的止损路径终局(P0-1 U2):超时入口的 outcome 文案素材(D3 强制可观测
1299
+ * 面——r3 SG-4,A2/A11 验收断言「outcome 止损路径为 stop 已送达 / 升级杀链」的载体);
1300
+ * settled-in-grace / escalated-kill 两值只由用户取消入口产生,超时入口不可达(保留
1301
+ * 联合完整供文案兜底)。
1302
+ */
1303
+ type AbortChainStopPath =
1304
+ | "stop-acked"
1305
+ | "stop-rejected"
1306
+ | "stop-unreachable-killed"
1307
+ | "no-session"
1308
+ | "settled-in-grace"
1309
+ | "escalated-kill";
1477
1310
 
1478
- /** 降级留痕(outcome.engineFallback 唯一通道;路由级 fallback 已存在时合并不覆盖)。 */
1479
- function applyDegradeFallback(outcome: AgentOutcome, degradedReason: string): void {
1480
- const prior = outcome.engineFallback;
1481
- outcome.engineFallback = {
1482
- from: prior?.from ?? "zcode:appserver",
1483
- reason: `${prior !== undefined ? `${prior.reason};` : ""}degraded: spawn(${degradedReason})`,
1311
+ /**
1312
+ * [P0-1 U2] channel 判死(TurnTimeoutError)后的收口三态:engine_timeout 前缀
1313
+ * (D4——与 engine_run_failed 分流,下游按前缀分流不经字符串反推超时语义),走
1314
+ * run-failed kind 承载(exitCode=null 异常终态口径与杀链超时合成终态一致)。sessionId
1315
+ * 留痕同 failedAppServerAttempt。
1316
+ * [P0-1 U4/D6] timeout 类(idle/ceiling 都算)恒标 transient——D6 明文的可重试形态;
1317
+ * retried 时文案补「已自动重试一次」句(F-1)。
1318
+ */
1319
+ function timeoutAppServerAttempt(
1320
+ err: TurnTimeoutError,
1321
+ currentSessionId: string | undefined,
1322
+ stopPath: AbortChainStopPath,
1323
+ opts: { retried?: boolean } = {},
1324
+ ): AttemptResult {
1325
+ return {
1326
+ kind: "run-failed",
1327
+ output: syntheticAppServerOutput(null),
1328
+ message: buildAppServerTimeoutMessage(err, currentSessionId, stopPath, opts.retried === true),
1329
+ transient: "timeout",
1330
+ ...(currentSessionId !== undefined ? { sessionId: currentSessionId } : {}),
1484
1331
  };
1485
1332
  }
1486
1333
 
1487
- /** prepare 期能力拒绝的载体(code message 前缀,调用方可程序化分流)。
1488
- * [U10① D6] execution 运行时面错误族成员:export 供宿主 instanceof 分流(纯追加,零逻辑改动)。 */
1334
+ /** 止损路径的可观测文案(§5.2 F-1:stop 已送达 / stop 无应答已升级杀链两分支各具名)。 */
1335
+ function stopPathText(stopPath: AbortChainStopPath): string {
1336
+ switch (stopPath) {
1337
+ case "stop-acked":
1338
+ return "session/stop 已送达(服务端接受停 turn)";
1339
+ case "stop-rejected":
1340
+ return "session/stop 报协议性 error(会话已被回收,控制面存活——止损由会话回收承担,不升级杀链)";
1341
+ case "stop-unreachable-killed":
1342
+ return `session/stop 无应答已升级杀链(SIGTERM→${ZCODE_KILL_GRACE_MS}ms→SIGKILL 收割共享进程)`;
1343
+ case "no-session":
1344
+ return "会话未建立(任务未开始执行,无在途消耗)";
1345
+ default:
1346
+ // settled-in-grace / escalated-kill 只属用户取消入口;超时入口不可达,防御兜底
1347
+ return "grace 窗口内终态落定或已走杀链";
1348
+ }
1349
+ }
1350
+
1351
+ /**
1352
+ * 超时族 outcome 文案(D4 + §5.2 F-1/F-2,两形态有别):idle 主判定静默时长 + 最后
1353
+ * 事件时刻(诊断面);ceiling 总上界判死附 env 自救通道(XYZ_ZCODE_TURN_MAX_TIMEOUT_MS
1354
+ * 可调/0 关闭——§2 目标 5 的用户可见出口)。恢复指引共段:重跑 + 连通性排查 + engine: pi。
1355
+ * retried([P0-1 U4] §5.2 F-1 样例句,u-z2 留的补句义务):仅在瞬时重试真实发生后
1356
+ * 为 true——恢复指引补「瞬时故障已自动重试一次仍超时;重试在止损链终局后启动,无
1357
+ * 新旧任务双跑窗」句(未重试形态不含,与行为一致)。
1358
+ */
1359
+ function buildAppServerTimeoutMessage(
1360
+ err: TurnTimeoutError,
1361
+ sessionId: string | undefined,
1362
+ stopPath: AbortChainStopPath,
1363
+ retried = false,
1364
+ ): string {
1365
+ const sid = sessionId !== undefined ? `(会话 ${sessionId})` : "";
1366
+ const lastEventText =
1367
+ err.lastEventAt !== undefined
1368
+ ? `,最后事件 ${new Date(err.lastEventAt).toISOString()}`
1369
+ : ",整轮未观察到任何事件(进程假死/协议静默形态)";
1370
+ const head =
1371
+ err.kind === "idle"
1372
+ ? `engine_timeout: zcode turn 连续静默 ${err.thresholdMs}ms(idle 判定${lastEventText},总耗时 ${err.elapsed}ms)${sid}。`
1373
+ : `engine_timeout: zcode turn 总上界 ${err.thresholdMs}ms 内未观察到终态(chatty-wedge 判定——事件流仍活跃而终态未到达,总耗时 ${err.elapsed}ms)${sid}。`;
1374
+ const selfHelp =
1375
+ err.kind === "ceiling"
1376
+ ? `若本任务属合法超长任务(预期超过 ${err.thresholdMs}ms),重跑前设 ${ZCODE_TURN_MAX_TIMEOUT_ENV} 为更大毫秒值或 0 关闭总上界(关闭后 chatty 形态不再自动回收,静默 wedged 仍由 idle 层兜底——自行权衡)。`
1377
+ : "";
1378
+ const rerunGuide = retried
1379
+ ? `👉 恢复指引:直接重跑本任务(瞬时故障已自动重试一次仍超时;重试在止损链终局后启动,无新旧任务双跑窗);`
1380
+ : `👉 恢复指引:直接重跑本任务;`;
1381
+ return (
1382
+ `${head}止损路径:${stopPathText(stopPath)}。\n` +
1383
+ `${rerunGuide}${selfHelp}若持续出现,检查 ZCode 桌面端模型连通性或改用 engine: pi。`
1384
+ );
1385
+ }
1386
+
1387
+ /**
1388
+ * prepare 期能力拒绝的历史载体(code 进 message 前缀,调用方可程序化分流)。
1389
+ * [U10① D6] execution 运行时面错误族成员:export 供宿主 instanceof 分流。
1390
+ * [D3-④ 合并注] 引擎内 shape 拒绝已上提 common/capability-gate(EngineError 承载),
1391
+ * 本引擎不再抛出;保留 export 维持错误族面兼容(execution-runtime-face.test 消费),
1392
+ * 待波 2 收口时随 export 面清理一并裁决。
1393
+ */
1489
1394
  export class ZcodeTaskShapeError extends Error {
1490
1395
  readonly code: string;
1491
1396
 
@@ -1518,7 +1423,7 @@ async function defaultProbeVersion(cliPath: string): Promise<string | undefined>
1518
1423
  } catch (err) {
1519
1424
  logger.debug(
1520
1425
  `[zcode-engine] probe version check failed (best-effort continue): ${
1521
- err instanceof Error ? err.message : String(err)
1426
+ toErrorMessage(err)
1522
1427
  }`,
1523
1428
  );
1524
1429
  return undefined;