@zhushanwen/subagent-core 0.2.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-43ONBFZX.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-V3VHZ2VX.js +59 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
  18. package/dist/execution/engine/engines/zcode/constants.js +39 -7
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +18234 -1277
  24. package/dist/index.d.cts +5646 -673
  25. package/dist/index.d.ts +5646 -673
  26. package/dist/index.js +17828 -1277
  27. package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
  28. package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
  29. package/package.json +7 -4
  30. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  31. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  32. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  33. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  34. package/src/__tests__/manifest-store.test.ts +10 -9
  35. package/src/__tests__/record-store-cache.test.ts +0 -2
  36. package/src/__tests__/record-store-index.test.ts +1 -2
  37. package/src/__tests__/review-fix-loop-script.test.ts +93 -3
  38. package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
  39. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  40. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  41. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  42. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  43. package/src/__tests__/session-runner.test.ts +1 -1
  44. package/src/__tests__/smoke.test.ts +9 -2
  45. package/src/core/__tests__/host-services.test.ts +2 -2
  46. package/src/core/__tests__/logger.test.ts +1 -1
  47. package/src/core/error-message.ts +9 -0
  48. package/src/core/host-services.ts +21 -5
  49. package/src/core/notify-ports.ts +18 -5
  50. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  51. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  52. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  53. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  54. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  55. package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
  56. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
  57. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  58. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  59. package/src/execution/__tests__/delivery-methods.test.ts +77 -62
  60. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  61. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  62. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  63. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  64. package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
  65. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  66. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  67. package/src/execution/__tests__/execution-record.test.ts +217 -60
  68. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  69. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  70. package/src/execution/__tests__/finalize-record.test.ts +278 -16
  71. package/src/execution/__tests__/gc-timer.test.ts +57 -2
  72. package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
  73. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  74. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  75. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  76. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  77. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  78. package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
  79. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  80. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  83. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  84. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  85. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  86. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  87. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  88. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  89. package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
  90. package/src/execution/__tests__/output-collector.test.ts +117 -6
  91. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  92. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  93. package/src/execution/__tests__/record-store.test.ts +89 -5
  94. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  95. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  96. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  97. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  98. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  105. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
  106. package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
  107. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  108. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  109. package/src/execution/__tests__/session-pending.test.ts +471 -0
  110. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  111. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  112. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  113. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  114. package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
  115. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
  116. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  117. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  118. package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
  119. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  120. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  121. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  122. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  123. package/src/execution/__tests__/spawned-children.test.ts +155 -11
  124. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  125. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  126. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  127. package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
  128. package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
  129. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  130. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
  131. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
  132. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
  133. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  134. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  135. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  136. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  137. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  138. package/src/execution/__tests__/timeout-integration.test.ts +13 -12
  139. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  140. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  141. package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
  142. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  143. package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
  144. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  145. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  146. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  147. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  148. package/src/execution/agent-registry.ts +298 -30
  149. package/src/execution/agent-result-mapper.ts +3 -0
  150. package/src/execution/agents-assembly.ts +88 -0
  151. package/src/execution/cold-resurrect.ts +199 -0
  152. package/src/execution/concurrency-pool.ts +71 -9
  153. package/src/execution/dialog-queue.ts +101 -3
  154. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  155. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  156. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  158. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  159. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  160. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  161. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  162. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
  163. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +94 -3
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engine-discovery.ts +12 -16
  181. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  182. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  183. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  184. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
  185. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  186. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  187. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  188. package/src/execution/engine/engines/pi/reader.ts +11 -0
  189. package/src/execution/engine/engines/pi/registration.ts +4 -2
  190. package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
  191. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  192. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  193. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  194. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  195. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  197. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  198. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
  199. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  200. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  202. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  203. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  209. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  210. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
  211. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  212. package/src/execution/engine/engines/zcode/connection.ts +598 -0
  213. package/src/execution/engine/engines/zcode/constants.ts +147 -14
  214. package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
  215. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  216. package/src/execution/engine/engines/zcode/preparer.ts +22 -150
  217. package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
  218. package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
  219. package/src/execution/engine/host-task-spec.ts +32 -36
  220. package/src/execution/engine/model-validation.ts +177 -0
  221. package/src/execution/engine/port.ts +65 -7
  222. package/src/execution/engine/registry.ts +53 -0
  223. package/src/execution/engine/routing.ts +75 -1
  224. package/src/execution/engine/types.ts +30 -103
  225. package/src/execution/execution-record.ts +222 -144
  226. package/src/execution/finalize-record.ts +181 -85
  227. package/src/execution/idle-gc.ts +28 -0
  228. package/src/execution/lifecycle-manager.ts +27 -3
  229. package/src/execution/lifecycle-predicates.ts +1 -1
  230. package/src/execution/manifest-store.ts +53 -62
  231. package/src/execution/notifier.ts +10 -10
  232. package/src/execution/notify-host.ts +212 -0
  233. package/src/execution/notify-ledger.ts +117 -16
  234. package/src/execution/record-entry.ts +8 -2
  235. package/src/execution/record-store.ts +148 -88
  236. package/src/execution/round-settlement.ts +93 -0
  237. package/src/execution/session-file-gc.ts +96 -62
  238. package/src/execution/session-pending.ts +213 -62
  239. package/src/execution/session-reconstructor.ts +256 -143
  240. package/src/execution/sessions-index.ts +127 -102
  241. package/src/execution/settled-watchdog.ts +311 -0
  242. package/src/execution/subagent-actions-core.ts +686 -0
  243. package/src/execution/subagent-service.ts +1106 -907
  244. package/src/execution/subprocess-agent-runner.ts +144 -97
  245. package/src/execution/types.ts +9 -0
  246. package/src/execution/ui-request-handler-factory.ts +126 -75
  247. package/src/execution/ui-request-queue.ts +50 -19
  248. package/src/execution/worktree-git-ops.ts +397 -0
  249. package/src/execution/worktree-manager.ts +72 -10
  250. package/src/execution/worktree-registry.ts +6 -12
  251. package/src/index.ts +542 -9
  252. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  253. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
  254. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  255. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  257. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  258. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
  259. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  260. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  261. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  262. package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
  263. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  264. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  265. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  266. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  267. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  268. package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
  269. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  270. package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
  271. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  272. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  273. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  274. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  275. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  276. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
  277. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  278. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  279. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  280. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  281. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  282. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  283. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  284. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  285. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  286. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  287. package/src/orchestration/agent-opts-resolver.ts +8 -8
  288. package/src/orchestration/args-meta.ts +198 -0
  289. package/src/orchestration/args-validator.ts +53 -31
  290. package/src/orchestration/execute-agent-call.ts +25 -79
  291. package/src/orchestration/file-run-store.ts +327 -0
  292. package/src/orchestration/launcher.ts +160 -104
  293. package/src/orchestration/lifecycle.ts +371 -97
  294. package/src/orchestration/models/agent-call.ts +7 -7
  295. package/src/orchestration/models/budget.ts +5 -5
  296. package/src/orchestration/models/ports.ts +4 -4
  297. package/src/orchestration/models/run-runtime.ts +15 -15
  298. package/src/orchestration/models/trace.ts +9 -9
  299. package/src/orchestration/models/types.ts +111 -22
  300. package/src/orchestration/models/workflow-run.ts +28 -28
  301. package/src/orchestration/models/workflow-script.ts +4 -4
  302. package/src/orchestration/run-snapshot.ts +266 -0
  303. package/src/orchestration/script-generate.ts +154 -0
  304. package/src/orchestration/script-lint.ts +123 -90
  305. package/src/orchestration/worker-handle.ts +10 -10
  306. package/src/orchestration/worker-host.ts +10 -10
  307. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
  308. package/src/orchestration/worker-script-builder.ts +401 -346
  309. package/src/orchestration/workflow-files.ts +37 -11
  310. package/src/orchestration/workflow-run-summary.ts +69 -0
  311. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  312. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  313. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  314. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  315. package/src/shared/__tests__/injection-render.test.ts +518 -0
  316. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  317. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  318. package/src/shared/__tests__/resource-discovery.test.ts +261 -217
  319. package/src/shared/agent-ref.ts +143 -2
  320. package/src/shared/atomic-write.ts +323 -0
  321. package/src/shared/bounded-serialize.ts +154 -0
  322. package/src/shared/injection-render.ts +279 -0
  323. package/src/shared/meta-parser.ts +182 -68
  324. package/src/shared/model-ref.ts +15 -2
  325. package/src/shared/resource-discovery.ts +97 -204
  326. package/src/shared/resource-meta.ts +14 -0
  327. package/src/shared/schema-jsonify.ts +1 -1
  328. package/src/shared/xml-injection.ts +9 -9
  329. package/workflows/README.md +9 -9
  330. package/workflows/chain.js +4 -2
  331. package/workflows/map-reduce.js +5 -3
  332. package/workflows/parallel.js +5 -3
  333. package/workflows/review-fix-loop-utils.cjs +165 -92
  334. package/workflows/review-fix-loop-utils.d.cts +287 -0
  335. package/workflows/review-fix-loop.js +18 -7
  336. package/workflows/scatter-gather.js +4 -2
  337. package/dist/chunk-3VOERJPJ.js +0 -22
  338. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  339. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  340. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  341. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  342. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  343. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  344. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  345. package/src/execution/execute-options-mapper.ts +0 -115
  346. package/src/execution/session-runner.ts +0 -1789
  347. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,968 @@
