@zhushanwen/subagent-core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
  2. package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
  3. package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
  4. package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
  5. package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
  6. package/dist/execution/engine/engines/zcode/constants.js +17 -27
  7. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  8. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  9. package/dist/execution/engine/engines/zcode/reader.js +2 -2
  10. package/dist/index.cjs +17100 -14082
  11. package/dist/index.d.cts +5336 -4033
  12. package/dist/index.d.ts +5336 -4033
  13. package/dist/index.js +17020 -14055
  14. package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
  16. package/dist.bundle/index.cjs +34933 -0
  17. package/package.json +3 -3
  18. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  19. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  20. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  21. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  22. package/src/__tests__/manifest-store.test.ts +10 -9
  23. package/src/__tests__/record-store-cache.test.ts +0 -2
  24. package/src/__tests__/record-store-index.test.ts +1 -2
  25. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  26. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  27. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  28. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  29. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  30. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  31. package/src/__tests__/session-runner.test.ts +1 -1
  32. package/src/core/__tests__/host-services.test.ts +2 -2
  33. package/src/core/__tests__/logger.test.ts +1 -1
  34. package/src/core/error-message.ts +9 -0
  35. package/src/core/host-services.ts +21 -5
  36. package/src/core/notify-ports.ts +18 -5
  37. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  38. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  39. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  40. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  41. package/src/execution/__tests__/alive-store.test.ts +1 -1
  42. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  43. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  44. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  45. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  46. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  47. package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
  48. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  49. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
  50. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  51. package/src/execution/__tests__/config.test.ts +1 -1
  52. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  53. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  54. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  55. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  56. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  57. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  58. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  59. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  60. package/src/execution/__tests__/execution-record.test.ts +217 -60
  61. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  62. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
  63. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  64. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  65. package/src/execution/__tests__/gc-timer.test.ts +56 -3
  66. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  67. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  68. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  69. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  70. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  71. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  72. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  73. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  74. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  75. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  76. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  77. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  78. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  79. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  80. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  81. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  82. package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
  83. package/src/execution/__tests__/output-collector.test.ts +117 -6
  84. package/src/execution/__tests__/pi-invocation.test.ts +36 -4
  85. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  86. package/src/execution/__tests__/record-store.test.ts +10 -9
  87. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  88. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  89. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  90. package/src/execution/__tests__/relay-env.test.ts +12 -1
  91. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  92. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  93. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  94. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  95. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  96. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  97. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  98. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  99. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  100. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  101. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  102. package/src/execution/__tests__/session-file-gc.test.ts +36 -1
  103. package/src/execution/__tests__/session-pending.test.ts +257 -14
  104. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  105. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  106. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  107. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  108. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  109. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  110. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  112. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  113. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +3 -3
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  117. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  118. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  119. package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
  120. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  122. package/src/execution/__tests__/subagent-actions-core.test.ts +48 -22
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  124. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
  125. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  126. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  127. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  128. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  129. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  130. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  131. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  132. package/src/execution/__tests__/sync-collect-recovery.test.ts +1117 -0
  133. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  134. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  135. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  136. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  137. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  138. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  139. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  140. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  141. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  142. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  143. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  144. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  145. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  146. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  147. package/src/execution/agent-registry.ts +74 -30
  148. package/src/execution/agent-result-mapper.ts +3 -0
  149. package/src/execution/cold-resurrect.ts +199 -0
  150. package/src/execution/collect-coordinator.ts +200 -0
  151. package/src/execution/config.ts +77 -5
  152. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  153. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  154. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  155. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  156. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  157. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  158. package/src/execution/engine/__tests__/common/pool-manager.test.ts +213 -53
  159. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  160. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  161. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  162. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  163. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  181. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  182. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  183. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  184. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  185. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  186. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  187. package/src/execution/engine/engines/pi/reader.ts +11 -0
  188. package/src/execution/engine/engines/pi/registration.ts +4 -2
  189. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  190. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  191. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  192. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  193. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  194. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  195. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  196. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  197. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  198. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  199. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  200. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  209. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  210. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  211. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  212. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  213. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  214. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  215. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  216. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  217. package/src/execution/engine/host-task-spec.ts +32 -36
  218. package/src/execution/engine/model-validation.ts +177 -0
  219. package/src/execution/engine/port.ts +24 -6
  220. package/src/execution/engine/routing.ts +75 -1
  221. package/src/execution/engine/types.ts +30 -103
  222. package/src/execution/execution-record.ts +229 -144
  223. package/src/execution/finalize-record.ts +141 -99
  224. package/src/execution/idle-gc.ts +28 -0
  225. package/src/execution/lifecycle-predicates.ts +1 -1
  226. package/src/execution/notifier.ts +234 -25
  227. package/src/execution/notify-host.ts +232 -0
  228. package/src/execution/record-entry.ts +16 -0
  229. package/src/execution/record-store.ts +280 -103
  230. package/src/execution/round-settlement.ts +93 -0
  231. package/src/execution/session-file-gc.ts +96 -62
  232. package/src/execution/session-reconstructor.ts +256 -143
  233. package/src/execution/sessions-index.ts +117 -47
  234. package/src/execution/settled-watchdog.ts +264 -52
  235. package/src/execution/subagent-actions-core.ts +84 -28
  236. package/src/execution/subagent-service.ts +1225 -964
  237. package/src/execution/subprocess-agent-runner.ts +144 -97
  238. package/src/execution/sync-rebuild.ts +87 -0
  239. package/src/execution/types.ts +72 -0
  240. package/src/execution/ui-request-handler-factory.ts +102 -74
  241. package/src/execution/ui-request-queue.ts +50 -19
  242. package/src/index.ts +442 -316
  243. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  244. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  245. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  246. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  247. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  248. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  249. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  250. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  251. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  252. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  253. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  254. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  255. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  256. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  257. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  258. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  259. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  260. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  261. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  262. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  263. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  264. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  265. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  266. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  267. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  268. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  269. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  270. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  271. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  272. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  273. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  274. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  275. package/src/orchestration/agent-opts-resolver.ts +1 -1
  276. package/src/orchestration/args-validator.ts +53 -31
  277. package/src/orchestration/execute-agent-call.ts +19 -73
  278. package/src/orchestration/launcher.ts +128 -81
  279. package/src/orchestration/lifecycle.ts +43 -45
  280. package/src/orchestration/models/ports.ts +4 -4
  281. package/src/orchestration/models/run-runtime.ts +2 -2
  282. package/src/orchestration/models/trace.ts +1 -1
  283. package/src/orchestration/models/types.ts +111 -22
  284. package/src/orchestration/models/workflow-run.ts +1 -1
  285. package/src/orchestration/script-lint.ts +91 -58
  286. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  287. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  288. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  289. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  290. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  291. package/src/shared/atomic-write.ts +12 -9
  292. package/src/shared/meta-parser.ts +158 -84
  293. package/src/shared/model-ref.ts +15 -2
  294. package/src/shared/resource-discovery.ts +9 -183
  295. package/src/shared/schema-jsonify.ts +1 -1
  296. package/workflows/review-fix-loop-utils.cjs +151 -89
  297. package/workflows/review-fix-loop-utils.d.cts +287 -0
  298. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  299. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  300. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  301. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  302. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  303. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  305. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  306. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  307. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  308. package/src/execution/execute-options-mapper.ts +0 -115
  309. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,149 @@
