@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
@@ -159,6 +159,21 @@ interface NegativeFileEntry {
159
159
  /** fileCache 值类型:正常条目或负缓存条目。 */
160
160
  type FileCacheValue = FileCacheEntry | NegativeFileEntry;
161
161
 
162
+ /** scanFile 单文件本轮 stat 戳集合(jsonl + 3 sidecar)。 */
163
+ interface FileStamps {
164
+ jsonl: Stamp;
165
+ cancelled: Stamp | null;
166
+ finalized: Stamp | null;
167
+ alive: Stamp | null;
168
+ }
169
+
170
+ /** sidecar payload 读取结果(索引命中与探测重建两分支共享的读点)。 */
171
+ interface SidecarPayloads {
172
+ tomb: CancelledTombstone | undefined;
173
+ aliveData: AliveMarker | undefined;
174
+ finalReason: string | undefined;
175
+ }
176
+
162
177
  /** 孤儿判定的末行读取初始窗口(常规 entry 远小于此,避免全文件读)。 */
163
178
  // eslint-disable-next-line no-magic-numbers -- 64KB = 64 * 1024 bytes 字节换算常数
164
179
  const LAST_LINE_WINDOW_BYTES = 64 * 1024;
@@ -244,40 +259,103 @@ function collectLastRecordEntries(content: string): Map<string, Record<string, u
244
259
  return lastById;
245
260
  }
246
261
 
