@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
@@ -109,28 +109,59 @@ export interface LoadedSessionsIndex {
109
109
  // 条目级校验(ES3:单条目损坏仅丢弃该条目,不放大为整体失效)
110
110
  // ============================================================
111
111
 
112
- /** 正条目类型谓词:镜像 isIdentityData(session-reconstructor.ts:244-253)的字段检查 + 索引特有戳/形态字段。 */
113
- function isPositiveIndexEntry(raw: unknown): raw is SessionsIndexEntry {
114
- if (typeof raw !== "object" || raw === null) return false;
115
- const v = raw as Record<string, unknown>;
112
+ // 以下字段组谓词按原 isPositiveIndexEntry 的检查顺序切分(stamp/identity 会话描述
113
+ // 可选 string depth/model/thinkingLevel),组内与组间短路求值顺序不变。
114
+
115
+ /** 执行模式白名单:宽容历史 "sync" 值(ExecutionMode 现值仅 "background","sync" 是旧数据合法值,放行不丢弃)。 */
116
+ function isKnownIndexMode(mode: unknown): boolean {
117
+ return mode === "sync" || mode === "background";
118
+ }
119
+
120
+ /** 戳 + 身份字段组(mtimeMs/size/id/agent/mode)。 */
121
+ function hasStampAndIdentityFields(v: Record<string, unknown>): boolean {
116
122
  return (
117
123
  typeof v.mtimeMs === "number" &&
118
124
  typeof v.size === "number" &&
119
125
  typeof v.id === "string" &&
120
126
  typeof v.agent === "string" &&
121
- // 宽容历史 "sync" 值(ExecutionMode 现值仅 "background","sync" 是旧数据合法值,放行不丢弃)
122
- (v.mode === "sync" || v.mode === "background") &&
123
- typeof v.task === "string" &&
124
- typeof v.slug === "string" &&
125
- typeof v.startedAt === "number" &&
127
+ isKnownIndexMode(v.mode)
128
+ );
129
+ }
130
+
131
+ /** 会话描述字段组(task/slug/startedAt)。 */
132
+ function hasSessionDescFields(v: Record<string, unknown>): boolean {
133
+ return typeof v.task === "string" && typeof v.slug === "string" && typeof v.startedAt === "number";
134
+ }
135
+
136
+ /** 可选 string 字段组(rootSessionId/parentRecordId:undefined 表示缺失,合法)。 */
137
+ function hasOptionalStringFields(v: Record<string, unknown>): boolean {
138
+ return (
126
139
  (v.rootSessionId === undefined || typeof v.rootSessionId === "string") &&
127
- (v.parentRecordId === undefined || typeof v.parentRecordId === "string") &&
140
+ (v.parentRecordId === undefined || typeof v.parentRecordId === "string")
141
+ );
142
+ }
143
+
144
+ /** 深度/模型/思考档字段组(model 空串合法——尾部探测拿不到 model 的合法结果,DS4)。 */
145
+ function hasModelFields(v: Record<string, unknown>): boolean {
146
+ return (
128
147
  typeof v.depth === "number" &&
129
148
  typeof v.model === "string" && // 空串合法(DS4)
130
149
  (v.thinkingLevel === undefined || typeof v.thinkingLevel === "string")
131
150
  );
132
151
  }
133
152
 
153
+ /** 正条目类型谓词:镜像 isIdentityData(session-reconstructor.ts:244-253)的字段检查 + 索引特有戳/形态字段。 */
154
+ function isPositiveIndexEntry(raw: unknown): raw is SessionsIndexEntry {
155
+ if (typeof raw !== "object" || raw === null) return false;
156
+ const v = raw as Record<string, unknown>;
157
+ return (
158
+ hasStampAndIdentityFields(v) &&
159
+ hasSessionDescFields(v) &&
160
+ hasOptionalStringFields(v) &&
161
+ hasModelFields(v)
162
+ );
163
+ }
164
+
134
165
  /** 负条目类型谓词(tagged union 形态检查)。 */
135
166
  function isNegativeIndexEntry(raw: unknown): raw is SessionsIndexNegativeEntry {
136
167
  if (typeof raw !== "object" || raw === null) return false;
@@ -153,64 +184,108 @@ export function validateIndexEntry(
153
184
  // 读侧(IF1:永不抛)
154
185
  // ============================================================
155
186
 
156
- /**
157
- * 读取指定目录(<enc> 段)的 sessions-index.json。永不抛:
158
- * - 文件不存在/读失败/JSON.parse 失败/顶层结构不符/版本低于自身
159
- * → { entries: 空 Map, higherVersion: false }(空索引 = 今天的全量行为)
160
- * - 版本高于自身 → { entries: 空 Map, higherVersion: true }
161
- * (整体忽略——即使 entries 合法也不消费,防陈旧 schema 灌入;RecordStore 据此抑制落盘)
162
- * - 版本匹配 → 逐条 validateIndexEntry(坏条目丢弃,其余保留)
163
- *
164
- * 只读精确路径 INDEX_FILENAME:.tmp. 残留文件不匹配文件名,天然被忽略。
165
- */
166
- export function loadIndex(encDir: string): LoadedSessionsIndex {
167
- const empty: LoadedSessionsIndex = { entries: new Map(), higherVersion: false };
168
- const indexPath = path.join(encDir, INDEX_FILENAME);
187
+ /** 磁盘顶层已通过形态校验后的窄化视图(version 数值 + entries 对象)。
188
+ * type alias(非 interface)——赋给 Record<string, unknown> 依赖隐式索引签名。 */
189
+ type ValidIndexTop = {
190
+ version: number;
191
+ entries: Record<string, unknown>;
192
+ };
193
+
194
+ /** unknown → Node fs 错误码(非 Error 或无 code → undefined)。 */
195
+ function errorCodeOf(err: unknown): string | undefined {
196
+ return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : undefined;
197
+ }
169
198
 
170
- let raw: string;
199
+ /** 读索引文件文本。读失败 → null:ENOENT = 正常首跑保持静默;其余读失败(EACCES 等
200
+ * 长期权限异常)留 debug 线索——空索引回退本身可自愈,但权限类异常不会自己消失,需可诊断。 */
201
+ function readIndexFile(indexPath: string, encDir: string): string | null {
171
202
  try {
172
- raw = fs.readFileSync(indexPath, "utf-8");
203
+ return fs.readFileSync(indexPath, "utf-8");
173
204
  } catch (err) {
174
- // ENOENT = 正常首跑,保持静默;其余读失败(EACCES 等长期权限异常)留 debug 线索
175
- // ——空索引回退本身可自愈,但权限类异常不会自己消失,需可诊断。
176
- const code = err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : undefined;
205
+ const code = errorCodeOf(err);
177
206
  if (code !== "ENOENT") {
178
207
  logger.debug("[subagents] sessions-index read failed, fallback to empty", {
179
208
  detail: { dir: encDir, code },
180
209
  });
181
210
  }
182
- return empty;
211
+ return null;
183
212
  }
213
+ }
184
214
 
185
- let parsed: unknown;
215
+ /** JSON.parse 索引内容。损坏(截断/外部编辑)→ null,走 debug:可降级自愈场景,不 console.error。 */
216
+ function parseIndexJson(raw: string, indexPath: string): unknown {
186
217
  try {
187
- parsed = JSON.parse(raw);
218
+ return JSON.parse(raw);
188
219
  } catch (err) {
189
- // 损坏(截断/外部编辑)走 debug:可降级自愈场景,不 console.error
190
220
  logger.debug("[subagents] sessions-index corrupted JSON, fallback to empty", {
191
221
  detail: { path: indexPath, error: err instanceof Error ? err.message : String(err) },
192
222
  });
193
- return empty;
223
+ return null;
194
224
  }
225
+ }
195
226
 
227
+ /** DM1 头部字段形态守卫(version 数值 + entries 非空 object 非数组)。 */
228
+ function isIndexTopHeader(v: Record<string, unknown>): v is ValidIndexTop {
229
+ return (
230
+ typeof v.version === "number" &&
231
+ typeof v.entries === "object" &&
232
+ v.entries !== null &&
233
+ !Array.isArray(v.entries)
234
+ );
235
+ }
236
+
237
+ /** 顶层结构校验(DM1)。不符 → null(两条 debug 文案与判定条件一一对应)。 */
238
+ function readTopLevel(parsed: unknown, indexPath: string): ValidIndexTop | null {
196
239
  if (typeof parsed !== "object" || parsed === null) {
197
240
  logger.debug("[subagents] sessions-index invalid top-level shape, fallback to empty", {
198
241
  detail: { path: indexPath },
199
242
  });
200
- return empty;
243
+ return null;
201
244
  }
202
245
  const top = parsed as Record<string, unknown>;
203
- if (
204
- typeof top.version !== "number" ||
205
- typeof top.entries !== "object" ||
206
- top.entries === null ||
207
- Array.isArray(top.entries)
208
- ) {
246
+ if (!isIndexTopHeader(top)) {
209
247
  logger.debug("[subagents] sessions-index invalid header fields, fallback to empty", {
210
248
  detail: { path: indexPath },
211
249
  });
212
- return empty;
250
+ return null;
251
+ }
252
+ return top;
253
+ }
254
+
255
+ /** 逐条校验收集:坏条目仅丢弃该条目,其余保留(ES3 条目级降级)。 */
256
+ function collectValidEntries(
257
+ entriesObj: Record<string, unknown>,
258
+ ): Map<string, SessionsIndexEntry | SessionsIndexNegativeEntry> {
259
+ const entries = new Map<string, SessionsIndexEntry | SessionsIndexNegativeEntry>();
260
+ for (const [key, value] of Object.entries(entriesObj)) {
261
+ const entry = validateIndexEntry(value);
262
+ if (entry !== undefined) entries.set(key, entry);
213
263
  }
264
+ return entries;
265
+ }
266
+
267
+ /**
268
+ * 读取指定目录(<enc> 段)的 sessions-index.json。永不抛:
269
+ * - 文件不存在/读失败/JSON.parse 失败/顶层结构不符/版本低于自身
270
+ * → { entries: 空 Map, higherVersion: false }(空索引 = 今天的全量行为)
271
+ * - 版本高于自身 → { entries: 空 Map, higherVersion: true }
272
+ * (整体忽略——即使 entries 合法也不消费,防陈旧 schema 灌入;RecordStore 据此抑制落盘)
273
+ * - 版本匹配 → 逐条 validateIndexEntry(坏条目丢弃,其余保留)
274
+ *
275
+ * 只读精确路径 INDEX_FILENAME:.tmp. 残留文件不匹配文件名,天然被忽略。
276
+ */
277
+ export function loadIndex(encDir: string): LoadedSessionsIndex {
278
+ const empty: LoadedSessionsIndex = { entries: new Map(), higherVersion: false };
279
+ const indexPath = path.join(encDir, INDEX_FILENAME);
280
+
281
+ const raw = readIndexFile(indexPath, encDir);
282
+ if (raw === null) return empty;
283
+
284
+ const parsed = parseIndexJson(raw, indexPath);
285
+ if (parsed === null) return empty;
286
+
287
+ const top = readTopLevel(parsed, indexPath);
288
+ if (top === null) return empty;
214
289
 
215
290
  if (top.version > INDEX_VERSION) {
216
291
  // 高版本:整体忽略(entries 即使合法也不消费);调用方据 higherVersion 抑制写盘
@@ -224,12 +299,7 @@ export function loadIndex(encDir: string): LoadedSessionsIndex {
224
299
  return empty;
225
300
  }
226
301
 
227
- const entries = new Map<string, SessionsIndexEntry | SessionsIndexNegativeEntry>();
228
- for (const [key, value] of Object.entries(top.entries)) {
229
- const entry = validateIndexEntry(value);
230
- if (entry !== undefined) entries.set(key, entry);
231
- }
232
- return { entries, higherVersion: false };
302
+ return { entries: collectValidEntries(top.entries), higherVersion: false };
233
303
  }
234
304
 
235
305
  // ============================================================
@@ -1,99 +1,311 @@
1
1
  // src/execution/settled-watchdog.ts
2
2
  //
3
- // [T2-③ / LC-1] chatMode settled 等待固定硬上限(回收层「上界族」共享原语)。
3
+ // [T2-③ / LC-1] chatMode settled 等待两段式守护(回收层「上界族 + 无进展检测」共享原语)。
4
4
  //
5
- // 设计:docs/design/subagent-core-unbounded-wait-audit.md §7.2 T2-③ / §4.1 LC-1。
6
- // chatMode 任意一轮等待 agent_settled 的窗口挂固定硬上限(默认 10 分钟)——settled
7
- // 永不到达(事件行丢失 / 子进程 wedged)时本 timer 是唯一独立回收通道:现状
8
- // 「settled 不 arm 则 idle timer 不挂、runSpawn 已在首轮返回、spawn watchdog 默认关」
9
- // 的三无窗口由此收敛(LC-1 同族变体)。
5
+ // 设计:docs/design/timeout-zcode-turn-and-settled-watchdog.md §6-D9 / §7(两段式重锚定,
6
+ // P0-4 核心)。等待 agent_settled 的窗口拆两段,判定语义与被保护对象逐段匹配:
10
7
  //
11
- // 固定上界(非事件刷新语义):该窗口的正常语义是 post-run 收尾(compact 检查等,
12
- // 秒级完成),窗口内的任何输出都不能证明 settled 终将到达——「刷新」会让「wedged
13
- // 但仍有周期性输出」的进程无限续命(LC-9 已证 stdout 可有调试行)。固定上界
14
- // by construction 覆盖静默与非静默两种 wedged 形态。多轮会话中每轮窗口独立计时
15
- //(重复 arm = 以新窗口覆盖旧计时,对齐 lifecycle-manager.armIdleTimer 的刷新语义)。
8
+ // 中段(prompt agent_end):工作段,输出即进展——无进展检测
9
+ // (armMidRoundNoProgress,锚点:prompt 发出时 arm;有效协议事件行刷新;
10
+ // 连续静默 SETTLED_MID_ROUND_NO_PROGRESS_MS=30min kill + 该轮失败终态化)。
11
+ // 对齐 keep-alive 无进展检测先例(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS 同构:
12
+ // 「仍在推进」由 stdout 有效事件定义而非总时长,修复旧 10min 全程固定窗对
13
+ // >10min 合法 chatMode 单轮的误杀)。
16
14
  //
17
- // 双挂载点共用同一原语——同一常量 + 同一挂载/清除 helper,仅两个调用点(设计
18
- // T2-③ 明示架构,两处各写一套恰是被否的「散布姿势」微缩复发):
19
- // - 首轮:session-runner.ts runSpawn(prompt 发出后挂;settled 到达 / close /
20
- // resolveRun 任一发生即清)
21
- // - 后续轮次热路径:subagent-service.ts deliverMessage(发出新一轮 prompt 后挂,
22
- // u-t2b 接线)
15
+ // 收尾段(agent_end agent_settled):post-run 收尾(compact 检查等)——固定硬上限
16
+ // (armSettledWatchdog,锚点:agent_end 到达时经 handoverMidRoundToSettled 交棒;
17
+ // 事件不刷新:收尾段内的任何输出都不能证明 settled 终将到达,刷新会让
18
+ // 「wedged 但仍有周期性输出」的进程无限续命——LC-9 已证 stdout 可有调试行)。
19
+ // 定值 600s 依据 P-T2c 已执行探针(probe/p-t2c-report.md):收尾段 6 轮真实会话
20
+ // 全部 <2ms、compact 30 万 tokens 40.1s,按探针自身降级规则 P99×10=401s<600s 成立。
21
+ //
22
+ // 两段独立计时:交棒时中段已过时间不继承——收尾段从 agent_end 起满 600s 才 fire
23
+ // (A7 验收判据「收尾段上界只在 agent_end 后计时」)。
24
+ //
25
+ // LC-1 三 wedged 场景覆盖复核(D9):① pi 版本偏斜无事件(agent_end 永不到达)→
26
+ // 中段 30min 静默回收;② post-run compact 卡死(agent_end 已到、settled 不来)→
27
+ // 收尾段 600s 回收;③ stdout 行损坏(settled 行被丢)→ 收尾段 600s 回收。
28
+ //
29
+ // 用户通道(规则 19):env XYZ_SUBAGENT_SETTLED_WATCHDOG_MS——>0 覆盖收尾段定值,
30
+ // ≤0 关闭两段(关闭即回到「settled 不 arm 则 idle timer 不挂、runSpawn 已在首轮返回、
31
+ // spawn watchdog 默认关」的三无窗口,warn 明示后果),未设/非法 = 默认 600s。
32
+ // 中段阈值 v1 不开 env(减法:与 keep-alive 先例同为纯常量)。
33
+ //
34
+ // 双挂载点共用同一原语——同一组常量 + 同一组挂载/交棒/清除 helper,仅两个 prompt
35
+ // 发出点(设计 T2-③ 明示架构,两处各写一套恰是被否的「散布姿势」微缩复发):
36
+ // - 首轮:session-runner.ts runSpawn(prompt 发出后 arm 中段)
37
+ // - 后续轮次热路径:subagent-service.ts deliverMessage(发出新一轮 prompt 后 arm 中段)
38
+ // 事件侧接线(两路径共用同一 stdout pump 闭包,均在 session-runner.ts):
39
+ // - 有效协议事件行 → refreshMidRoundNoProgress(中段刷新)
40
+ // - agent_end(chatMode,!willRetry)→ handoverMidRoundToSettled(交棒收尾段)
41
+ // - agent_settled / close / 收尾 → disarmSettledWatchdog(两段一并清)
23
42
  //
24
43
  // 与 lifecycle-manager 的 idle timer 互补:idle timer 管 settled 已到达后的空闲
25
- // 回收,本上界管 settled 永不到达的 wedged——两条正交通道不互相替代。
44
+ // 回收,本原语管 settled 永不到达的 wedged——两条正交通道不互相替代。
26
45
  //
27
- // 实现形态对齐 armIdleTimer(recordId → timer 的模块级记账):重复 arm 先清旧
46
+ // 实现形态对齐 armIdleTimer(recordId → entry 的模块级记账):重复 arm 先清旧
28
47
  // timer、到期回调先自删条目再执行——挂载/清除责任内聚在原语,调用方不管理句柄
29
- // 覆盖(防「忘清旧窗」散布错误)。
48
+ // 覆盖(防「忘清旧窗」散布错误)。同一 record 任意时刻只有一段 armed(交棒清中段
49
+ // 才挂收尾段),entry 记录当前段 + 收尾段回调(交棒点无需知道挂载方是谁)。
30
50
 
31
51
  import { getLogger } from "../core/logger.ts";
32
52
  import { assertSafeTimerDelay } from "../shared/timer-delay.ts";
33
53
 
34
54
  const logger = getLogger("subagents");
35
55
 
56
+ /** 时间单位换算常量(命名后供 watchdog 常量组合,对齐 session-runner 同名先例,消除裸乘法字面量)。 */
57
+ const MS_PER_SECOND = 1000;
58
+ const SECONDS_PER_MINUTE = 60;
59
+
60
+ /** 中段无进展阈值(分钟):对齐 KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS 的 30min 量级。 */
61
+ const MID_ROUND_MINUTES = 30;
62
+
36
63
  /**
37
- * settled 等待固定硬上限(10 分钟)。
64
+ * 收尾段固定硬上限默认值(600s,值不变——锚点从 prompt 发出改挂 agent_end 之后)。
38
65
  *
39
- * [P-T2c 定案] 真实 pi 会话 agent_end→agent_settled 间隔 6 轮全部 <2ms(两事件行
40
- * chunk 到达);30 万 tokens 上下文显式 compaction 耗时 40.1s——10min 上限余量
41
- * 4 个数量级以上(probe/p-t2c-report.md)。维持设计默认值,仅此一处定义。
66
+ * [P-T2c 已执行探针定案] 真实 pi 会话 agent_end→agent_settled 间隔 6 轮全部 <2ms
67
+ *(两事件行同 chunk 到达);30 万 tokens 上下文显式 compaction 耗时 40.1s——按探针
68
+ * 自身降级规则 P99×10 = 401s < 600s,600s 成立(probe/p-t2c-report.md)。
69
+ * 可被 env XYZ_SUBAGENT_SETTLED_WATCHDOG_MS(>0)覆盖,仅此一处定义。
42
70
  */
43
71
  export const SETTLED_WATCHDOG_TIMEOUT_MS = 600_000;
44
72
 
45
- /** recordId → armed settled watchdog timer。仅在等待窗口内 armed(挂载/清除成对)。 */
46
- const armedTimers = new Map<string, NodeJS.Timeout>();
73
+ /**
74
+ * 中段(prompt agent_end)无进展检测的连续静默阈值(30min)。
75
+ *
76
+ * 对齐 KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS 先例(30min 无进展检测,量级同源)。
77
+ * 刷新面严格限定 stdout pump 解析出的**有效协议事件行**(message_* 与 tool_* 与 turn_end
78
+ * 等 SdkEvent)——LC-9 的 invalid 行(非法 JSON / 缺 type 字段的调试噪音)不刷新,
79
+ * 防调试输出续命。v1 不开 env(设计 §6-D9 定案:与 keep-alive 同为纯常量)。
80
+ * [export] 测试可观测。
81
+ */
82
+ export const SETTLED_MID_ROUND_NO_PROGRESS_MS = MID_ROUND_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
47
83
 
48
84
  /**
49
- * 挂载(或按新窗口重挂)某 record 的 settled 等待硬上限。
85
+ * 收尾段定值的用户覆盖 env(规则 19:用户显式指定才生效)。
50
86
  *
51
- * - 重复 arm(同 recordId)先 clearTimeout 旧 timer——每轮窗口独立计时,不叠加。
52
- * - 到期回调先从 Map 移除自身再执行 onTimeout(onTimeout 内重新 arm 不会被误删)。
87
+ * >0 覆盖收尾段(SETTLED_WATCHDOG_TIMEOUT_MS 默认);≤0 关闭两段(回到三无窗口,
88
+ * warn 明示后果);未设 = 默认 600s;非数字 = 非法回落默认 + warn 留痕(对齐
89
+ * XYZ_SUBAGENT_IDLE_TIMEOUT_MS 的 LC-7 教训:非法回落必须可见)。前缀用
90
+ * XYZ_SUBAGENT_*(ENV_WHITELIST_PREFIXES 白名单,PI_ 前缀在桌面 spawn 链被静默
91
+ * 丢弃——同 XYZ_SUBAGENT_IDLE_TIMEOUT_MS 改名教训)。
92
+ */
93
+ export const SETTLED_WATCHDOG_ENV = "XYZ_SUBAGENT_SETTLED_WATCHDOG_MS";
94
+
95
+ /** watchdog fire 时传给回调的段信息(失败文案按段分叉:窗长语义不同)。 */
96
+ export interface SettledWatchdogFireInfo {
97
+ /** 触发段:mid-round = 中段无进展;settled = 收尾段固定上界。 */
98
+ phase: "mid-round" | "settled";
99
+ /** 本段实际等待窗长(ms)——中段为常量,收尾段为 env 覆盖后的生效值。 */
100
+ waitedMs: number;
101
+ }
102
+
103
+ /** 单段 armed 的记账条目。同一 record 任意时刻至多一段 armed(交棒先清后挂)。 */
104
+ interface SettledWatchdogEntry {
105
+ timer: NodeJS.Timeout;
106
+ phase: "mid-round" | "settled";
107
+ /** 中段回调:armMidRoundNoProgress 登记;armSettledWatchdog 直挂收尾段时无。 */
108
+ onMidTimeout?: (info: SettledWatchdogFireInfo) => void;
109
+ /** 收尾段回调:prompt 发出点随 armMidRoundNoProgress 登记,交棒点经此取用。 */
110
+ onSettleTimeout: (info: SettledWatchdogFireInfo) => void;
111
+ }
112
+
113
+ /** recordId → armed watchdog entry。仅在等待窗口内 armed(挂载/清除成对)。 */
114
+ const armedEntries = new Map<string, SettledWatchdogEntry>();
115
+
116
+ // ── env 解析(惰性首读定案 + 缓存;_resetSettledWatchdogsForTest 清缓存供测试改 env)──
117
+
118
+ let envCache: { disabled: boolean; overrideMs?: number } | undefined;
119
+
120
+ function resolveSettledWatchdogEnv(): { disabled: boolean; overrideMs?: number } {
121
+ if (envCache) return envCache;
122
+ envCache = { disabled: false };
123
+ const raw = process.env[SETTLED_WATCHDOG_ENV];
124
+ if (raw === undefined || raw.trim() === "") return envCache; // 未设 = 默认 600s
125
+ const parsed = Number(raw);
126
+ if (!Number.isFinite(parsed)) {
127
+ logger.warn(
128
+ `[settled-watchdog] ${SETTLED_WATCHDOG_ENV}="${raw}" is invalid (expected a millisecond number) — ` +
129
+ `falling back to default settled phase limit (${SETTLED_WATCHDOG_TIMEOUT_MS}ms); ` +
130
+ `set a plain ms value (e.g. 60000) to override, or 0 to disable both phases`,
131
+ );
132
+ return envCache;
133
+ }
134
+ if (parsed <= 0) {
135
+ logger.warn(
136
+ `[settled-watchdog] ${SETTLED_WATCHDOG_ENV}=${parsed} disables BOTH watchdog phases (mid-round ` +
137
+ `no-progress + settled phase limit). Consequence: a wedged chatMode round (no agent_end, or ` +
138
+ `agent_settled never arriving) has NO independent recovery timer — the process leaks until the ` +
139
+ `host exits (the "three-no-window" shape). Recovery: unset the env or set a positive ms value.`,
140
+ );
141
+ envCache.disabled = true;
142
+ return envCache;
143
+ }
144
+ envCache.overrideMs = parsed;
145
+ return envCache;
146
+ }
147
+
148
+ /** 收尾段生效窗长:env >0 覆盖,否则默认 600s(文案拼接 / 单测断言用)。 */
149
+ export function getSettledWatchdogTimeoutMs(): number {
150
+ return resolveSettledWatchdogEnv().overrideMs ?? SETTLED_WATCHDOG_TIMEOUT_MS;
151
+ }
152
+
153
+ /** 两段守护是否被 env ≤0 显式关闭(arm 时 no-op 的唯一来源)。 */
154
+ export function isSettledWatchdogDisabled(): boolean {
155
+ return resolveSettledWatchdogEnv().disabled;
156
+ }
157
+
158
+ /**
159
+ * 清除某 record 的 entry(settled 到达 / close / resolveRun / 终态化处置任一发生即清,
160
+ * 两段一并清)。不存在 armed entry 时 no-op。
161
+ */
162
+ function clearEntry(recordId: string): void {
163
+ const entry = armedEntries.get(recordId);
164
+ if (!entry) return;
165
+ clearTimeout(entry.timer);
166
+ armedEntries.delete(recordId);
167
+ }
168
+
169
+ /**
170
+ * 挂载(或按新窗口重挂)某 record 的**中段**无进展检测(prompt 发出点调用)。
171
+ *
172
+ * - 重复 arm(同 recordId:新一轮 prompt)先清旧 entry——每轮窗口独立计时,不叠加
173
+ * (对齐 armIdleTimer 刷新语义);旧收尾段未清的异常形态也被本覆盖清掉(新 prompt
174
+ * = 新等待窗)。
175
+ * - 收尾段回调 onSettleTimeout 随 entry 记账:agent_end 交棒点
176
+ * (handoverMidRoundToSettled)无需知道挂载方是谁(首轮 runSpawn / 热路径
177
+ * deliverMessage 各传各的处置闭包)。
53
178
  * - timer unref:不阻止主进程退出(回收兜底不阻塞 shutdown,对齐 idle timer /
54
179
  * spawn watchdog 的 unref 语义)。
55
- * - 到期行为由调用方注入 onTimeout(kill 进程 + 该轮失败终态化)——本模块不持有
180
+ * - 到期行为由调用方注入(kill 进程 + 该轮失败终态化)——本模块不持有
56
181
  * ChildProcess / record,与 lifecycle-manager 同构(副作用能力由调用方注入,
57
182
  * 本模块只管 timer 生命周期,可独立编译 + 单测)。
183
+ * - env 显式关闭(≤0)时 no-op(解析时已 warn 明示后果)。
58
184
  */
59
- export function armSettledWatchdog(recordId: string, onTimeout: () => void): void {
185
+ export function armMidRoundNoProgress(
186
+ recordId: string,
187
+ handlers: {
188
+ onMidTimeout: (info: SettledWatchdogFireInfo) => void;
189
+ onSettleTimeout: (info: SettledWatchdogFireInfo) => void;
190
+ },
191
+ ): void {
192
+ if (isSettledWatchdogDisabled()) return;
193
+ clearEntry(recordId);
60
194
  // 常量恒在安全域内;入口统一校验防未来值改可配置时静默引入 1ms 溢出语义反转。
61
- assertSafeTimerDelay(SETTLED_WATCHDOG_TIMEOUT_MS, "settled watchdog");
62
- // 重挂 = 新窗口:先清旧 timer,防同一 record 叠加多个 armed timer。
63
- disarmSettledWatchdog(recordId);
195
+ assertSafeTimerDelay(SETTLED_MID_ROUND_NO_PROGRESS_MS, "settled watchdog (mid-round)");
64
196
  const timer = setTimeout(() => {
65
- armedTimers.delete(recordId);
66
- logger.debug(`[settled-watchdog] fired for ${recordId} after ${SETTLED_WATCHDOG_TIMEOUT_MS}ms without agent_settled`);
67
- onTimeout();
68
- }, SETTLED_WATCHDOG_TIMEOUT_MS);
197
+ armedEntries.delete(recordId);
198
+ logger.debug(
199
+ `[settled-watchdog] mid-round fired for ${recordId} after ${SETTLED_MID_ROUND_NO_PROGRESS_MS}ms without a valid protocol event`,
200
+ );
201
+ handlers.onMidTimeout({ phase: "mid-round", waitedMs: SETTLED_MID_ROUND_NO_PROGRESS_MS });
202
+ }, SETTLED_MID_ROUND_NO_PROGRESS_MS);
69
203
  timer.unref();
70
- armedTimers.set(recordId, timer);
204
+ armedEntries.set(recordId, {
205
+ timer,
206
+ phase: "mid-round",
207
+ onMidTimeout: handlers.onMidTimeout,
208
+ onSettleTimeout: handlers.onSettleTimeout,
209
+ });
71
210
  }
72
211
 
73
212
  /**
74
- * 清除某 record settled 等待硬上限(settled 到达 / close / resolveRun 任一发生
75
- * 即清)。不存在 armed timer 时 no-op。
213
+ * 挂载(或按新窗口重挂)某 record 的**收尾段**固定硬上限(锚点:agent_end 之后)。
214
+ *
215
+ * 生产路径经 handoverMidRoundToSettled 调用(交棒语义内聚);导出保留供清理面
216
+ * 测试直接挂载(disarm 消费同一 entry,与段无关)与未来独立收尾场景。
217
+ * - 事件不刷新(收尾段语义:窗口内输出不能证明 settled 将到达——见头注释)。
218
+ * - env >0 覆盖窗长;到期回调先自删条目再执行(onTimeout 内重新 arm 不会被误删)。
219
+ * - env 显式关闭时 no-op。
76
220
  */
77
- export function disarmSettledWatchdog(recordId: string): void {
78
- const timer = armedTimers.get(recordId);
79
- if (!timer) return;
80
- clearTimeout(timer);
81
- armedTimers.delete(recordId);
221
+ export function armSettledWatchdog(recordId: string, onTimeout: (info: SettledWatchdogFireInfo) => void): void {
222
+ if (isSettledWatchdogDisabled()) return;
223
+ clearEntry(recordId);
224
+ const windowMs = getSettledWatchdogTimeoutMs();
225
+ // env 可配置值:入口校验防越界值静默引入 1ms 溢出语义反转(非法值在 env 解析层
226
+ // 已回落,此处是纵深防御)。
227
+ assertSafeTimerDelay(windowMs, "settled watchdog");
228
+ const timer = setTimeout(() => {
229
+ armedEntries.delete(recordId);
230
+ logger.debug(
231
+ `[settled-watchdog] settled phase fired for ${recordId} after ${windowMs}ms without agent_settled`,
232
+ );
233
+ onTimeout({ phase: "settled", waitedMs: windowMs });
234
+ }, windowMs);
235
+ timer.unref();
236
+ armedEntries.set(recordId, { timer, phase: "settled", onSettleTimeout: onTimeout });
237
+ }
238
+
239
+ /**
240
+ * [D9 交棒] agent_end 到达:中段让位收尾段。
241
+ *
242
+ * 清中段计时(已过时间不继承),用挂载点记账的 onSettleTimeout 挂收尾段(从
243
+ * agent_end 起独立计时)。未挂载(非 chatMode / env 关闭 / 已清)或已交棒 /
244
+ * 已 fire 时幂等 no-op——stdout pump 对每轮 agent_end 都调本函数,幂等由
245
+ * phase 判定保证。
246
+ */
247
+ export function handoverMidRoundToSettled(recordId: string): void {
248
+ const entry = armedEntries.get(recordId);
249
+ if (!entry || entry.phase !== "mid-round") return;
250
+ const onSettleTimeout = entry.onSettleTimeout;
251
+ clearEntry(recordId);
252
+ armSettledWatchdog(recordId, onSettleTimeout);
253
+ }
254
+
255
+ /**
256
+ * [D9 中段刷新] 有效协议事件行到达:刷新中段静默计时(重挂同窗,先清旧)。
257
+ *
258
+ * 未挂载 / 已交棒(收尾段不刷新——刷新会让收尾段失去唯一可收敛形态,D9 被否 (b)
259
+ * 方案的否决理由)/ 已 fire 时 no-op。刷新面由调用方限定在 stdout pump 的合法
260
+ * SdkEvent 行(invalid 行不调用本函数)。
261
+ */
262
+ export function refreshMidRoundNoProgress(recordId: string): void {
263
+ const entry = armedEntries.get(recordId);
264
+ if (!entry || entry.phase !== "mid-round") return;
265
+ clearTimeout(entry.timer);
266
+ assertSafeTimerDelay(SETTLED_MID_ROUND_NO_PROGRESS_MS, "settled watchdog (mid-round refresh)");
267
+ const onMidTimeout = entry.onMidTimeout;
268
+ const timer = setTimeout(() => {
269
+ armedEntries.delete(recordId);
270
+ logger.debug(
271
+ `[settled-watchdog] mid-round fired for ${recordId} after ${SETTLED_MID_ROUND_NO_PROGRESS_MS}ms without a valid protocol event`,
272
+ );
273
+ onMidTimeout?.({ phase: "mid-round", waitedMs: SETTLED_MID_ROUND_NO_PROGRESS_MS });
274
+ }, SETTLED_MID_ROUND_NO_PROGRESS_MS);
275
+ timer.unref();
276
+ entry.timer = timer;
82
277
  }
83
278
 
84
279
  /**
85
- * 查询某 record 是否有 armed settled watchdog(测试/接入期断言用)。
280
+ * 查询某 record 是否有 armed watchdog entry(测试/接入期断言用;两段同账本)。
86
281
  */
87
282
  export function hasSettledWatchdog(recordId: string): boolean {
88
- return armedTimers.has(recordId);
283
+ return armedEntries.has(recordId);
284
+ }
285
+
286
+ /**
287
+ * 查询某 record 当前 armed 段(测试断言「交棒切换」用)。未挂载返回 undefined。
288
+ */
289
+ export function getSettledWatchdogPhase(recordId: string): "mid-round" | "settled" | undefined {
290
+ return armedEntries.get(recordId)?.phase;
291
+ }
292
+
293
+ /**
294
+ * 清除某 record 的 armed watchdog(settled 到达 / close / resolveRun 任一发生
295
+ * 即清,两段一并清)。不存在 armed entry 时 no-op。
296
+ */
297
+ export function disarmSettledWatchdog(recordId: string): void {
298
+ clearEntry(recordId);
89
299
  }
90
300
 
91
301
  /**
92
- * 清空全部 armed timer(测试隔离用,beforeEach 调;命名对齐 _resetLifecycleState 先例)。
302
+ * 清空全部 armed entry + env 解析缓存(测试隔离用,beforeEach 调;命名对齐
303
+ * _resetLifecycleState 先例。清缓存使测试内 vi.stubEnv 改 env 后重新解析生效)。
93
304
  */
94
305
  export function _resetSettledWatchdogsForTest(): void {
95
- for (const timer of armedTimers.values()) {
96
- clearTimeout(timer);
306
+ for (const entry of armedEntries.values()) {
307
+ clearTimeout(entry.timer);
97
308
  }
98
- armedTimers.clear();
309
+ armedEntries.clear();
310
+ envCache = undefined;
99
311
  }