1
+ // session-view-service-zcode-dbpath.test.ts —— zcode ①级 dbPath 白名单的分支守护。
2
+ //
3
+ // 三视角:①构建者——白名单三分支(绝对=宿主 db 精确匹配 / 绝对≠宿主 db 拒绝 /
4
+ // 相对池锚定 + 越界拒绝)逐路径断言;②使用者——共享 HOME 形态 record(写侧
5
+ // zcode-engine 恒绝对 dbPath)tier1 不再被误拒静默降②级;③观察者——reader 调用
6
+ // 参数(白名单通过后传入的 dbPath 精确值)与未调用事实(拒绝路径 reader 零触达)。
7
+ //
8
+ // readZcodeSessionView 以 vi.mock 替身:本文件测的是白名单分支(不可触达真实
9
+ // ~/.zcode/cli/db/db.sqlite——那是用户真机数据),reader 本体行为由
10
+ // engines/zcode/__tests__/reader.test.ts 真库覆盖。
11
+
12
+ import { mkdtempSync, rmSync } from "node:fs";
13
+ import { homedir, tmpdir } from "node:os";
14
+ import { join, resolve } from "node:path";
15
+
16
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
17
+
18
+ vi.mock("../../engines/zcode/reader.ts", () => ({
19
+ readZcodeSessionView: vi.fn(),
20
+ }));
21
+
22
+ import { readZcodeSessionView } from "../../engines/zcode/reader.ts";
23
+ import { ZCODE_HOST_DB_SUFFIX } from "../../engines/zcode/constants.ts";
24
+ import { JournalWriter } from "../../common/event-journal.ts";
25
+ import { readSubagentHistoryMessages } from "../../common/session-view-service.ts";
26
+ import { resetNativeSessionReaders } from "../../common/session-view-service.ts";
27
+ import type { SubagentRecordSnapshot } from "../../common/session-view-types.ts";
28
+ import type { AgentEvent } from "../../../types.ts";
29
+ import type { SessionView } from "../../types.ts";
30
+
31
+ const mockedRead = vi.mocked(readZcodeSessionView);
32
+
33
+ /** 共享 HOME 形态写侧恒写入的唯一合法绝对 dbPath(与 zcode-engine hostZcodeDbPath 同源推导)。 */
34
+ const HOST_DB = resolve(homedir(), ...ZCODE_HOST_DB_SUFFIX);
35
+
36
+ let dataDir: string;
37
+
38
+ beforeEach(() => {
39
+ dataDir = mkdtempSync(join(tmpdir(), "svs-dbpath-test-"));
40
+ mockedRead.mockReset();
41
+ });
42
+
43
+ afterEach(() => {
44
+ rmSync(dataDir, { recursive: true, force: true });
45
+ resetNativeSessionReaders();
46
+ });
47
+
48
+ function makeRecord(overrides: Partial<SubagentRecordSnapshot> = {}): SubagentRecordSnapshot {
49
+ return {
50
+ subagentId: "sub-1",
51
+ task: "do the thing",
52
+ startedAt: 1_000,
53
+ engine: "zcode",
54
+ engineHandle: {
55
+ sessionRef: { sessionId: "sess-1", dbPath: HOST_DB },
56
+ poolKey: "shared",
57
+ },
58
+ ...overrides,
59
+ };
60
+ }
61
+
62
+ async function writeJournal(path: string, events: AgentEvent[]): Promise<void> {
63
+ const writer = new JournalWriter({ path, taskId: "sub-1", engineId: "zcode" });
64
+ for (const ev of events) writer.append(ev);
65
+ await writer.close();
66
+ }
67
+
68
+ /** record 带②级可达 journal(tier1 被拒后的可观察落点:journal 内容被投影)。 */
69
+ async function journalRecord(dbPath: string): Promise<SubagentRecordSnapshot> {
70
+ const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
71
+ await writeJournal(journalPath, [{ type: "text_delta", delta: "journal says" }]);
72
+ return makeRecord({
73
+ result: "outcome fallback",
74
+ engineHandle: {
75
+ sessionRef: { sessionId: "sess-1", dbPath },
76
+ poolKey: "shared",
77
+ journalPath,
78
+ },
79
+ });
80
+ }
81
+
82
+ describe("zcode ①级 dbPath 白名单(分支守护)", () => {
83
+ it("绝对 dbPath = 宿主 db(SSOT 精确匹配)→ 白名单通过,reader 收到原样路径,tier1 命中", async () => {
84
+ const view: SessionView = {
85
+ engineId: "zcode",
86
+ sessionId: "sess-1",
87
+ turns: [{ text: "from host db", thinking: "", toolCalls: [], closed: true }],
88
+ source: "native",
89
+ };
90
+ mockedRead.mockResolvedValue(view);
91
+ const messages = await readSubagentHistoryMessages(makeRecord(), dataDir);
92
+ expect(mockedRead).toHaveBeenCalledTimes(1);
93
+ expect(mockedRead).toHaveBeenCalledWith(HOST_DB, "sess-1");
94
+ expect(messages[1]).toMatchObject({ role: "assistant", content: "from host db" });
95
+ });
96
+
97
+ it("绝对 dbPath ≠ 宿主 db → 拒绝 tier1(reader 零触达),降②级 journal", async () => {
98
+ const record = await journalRecord("/tmp/attacker-chosen/db.sqlite");
99
+ const messages = await readSubagentHistoryMessages(record, dataDir);
100
+ expect(mockedRead).not.toHaveBeenCalled();
101
+ expect(messages[1]).toMatchObject({ role: "assistant", content: "journal says" });
102
+ });
103
+
104
+ it("宿主 db 路径的目录内变体(/../ 注入)≠ 精确匹配 → 同样拒绝", async () => {
105
+ // 白名单是文本级精确匹配(resolve 不可用于反构造——会把 .. 规约掉变回正身)
106
+ const sneaky = `${HOST_DB}/../db.sqlite`;
107
+ const record = await journalRecord(sneaky);
108
+ const messages = await readSubagentHistoryMessages(record, dataDir);
109
+ expect(mockedRead).not.toHaveBeenCalled();
110
+ expect(messages[1]).toMatchObject({ content: "journal says" });
111
+ });
112
+
113
+ it("相对 dbPath 池内(旧池时代 record)→ 锚池目录解析,reader 收到池内绝对路径", async () => {
114
+ const view: SessionView = {
115
+ engineId: "zcode",
116
+ sessionId: "sess-1",
117
+ turns: [{ text: "legacy pool", thinking: "", toolCalls: [], closed: true }],
118
+ source: "native",
119
+ };
120
+ mockedRead.mockResolvedValue(view);
121
+ const record = makeRecord({
122
+ engineHandle: {
123
+ sessionRef: { sessionId: "sess-1", dbPath: "pool-db/db.sqlite" },
124
+ poolKey: "shared",
125
+ },
126
+ });
127
+ const messages = await readSubagentHistoryMessages(record, dataDir);
128
+ expect(mockedRead).toHaveBeenCalledWith(
129
+ join(dataDir, "engines", "zcode", "shared", "pool-db", "db.sqlite"),
130
+ "sess-1",
131
+ );
132
+ expect(messages[1]).toMatchObject({ content: "legacy pool" });
133
+ });
134
+
135
+ it("相对 dbPath ../ 逃逸池目录 → 拒绝 tier1(reader 零触达),降②级 journal", async () => {
136
+ const record = await journalRecord("../../escape/db.sqlite");
137
+ const messages = await readSubagentHistoryMessages(record, dataDir);
138
+ expect(mockedRead).not.toHaveBeenCalled();
139
+ expect(messages[1]).toMatchObject({ content: "journal says" });
140
+ });
141
+
142
+ it("白名单通过但 reader 抛错(结构化降级契约)→ 仍降②级不崩", async () => {
143
+ mockedRead.mockRejectedValue(new Error("engine_session_read_failed: db 文件不存在"));
144
+ const record = await journalRecord(HOST_DB);
145
+ const messages = await readSubagentHistoryMessages(record, dataDir);
146
+ expect(mockedRead).toHaveBeenCalledTimes(1);
147
+ expect(messages[1]).toMatchObject({ content: "journal says" });
148
+ });
149
+ });
@@ -0,0 +1,499 @@
1
+ // session-view-service.test.ts —— D1 journal 读取链收敛的守护测试。
2
+ //
3
+ // 三视角:①构建者——三级降级编排(①native ②journal ③outcome-only)逐级降级、
4
+ // registry 分发查表;②使用者——投影 parity(zcode 现役 journal 形态的新旧链
5
+ // 等价:coarse 成功两事件 → ③级、失败 error 事件 → 物化 assistant);
6
+ // ③观察者——message_end 带 error 的记账语义(门②补录样本:现役 zcode 不产出,
7
+ // 作为守护实现的回归校验数据源)。
8
+ //
9
+ // parity 基准 = 被收敛前的 runtime 手写链(subagent-engine-history.ts,已删):
10
+ // 同一 journal 输入,HistoryMessage[] 除随机 id 外逐字段等价。
11
+
12
+ import { mkdtempSync, rmSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+
16
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
17
+
18
+ import { JournalWriter } from "../../common/event-journal.ts";
19
+ import {
20
+ extractEngineId,
21
+ readSubagentHistoryMessages,
22
+ registerNativeSessionReader,
23
+ resetNativeSessionReaders,
24
+ } from "../../common/session-view-service.ts";
25
+ import { parseEngineHandle } from "../../common/session-view-types.ts";
26
+ import type { SubagentRecordSnapshot } from "../../common/session-view-types.ts";
27
+ import type { AgentEvent } from "../../../types.ts";
28
+ import type { SessionView } from "../../types.ts";
29
+
30
+ let dataDir: string;
31
+
32
+ beforeEach(() => {
33
+ dataDir = mkdtempSync(join(tmpdir(), "session-view-test-"));
34
+ });
35
+
36
+ afterEach(() => {
37
+ rmSync(dataDir, { recursive: true, force: true });
38
+ // reader registry 是进程级状态——重置回内置集,防 fake 注入跨用例泄漏
39
+ resetNativeSessionReaders();
40
+ });
41
+
42
+ /** 最小 record 快照(默认 zcode + 池内相对 dbPath + 可选 journalPath)。 */
43
+ function makeRecord(overrides: Partial<SubagentRecordSnapshot> = {}): SubagentRecordSnapshot {
44
+ return {
45
+ subagentId: "sub-1",
46
+ task: "do the thing",
47
+ startedAt: 1_000,
48
+ engine: "zcode",
49
+ engineHandle: {
50
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
51
+ poolKey: "shared",
52
+ },
53
+ ...overrides,
54
+ };
55
+ }
56
+
57
+ /** journal 真实落盘(写侧 SSOT JournalWriter——读取链全真)。 */
58
+ async function writeJournal(path: string, events: AgentEvent[]): Promise<void> {
59
+ const writer = new JournalWriter({ path, taskId: "sub-1", engineId: "zcode" });
60
+ for (const ev of events) writer.append(ev);
61
+ await writer.close();
62
+ }
63
+
64
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
65
+
66
+ // ============================================================
67
+ // 单一 handle guard(双守卫收敛点)
68
+ // ============================================================
69
+
70
+ describe("parseEngineHandle(唯一 guard)", () => {
71
+ it("合法形状:sessionRef 透传 + journalPath 可选", () => {
72
+ expect(
73
+ parseEngineHandle({
74
+ sessionRef: { sessionId: "s", dbPath: "db.sqlite" },
75
+ poolKey: "shared",
76
+ journalPath: "/tmp/j.jsonl",
77
+ }),
78
+ ).toEqual({
79
+ sessionRef: { sessionId: "s", dbPath: "db.sqlite" },
80
+ poolKey: "shared",
81
+ journalPath: "/tmp/j.jsonl",
82
+ });
83
+ expect(
84
+ parseEngineHandle({ sessionRef: { sessionId: "s" }, poolKey: "shared" }),
85
+ ).toEqual({ sessionRef: { sessionId: "s" }, poolKey: "shared" });
86
+ });
87
+
88
+ it("坏形状整体拒绝:非对象 / poolKey 缺失或空 / sessionRef 非法", () => {
89
+ expect(parseEngineHandle(undefined)).toBeUndefined();
90
+ expect(parseEngineHandle("x")).toBeUndefined();
91
+ expect(parseEngineHandle({ sessionRef: {}, poolKey: "" })).toBeUndefined();
92
+ expect(parseEngineHandle({ sessionRef: {}, poolKey: 1 })).toBeUndefined();
93
+ expect(parseEngineHandle({ poolKey: "shared" })).toBeUndefined();
94
+ expect(parseEngineHandle({ sessionRef: [], poolKey: "shared" })).toBeUndefined();
95
+ });
96
+
97
+ it("sessionRef 含非 string 值 → 整体拒绝(收敛选 extractor 严格版)", () => {
98
+ expect(
99
+ parseEngineHandle({ sessionRef: { sessionId: "s", n: 1 }, poolKey: "shared" }),
100
+ ).toBeUndefined();
101
+ });
102
+
103
+ it("journalPath 空串视为缺省", () => {
104
+ expect(
105
+ parseEngineHandle({ sessionRef: {}, poolKey: "shared", journalPath: "" }),
106
+ ).toEqual({ sessionRef: {}, poolKey: "shared" });
107
+ });
108
+ });
109
+
110
+ describe("extractEngineId", () => {
111
+ it("非 string / 空串 → 缺省 pi(存量 record 零迁移);非空透传", () => {
112
+ expect(extractEngineId(makeRecord({ engine: undefined }))).toBe("pi");
113
+ expect(extractEngineId(makeRecord({ engine: 1 }))).toBe("pi");
114
+ expect(extractEngineId(makeRecord({ engine: "" }))).toBe("pi");
115
+ expect(extractEngineId(makeRecord({ engine: "zcode" }))).toBe("zcode");
116
+ });
117
+
118
+ it("缺省引擎 id 与 registry 的 DEFAULT_ENGINE_ID 同值(本地锚定防漂移守护)", async () => {
119
+ const { DEFAULT_ENGINE_ID } = await import("../../registry.ts");
120
+ expect(extractEngineId(makeRecord({ engine: undefined }))).toBe(DEFAULT_ENGINE_ID);
121
+ });
122
+ });
123
+
124
+ // ============================================================
125
+ // 三级降级编排
126
+ // ============================================================
127
+
128
+ describe("降级链编排", () => {
129
+ it("pi 引擎返回 [](A1 守护:pi 历史走调用方 JSONL 直读链)", async () => {
130
+ expect(await readSubagentHistoryMessages(makeRecord({ engine: undefined }), dataDir)).toEqual(
131
+ [],
132
+ );
133
+ expect(await readSubagentHistoryMessages(makeRecord({ engine: "pi" }), dataDir)).toEqual([]);
134
+ });
135
+
136
+ it("①级命中:registry 查表分发到 native reader,SessionView 投影(usage 挂末 turn)", async () => {
137
+ const view: SessionView = {
138
+ engineId: "zcode",
139
+ sessionId: "sess-1",
140
+ turns: [
141
+ { text: "answer", thinking: "hmm", toolCalls: [], closed: true },
142
+ { text: "more", thinking: "", toolCalls: [], closed: true },
143
+ ],
144
+ usage: { input: 7, output: 3, cacheRead: 0, cacheWrite: 0, cost: 0, total: 10 },
145
+ source: "native",
146
+ };
147
+ registerNativeSessionReader("zcode", async () => view);
148
+ const messages = await readSubagentHistoryMessages(makeRecord(), dataDir);
149
+ expect(messages).toHaveLength(3);
150
+ expect(messages[0]).toMatchObject({ role: "user", content: "do the thing", timestamp: 1_000 });
151
+ expect(messages[1]).toMatchObject({
152
+ role: "assistant",
153
+ content: "answer",
154
+ thinking: [{ content: "hmm", collapsed: true }],
155
+ timestamp: 1_001,
156
+ });
157
+ // SessionView.usage 是聚合(无 per-turn 拆分)——挂最后一个 turn 供 GUI 展示
158
+ expect(messages[2]).toMatchObject({
159
+ role: "assistant",
160
+ content: "more",
161
+ usage: { inputTokens: 7, outputTokens: 3 },
162
+ timestamp: 1_002,
163
+ });
164
+ });
165
+
166
+ it("①级 db 不存在(真 zcode reader throw)→ ②级 journal 命中", async () => {
167
+ const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
168
+ await writeJournal(journalPath, [
169
+ { type: "text_delta", delta: "part one. " },
170
+ { type: "text_delta", delta: "part two." },
171
+ { type: "message_end", usage: { input: 10, output: 5, cacheRead: 0, cacheWrite: 0 } },
172
+ { type: "turn_end" },
173
+ ]);
174
+ const record = makeRecord({
175
+ engineHandle: {
176
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
177
+ poolKey: "shared",
178
+ journalPath,
179
+ },
180
+ });
181
+ const messages = await readSubagentHistoryMessages(record, dataDir);
182
+ expect(messages).toHaveLength(2);
183
+ expect(messages[1]).toMatchObject({
184
+ role: "assistant",
185
+ content: "part one. part two.",
186
+ usage: { inputTokens: 10, outputTokens: 5 },
187
+ status: "complete",
188
+ timestamp: 1_001,
189
+ });
190
+ });
191
+
192
+ it("②级也不可达(无 journalPath)→ ③级 outcome-only,永不返回空数组", async () => {
193
+ // 真 zcode reader:dbPath 指向不存在文件 → ①级 throw 降级;handle 无 journalPath → ③级
194
+ const messages = await readSubagentHistoryMessages(
195
+ makeRecord({ result: "final answer", endedAt: 2_000 }),
196
+ dataDir,
197
+ );
198
+ expect(messages).toHaveLength(2);
199
+ expect(messages[0]).toMatchObject({ role: "user", content: "do the thing" });
200
+ expect(messages[1]).toMatchObject({
201
+ role: "assistant",
202
+ content: "final answer",
203
+ status: "complete",
204
+ timestamp: 2_000,
205
+ });
206
+ });
207
+
208
+ it("③级 error 终态:result 缺 error 在 → status=error + error 字段;双缺占位", async () => {
209
+ const failed = await readSubagentHistoryMessages(
210
+ makeRecord({ error: "engine_run_failed: boom", endedAt: 2_000 }),
211
+ dataDir,
212
+ );
213
+ expect(failed[1]).toMatchObject({
214
+ content: "engine_run_failed: boom",
215
+ status: "error",
216
+ error: "engine_run_failed: boom",
217
+ timestamp: 2_000,
218
+ });
219
+ const noOutcome = await readSubagentHistoryMessages(makeRecord(), dataDir);
220
+ expect(noOutcome[1]).toMatchObject({ content: "(no outcome recorded)", status: "complete" });
221
+ });
222
+
223
+ it("handle 缺失 → ③级(空值防御,不依赖写侧完成时序)", async () => {
224
+ const messages = await readSubagentHistoryMessages(
225
+ makeRecord({ engineHandle: undefined, result: "r" }),
226
+ dataDir,
227
+ );
228
+ expect(messages).toHaveLength(2);
229
+ expect(messages[1]).toMatchObject({ content: "r" });
230
+ });
231
+ });
232
+
233
+ // ============================================================
234
+ // registry 分发(取代引擎 id 硬编码)
235
+ // ============================================================
236
+
237
+ describe("reader registry 分发", () => {
238
+ it("未注册引擎(未来)→ ③级保底(详情页至少有摘要卡)", async () => {
239
+ const messages = await readSubagentHistoryMessages(
240
+ makeRecord({ engine: "kimi", result: "r" }),
241
+ dataDir,
242
+ );
243
+ expect(messages).toHaveLength(2);
244
+ expect(messages[1]).toMatchObject({ content: "r" });
245
+ });
246
+
247
+ it("新引擎注册 reader 后自动获得①级(接入第三引擎 runtime 零改动)", async () => {
248
+ registerNativeSessionReader("kimi", async () => ({
249
+ engineId: "kimi",
250
+ turns: [{ text: "kimi says", thinking: "", toolCalls: [], closed: true }],
251
+ source: "native",
252
+ }));
253
+ const messages = await readSubagentHistoryMessages(
254
+ makeRecord({ engine: "kimi" }),
255
+ dataDir,
256
+ );
257
+ expect(messages[1]).toMatchObject({ role: "assistant", content: "kimi says" });
258
+ });
259
+
260
+ it("同 id 覆盖注册(幂等,测试注入 fake 的机制面)", async () => {
261
+ const view: SessionView = {
262
+ engineId: "zcode",
263
+ turns: [{ text: "overridden", thinking: "", toolCalls: [], closed: true }],
264
+ source: "native",
265
+ };
266
+ registerNativeSessionReader("zcode", async () => view);
267
+ const messages = await readSubagentHistoryMessages(makeRecord(), dataDir);
268
+ expect(messages[1]).toMatchObject({ content: "overridden" });
269
+ });
270
+ });
271
+
272
+ // ============================================================
273
+ // 投影 parity(zcode 现役 journal 形态,新旧链等价)
274
+ // ============================================================
275
+
276
+ describe("②级重放投影 parity(基准 = 收敛前 runtime 手写链)", () => {
277
+ /** 走完整②级链(真 zcode reader ①级 miss → journal 重放)。 */
278
+ async function replayViaJournal(
279
+ events: AgentEvent[],
280
+ recordOverrides: Partial<SubagentRecordSnapshot> = {},
281
+ ): Promise<Awaited<ReturnType<typeof readSubagentHistoryMessages>>> {
282
+ const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
283
+ await writeJournal(journalPath, events);
284
+ const record = makeRecord({
285
+ ...recordOverrides,
286
+ engineHandle: {
287
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
288
+ poolKey: "shared",
289
+ journalPath,
290
+ },
291
+ });
292
+ return readSubagentHistoryMessages(record, dataDir);
293
+ }
294
+
295
+ it("成功 journal(coarse 两事件:message_end+turn_end)→ 无 assistant 内容,降③级 outcome-only", async () => {
296
+ // 旧链推演:applyMessageEnd 无未闭合 turn → usage 丢弃;sawAssistantContent=false → ③级
297
+ const messages = await replayViaJournal(
298
+ [
299
+ { type: "message_end", usage: { input: 12_599, output: 17, cacheRead: 512, cacheWrite: 0 } },
300
+ { type: "turn_end" },
301
+ ],
302
+ { result: "final answer" },
303
+ );
304
+ expect(messages).toHaveLength(2);
305
+ expect(messages[0]).toMatchObject({ role: "user", content: "do the thing", timestamp: 1_000 });
306
+ expect(messages[1]).toMatchObject({
307
+ role: "assistant",
308
+ // ③级:result 优先(record 字段),journal 的 coarse usage 不进消息
309
+ content: "final answer",
310
+ status: "complete",
311
+ });
312
+ });
313
+
314
+ it("失败 journal([error] 单事件)→ 物化 assistant(error 文本 + complete + ts=base+1)", async () => {
315
+ const messages = await replayViaJournal([
316
+ { type: "error", message: "engine_run_failed: zcode exited 1" },
317
+ ]);
318
+ expect(messages).toHaveLength(2);
319
+ expect(messages[1]).toMatchObject({
320
+ role: "assistant",
321
+ content: "engine_run_failed: zcode exited 1",
322
+ status: "complete",
323
+ timestamp: 1_001,
324
+ });
325
+ });
326
+
327
+ it("失败 journal([error, message_end(usage), turn_end] schema 形态)→ 物化 assistant 带聚合 usage", async () => {
328
+ const messages = await replayViaJournal([
329
+ { type: "error", message: "schema_emulation_failed: two rounds failed" },
330
+ { type: "message_end", usage: { input: 100, output: 20, cacheRead: 0, cacheWrite: 0 } },
331
+ { type: "turn_end" },
332
+ ]);
333
+ expect(messages).toHaveLength(2);
334
+ expect(messages[1]).toMatchObject({
335
+ role: "assistant",
336
+ content: "schema_emulation_failed: two rounds failed",
337
+ status: "complete",
338
+ usage: { inputTokens: 100, outputTokens: 20 },
339
+ timestamp: 1_001,
340
+ });
341
+ });
342
+
343
+ it("流式形态(deltas + tool 配对 + turn 边界)→ per-turn assistant + per-turn usage", async () => {
344
+ const messages = await replayViaJournal([
345
+ { type: "text_delta", delta: "part one. " },
346
+ { type: "thinking_delta", delta: "hmm" },
347
+ { type: "tool_start", toolName: "bash", args: { cmd: "ls" } },
348
+ { type: "tool_end", toolName: "bash", result: { content: ["done"] } },
349
+ { type: "message_end", usage: { input: 10, output: 5, cacheRead: 0, cacheWrite: 0 } },
350
+ { type: "turn_end" },
351
+ { type: "text_delta", delta: "second turn" },
352
+ { type: "turn_end" },
353
+ ]);
354
+ expect(messages).toHaveLength(3);
355
+ const first = messages[1];
356
+ expect(first).toMatchObject({
357
+ role: "assistant",
358
+ content: "part one. ",
359
+ thinking: [{ content: "hmm", collapsed: true }],
360
+ usage: { inputTokens: 10, outputTokens: 5 },
361
+ timestamp: 1_001,
362
+ });
363
+ expect(first?.toolCalls).toHaveLength(1);
364
+ expect(first?.toolCalls?.[0]).toMatchObject({
365
+ toolName: "bash",
366
+ input: { cmd: "ls" },
367
+ output: "done",
368
+ status: "completed",
369
+ startTime: 1_001,
370
+ endTime: 1_001,
371
+ });
372
+ expect(first?.toolCalls?.[0]?.id).toMatch(UUID_RE);
373
+ expect(messages[2]).toMatchObject({
374
+ role: "assistant",
375
+ content: "second turn",
376
+ timestamp: 1_002,
377
+ });
378
+ });
379
+
380
+ it("tool_end isError → status=error;result.details 透传;args 缺省 {}", async () => {
381
+ const messages = await replayViaJournal([
382
+ { type: "tool_start", toolName: "read" },
383
+ {
384
+ type: "tool_end",
385
+ toolName: "read",
386
+ result: { content: ["err text"], details: { path: "/x" } },
387
+ isError: true,
388
+ },
389
+ { type: "turn_end" },
390
+ ]);
391
+ const tc = messages[1]?.toolCalls?.[0];
392
+ expect(tc).toMatchObject({
393
+ toolName: "read",
394
+ input: {},
395
+ output: "err text",
396
+ details: { path: "/x" },
397
+ status: "error",
398
+ });
399
+ });
400
+
401
+ it("journal 白名单:journalPath 越界(engines 根外)→ 拒绝并降③级", async () => {
402
+ const outsidePath = join(tmpdir(), "outside-journal-sub-1.jsonl");
403
+ await writeJournal(outsidePath, [{ type: "text_delta", delta: "should not appear" }]);
404
+ const record = makeRecord({
405
+ result: "fallback",
406
+ engineHandle: {
407
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
408
+ poolKey: "shared",
409
+ journalPath: outsidePath,
410
+ },
411
+ });
412
+ const messages = await readSubagentHistoryMessages(record, dataDir);
413
+ expect(messages[1]).toMatchObject({ content: "fallback" });
414
+ });
415
+
416
+ it("空 journal(无事件)→ ③级", async () => {
417
+ const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
418
+ await writeJournal(journalPath, []);
419
+ const record = makeRecord({
420
+ result: "r",
421
+ engineHandle: {
422
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
423
+ poolKey: "shared",
424
+ journalPath,
425
+ },
426
+ });
427
+ const messages = await readSubagentHistoryMessages(record, dataDir);
428
+ expect(messages[1]).toMatchObject({ content: "r" });
429
+ });
430
+ });
431
+
432
+ // ============================================================
433
+ // message_end error 记账(门②补录样本——守护实现的回归校验)
434
+ // ============================================================
435
+
436
+ describe("message_end 携带 error 的记账语义(现役 zcode 不产出,回归校验)", () => {
437
+ it("有内容 + message_end(error) 且无 turn_end 清除 → 末条 assistant 记账 status=error", async () => {
438
+ const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
439
+ await writeJournal(journalPath, [
440
+ { type: "text_delta", delta: "partial work" },
441
+ { type: "message_end", error: "provider error" },
442
+ ]);
443
+ const record = makeRecord({
444
+ engineHandle: {
445
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
446
+ poolKey: "shared",
447
+ journalPath,
448
+ },
449
+ });
450
+ const messages = await readSubagentHistoryMessages(record, dataDir);
451
+ expect(messages[1]).toMatchObject({
452
+ role: "assistant",
453
+ content: "partial work",
454
+ status: "error",
455
+ error: "provider error",
456
+ });
457
+ });
458
+
459
+ it("无内容 + message_end(error) → 物化单条(记账文本可见,不降③级)", async () => {
460
+ const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
461
+ await writeJournal(journalPath, [{ type: "message_end", error: "provider error" }]);
462
+ const record = makeRecord({
463
+ engineHandle: {
464
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
465
+ poolKey: "shared",
466
+ journalPath,
467
+ },
468
+ });
469
+ const messages = await readSubagentHistoryMessages(record, dataDir);
470
+ expect(messages).toHaveLength(2);
471
+ expect(messages[1]).toMatchObject({
472
+ role: "assistant",
473
+ content: "provider error",
474
+ timestamp: 1_001,
475
+ });
476
+ });
477
+
478
+ it("turn_end 在 message_end(error) 之后 → 瞬态清除(重放不残留记账)", async () => {
479
+ const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
480
+ await writeJournal(journalPath, [
481
+ { type: "text_delta", delta: "recovered" },
482
+ { type: "message_end", error: "transient" },
483
+ { type: "turn_end" },
484
+ { type: "text_delta", delta: " ok" },
485
+ { type: "turn_end" },
486
+ ]);
487
+ const record = makeRecord({
488
+ engineHandle: {
489
+ sessionRef: { sessionId: "sess-1", dbPath: ".zcode/cli/db/db.sqlite" },
490
+ poolKey: "shared",
491
+ journalPath,
492
+ },
493
+ });
494
+ const messages = await readSubagentHistoryMessages(record, dataDir);
495
+ // turn_end 清 lastError(瞬态恢复语义,C5 reducer 守护行为)——投影无 error 记账
496
+ expect(messages[1]?.status).toBe("complete");
497
+ expect(messages[1]?.error).toBeUndefined();
498
+ });
499
+ });