262
+ /** entry data 字段的安全 string 读取(非 string → undefined)。 */
263
+ function entryStr(d: Record<string, unknown>, k: string): string | undefined {
264
+ return typeof d[k] === "string" ? (d[k] as string) : undefined;
265
+ }
266
+
267
+ /** entry data 字段的安全 number 读取(非 number → undefined)。 */
268
+ function entryNum(d: Record<string, unknown>, k: string): number | undefined {
269
+ return typeof d[k] === "number" ? (d[k] as number) : undefined;
270
+ }
271
+
272
+ /** 终态域投影:status 只认 "closed" 字面量(其余含缺省 → "running",旧调用方行为不变);
273
+ * closedReason 经枚举守卫(非法/缺省 → undefined)。 */
274
+ function readEntryTerminalFields(
275
+ d: Record<string, unknown>,
276
+ ): Pick<SubagentRecord, "status" | "closedReason"> {
277
+ const closedReason = entryStr(d, "closedReason");
278
+ return {
279
+ status: d.status === "closed" ? "closed" : "running",
280
+ closedReason: isValidClosedReason(closedReason) ? closedReason : undefined,
281
+ };
282
+ }
283
+
284
+ /** 批收集域投影(U5 E1):仅显式字面量收敛,缺省 undefined(JSON 序列化自然缺省)。 */
285
+ function readEntryBatchFields(
286
+ d: Record<string, unknown>,
287
+ ): Pick<SubagentRecord, "collectMode" | "batchFinalized" | "resumable"> {
288
+ return {
289
+ collectMode: d.collectMode === "sync" ? "sync" : undefined,
290
+ batchFinalized: d.batchFinalized === true ? true : undefined,
291
+ resumable: d.resumable === true ? true : undefined,
292
+ };
293
+ }
294
+
295
+ /** engine 域投影:engineFallback/engineHandle 经运行时 guard(未知 JSON 不裸收)。 */
296
+ function readEntryEngineFields(
297
+ d: Record<string, unknown>,
298
+ ): Pick<SubagentRecord, "engine" | "engineFallback" | "engineHandle"> {
299
+ return {
300
+ engine: entryStr(d, "engine"),
301
+ engineFallback: isEngineFallbackShape(d.engineFallback) ? d.engineFallback : undefined,
302
+ engineHandle: isEngineHandleShape(d.engineHandle) ? d.engineHandle : undefined,
303
+ };
304
+ }
305
+
247
306
  /** entry data 即 SubagentRecord v1 快照——带运行时 guard 重建(taste/no-unsafe-cast)。
248
- * 损坏 entry(agent/task/startedAt 任一缺失)返回 null,由调用方跳过。 */
307
+ * 损坏 entry(agent/task/startedAt 任一缺失)返回 null,由调用方跳过。
308
+ * [U5 E1] 投影白名单扩展(设计 §3.1.3「标记读取通路」):collectMode/batchFinalized
309
+ * + 终态五字段 status/endedAt/closedReason/result/error——原实现硬编码
310
+ * status:"running" 且不投影终态,E1 重建成员恒被视为 running,「全员终态→补发」
311
+ * 判定永假、补发内容缺失,整条补发路径成死代码。status 守卫只认 "closed" 字面量
312
+ * (其余含缺省 → "running",旧调用方 recoverEntryOnlyOrphans 行为不变——其候选
313
+ * 守卫已滤非 running 末条);closedReason 经 isValidClosedReason 枚举守卫。
314
+ * [v2 D3] 再补 resumable/sessionFile 两投影:成功成员崩溃时末条恒为轮终
315
+ * running+resumable entry(SP-5 有意语义),不投影 resumable 则 E1 无法与协调器
316
+ * hasRunningSync 同构豁免(§2.3 断链 3);sessionFile 原硬编码 undefined,导致
317
+ * E1 落标路径重建快照丢失反查索引锚(断链 1 前置依赖)。recoverEntryOnlyOrphans
318
+ * 的候选判定(isEntryOrphanCandidate)只认 status==="running",两新字段不参与
319
+ * 判定(P-rebuild 探针守卫面)。
320
+ * [E1 恢复批语义修复] 再补 patchFile 投影:entry data 携带该字段
321
+ * (toSubagentRecordEntry 落盘含 patchFile)但原投影丢弃 → E1 补发记录丢失
322
+ * worktree patch 的 git-apply 回收指针(正常 flush 路径经 toNotifyRecord 特意
323
+ * 携带,notify-host.ts patchFile 透传)。undefined 经 JSON.stringify 自然缺省,
324
+ * 无 patchFile 的存量 entry 序列化字节不变(落标出口经 toSubagentRecordEntry
325
+ * 按名重投影,重建对象字段顺序不影响序列化字节形态)。
326
+ * 字段顺序 = 对象字面量原序(终态/批收集/engine 域以 spread 在原位置展开),
327
+ * entry 序列化字节形态不变。 */
249
328
  function rebuildEntryRecord(id: string, d: Record<string, unknown>): SubagentRecord | null {
250
- const str = (k: string): string | undefined => (typeof d[k] === "string" ? (d[k] as string) : undefined);
251
- const num = (k: string): number | undefined => (typeof d[k] === "number" ? (d[k] as number) : undefined);
252
- const agent = str("agent");
253
- const task = str("task");
254
- const startedAt = num("startedAt");
329
+ const agent = entryStr(d, "agent");
330
+ const task = entryStr(d, "task");
331
+ const startedAt = entryNum(d, "startedAt");
255
332
  if (agent === undefined || task === undefined || startedAt === undefined) return null; // 损坏 entry:跳过
256
333
  return {
257
334
  id,
258
335
  agent,
259
336
  task,
260
- slug: str("slug") ?? "",
261
- status: "running",
337
+ slug: entryStr(d, "slug") ?? "",
338
+ ...readEntryTerminalFields(d),
262
339
  mode: "background",
263
340
  startedAt,
264
- rootSessionId: str("rootSessionId"),
265
- parentRecordId: str("parentRecordId"),
266
- depth: num("depth") ?? 0,
267
- endedAt: undefined,
268
- turns: num("turns") ?? 0,
269
- totalTokens: num("totalTokens") ?? 0,
270
- model: str("model") ?? "",
271
- thinkingLevel: str("thinkingLevel"),
341
+ rootSessionId: entryStr(d, "rootSessionId"),
342
+ parentRecordId: entryStr(d, "parentRecordId"),
343
+ depth: entryNum(d, "depth") ?? 0,
344
+ endedAt: entryNum(d, "endedAt"),
345
+ turns: entryNum(d, "turns") ?? 0,
346
+ totalTokens: entryNum(d, "totalTokens") ?? 0,
347
+ model: entryStr(d, "model") ?? "",
348
+ thinkingLevel: entryStr(d, "thinkingLevel"),
272
349
  eventLog: [],
273
350
  displayItems: [],
274
- sessionFile: undefined,
351
+ result: entryStr(d, "result"),
352
+ error: entryStr(d, "error"),
353
+ sessionFile: entryStr(d, "sessionFile"),
354
+ patchFile: entryStr(d, "patchFile"),
275
355
  chatMode: d.chatMode === true,
276
- round: num("round"),
277
- engine: str("engine"),
278
- engineFallback:
279
- isEngineFallbackShape(d.engineFallback) ? d.engineFallback : undefined,
280
- engineHandle: isEngineHandleShape(d.engineHandle) ? d.engineHandle : undefined,
356
+ round: entryNum(d, "round"),
357
+ ...readEntryEngineFields(d),
358
+ ...readEntryBatchFields(d),
281
359
  };
282
360
  }
283
361
 
@@ -335,6 +413,41 @@ function sameNullableStamp(a: Stamp | null, b: Stamp | null): boolean {
335
413
  return sameStamp(a, b);
336
414
  }
337
415
 