1
+ // src/execution/engine/engines/zcode/session-channel.ts
2
+ //
3
+ // ZcodeEngine app-server 会话层(R3)。设计权威源:
4
+ // docs/design/zcode-engine-appserver-resident.md §3.3 D4(会话生命周期:每任务自包含
5
+ // create→run→close,不做热会话复用)/ §2.4 目标数据流 ①-⑦ / §3.4 不变量 1
6
+ // (text_delta 拼接 == read 全文、终态唯一 turn.terminal 权威、收尾帧 usage 完整)
7
+ // 与 2(resolve 严格晚于全部事件回调)/ 附录 A.2(任务生命周期帧序列——协议权威,
8
+ // 本文件帧字段逐字来源)。旧实现参照:zsw 仓 84b63a0^ lib/runner-appserver.js 的
9
+ // _createTurn/_fetchFinalResponse 同等层(终态判定 / read 兜底链为真机已验证形态,
10
+ // TS 重写收窄到 D4 单任务自包含面——-32004 四步恢复序 / resume / 热会话复用整体
11
+ // out of scope,见设计 D4 连带声明)。
12
+ //
13
+ // 分层边界(与 R4 的接线面):
14
+ // - 本层只做「单任务会话通道」:create/subscribe/send/终态/read/close + runTurn
15
+ // 组合入口。AgentEvent 合成(text_delta→事件流、message_end.usage 映射走
16
+ // parser.mapZcodeUsage 同源)与 abort 链(session/stop→杀链)归 R4 编排层;
17
+ // - 连接生命周期(惰性启动/重建/dispose)归 AppServerConnection(R2)与 R4;
18
+ // - 错误透传纪律:RPC error 应答(AppServerRpcError,含 code/message/data)原样
19
+ // 上抛不吞不包装——R5 降级链按 code(-32601/-32602 漂移类)归类(§3.3 错误规格表)。
20
+ //
21
+ // 终态判定(D4 + 不变量 1)双保险:
22
+ // 1. turn.terminal 权威:v4/telemetry/event {kind:"turn.terminal"} 到达即终态
23
+ // (success/error 均 算终态——旧实现实证:不归类会挂到超时);
24
+ // 2. 宽松匹配防洪堤:turn.terminal 缺失/迟到时,收尾帧(session/event
25
+ // payload.response 非空)即终态——防协议小漂移把任务挂死到超时预算。
26
+ // 终态判定后:收尾帧数据(response/usage)仍吸收(它属终态数据不是增量回调,
27
+ // 且与 turn.terminal 常在同一 stdout 批次到达);迟到 delta 不再触发回调
28
+ // (不变量 2 的另一半:resolve 后不再发事件)。
29
+ //
30
+ // 连接崩溃的 turn 收割(R4 已补齐):SessionChannel 在构造时订阅 AppServerConnection
31
+ // 的 onClose 面——进程死亡(崩溃/我方杀链)时立即 fail 全部在途 turn(错误即连接层
32
+ // 的崩溃 reason,含 stderr 尾部),不再依赖 turn 等待预算挂满才收割。
33
+ // (onClose 由连接层保证在全部在途 request reject 之后触发。)
34
+ //
35
+ // turn 等待两 timer 状态机(P0-1 根修,设计权威源
36
+ // docs/design/timeout-zcode-turn-and-settled-watchdog.md §6 D1/D2):旧 300s 固定
37
+ // 墙钟(timer 从 send 起跳、事件不刷新,T001 实测 21% 活跃任务被误杀)替换为——
38
+ // 1. idle 主判定:本 turn 任何事件(session/event、telemetry stream.chunk/
39
+ // turn.terminal)刷新计时;连续静默达阈值判「执行已不可推进」(活跃事件流
40
+ // 零误杀,ADR-0047 逆否面);缺省 30min(ZCODE_TURN_IDLE_TIMEOUT_MS,
41
+ // ⛔P-Z1 标定前先验值);
42
+ // 2. 总上界回收兜底:从挂载起固定不刷新,兜 idle 覆盖不了的 chatty-wedge
43
+ // (事件持续但终态永不到达);缺省 60min(ZCODE_TURN_MAX_TIMEOUT_MS,
44
+ // ⛔P-Z0 标定前先验值;对超上界合法极长任务是显式接受的残余误杀面)。
45
+ // 任一 fire → reject TurnTimeoutError{kind:"idle"|"ceiling", lastEventAt,
46
+ // elapsed}(类型化,R4 引擎据此分流与合成 engine_timeout 文案)。两阈值 env
47
+ // 可调(XYZ_ZCODE_TURN_IDLE_TIMEOUT_MS / XYZ_ZCODE_TURN_MAX_TIMEOUT_MS)、
48
+ // ≤0 显式关闭(关闭时 warn 明示后果——规则 19 opt-out,A10① 断言依据)。
49
+ // create 应答先于挂 timer 到达(runTurn 先 createSession 后 openTurn),不参与
50
+ // 刷新。
51
+
52
+ import { createHash } from "node:crypto";
53
+
54
+ import { getLogger } from "../../../../core/logger.ts";
55
+
56
+ import type { AppServerConnection } from "./connection.ts";
57
+ import {
58
+ ZCODE_APPSERVER_TURN_CLOSE_TIMEOUT_MS,
59
+ ZCODE_APPSERVER_TURN_READ_TIMEOUT_MS,
60
+ ZCODE_TURN_IDLE_TIMEOUT_ENV,
61
+ ZCODE_TURN_IDLE_TIMEOUT_MS,
62
+ ZCODE_TURN_MAX_TIMEOUT_ENV,
63
+ ZCODE_TURN_MAX_TIMEOUT_MS,
64
+ parseZcodeTurnTimeoutEnv,
65
+ } from "./constants.ts";
66
+ import { toErrorMessage } from "../../../../core/error-message.ts";
67
+
68
+ const logger = getLogger("subagents");
69
+
70
+ /** session/subscribe 的 deliveryKind 必填值(A.2 ②——缺失则终态事件不达、会话假死,旧实测)。 */
71
+ export const SUBSCRIBE_DELIVERY_KIND = "desktop-continuous";
72
+
73
+ /** workspaceKey 取 sha256 hex 的前导字符数(旧实现实测形态 'ws-' + 16 hex 短 hash)。 */
74
+ const WORKSPACE_KEY_HASH_CHARS = 16;
75
+
76
+ /** create 应答异常时的摘要截断长度(够定位形态、不刷屏——与旧实现同值)。 */
77
+ const CREATE_REPLY_LOG_CHARS = 300;
78
+
79
+ /** 推送帧文本进日志的截断长度(诊断够用即可)。 */
80
+ const DELTA_LOG_CHARS = 120;
81
+
82
+ function isRecord(v: unknown): v is Record<string, unknown> {
83
+ return typeof v === "object" && v !== null && !Array.isArray(v);
84
+ }
85
+
86
+ function errMessage(err: unknown): string {
87
+ return toErrorMessage(err);
88
+ }
89
+
90
+ // ============================================================
91
+ // 协议形态提取(纯函数,conformance/golden 回放层可复用)
92
+ // ============================================================
93
+
94
+ /**
95
+ * workspacePath → 稳定短 hash(A.2 ①:workspaceKey 算法不限;取旧实现实测形态
96
+ * 'ws-' + sha256 前 16 hex)。
97
+ */
98
+ export function stableWorkspaceKey(workspacePath: string): string {
99
+ return (
100
+ "ws-" +
101
+ createHash("sha256")
102
+ .update(workspacePath)
103
+ .digest("hex")
104
+ .slice(0, WORKSPACE_KEY_HASH_CHARS)
105
+ );
106
+ }
107
+
108
+ /**
109
+ * 从 session/create 应答提取 sessionId。A.2 ①:权威位置 result.session.sessionId
110
+ * (**projection.sessionId 恒 "unknown",勿用**);session.id / 顶层 sessionId 为
111
+ * 历史/未来形态的宽松兜底(旧实现实测提取链)。
112
+ */
113
+ export function extractCreatedSessionId(created: unknown): string | undefined {
114
+ if (!isRecord(created)) return undefined;
115
+ const session = isRecord(created.session) ? created.session : undefined;
116
+ if (
117
+ session !== undefined &&
118
+ typeof session.sessionId === "string" &&
119
+ session.sessionId !== ""
120
+ ) {
121
+ return session.sessionId;
122
+ }
123
+ if (
124
+ session !== undefined &&
125
+ typeof session.id === "string" &&
126
+ session.id !== ""
127
+ ) {
128
+ return session.id;
129
+ }
130
+ if (typeof created.sessionId === "string" && created.sessionId !== "") {
131
+ return created.sessionId;
132
+ }
133
+ return undefined;
134
+ }
135
+
136
+ /** 推送帧的会话归属(A.2:session/event 帧带 params.sessionId;telemetry 帧未标——
137
+ * 无 sid 时由 lookupTurn 按唯一在途兜底归因,多在途宁丢勿错,旧实现同策略)。 */
138
+ function extractPushSessionId(params: unknown): string | undefined {
139
+ if (!isRecord(params)) return undefined;
140
+ if (typeof params.sessionId === "string" && params.sessionId !== "")
141
+ return params.sessionId;
142
+ if (
143
+ isRecord(params.session) &&
144
+ typeof params.session.id === "string" &&
145
+ params.session.id !== ""
146
+ ) {
147
+ return params.session.id;
148
+ }
149
+ return undefined;
150
+ }
151
+
152
+ /** read 应答消息条目的 role(实测主形态在 m.info.role,旧形态在 m.role)。 */
153
+ function messageRole(m: Record<string, unknown>): string | undefined {
154
+ if (typeof m.role === "string") return m.role;
155
+ if (isRecord(m.info) && typeof m.info.role === "string") return m.info.role;
156
+ return undefined;
157
+ }
158
+
159
+ /** parts[] 中 type:'text' 部件的 text 拼接(A.2 ⑥ 主形态;无 text 部件 → undefined)。 */
160
+ function partsToText(parts: unknown): string | undefined {
161
+ if (!Array.isArray(parts)) return undefined;
162
+ const texts: string[] = [];
163
+ for (const p of parts) {
164
+ if (isRecord(p) && p.type === "text" && typeof p.text === "string")
165
+ texts.push(p.text);
166
+ }
167
+ return texts.length > 0 ? texts.join("") : undefined;
168
+ }
169
+
170
+ /** content 三形态(字符串 / {text} / 块数组)的旧形态兼容提取。 */
171
+ function contentToText(content: unknown): string | undefined {
172
+ if (typeof content === "string") return content;
173
+ if (isRecord(content) && typeof content.text === "string")
174
+ return content.text;
175
+ if (Array.isArray(content)) {
176
+ const texts = content.map((b) => {
177
+ if (typeof b === "string") return b;
178
+ return isRecord(b) && typeof b.text === "string" ? b.text : "";
179
+ });
180
+ const joined = texts.join("");
181
+ return joined !== "" ? joined : undefined;
182
+ }
183
+ return undefined;
184
+ }
185
+
186
+ /**
187
+ * 从 session/read 应答提取最后一条 assistant 全文(A.2 ⑥:messages[].parts[] 中
188
+ * type:'text' 的 text 拼接;role 在 m.info.role 实测主形态,m.role/m.content 旧形态
189
+ * 兼容——旧实现已验证宽容链)。提取不到返回 undefined(调用方走收尾帧→delta 聚合)。
190
+ */
191
+ export function extractAssistantText(readResult: unknown): string | undefined {
192
+ if (typeof readResult === "string") return readResult;
193
+ if (!isRecord(readResult)) return undefined;
194
+ if (Array.isArray(readResult.messages)) {
195
+ const last = [...readResult.messages]
196
+ .reverse()
197
+ .find(
198
+ (m): m is Record<string, unknown> =>
199
+ isRecord(m) &&
200
+ (messageRole(m) === "assistant" || messageRole(m) === undefined)
201
+ );
202
+ if (last !== undefined) {
203
+ const viaContent = contentToText(last.content);
204
+ if (viaContent !== undefined) return viaContent;
205
+ const viaParts = partsToText(last.parts);
206
+ if (viaParts !== undefined) return viaParts;
207
+ }
208
+ }
209
+ for (const k of ["text", "response", "content", "message"] as const) {
210
+ if (typeof readResult[k] === "string") return readResult[k] as string;
211
+ }
212
+ return undefined;
213
+ }
214
+
215
+ /**
216
+ * 从 session/read 应答提取 usage。tokens 在最后一条 assistant 消息 parts 的
217
+ * step-finish 部件 tokens 字段——结构属 A.5 未确认项(未真机复核),宽容解析:
218
+ * tokens 是对象即收、字段缺席不抛;顶层 usage 旧假设形态兜底。
219
+ */
220
+ export function extractReadUsage(
221
+ readResult: unknown
222
+ ): SessionTurnUsage | undefined {
223
+ if (!isRecord(readResult)) return undefined;
224
+ const messages = Array.isArray(readResult.messages)
225
+ ? readResult.messages
226
+ : [];
227
+ for (const m of [...messages].reverse()) {
228
+ if (
229
+ !isRecord(m) ||
230
+ messageRole(m) !== "assistant" ||
231
+ !Array.isArray(m.parts)
232
+ )
233
+ continue;
234
+ const finish = [...m.parts]
235
+ .reverse()
236
+ .find(
237
+ (p) => isRecord(p) && p.type === "step-finish" && isRecord(p.tokens)
238
+ );
239
+ if (finish !== undefined && isRecord(finish.tokens)) return finish.tokens;
240
+ }
241
+ return isRecord(readResult.usage) ? readResult.usage : undefined;
242
+ }
243
+
244
+ // ============================================================
245
+ // 类型(R4 引擎 run 的消费面)
246
+ // ============================================================
247
+
248
+ /** session/create 的 per-session 模型(A.2 ①:strict 对象——字符串会被 -32602 拒收)。 */
249
+ export interface SessionModelSpec {
250
+ providerId: string;
251
+ modelId: string;
252
+ variant?: string;
253
+ }
254
+
255
+ /** session/create 参数(A.2 ① 键集逐字;workspaceKey 由 workspacePath 派生,不手传)。 */
256
+ export interface SessionCreateParams {
257
+ workspacePath: string;
258
+ /** 权限模式(如 "yolo")——值语义不限(A.2),由调用方(R4)决定。 */
259
+ mode: string;
260
+ model?: SessionModelSpec;
261
+ thoughtLevel?: string;
262
+ toolAllowlist?: string[];
263
+ toolDenylist?: string[];
264
+ }
265
+
266
+ /**
267
+ * 一轮 usage(宽容形态):主形态收尾帧 {inputTokens, outputTokens}(A.2 ⑤ 权威);
268
+ * 兜底形态 read step-finish tokens {input,...}(A.5 未确认项)。字段名到 AgentUsage
269
+ * 的映射归 R4(与 parser.mapZcodeUsage 同源),本层不做字段转换。
270
+ */
271
+ export interface SessionTurnUsage {
272
+ inputTokens?: unknown;
273
+ outputTokens?: unknown;
274
+ [key: string]: unknown;
275
+ }
276
+
277
+ /** runTurn 的流式回调(R4 映射为 AgentEvent {type:"text_delta", delta})。 */
278
+ export interface SessionTurnCallbacks {
279
+ /** payload.delta 实时文本增量(A.2 ④:文本在 session/event,stream.chunk 无文本)。 */
280
+ onTextDelta?: (delta: string) => void;
281
+ /**
282
+ * [R4] create 应答后立即回调(sessionId 已知、早于 subscribe/send/终态)。引擎
283
+ * 编排层的 onHandleReady 回填(§3.4 不变量 3)与 abort 链的 session/stop 目标
284
+ * 都挂在这个时点——runTurn 的 resolve 形态在终态前拿不到 sessionId。
285
+ */
286
+ onSessionCreated?: (sessionId: string) => void;
287
+ }
288
+
289
+ /** turn 超时的判定形态(P0-1 D1:idle 主判定 / 总上界兜底——引擎分流与文案的判据)。 */
290
+ export type TurnTimeoutKind = "idle" | "ceiling";
291
+
292
+ /**
293
+ * turn 等待两 timer 的类型化超时错误(P0-1 D1/D4)。引擎(R4)按 `kind` 分流:
294
+ * 超时入口接 abort 链(stop 应答三态裁决)+ `engine_timeout` 前缀合成——不经
295
+ * 字符串匹配。字段供 outcome 文案使用:`elapsed` 距 openTurn 挂载的总时长;
296
+ * `lastEventAt` 最后一次事件到达时刻(整轮无任何事件时 undefined——进程假死/
297
+ * 协议静默形态的证据)。
298
+ */
299
+ export class TurnTimeoutError extends Error {
300
+ readonly kind: TurnTimeoutKind;
301
+ readonly elapsed: number;
302
+ readonly lastEventAt: number | undefined;
303
+ readonly thresholdMs: number;
304
+
305
+ constructor(
306
+ kind: TurnTimeoutKind,
307
+ parts: {
308
+ thresholdMs: number;
309
+ elapsed: number;
310
+ lastEventAt: number | undefined;
311
+ }
312
+ ) {
313
+ super(
314
+ kind === "idle"
315
+ ? `zcode turn idle 判死:连续 ${parts.thresholdMs}ms 未观察到本 turn 任何事件` +
316
+ "(session/event 与 v4/telemetry/event 均静默),终态未到达。" +
317
+ (parts.lastEventAt === undefined
318
+ ? "本轮自 send 起未观察到任何事件(进程假死/协议静默形态)。"
319
+ : `最后事件时刻 ${new Date(parts.lastEventAt).toISOString()}。`) +
320
+ "恢复指引:直接重跑本任务;若持续出现,检查 ZCode 桌面端模型连通性或改用 engine: pi。"
321
+ : `zcode turn 总上界判死:${parts.thresholdMs}ms 内未观察到终态` +
322
+ "(turn.terminal 与收尾帧均未到达;idle 判定未触发——事件流仍活跃,chatty-wedge 形态)。" +
323
+ (parts.lastEventAt === undefined
324
+ ? ""
325
+ : `最后事件时刻 ${new Date(parts.lastEventAt).toISOString()}。`) +
326
+ `恢复指引:直接重跑本任务;若本任务属合法超长任务(预期超过 ${parts.thresholdMs}ms 总上界),` +
327
+ `重跑前设 ${ZCODE_TURN_MAX_TIMEOUT_ENV} 为更大毫秒值,或设 0 关闭总上界` +
328
+ "(关闭后 chatty 形态不再自动回收,静默 wedged 仍由 idle 层兜底——自行权衡)。"
329
+ );
330
+ this.name = "TurnTimeoutError";
331
+ this.kind = kind;
332
+ this.elapsed = parts.elapsed;
333
+ this.lastEventAt = parts.lastEventAt;
334
+ this.thresholdMs = parts.thresholdMs;
335
+ }
336
+ }
337
+
338
+ export interface SessionTurnOptions extends SessionTurnCallbacks {
339
+ /**
340
+ * 显式总上界(ms,P0-1 D1/D2 语义收窄:不再是从 send 起跳的固定墙钟缺省预算,
341
+ * 而是「显式总上界」传参面——缺省走 env `XYZ_ZCODE_TURN_MAX_TIMEOUT_MS` →
342
+ * `ZCODE_TURN_MAX_TIMEOUT_MS`(60min 先验值);≤0 显式关闭该 timer。工具面
343
+ * 不暴露(D2),引擎内部传参点(D6 重试预算继承传剩余值)。
344
+ */
345
+ turnTimeoutMs?: number;
346
+ /**
347
+ * idle 主判定静默阈值(ms,P0-1 D2 内部传参点):缺省走 env
348
+ * `XYZ_ZCODE_TURN_IDLE_TIMEOUT_MS` → `ZCODE_TURN_IDLE_TIMEOUT_MS`(30min
349
+ * 先验值);≤0 显式关闭该 timer。
350
+ */
351
+ idleTimeoutMs?: number;
352
+ }
353
+
354
+ /** 终态信号来源(D4:turn.terminal 权威;final-frame = 宽松判定防洪堤)。 */
355
+ export type TerminalSource = "turn.terminal" | "final-frame";
356
+
357
+ export interface SessionTurnResult {
358
+ sessionId: string;
359
+ /** 全文优先级:read 兜底(权威)→ 收尾帧 response → delta 聚合。 */
360
+ response: string;
361
+ /** usage 优先级:收尾帧(A.2 权威)→ read step-finish tokens(宽容)→ 缺席。 */
362
+ usage?: SessionTurnUsage;
363
+ terminal: { status: string; source: TerminalSource };
364
+ /**
365
+ * 权威终态 turn.terminal 的 status 记录(P0-1 D5①/S5 修复):权威终态晚于
366
+ * final-frame 宽松终态到达时只记录不改写已落定 `terminal`——消费层据此识破
367
+ * 假成功(final-frame 恒 success)。无 turn.terminal 到达时缺席。
368
+ */
369
+ lastTerminalStatus?: string;
370
+ /**
371
+ * 权威终态 turn.terminal 的错误详情(⛔P-Z2 实证:真实 failed 终态的 errorCode/
372
+ * errorMessage 只在 terminal 帧携带——read/delta 拿不到,丢即诊断信息永久丢失)。
373
+ * 先到/迟到都记录(与 lastTerminalStatus 同型);至少其一存在才带此字段。
374
+ */
375
+ lastTerminalError?: { code?: string; message?: string };
376
+ }
377
+
378
+ // ============================================================
379
+ // 会话通道
380
+ // ============================================================
381
+
382
+ /** 终态信息(turn.terminal 的 status 原样透传——错误语义由 R4 结果层处理)。 */
383
+ interface TerminalInfo {
384
+ status: string;
385
+ source: TerminalSource;
386
+ }
387
+
388
+ /**
389
+ * 一个在途 turn 的全部状态(推送泵的归因目标)。settle/fail 由 openTurn 装配
390
+ * (互斥守卫 + timer 清理 + done promise 落定),泵内只调用不感知装配细节。
391
+ */
392
+ interface ActiveTurn {
393
+ readonly sessionId: string;
394
+ readonly callbacks: SessionTurnCallbacks;
395
+ /** 终态判定是否已落定(settle/fail 任一先行即 true——后续重复终态不再受理)。 */
396
+ settled: boolean;
397
+ deltas: string[];
398
+ finalText: string | undefined;
399
+ finalUsage: SessionTurnUsage | undefined;
400
+ terminal: TerminalInfo | undefined;
401
+ /** 权威终态 turn.terminal 的 status 记录(先到/迟到都记——D5①)。 */
402
+ lastTerminalStatus: string | undefined;
403
+ /** 权威终态 turn.terminal 的错误详情(先到/迟到都记——⛔P-Z2,terminal 帧独有)。 */
404
+ lastTerminalError: { code?: string; message?: string } | undefined;
405
+ /** 本轮生效阈值(openTurn 解析后的值,fire 时进 TurnTimeoutError 文案)。 */
406
+ idleMs: number;
407
+ ceilingMs: number;
408
+ /** 挂载时刻(elapsed 起算点)。 */
409
+ startedAt: number;
410
+ /** 最后一次事件到达时刻(epoch ms;整轮无事件 undefined——idle 判定的证据面)。 */
411
+ lastEventAt: number | undefined;
412
+ /** 两 timer 句柄(刷新=clearTimeout+重挂;settle/fail/fire 统一清理)。 */
413
+ idleTimer: NodeJS.Timeout | undefined;
414
+ ceilingTimer: NodeJS.Timeout | undefined;
415
+ settle: (t: TerminalInfo) => void;
416
+ fail: (err: Error) => void;
417
+ /** 超时落定(openTurn 装配:互斥守卫 + 清 timer + 类型化 reject)。 */
418
+ fireTimeout: (kind: TurnTimeoutKind) => void;
419
+ }
420
+
421
+ /** 两 timer 统一清理(settle/fail/超时 fire/runTurn finally 四路共匯)。 */
422
+ function clearTurnTimers(turn: ActiveTurn): void {
423
+ if (turn.idleTimer !== undefined) {
424
+ clearTimeout(turn.idleTimer);
425
+ turn.idleTimer = undefined;
426
+ }
427
+ if (turn.ceilingTimer !== undefined) {
428
+ clearTimeout(turn.ceilingTimer);
429
+ turn.ceilingTimer = undefined;
430
+ }
431
+ }
432
+
433
+ /** setTimeout + unref(守卫进程退出不被 turn 预算拖住——既有 300s timer 同形态)。 */
434
+ function armTurnTimer(onFire: () => void, ms: number): NodeJS.Timeout {
435
+ const timer = setTimeout(onFire, ms);
436
+ if (typeof timer.unref === "function") timer.unref();
437
+ return timer;
438
+ }
439
+
440
+ /**
441
+ * 单个 turn timer 阈值解析(P0-1 D2):显式传参 > env > 缺省默认。env ≤0 与显式
442
+ * ≤0 同为「显式关闭」语义(与 `XYZ_SUBAGENT_IDLE_TIMEOUT_MS` 先例的刻意分歧,
443
+ * 设计 D2/r3 SG-5 登记);env 非法 warn+回落默认。**关闭必须 warn 明示后果**——
444
+ * 静默失去回收层 = 「以为有兜底、实际裸奔」,生效行为必须可见(A10① 断言依据)。
445
+ */
446
+ function resolveTurnTimerMs(parts: {
447
+ explicit: number | undefined;
448
+ envName: string;
449
+ fallbackMs: number;
450
+ label: string;
451
+ offConsequence: string;
452
+ }): number {
453
+ let value: number;
454
+ let source: string;
455
+ if (parts.explicit !== undefined) {
456
+ value = parts.explicit;
457
+ source = "显式传参";
458
+ } else {
459
+ const raw = process.env[parts.envName];
460
+ const parsed = parseZcodeTurnTimeoutEnv(raw);
461
+ if (parsed.state === "valid") {
462
+ value = parsed.ms;
463
+ source = `env ${parts.envName}=${raw}`;
464
+ } else {
465
+ if (parsed.state === "invalid") {
466
+ logger.warn(
467
+ `[session-channel] ${parts.envName}="${raw}" 非法(应为毫秒数字)——回落默认 ${parts.fallbackMs}ms(${parts.label})。设置正毫秒值覆盖,或 0 显式关闭`
468
+ );
469
+ }
470
+ value = parts.fallbackMs;
471
+ source = "默认值";
472
+ }
473
+ }
474
+ if (value <= 0) {
475
+ logger.warn(
476
+ `[session-channel] zcode turn ${parts.label}已关闭(${source})——${parts.offConsequence}。设正毫秒值(env ${parts.envName} 或显式传参)恢复回收层`
477
+ );
478
+ }
479
+ return value;
480
+ }
481
+
482
+ /**
483
+ * 单任务会话通道(D4):在一条共享 AppServerConnection 上实现
484
+ * create→subscribe→send→(事件流)→终态→read 兜底→resolve→close 的自包含生命周期。
485
+ * 多 session 可在同一条连接上并发(sessionId 归因防串线;无 sid 的推送帧仅归因到
486
+ * 唯一在途 turn,多在途宁丢勿错)。
487
+ */
488
+ export class SessionChannel {
489
+ private readonly conn: AppServerConnection;
490
+ /** sessionId → 在途 turn(终态落定后仍保留到 runTurn 收尾——收尾帧数据吸收窗口)。 */
491
+ private readonly activeTurns = new Map<string, ActiveTurn>();
492
+ private readonly offHandlers: Array<() => void>;
493
+ /**
494
+ * [P0-1 U5/D7] dispose 标志:dispose 收割后 closeSession 短路——收割形态下连接
495
+ * 可能仍 alive(close 事件迟到,finalize 未跑、child 非 null),此时发 close
496
+ * request 会写死进程(请求挂到控制面超时)或触发惰性重建(凭空 spawn 无人使用的
497
+ * 进程,违背 dispose 防泄漏语义);进程随即被引擎杀链回收,会话驻留随之消亡。
498
+ */
499
+ private disposed = false;
500
+
501
+ constructor(conn: AppServerConnection) {
502
+ this.conn = conn;
503
+ this.offHandlers = [
504
+ conn.onNotification("session/event", (params) =>
505
+ this.handleSessionEvent(params)
506
+ ),
507
+ conn.onNotification("v4/telemetry/event", (params) =>
508
+ this.handleTelemetry(params)
509
+ ),
510
+ // 连接崩溃收割:进程死亡(崩溃/我方杀链)立即 fail 全部在途 turn——
511
+ // onClose 契约保证触发时连接层在途 request 已全部 reject,此处补齐
512
+ // 「无在途 request 的 turn」的收割(否则挂到 turn idle/总上界预算耗尽)
513
+ conn.onClose((reason) => this.failAllTurns(`app-server ${reason}`)),
514
+ ];
515
+ }
516
+
517
+ /**
518
+ * [P0-1 U5/D7] dispose 前收割 + 退订:「退订 = 不会再有事件」在结构上蕴含「在途
519
+ * turn 不应再等」——close 缺失/被吞没形态(onClose 收割主路径未触发)下,在途
520
+ * turn 于退订前经既有 failAllTurns 收敛为明确失败,不再挂满自身 idle/总上界预算
521
+ * (设计 §3.4 退化路径闭合;race 窗口在引擎 shutdownRuntimeAndDisposeChannel,
522
+ * 量级与 awaitConnFinalized 同源 ZCODE_APPSERVER_HARVEST_GRACE_MS)。幂等:与
523
+ * onClose 收割先到者赢(turn.fail 的 settled 守卫——已落定 turn 不改写),
524
+ * activeTurns 已空则 no-op。不影响连接自身生命周期(R4)。
525
+ */
526
+ dispose(): void {
527
+ this.disposed = true;
528
+ this.failAllTurns(
529
+ "zcode session channel 已 dispose:连接 close 事件未在收割窗口内到达," +
530
+ "在途 turn 于退订前收割为明确失败(不再挂到 turn 自身预算耗尽)。恢复指引:直接重跑本任务。",
531
+ );
532
+ for (const off of this.offHandlers.splice(0)) off();
533
+ }
534
+
535
+ /** 连接死亡收割:全部在途 turn 立即 reject(reason 即连接层崩溃错误)。 */
536
+ private failAllTurns(reason: string): void {
537
+ const err = new Error(reason);
538
+ for (const turn of [...this.activeTurns.values()]) {
539
+ turn.fail(err);
540
+ this.activeTurns.delete(turn.sessionId);
541
+ }
542
+ }
543
+
544
+ /**
545
+ * session/create(A.2 ① 键集逐字:对面 zod strict 拒未知键,出站必须精确)。
546
+ * persistence:"immediate" 固定携带(D4:SQLite 持久化,上游不可覆盖);空工具
547
+ * 清单不设键(防上游带空数组)。sessionId 从 result.session.sessionId 提取
548
+ * (projection.sessionId 恒 "unknown" 勿用)。
549
+ */
550
+ async createSession(params: SessionCreateParams): Promise<string> {
551
+ const frame: Record<string, unknown> = {
552
+ workspace: {
553
+ workspacePath: params.workspacePath,
554
+ workspaceKey: stableWorkspaceKey(params.workspacePath),
555
+ },
556
+ mode: params.mode,
557
+ ...(params.model !== undefined ? { model: params.model } : {}),
558
+ ...(params.thoughtLevel !== undefined
559
+ ? { thoughtLevel: params.thoughtLevel }
560
+ : {}),
561
+ ...(params.toolAllowlist !== undefined && params.toolAllowlist.length > 0
562
+ ? { toolAllowlist: params.toolAllowlist }
563
+ : {}),
564
+ ...(params.toolDenylist !== undefined && params.toolDenylist.length > 0
565
+ ? { toolDenylist: params.toolDenylist }
566
+ : {}),
567
+ persistence: "immediate",
568
+ };
569
+ const created = await this.conn.request("session/create", frame);
570
+ const sessionId = extractCreatedSessionId(created);
571
+ if (sessionId === undefined) {
572
+ throw new Error(
573
+ `session/create 未返回 sessionId: ${JSON.stringify(created).slice(
574
+ 0,
575
+ CREATE_REPLY_LOG_CHARS
576
+ )}`
577
+ );
578
+ }
579
+ return sessionId;
580
+ }
581
+
582
+ /** session/subscribe(A.2 ②:deliveryKind 必填——缺失则终态事件不达、会话假死)。 */
583
+ async subscribe(sessionId: string): Promise<void> {
584
+ await this.conn.request("session/subscribe", {
585
+ sessionId,
586
+ deliveryKind: SUBSCRIBE_DELIVERY_KIND,
587
+ });
588
+ }
589
+
590
+ /** session/send(A.2 ③:字段是 content 不是 text)→ {accepted:true}。 */
591
+ async send(sessionId: string, content: string): Promise<void> {
592
+ const r = await this.conn.request("session/send", { sessionId, content });
593
+ if (isRecord(r) && r.accepted === false) {
594
+ throw new Error(
595
+ "session/send 返回 accepted:false(投递未被接受——会话可能已失效或拒绝投递)"
596
+ );
597
+ }
598
+ }
599
+
600
+ /**
601
+ * session/close(A.2 ⑦,D4 用后即毁——回收引擎驻留内存,SQLite 持久化保留)。
602
+ * best-effort:失败只 warn 不抛(会话已终态,close 失败只泄漏一个驻留会话,
603
+ * 不应炸掉已成功/已失败的任务结论)。连接已死时跳过——close 是 request 语义,
604
+ * 对死连接发请求会触发惰性重建(凭空 spawn 一代无人使用的进程)。
605
+ */
606
+ async closeSession(sessionId: string): Promise<void> {
607
+ // [P0-1 U5/D7] dispose 收割后短路(先于 alive 守卫——收割形态下 alive 可仍为
608
+ // true,见 disposed 字段注释)
609
+ if (this.disposed) return;
610
+ if (!this.conn.alive) return;
611
+ try {
612
+ await this.conn.request(
613
+ "session/close",
614
+ { sessionId },
615
+ { timeoutMs: ZCODE_APPSERVER_TURN_CLOSE_TIMEOUT_MS }
616
+ );
617
+ } catch (err) {
618
+ logger.warn(
619
+ `session/close 失败(会话 ${sessionId},best-effort 忽略): ${errMessage(
620
+ err
621
+ )}`
622
+ );
623
+ }
624
+ }
625
+
626
+ /**
627
+ * 单任务自包含入口(§2.4 ①-⑦):create → subscribe → send → 事件流
628
+ * (onTextDelta)→ 终态 → read 兜底 → resolve {response, usage, sessionId}
629
+ * → close。resolve 严格晚于全部事件回调(不变量 2);close 走 finally——成功/
630
+ * 失败路径都回收会话(D4)。RPC 错误(含 -32601/-32602 漂移类)原样透传(R5 消费)。
631
+ */
632
+ async runTurn(
633
+ params: SessionCreateParams,
634
+ content: string,
635
+ opts: SessionTurnOptions = {}
636
+ ): Promise<SessionTurnResult> {
637
+ if (content === "")
638
+ throw new Error("runTurn: content 必填(session/send 空投递无意义)");
639
+ const sessionId = await this.createSession(params);
640
+ // create 应答后立即回调(§3.4 不变量 3 的 sessionRef 时点;引擎的
641
+ // onHandleReady 回填与 abort 链 stop 目标都从这里取 sessionId)
642
+ opts.onSessionCreated?.(sessionId);
643
+ const opened = this.openTurn(sessionId, opts);
644
+ try {
645
+ await this.subscribe(sessionId);
646
+ try {
647
+ await this.send(sessionId, content);
648
+ } catch (err) {
649
+ // send 未送达(-32010 busy / -32602 漂移 / 连接死):turn 无从等待终态
650
+ opened.turn.fail(err instanceof Error ? err : new Error(String(err)));
651
+ void opened.done.catch(() => {}); // done 已无 await 方,防 dangling rejection
652
+ throw err;
653
+ }
654
+ const terminal = await opened.done;
655
+ // 终态后 read 兜底(D4:read 是全文权威来源——不变量 1 双来源之二;
656
+ // 失败/提取不到走收尾帧→delta 聚合降级链,不抛)
657
+ const readResult = await this.readBestEffort(sessionId);
658
+ const readText = extractAssistantText(readResult);
659
+ const response =
660
+ readText ?? opened.turn.finalText ?? opened.turn.deltas.join("");
661
+ const usage = opened.turn.finalUsage ?? extractReadUsage(readResult);
662
+ return {
663
+ sessionId,
664
+ response,
665
+ ...(usage !== undefined ? { usage } : {}),
666
+ terminal,
667
+ ...(opened.turn.lastTerminalStatus !== undefined
668
+ ? { lastTerminalStatus: opened.turn.lastTerminalStatus }
669
+ : {}),
670
+ ...(opened.turn.lastTerminalError !== undefined
671
+ ? { lastTerminalError: opened.turn.lastTerminalError }
672
+ : {}),
673
+ };
674
+ } finally {
675
+ opened.stopTimers();
676
+ this.activeTurns.delete(sessionId);
677
+ await this.closeSession(sessionId);
678
+ }
679
+ }
680
+
681
+ // ============================================================
682
+ // 内部:turn 生命周期与推送泵
683
+ // ============================================================
684
+
685
+ private openTurn(
686
+ sessionId: string,
687
+ opts: SessionTurnOptions
688
+ ): { turn: ActiveTurn; done: Promise<TerminalInfo>; stopTimers: () => void } {
689
+ let resolveDone!: (t: TerminalInfo) => void;
690
+ let rejectDone!: (err: Error) => void;
691
+ const done = new Promise<TerminalInfo>((res, rej) => {
692
+ resolveDone = res;
693
+ rejectDone = rej;
694
+ });
695
+ const turn: ActiveTurn = {
696
+ sessionId,
697
+ callbacks: { onTextDelta: opts.onTextDelta },
698
+ settled: false,
699
+ deltas: [],
700
+ finalText: undefined,
701
+ finalUsage: undefined,
702
+ terminal: undefined,
703
+ lastTerminalStatus: undefined,
704
+ lastTerminalError: undefined,
705
+ idleMs: 0,
706
+ ceilingMs: 0,
707
+ startedAt: Date.now(),
708
+ lastEventAt: undefined,
709
+ idleTimer: undefined,
710
+ ceilingTimer: undefined,
711
+ // 装配占位:下方 timer 创建后重绑(settle/fail 需要清理两 timer)
712
+ settle: () => {},
713
+ fail: () => {},
714
+ fireTimeout: () => {},
715
+ };
716
+ turn.idleMs = resolveTurnTimerMs({
717
+ explicit: opts.idleTimeoutMs,
718
+ envName: ZCODE_TURN_IDLE_TIMEOUT_ENV,
719
+ fallbackMs: ZCODE_TURN_IDLE_TIMEOUT_MS,
720
+ label: "idle 主判定",
721
+ offConsequence:
722
+ "静默 wedged(无事件)形态将无自动回收,任务可能挂到宿主进程退出",
723
+ });
724
+ turn.ceilingMs = resolveTurnTimerMs({
725
+ explicit: opts.turnTimeoutMs,
726
+ envName: ZCODE_TURN_MAX_TIMEOUT_ENV,
727
+ fallbackMs: ZCODE_TURN_MAX_TIMEOUT_MS,
728
+ label: "总上界",
729
+ offConsequence:
730
+ "chatty-wedge(有事件无终态)形态将无自动回收,仅剩 idle 静默判定兜底",
731
+ });
732
+ // 两 timer 状态机(P0-1 D1):idle 事件刷新重挂、总上界固定倒数;
733
+ // 任一 fire → 类型化 TurnTimeoutError reject(kind 供 R4 分流)。
734
+ const fireTimeout = (kind: TurnTimeoutKind): void => {
735
+ if (turn.settled) return;
736
+ turn.settled = true;
737
+ clearTurnTimers(turn);
738
+ this.activeTurns.delete(sessionId);
739
+ rejectDone(
740
+ new TurnTimeoutError(kind, {
741
+ thresholdMs: kind === "idle" ? turn.idleMs : turn.ceilingMs,
742
+ elapsed: Date.now() - turn.startedAt,
743
+ lastEventAt: turn.lastEventAt,
744
+ })
745
+ );
746
+ };
747
+ turn.fireTimeout = fireTimeout;
748
+ if (turn.idleMs > 0) {
749
+ turn.idleTimer = armTurnTimer(() => turn.fireTimeout("idle"), turn.idleMs);
750
+ }
751
+ if (turn.ceilingMs > 0) {
752
+ turn.ceilingTimer = armTurnTimer(
753
+ () => turn.fireTimeout("ceiling"),
754
+ turn.ceilingMs
755
+ );
756
+ }
757
+ turn.settle = (t: TerminalInfo): void => {
758
+ if (turn.settled) return;
759
+ turn.settled = true;
760
+ turn.terminal = t;
761
+ clearTurnTimers(turn);
762
+ resolveDone(t);
763
+ };
764
+ turn.fail = (err: Error): void => {
765
+ if (turn.settled) return;
766
+ turn.settled = true;
767
+ clearTurnTimers(turn);
768
+ rejectDone(err);
769
+ };
770
+ this.activeTurns.set(sessionId, turn);
771
+ return { turn, done, stopTimers: () => clearTurnTimers(turn) };
772
+ }
773
+
774
+ /**
775
+ * idle 主判定的事件刷新(P0-1 D1):本 turn 任何事件到达即重置 idle 倒数——
776
+ * 活跃事件流零误杀的结构保证。总上界不受影响(固定倒数)。已落定 turn 无
777
+ * timer 可刷新(只剩 lastEventAt 记账)。
778
+ */
779
+ private refreshIdle(turn: ActiveTurn): void {
780
+ turn.lastEventAt = Date.now();
781
+ if (turn.settled || turn.idleTimer === undefined) return;
782
+ clearTimeout(turn.idleTimer);
783
+ turn.idleTimer = armTurnTimer(() => turn.fireTimeout("idle"), turn.idleMs);
784
+ }
785
+
786
+ private lookupTurn(sessionId: string | undefined): ActiveTurn | undefined {
787
+ if (sessionId !== undefined) return this.activeTurns.get(sessionId);
788
+ // 无 sid(A.2 telemetry 帧未标会话归属):仅敢归因到唯一未落定 turn,
789
+ // 多在途宁丢勿错(旧实现同策略——防串线)
790
+ const pending: ActiveTurn[] = [];
791
+ for (const t of this.activeTurns.values()) if (!t.settled) pending.push(t);
792
+ return pending.length === 1 ? pending[0] : undefined;
793
+ }
794
+
795
+ /**
796
+ * turn.terminal 专用归因(P0-1 U1/S5 归因放宽):在 `lookupTurn` 基础上放宽
797
+ * 「已落定 turn 不再受理」——权威终态晚于 final-frame 宽松终态到达(runTurn
798
+ * 收尾窗口内 turn 仍在册)时仍归因,**只记录 status 不改写落定结果**(D5①)。
799
+ * 带 sid 精确归因(落定与否均可);无 sid 仍守宁丢勿错:唯一未落定优先,
800
+ * 全部落定且在册仅剩一个(单任务收尾窗口的典型形态)才归因,多在途丢弃。
801
+ */
802
+ private lookupTurnForTerminal(
803
+ sessionId: string | undefined
804
+ ): ActiveTurn | undefined {
805
+ if (sessionId !== undefined) return this.activeTurns.get(sessionId);
806
+ const pending: ActiveTurn[] = [];
807
+ for (const t of this.activeTurns.values()) if (!t.settled) pending.push(t);
808
+ if (pending.length === 1) return pending[0];
809
+ if (pending.length === 0 && this.activeTurns.size === 1) {
810
+ return [...this.activeTurns.values()][0];
811
+ }
812
+ return undefined;
813
+ }
814
+
815
+ private handleSessionEvent(params: unknown): void {
816
+ const turn = this.lookupTurn(extractPushSessionId(params));
817
+ if (turn === undefined) return;
818
+ const payload =
819
+ isRecord(params) && isRecord(params.payload) ? params.payload : undefined;
820
+ if (payload === undefined) return;
821
+ // 事件到达即刷新 idle 主判定(P0-1 D1——本 turn 的任何 session/event 都算进展)
822
+ this.refreshIdle(turn);
823
+ if (this.applyFinalFrame(turn, payload)) return;
824
+ this.applyStreamDelta(turn, payload);
825
+ }
826
+
827
+ /**
828
+ * 收尾帧判定与吸收(A.2 ⑤ 两种形态:payload.response / stopReason+content 变体)。
829
+ * 命中任一形态则落 finalText/finalUsage 并以 final-frame 宽松终态收口,返回 true。
830
+ */
831
+ private applyFinalFrame(
832
+ turn: ActiveTurn,
833
+ payload: Record<string, unknown>
834
+ ): boolean {
835
+ if (typeof payload.response === "string" && payload.response !== "") {
836
+ // 收尾帧(A.2 ⑤):全文 + usage。终态落定后到达仍吸收数据(它常与
837
+ // turn.terminal 同批次、且是 usage 权威来源——属终态数据不是增量回调)
838
+ turn.finalText = payload.response;
839
+ if (isRecord(payload.usage)) turn.finalUsage = payload.usage;
840
+ turn.settle({ status: "success", source: "final-frame" });
841
+ return true;
842
+ }
843
+ if (
844
+ payload.stopReason === "stop" &&
845
+ typeof payload.content === "string" &&
846
+ payload.content !== ""
847
+ ) {
848
+ // 收尾的 content 形态变体(旧实测与 response 同值,携带 usage)
849
+ turn.finalText = payload.content;
850
+ if (isRecord(payload.usage)) turn.finalUsage = payload.usage;
851
+ turn.settle({ status: "success", source: "final-frame" });
852
+ return true;
853
+ }
854
+ return false;
855
+ }
856
+
857
+ /** 增量 delta 分发:非空 delta 入账 + 回调;终态后迟到丢弃(不变量 2)。 */
858
+ private applyStreamDelta(
859
+ turn: ActiveTurn,
860
+ payload: Record<string, unknown>
861
+ ): void {
862
+ if (typeof payload.delta !== "string" || payload.delta === "") return;
863
+ if (turn.settled) {
864
+ logger.warn(
865
+ `终态后迟到的 delta 丢弃(会话 ${
866
+ turn.sessionId
867
+ },不变量 2:resolve 后不再发事件): ${payload.delta.slice(
868
+ 0,
869
+ DELTA_LOG_CHARS
870
+ )}`
871
+ );
872
+ return;
873
+ }
874
+ turn.deltas.push(payload.delta);
875
+ turn.callbacks.onTextDelta?.(payload.delta);
876
+ }
877
+
878
+ private handleTelemetry(params: unknown): void {
879
+ if (!isRecord(params)) return;
880
+ if (params.kind === "turn.terminal") {
881
+ this.applyTerminalTelemetry(params);
882
+ return;
883
+ }
884
+ if (params.kind === "stream.chunk") {
885
+ this.applyStreamChunkTelemetry(params);
886
+ }
887
+ }
888
+
889
+ /** turn.terminal 遥测的吸收(含已落定 turn 的只记录路径,见 D5①)。 */
890
+ private applyTerminalTelemetry(params: Record<string, unknown>): void {
891
+ // 终态权威(A.2 ⑤):status success/error 均算终态(旧实证:不归类挂到超时)。
892
+ // 归因放宽(P0-1 S5):已落定 turn(final-frame 宽松终态先到)仍归因,
893
+ // 权威 status 只记录不改写落定结果(D5①——假成功的识破依据)。
894
+ const turn = this.lookupTurnForTerminal(extractPushSessionId(params));
895
+ if (turn === undefined) return;
896
+ const status =
897
+ typeof params.status === "string" ? params.status : "unknown";
898
+ turn.lastTerminalStatus = status;
899
+ this.recordTerminalError(turn, params);
900
+ if (turn.settled) {
901
+ logger.warn(
902
+ `权威终态晚于落定结果到达(会话 ${turn.sessionId},已落定 source=${
903
+ turn.terminal?.source
904
+ }):turn.terminal status="${status}" 仅记录不改写(P0-1 D5①)`
905
+ );
906
+ return;
907
+ }
908
+ turn.settle({ status, source: "turn.terminal" });
909
+ }
910
+
911
+ /** terminal 帧专属错误信息入账(⛔P-Z2 实证:真实 failed 终态的 errorCode/errorMessage
912
+ * 只在 terminal 帧携带,read/delta 拿不到——先到/迟到都记录,消费层合成失败
913
+ * 文案时优先采信)。二者均缺失时不挂 lastTerminalError 字段。 */
914
+ private recordTerminalError(
915
+ turn: ActiveTurn,
916
+ params: Record<string, unknown>
917
+ ): void {
918
+ const errorCode =
919
+ typeof params.errorCode === "string" ? params.errorCode : undefined;
920
+ const errorMessage =
921
+ typeof params.errorMessage === "string" ? params.errorMessage : undefined;
922
+ if (errorCode === undefined && errorMessage === undefined) return;
923
+ turn.lastTerminalError = {
924
+ ...(errorCode !== undefined ? { code: errorCode } : {}),
925
+ ...(errorMessage !== undefined ? { message: errorMessage } : {}),
926
+ };
927
+ }
928
+
929
+ /** stream.chunk 遥测的吸收(无文本遥测 + 文本字段漂移兜底)。 */
930
+ private applyStreamChunkTelemetry(params: Record<string, unknown>): void {
931
+ // A.2 ④:stream.chunk 无文本(chunkLength 遥测)。A.5 未确认项:是否偶发
932
+ // 携带文本字段——保留旧实现的形态漂移兜底:带文本则当 delta 收(不变量 1)
933
+ const turn = this.lookupTurn(extractPushSessionId(params));
934
+ if (turn === undefined || turn.settled) return;
935
+ // 遥测到达即刷新 idle 主判定(P0-1 D1:telemetry 事件同算进展)
936
+ this.refreshIdle(turn);
937
+ for (const key of ["chunk", "text", "content"] as const) {
938
+ const v = params[key];
939
+ if (typeof v === "string" && v !== "") {
940
+ turn.deltas.push(v);
941
+ turn.callbacks.onTextDelta?.(v);
942
+ return;
943
+ }
944
+ }
945
+ }
946
+
947
+ /** 终态后 read(A.2 ⑥)。失败/超时不抛——降级链由 runTurn 收口(read 只兜底不致命)。
948
+ * 连接已死时跳过(与 closeSession 同判——对死连接发请求会凭空重建进程)。 */
949
+ private async readBestEffort(sessionId: string): Promise<unknown> {
950
+ if (!this.conn.alive) return undefined;
951
+ try {
952
+ return await this.conn.request(
953
+ "session/read",
954
+ { sessionId },
955
+ {
956
+ timeoutMs: ZCODE_APPSERVER_TURN_READ_TIMEOUT_MS,
957
+ }
958
+ );
959
+ } catch (err) {
960
+ logger.warn(
961
+ `session/read 兜底失败(会话 ${sessionId},降级收尾帧/delta 聚合): ${errMessage(
962
+ err
963
+ )}`
964
+ );
965
+ return undefined;
966
+ }
967
+ }
968
+ }