416
+ /** 缓存条目与本轮 stat 戳全同(jsonl + 3 sidecar,null 语义对齐)→ 零读取复用。 */
417
+ function isFreshCache(cached: FileCacheValue, stamps: FileStamps): boolean {
418
+ return (
419
+ sameStamp(cached.jsonl, stamps.jsonl) &&
420
+ sameNullableStamp(cached.cancelled, stamps.cancelled) &&
421
+ sameNullableStamp(cached.finalized, stamps.finalized) &&
422
+ sameNullableStamp(cached.alive, stamps.alive)
423
+ );
424
+ }
425
+
426
+ /**
427
+ * identity 三级定位——头部 64KB(首轮会话,~34%)→ 尾部 64KB(续聊场景最后一轮
428
+ * session_start 追加,~65%)→ 全文 fallback(~0.2%)。均不解析 message entries。
429
+ * size ≤ 头部读取上限时 head 读到的即全文,tail/anywhere 只会重复读同一份内容——
430
+ * 直接判负(head miss = 全文无 identity),省去同内容两连读。
431
+ */
432
+ function detectIdentity(file: string, size: number): IdentityHeaderRecon | undefined {
433
+ return size <= IDENTITY_HEAD_BYTES
434
+ ? readIdentityHeader(file)
435
+ : readIdentityHeader(file) ?? readIdentityTail(file) ?? readIdentityAnywhere(file);
436
+ }
437
+
438
+ /**
439
+ * sidecar payload 读取(读顺序:tombstone → alive marker → finalized reason)。
440
+ * tombstone/alive 是活态数据,调用方沿用每轮重读语义;finalized reason 静态数据
441
+ * 仅在 sidecar 存在时读一次(文件小,成本可忽略)。
442
+ */
443
+ function readSidecarPayloads(file: string, stamps: FileStamps): SidecarPayloads {
444
+ return {
445
+ tomb: stamps.cancelled !== null ? readCancelledTombstone(file) : undefined,
446
+ aliveData: stamps.alive !== null ? readAliveMarker(file) : undefined,
447
+ finalReason: stamps.finalized !== null ? readFinalizedReason(file) : undefined,
448
+ };
449
+ }
450
+
338
451
  /** Pi ExtensionAPI 的最小子集(仅 collectRecords 跳过损坏 manifest 时上报用)。
339
452
  * 解构为局部类型,避免与 subagent-service 的 PiLike 循环依赖。 */
340
453
  export type RecordStorePi = {
@@ -595,35 +708,53 @@ export class RecordStore {
595
708
  * - 文件不可读(IO 错误,可能暂时)→ 不判终态,落 resumable entry(防御性路径,
596
709
  * IO 恢复后重开可重判)。
597
710
  *
711
+ * [v2 D3] mainSessionFile = 覆写 merge 数据源(主 session 每 id 末条 entry,与 E1
712
+ * 的 scanLastRecordEntries 同款通路):崩溃前的批域标记(collectMode/batchFinalized)
713
+ * 与轮终 result/model 只活在主文件 entry——重建矩阵(buildRecord)的数据源
714
+ * (sidecar/子文件 identity)不含它们,覆写前不 merge 就会被抹掉(v2 §2.2 断链 2:
715
+ * E1 候选集恒空的真根因)。缺省(undefined)时 merge 无源,行为与旧版一致。
716
+ * 参数为追加式第二参(rootSessionFilter 保持首参):既有调用面只传过滤参。
717
+ *
598
718
  * 防重:orphanJudged 实例级缓存(resumable 形态无 sidecar 锚,同进程重复调用跳过;
599
719
  * 终态形态双重防护 = sidecar + 缓存)。调用方:index.ts session_start 恢复段(一次)。
600
720
  */
601
- recoverOrphanRecords(rootSessionFilter?: string): void {
721
+ recoverOrphanRecords(rootSessionFilter?: string, mainSessionFile?: string): void {
722
+ const lastById = new Map(this.scanLastRecordEntries(mainSessionFile).map((r) => [r.id, r]));
602
723
  for (const rec of this.reconstructAll(rootSessionFilter)) {
603
724
  // 分支 4 命中集 = running 且无活进程实例(分支 3 带 externalInstance,分支 1/2 已 closed)。
604
725
  if (rec.status !== "running" || rec.externalInstance !== undefined) continue;
605
726
  if (this.orphanJudged.has(rec.id)) continue;
606
727
  this.orphanJudged.add(rec.id);
607
- this.finalizeOrphanRecord(rec);
728
+ this.finalizeOrphanRecord(rec, lastById.get(rec.id));
608
729
  }
609
730
  }
610
731
 
611
732
  /**
612
733
  * 单孤儿 record 的终态判定与落 entry(residual-fixes §5.2 三判据 + chat 分流)。
613
734
  * 防重锚(orphanJudged 标记)已由调用方完成。
735
+ *
736
+ * [v2 D3] 覆写前 merge(lastEntry = 主 session 同 id 末条 entry 重建,调用方构建):
737
+ * 覆写是状态迁移不是信息重建,迁移不应丢末条既有信息。三个落 entry 分支(chatMode
738
+ * 分流 / IO 保守 / 终态覆写)统一基于 merge 后的 rec,同款口径。
614
739
  */
615
- private finalizeOrphanRecord(rec: SubagentRecord): void {
616
- if (rec.chatMode === true) {
740
+ private finalizeOrphanRecord(rec: SubagentRecord, lastEntry?: SubagentRecord): void {
741
+ // 仅补 undefined/空值字段、不覆盖已有值——覆写自带的 status/closedReason/endedAt/
742
+ // error 不在 merge 字段集,天然不受影响。批域字段(collectMode/batchFinalized)对
743
+ // 非 sync 成员恒 no-op(末条 entry collectMode undefined → 无值可补);result/model
744
+ // 的修补对 async/chat 成员同样生效——拉齐 light 重建丢 result/model 与 full 重建的
745
+ // 既有形态不一致(v2 D3 影响面诚实口径),不触碰任何通知文案(golden 不锁 entry 字节)。
746
+ const rec0 = lastEntry === undefined ? rec : RecordStore.mergeOrphanLastEntry(rec, lastEntry);
747
+ if (rec0.chatMode === true) {
617
748
  // chat 会话跨重启等续聊:保留 running(可续聊),仅落执行态信号。
618
- this.reportSubagentRecord({ ...rec, resumable: true });
749
+ this.reportSubagentRecord({ ...rec0, resumable: true });
619
750
  return;
620
751
  }
621
- const sessionFile = rec.sessionFile;
752
+ const sessionFile = rec0.sessionFile;
622
753
  if (sessionFile === undefined) return; // 无子文件锚(目录扫描源不可达,防御)
623
754
  const lastLine = readLastJsonlLine(sessionFile);
624
755
  if (!lastLine.ok) {
625
756
  // IO 错误可能暂时——判终态不可逆,保守落 resumable 等重开重判。
626
- this.reportSubagentRecord({ ...rec, resumable: true });
757
+ this.reportSubagentRecord({ ...rec0, resumable: true });
627
758
  return;
628
759
  }
629
760
  let parseOk = false;
@@ -639,7 +770,7 @@ export class RecordStore {
639
770
  // 把正常完成的记录误标成断联。
640
771
  writeFinalized(sessionFile, "gc");
641
772
  this.reportSubagentRecord({
642
- ...rec,
773
+ ...rec0,
643
774
  status: "closed",
644
775
  closedReason: "gc",
645
776
  endedAt: Date.now(),
@@ -647,6 +778,25 @@ export class RecordStore {
647
778
  });
648
779
  }
649
780
 
781
+ /** [v2 D3] 孤儿覆写 merge 字段集:末条 entry 的批域标记 + 轮终正文/模型,仅补 rec 侧
782
+ * undefined/空值(model 的空值形态是 ""——light 重建无 model_change entry 时起步
783
+ * 空串),不覆盖已有值。merge 后写 entry 经 reportSubagentRecord →
784
+ * toSubagentRecordEntry 序列化,undefined 字段自然缺省(不引入显式 null)。 */
785
+ private static mergeOrphanLastEntry(rec: SubagentRecord, last: SubagentRecord): SubagentRecord {
786
+ const pickStr = (cur: string | undefined, src: string | undefined): string | undefined =>
787
+ cur !== undefined && cur !== "" ? cur : src;
788
+ return {
789
+ ...rec,
790
+ collectMode: rec.collectMode ?? last.collectMode,
791
+ batchFinalized: rec.batchFinalized ?? last.batchFinalized,
792
+ result: pickStr(rec.result, last.result),
793
+ // 类型收尾:两侧实参恒 string(rec.model 类型非可选;last.model 重建投影自带
794
+ // `?? ""`),pickStr 签名宽返回 string|undefined —— `?? ""` 运行时不可达,
795
+ // 仅满足 model 非可选类型,空串回退语义不变(cur 空 → src,src 也空 → "")。
796
+ model: pickStr(rec.model, last.model) ?? "",
797
+ };
798
+ }
799
+
650
800
  /**
651
801
  * [E2E 实测缺口] entry-born 孤儿恢复:register entry 已落主 session、但子 session 文件
652
802
  * 从未创建(父进程死在 spawn 窗口期——register 写点与子进程首笔写入之间的窗口;外部
@@ -712,6 +862,32 @@ export class RecordStore {
712
862
  });
713
863
  }
714
864
 
865
+ /**
866
+ * [E1/U5] sync 批崩溃恢复扫描:主 session 文件「每 id 末条 subagent-record entry」
867
+ * (collectLastRecordEntries + rebuildEntryRecord 组合通路,设计 §3.1.3「标记读取
868
+ * 通路」——batchFinalized 落标 entry 写主 session 文件,本扫描同文件域才可见;禁走
869
+ * collectRecords light 路径,它只读子文件 identity 头+sidecar,主 session 落标
870
+ * entry 不可见)。返回每 id 末条重建的完整 record(含 collectMode/batchFinalized /
871
+ * 终态五字段,损坏 entry 跳过);调用方(service.recoverSyncCollectBatch 的 E1 过滤、
872
+ * recoverOrphanRecords 的覆写 merge)自行取舍。主文件不可读(含新 session 未 flush
873
+ * 的 ENOENT)→ 空数组静默。
874
+ */
875
+ scanLastRecordEntries(mainSessionFile: string | undefined): SubagentRecord[] {
876
+ if (mainSessionFile === undefined) return [];
877
+ let content: string;
878
+ try {
879
+ content = fs.readFileSync(mainSessionFile, "utf-8");
880
+ } catch {
881
+ return []; // 与 recoverEntryOnlyOrphans 同判:best-effort 恢复,不可读静默跳过
882
+ }
883
+ const out: SubagentRecord[] = [];
884
+ for (const [id, d] of collectLastRecordEntries(content)) {
885
+ const rec = rebuildEntryRecord(id, d);
886
+ if (rec !== null) out.push(rec);
887
+ }
888
+ return out;
889
+ }
890
+
715
891
  /** 订阅变更。返回取消订阅函数。 */
716
892
  onChange(listener: ChangeListener): () => void {
717
893
  this.listeners.add(listener);
@@ -865,18 +1041,15 @@ export class RecordStore {
865
1041
  this.fileCache.delete(file);
866
1042
  return null;
867
1043
  }
868
- const cancelled = statStamp(`${file}.cancelled`);
869
- const finalized = statStamp(`${file}.finalized`);
870
- const alive = statStamp(`${file}.alive`);
1044
+ const stamps: FileStamps = {
1045
+ jsonl,
1046
+ cancelled: statStamp(`${file}.cancelled`),
1047
+ finalized: statStamp(`${file}.finalized`),
1048
+ alive: statStamp(`${file}.alive`),
1049
+ };
871
1050
 
872
1051
  const cached = this.fileCache.get(file);
873
- if (
874
- cached !== undefined &&
875
- sameStamp(cached.jsonl, jsonl) &&
876
- sameNullableStamp(cached.cancelled, cancelled) &&
877
- sameNullableStamp(cached.finalized, finalized) &&
878
- sameNullableStamp(cached.alive, alive)
879
- ) {
1052
+ if (cached !== undefined && isFreshCache(cached, stamps)) {
880
1053
  if (cached.negative) return null; // 负缓存命中:确认无 identity,零读取跳过
881
1054
  // pid 探活结果不落盘(进程死亡无 IO)——分支 3 的 running 项每扫重查,
882
1055
  // 保留原语义(旧实现每次 collectRecords 都重新 isProcessAlive)。
@@ -885,83 +1058,54 @@ export class RecordStore {
885
1058
  }
886
1059
 
887
1060
  // [perf L-1] 磁盘索引查询(首扫惰性装载,miss/空索引时 get 恒 undefined = 无索引)。
888
- // 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。sidecar payload(tombstone/
889
- // alive)是活态数据,沿用探测分支的每轮重读语义;finalized reason 静态数据仅在
890
- // sidecar 存在时读一次(文件小,成本可忽略)。
891
- if (this.indexEntries !== null) {
892
- const hit = this.indexEntries.get(path.basename(file));
893
- if (hit !== undefined && hit.mtimeMs === jsonl.mtimeMs && hit.size === jsonl.size) {
894
- if (hit.negative === true) {
895
- // 负条目命中:「确认无 identity」跨实例持久,零探测跳过(与下方负缓存同款形态)。
896
- this.fileCache.set(file, { negative: true, jsonl, cancelled, finalized, alive });
897
- return null;
898
- }
899
- const tomb = cancelled !== null ? readCancelledTombstone(file) : undefined;
900
- const aliveData = alive !== null ? readAliveMarker(file) : undefined;
901
- const finalReason = finalized !== null ? readFinalizedReason(file) : undefined;
902
- const entry: FileCacheEntry = {
903
- light: RecordStore.buildRecord(
904
- { ...hit, forkDepth: undefined, sessionFile: file },
905
- { tomb, finalized: finalized !== null, finalizedReason: finalReason, alive: aliveData, jsonlMtimeMs: jsonl.mtimeMs, now },
906
- ),
907
- full: undefined,
908
- jsonl,
909
- cancelled,
910
- finalized,
911
- alive,
912
- tomb,
913
- aliveData,
914
- finalReason,
915
- };
916
- this.fileCache.set(file, entry);
917
- this.idToFile.set(hit.id, file);
918
- return entry;
919
- }
920
- }
1061
+ // 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。undefined = 未命中
1062
+ // (落到下方探测),null = 负条目命中(零探测跳过)。
1063
+ const fromIndex = this.buildEntryFromIndex(file, stamps, now);
1064
+ if (fromIndex !== undefined) return fromIndex;
921
1065
 
922
1066
  // [perf L-1] 索引 miss/戳不匹配落到原三级探测:本轮探测结果必须进索引(含负探测)。
923
1067
  // 覆盖两种形态:首扫(映像已装载但 miss/不匹配)与后续轮次(映像已释放,凡进重建分支必是戳变化)。
924
1068
  this.indexDirty = true;
925
1069
 
926
- // 重建:identity 三级定位——头部 64KB(首轮会话,~34%)→ 尾部 64KB(续聊场景
927
- // 最后一轮 session_start 追加,~65%)→ 全文 fallback(~0.2%)。均不解析 message entries。
928
- // size ≤ 头部读取上限时 head 读到的即全文,tail/anywhere 只会重复读同一份内容——
929
- // 直接判负(head miss = 全文无 identity),省去同内容两连读。
930
- const header =
931
- jsonl.size <= IDENTITY_HEAD_BYTES
932
- ? readIdentityHeader(file)
933
- : readIdentityHeader(file) ?? readIdentityTail(file) ?? readIdentityAnywhere(file);
1070
+ const header = detectIdentity(file, jsonl.size);
934
1071
  if (!header) {
935
1072
  // 负缓存:确认无 identity。后续扫描 stat 命中直接跳过;戳变化(文件补写)自动重试。
936
- this.fileCache.set(file, { negative: true, jsonl, cancelled, finalized, alive });
1073
+ this.fileCache.set(file, { negative: true, ...stamps });
937
1074
  return null;
938
1075
  }
939
- const tomb = cancelled !== null ? readCancelledTombstone(file) : undefined;
940
- const aliveData = alive !== null ? readAliveMarker(file) : undefined;
941
- const finalReason = finalized !== null ? readFinalizedReason(file) : undefined;
942
- const entry: FileCacheEntry = {
943
- light: RecordStore.buildRecord(header, {
944
- tomb,
945
- finalized: finalized !== null,
946
- finalizedReason: finalReason,
947
- alive: aliveData,
948
- jsonlMtimeMs: jsonl.mtimeMs,
949
- now,
950
- }),
951
- full: undefined,
952
- jsonl,
953
- cancelled,
954
- finalized,
955
- alive,
956
- tomb,
957
- aliveData,
958
- finalReason,
959
- };
1076
+ const payloads = readSidecarPayloads(file, stamps);
1077
+ const entry = RecordStore.buildFileCacheEntry(header, file, stamps, payloads, now);
960
1078
  this.fileCache.set(file, entry);
961
1079
  this.idToFile.set(header.id, file);
962
1080
  return entry;
963
1081
  }
964
1082
 
1083
+ /**
1084
+ * [perf L-1] 磁盘索引查询(首扫惰性装载,miss/空索引时 get 恒 undefined = 无索引)。
1085
+ * 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。sidecar payload(tombstone/
1086
+ * alive)是活态数据,沿用探测分支的每轮重读语义;finalized reason 静态数据仅在
1087
+ * sidecar 存在时读一次(文件小,成本可忽略)。
1088
+ *
1089
+ * 返回 undefined = 索引未命中/戳不匹配(调用方落到原三级探测);null = 负条目命中
1090
+ * (「确认无 identity」跨实例持久,零探测跳过,与内存负缓存同款形态)。
1091
+ */
1092
+ private buildEntryFromIndex(file: string, stamps: FileStamps, now: number): FileCacheEntry | null | undefined {
1093
+ if (this.indexEntries === null) return undefined;
1094
+ const hit = this.indexEntries.get(path.basename(file));
1095
+ if (hit === undefined || hit.mtimeMs !== stamps.jsonl.mtimeMs || hit.size !== stamps.jsonl.size) {
1096
+ return undefined;
1097
+ }
1098
+ if (hit.negative === true) {
1099
+ this.fileCache.set(file, { negative: true, ...stamps });
1100
+ return null;
1101
+ }
1102
+ const payloads = readSidecarPayloads(file, stamps);
1103
+ const entry = RecordStore.buildFileCacheEntry({ ...hit, forkDepth: undefined, sessionFile: file }, file, stamps, payloads, now);
1104
+ this.fileCache.set(file, entry);
1105
+ this.idToFile.set(hit.id, file);
1106
+ return entry;
1107
+ }
1108
+
965
1109
  /**
966
1110
  * [perf L-1] 扫描尾索引落盘(节流):释放映像 → dirty/高版本/60s 节流窗三重门 →
967
1111
  * fire-and-forget saveIndex(fileCache 全量投影)。写决策与发起在同步栈(collectRecords
@@ -1089,6 +1233,34 @@ export class RecordStore {
1089
1233
  entry.light.externalInstance = live ? marker : undefined;
1090
1234
  }
1091
1235
 
1236
+ /** identity 基底 + sidecar 状态矩阵 → 缓存条目(索引命中与探测重建两分支的公共装配点)。 */
1237
+ private static buildFileCacheEntry(
1238
+ base: IdentityHeaderRecon,
1239
+ file: string,
1240
+ stamps: FileStamps,
1241
+ payloads: SidecarPayloads,
1242
+ now: number,
1243
+ ): FileCacheEntry {
1244
+ return {
1245
+ light: RecordStore.buildRecord(base, {
1246
+ tomb: payloads.tomb,
1247
+ finalized: stamps.finalized !== null,
1248
+ finalizedReason: payloads.finalReason,
1249
+ alive: payloads.aliveData,
1250
+ jsonlMtimeMs: stamps.jsonl.mtimeMs,
1251
+ now,
1252
+ }),
1253
+ full: undefined,
1254
+ jsonl: stamps.jsonl,
1255
+ cancelled: stamps.cancelled,
1256
+ finalized: stamps.finalized,
1257
+ alive: stamps.alive,
1258
+ tomb: payloads.tomb,
1259
+ aliveData: payloads.aliveData,
1260
+ finalReason: payloads.finalReason,
1261
+ };
1262
+ }
1263
+
1092
1264
  /** identity 基底(头部 light 或全量 recon)+ 四分支 sidecar 状态矩阵 → SubagentRecord。 */
1093
1265
  private static buildRecord(
1094
1266
  base: IdentityHeaderRecon | ReconstructedRecord,
@@ -1274,6 +1446,11 @@ export class RecordStore {
1274
1446
  engineFallback: r.engineFallback,
1275
1447
  // U2:engineHandle 经 entry 持久化(register/archive 双写点均经本投影),无则 undefined 自然省略
1276
1448
  engineHandle: r.engineHandle,
1449
+ // [U5 修复 U2 披露的投影缺口] 同步收集两字段随本投影持久化(register entry /
1450
+ // archive entry 双写点)——原缺失时闭合判定 flushBatch 重建、E1 重建扫描等消费方
1451
+ // 读不到原始值。undefined 经 JSON.stringify 自然缺省,旧 entry 零迁移。
1452
+ collectMode: r.collectMode,
1453
+ batchFinalized: r.batchFinalized,
1277
1454
  };
1278
1455
  }
1279
1456
  }
@@ -0,0 +1,93 @@
1
+ // [D4-② 轮次结算职责轴] chatMode 轮次完成的业务闭包(原 SubagentService
2
+ // buildSessionRunnerContext 内的 onRoundSettled 回调 ~95 行区域)整体搬移至此
3
+ // (行为逐字节等价:搬移 + 依赖注入,不重写逻辑)。变化轴:改轮次增量派生 /
4
+ // notify 时序 / base 推进 / closeAfterRound 消费语义,只改本文件;Service 经
5
+ // buildSessionRunnerContext 注入三个依赖回调(notify / 持久化上报 / close 兑现)。
6
+
7
+ import { getLogger } from "../core/logger.ts";
8
+
9
+ import { getFullTextFrom, nextRoundBaseTurnIndex } from "./execution-record.ts";
10
+ import type { ExecutionRecord } from "./types.ts";
11
+
12
+ const logger = getLogger("subagents");
13
+
14
+ /** 轮次结算的依赖注入(Service 侧供给)。 */
15
+ export interface RoundSettlementDeps {
16
+ /** 本轮增量的通知投递(Service.notifyComplete——经 notifyHost)。 */
17
+ notifyComplete: (record: ExecutionRecord) => void;
18
+ /** 轮终迁移持久化上报(store.reportRecordTransition——live ≡ reload 等价性)。 */
19
+ reportRecordTransition: (record: ExecutionRecord) => void;
20
+ /** closeAfterRound 兑现(Service.closeAfterRoundSettled——chatMode 优雅关闭的终态化)。 */
21
+ closeAfterRoundSettled: (record: ExecutionRecord) => Promise<void>;
22
+ }
23
+
24
+ /** 构造 onRoundSettled 回调(session-runner agent_settled 时调用)。
25
+ *
26
+ * [V2 决策 2] chatMode 首轮闭环:session-runner 已 arm idle timer,
27
+ * isIdle=true 让 notify 守卫放行(时序:armIdleTimer → onRoundSettled)。
28
+ * 回调体原为 subagent-service.ts buildSessionRunnerContext 的内联闭包,D4-② 拆出。 */
29
+ export function createRoundSettler(deps: RoundSettlementDeps): (record: ExecutionRecord) => void {
30
+ return (record) => {
31
+ // v4 B-1:status 保持 running(旧 idle 折入 running);session-runner 已 arm idle timer,
32
+ // isIdle=true 让 notify 守卫放行(时序:armIdleTimer → onRoundSettled,见 session-runner.ts:670)。
33
+ // round 可能初始 undefined(与 notifier.ts `record.round ?? 0` 兜底一致),
34
+ // 首轮 0+1=1。round 是 notifier dedup key 的组成部分,递增后同 id 下一轮不被 60s dedup 吞。
35
+ record.round = (record.round ?? 0) + 1;
36
+ // [N2][增量] 轮次回复写点(增量语义):roundText 自 roundBaseTurnIndex 起派生本轮增量
37
+ //(undefined 视为 0——首轮增量 = 全量,与改造前首轮逐字节一致)。成功轮次的 MF-2 原写点
38
+ //(doFinalizeRoundToIdle)不可达——agent_settled 恒 arm idle timer → runAndFinalize 恒
39
+ // early return。写入 record.result 后再 notify;本轮无非空增量且无 lastError(纯工具轮 /
40
+ // interrupt 抢占轮 / 模型空回复)时固定占位 "(no output this round)"(D5:增量语义下沿用
41
+ // 旧 record.result = 上一轮增量 → 本轮通知正文 = 上一轮内容,父 agent 误读为原样重复回复;
42
+ // lastError 兜底保留让失败轮通知可读)。后续 closeAfterRoundSettled 的合成 result 读
43
+ // record.result,同样携带本轮增量。
44
+ const roundText = getFullTextFrom(record, record.roundBaseTurnIndex ?? 0);
45
+ record.result = roundText ||
46
+ (record.lastError ? `round did not complete: ${record.lastError}` : "(no output this round)");
47
+ // 先送达本轮增量(notify),再推进 base / 消费 closeAfterRound——终态通知由
48
+ // closeAfterRoundSettled / closeChatIdle 的 notifyClosed 显式发出(dedup 身份为裸 id,
49
+ // 与本次 round notify 的 id:round key 区分),保证「本轮增量 + 终态通知」都送达;
50
+ // 轮次收尾 .then 的冷路径 notifyComplete 仍与本次 round notify 同 key 被 60s
51
+ // dedup 吞(不构成第三条)。
52
+ //
53
+ // 幂等性(覆盖面如实限定):同步路径 at-least-once——notifyComplete(同步 void)抛错时
54
+ // 推进/消费被跳过 → base 不推进 → 增量未消费,下轮 roundText 必含本轮文本(重发载体为
55
+ // 后续轮次增量拼接)。轮次收尾 .then 的冷路径 notifyComplete 不构成重发通道
56
+ // (notifier dedup.set 与 pending.splice 均先于 sendMessage,同 key `${id}:${round}`——
57
+ // round 已递增——60s 窗内重入被吞)。异步 flush 窗口不保证:合并 timer armed(其他 busy
58
+ // background 在场)或 isIdle 退避期间 notify 的『成功』只是入队,实际 sendMessage 发生在
59
+ // base 推进之后的异步时机;该窗口进程崩溃或 sendMessage 失败 → 丢失不可重发(现状全量
60
+ // 重发的次轮自愈在增量语义下消失),wave1 期恢复通道仅父 agent 经 /subagents 详情读取
61
+ // record.result,wave2 指针行落地后补全。反序(先推进后 notify)在同步路径 notify 失败时
62
+ // 静默丢增量且无任何重算机会,故 notify 后推进是定案。重复发送由 notifier dedup key
63
+ // 60s 窗界定。
64
+ deps.notifyComplete(record);
65
+ // R1 观测哨(不变式违反形态):推进前检查末 turn 未闭合且 text 非空——pi 现序下不可达
66
+ //(带 usage 的 message_end 恒先于 turn_end,settle 时 turn 全闭合,见 types.ts
67
+ // roundBaseTurnIndex 注释的行号锚定),ES1 单测自造事件序列锁不住 pi 层变化;pi 升级若
68
+ // 改变 turn_end/agent_end 时序,此哨兵留痕(该形态下公式仍把文本计入本轮,不丢数据)。
69
+ const lastTurn = record.turns[record.turns.length - 1];
70
+ if (lastTurn !== undefined && !lastTurn.closed && lastTurn.text.length > 0) {
71
+ logger.warn(
72
+ `[subagents] round settle with unclosed non-empty turn (record=${record.id}, turnIndex=${record.turns.length - 1}) — pi turn_end/agent_end ordering may have changed`,
73
+ );
74
+ }
75
+ // [增量] base 推进(notify 之后):下一轮增量从本轮边界起。滞后空 turn 不计入边界
76
+ //(防御分支,留在下一轮增量内防丢文本——nextRoundBaseTurnIndex 注释)。
77
+ record.roundBaseTurnIndex = nextRoundBaseTurnIndex(record);
78
+ // 轮终迁移持久化(residual-fixes U3 补全):热路径轮终不经 doFinalizeRoundToIdle
79
+ //(agent_settled 恒 arm idle timer → runAndFinalize 恒 early return,MF-2 原写点
80
+ // 不可达)——不 appendEntry 则 runtime/W18 派生缓存不失效,renderer 停留在
81
+ // register 快照(无 result),chat 等续聊的 waiting 形态显示不出来、spinner
82
+ // 卡死。显式上报:entry 携带本轮 result/round/chatMode(§5.4:result 有值 +
83
+ // chatMode=true → waiting)。closeAfterRound 的终态 entry 在此后追加,序不变。
84
+ deps.reportRecordTransition(record);
85
+ // [M5] closeAfterRound 消费点:chatMode 每轮完成的统一汇聚点(热路径轮不经
86
+ // runAndFinalize CAS 分支——agent_settled 恒 arm idle timer → runAndFinalize 恒
87
+ // early return,旧消费点对 chatMode 不可达,标志置了无人消费、tool 谎报 closed:true)。
88
+ if (record.closeAfterRound) {
89
+ record.closeAfterRound = undefined;
90
+ void deps.closeAfterRoundSettled(record);
91
+ }
92
+ };
93
+ }