@zhushanwen/subagent-core 0.5.1 → 0.7.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 (318) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-KXVBIVOU.js +16 -0
  3. package/dist/chunk-T2SYBW3I.js +24 -0
  4. package/dist/chunk-VURUAGMM.js +2305 -0
  5. package/dist/{types-Dv4QhSJ_.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +835 -668
  6. package/dist/{types-Dv4QhSJ_.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +835 -668
  7. package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
  8. package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
  9. package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
  10. package/dist/execution/engine/engine-discovery-scan.js +20 -0
  11. package/dist/execution/engine/paths.cjs +6 -23
  12. package/dist/execution/engine/paths.d.cts +1 -8
  13. package/dist/execution/engine/paths.d.ts +1 -8
  14. package/dist/execution/engine/paths.js +1 -1
  15. package/dist/execution/relay-env.cjs +12 -28
  16. package/dist/execution/relay-env.d.cts +1 -33
  17. package/dist/execution/relay-env.d.ts +1 -33
  18. package/dist/execution/relay-env.js +5 -3
  19. package/dist/index.cjs +14123 -15467
  20. package/dist/index.d.cts +1179 -1540
  21. package/dist/index.d.ts +1179 -1540
  22. package/dist/index.js +7694 -10986
  23. package/dist.bundle/index.cjs +33485 -0
  24. package/package.json +20 -26
  25. package/src/__tests__/record-store-last-line.test.ts +16 -7
  26. package/src/core/error-message.ts +7 -6
  27. package/src/core/host-services.ts +8 -2
  28. package/src/core/notify-ports.ts +17 -2
  29. package/src/execution/__tests__/agent-registry.test.ts +1 -1
  30. package/src/execution/__tests__/alive-store.test.ts +1 -1
  31. package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
  32. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
  33. package/src/execution/__tests__/collect-budget.test.ts +291 -0
  34. package/src/execution/__tests__/collect-coordinator-service.test.ts +316 -0
  35. package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
  36. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +229 -0
  37. package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
  38. package/src/execution/__tests__/config.test.ts +1 -1
  39. package/src/execution/__tests__/delivery-methods.test.ts +179 -237
  40. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
  41. package/src/execution/__tests__/execute-nesting.test.ts +20 -13
  42. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +21 -34
  43. package/src/execution/__tests__/finalized-marker.test.ts +1 -1
  44. package/src/execution/__tests__/gc-timer.test.ts +8 -23
  45. package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
  46. package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
  47. package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
  48. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
  49. package/src/execution/__tests__/idle-gc.test.ts +200 -0
  50. package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
  51. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
  52. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
  53. package/src/execution/__tests__/notify-batch.test.ts +500 -0
  54. package/src/execution/__tests__/notify-ledger.test.ts +27 -0
  55. package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
  56. package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
  57. package/src/execution/__tests__/record-store.test.ts +60 -6
  58. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
  59. package/src/execution/__tests__/relay-env.test.ts +12 -1
  60. package/src/execution/__tests__/round-supervisor.test.ts +293 -0
  61. package/src/execution/__tests__/session-file-gc.test.ts +1 -1
  62. package/src/execution/__tests__/session-pending.test.ts +119 -17
  63. package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
  64. package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
  65. package/src/execution/__tests__/subagent-actions-core.test.ts +6 -0
  66. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +14 -11
  67. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
  68. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
  69. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
  70. package/src/execution/__tests__/subagent-service.test.ts +2 -2
  71. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
  72. package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
  73. package/src/execution/__tests__/sync-collect-recovery.test.ts +1102 -0
  74. package/src/execution/__tests__/tombstone-store.test.ts +1 -1
  75. package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
  76. package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
  77. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
  78. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
  79. package/src/execution/__tests__/worktree-registry.test.ts +1 -1
  80. package/src/execution/agent-registry.ts +6 -2
  81. package/src/execution/collect-coordinator.ts +200 -0
  82. package/src/execution/config.ts +77 -5
  83. package/src/execution/dialog-queue.ts +6 -73
  84. package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
  85. package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
  86. package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
  87. package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
  88. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
  89. package/src/execution/engine/__tests__/common/pool-manager.test.ts +2 -2
  90. package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
  91. package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
  92. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
  93. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
  94. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
  95. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
  96. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
  97. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
  98. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
  99. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +82 -55
  100. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
  101. package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
  102. package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
  103. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +8 -3
  104. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
  105. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
  106. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
  107. package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
  108. package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
  109. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
  110. package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
  111. package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
  112. package/src/execution/engine/__tests__/registry.test.ts +206 -1
  113. package/src/execution/engine/__tests__/routing.test.ts +97 -0
  114. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
  115. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
  116. package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
  117. package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
  118. package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
  119. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
  120. package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
  121. package/src/execution/engine/client/client-options.ts +103 -0
  122. package/src/execution/engine/client/engine-client.ts +687 -0
  123. package/src/execution/engine/client/mirror.ts +167 -0
  124. package/src/execution/engine/client/pid-file.ts +392 -0
  125. package/src/execution/engine/client/reaper.ts +95 -0
  126. package/src/execution/engine/client/remote-engine.ts +441 -0
  127. package/src/execution/engine/client/reverse-router.ts +255 -0
  128. package/src/execution/engine/common/capability-gate.ts +109 -22
  129. package/src/execution/engine/common/errors.ts +9 -25
  130. package/src/execution/engine/common/event-journal.ts +4 -50
  131. package/src/execution/engine/common/journal-replay.ts +3 -1
  132. package/src/execution/engine/common/kill-chain.ts +18 -251
  133. package/src/execution/engine/common/nesting-guard.ts +18 -114
  134. package/src/execution/engine/common/pool-manager.ts +4 -0
  135. package/src/execution/engine/common/session-view-service.ts +56 -84
  136. package/src/execution/engine/config.ts +151 -0
  137. package/src/execution/engine/d8-compat.ts +346 -0
  138. package/src/execution/engine/engine-discovery-roots.ts +107 -0
  139. package/src/execution/engine/engine-discovery-scan.ts +269 -0
  140. package/src/execution/engine/engine-discovery.ts +30 -7
  141. package/src/execution/engine/engine-inspect-package.ts +282 -0
  142. package/src/execution/engine/engine-manifest.ts +244 -0
  143. package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
  144. package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
  145. package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
  146. package/src/execution/engine/host/host-bridge.ts +150 -0
  147. package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
  148. package/src/execution/engine/host/pi-host-binding.ts +91 -0
  149. package/src/execution/engine/host/spawned-children.ts +139 -0
  150. package/src/execution/engine/host-task-spec.ts +29 -2
  151. package/src/execution/engine/model-validation.ts +55 -2
  152. package/src/execution/engine/paths.ts +16 -41
  153. package/src/execution/engine/port.ts +48 -2
  154. package/src/execution/engine/registry.ts +153 -34
  155. package/src/execution/engine/routing.ts +225 -52
  156. package/src/execution/engine/types.ts +25 -200
  157. package/src/execution/execution-record.ts +7 -0
  158. package/src/execution/finalize-record.ts +40 -11
  159. package/src/execution/idle-gc.ts +89 -14
  160. package/src/execution/lifecycle-manager.ts +10 -7
  161. package/src/execution/lifecycle-predicates.ts +10 -8
  162. package/src/execution/notifier.ts +234 -25
  163. package/src/execution/notify-host.ts +21 -1
  164. package/src/execution/record-entry.ts +16 -0
  165. package/src/execution/record-store.ts +189 -31
  166. package/src/execution/relay-env.ts +19 -36
  167. package/src/execution/round-supervisor/domain.ts +89 -0
  168. package/src/execution/round-supervisor/index.ts +33 -0
  169. package/src/execution/round-supervisor/notify-accounting.ts +87 -0
  170. package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
  171. package/src/execution/round-supervisor/service-binding.test.ts +661 -0
  172. package/src/execution/round-supervisor/service-binding.ts +273 -0
  173. package/src/execution/round-supervisor/supervisor.ts +426 -0
  174. package/src/execution/session-pending.ts +68 -74
  175. package/src/execution/settled-watchdog.ts +39 -0
  176. package/src/execution/subagent-actions-core.ts +62 -6
  177. package/src/execution/subagent-service.ts +1161 -406
  178. package/src/execution/subprocess-agent-runner.ts +16 -8
  179. package/src/execution/sync-rebuild.ts +87 -0
  180. package/src/execution/types.ts +104 -117
  181. package/src/execution/ui-channels.ts +13 -214
  182. package/src/execution/ui-request-handler-factory.ts +7 -4
  183. package/src/execution/ui-request-observability.ts +2 -2
  184. package/src/execution/workflow-state-root.ts +67 -0
  185. package/src/execution/worktree-git-ops.ts +5 -0
  186. package/src/execution/worktree-manager.ts +11 -1
  187. package/src/index.ts +55 -26
  188. package/src/orchestration/__tests__/config-loader.test.ts +1 -1
  189. package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
  190. package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
  191. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
  192. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
  193. package/src/orchestration/__tests__/script-generate.test.ts +5 -5
  194. package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
  195. package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
  196. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
  197. package/src/orchestration/execute-agent-call.ts +1 -1
  198. package/src/orchestration/file-run-store.ts +144 -59
  199. package/src/orchestration/models/types.ts +43 -42
  200. package/src/shared/__tests__/atomic-write.test.ts +1 -1
  201. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
  202. package/src/shared/zcode-model-ref.ts +31 -0
  203. package/dist/chunk-43ONBFZX.js +0 -240
  204. package/dist/chunk-A4IVZWVX.js +0 -31
  205. package/dist/chunk-APZY4IME.js +0 -27
  206. package/dist/chunk-V3VHZ2VX.js +0 -59
  207. package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
  208. package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
  209. package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
  210. package/dist/execution/engine/engines/zcode/constants.js +0 -54
  211. package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
  212. package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
  213. package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
  214. package/dist/execution/engine/engines/zcode/reader.js +0 -9
  215. package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
  216. package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
  217. package/src/__tests__/session-runner.test.ts +0 -69
  218. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  219. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
  220. package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
  221. package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
  222. package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
  223. package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
  224. package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
  225. package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
  226. package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
  227. package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
  228. package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
  229. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
  230. package/src/execution/__tests__/output-collector.test.ts +0 -469
  231. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  232. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
  233. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  234. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
  235. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
  236. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
  237. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
  238. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  239. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  240. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  241. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
  242. package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
  243. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
  244. package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
  245. package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
  246. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
  247. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
  248. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
  249. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
  250. package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
  251. package/src/execution/__tests__/spawn-args.test.ts +0 -446
  252. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
  253. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  254. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
  255. package/src/execution/__tests__/spawned-children.test.ts +0 -236
  256. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  257. package/src/execution/__tests__/stdin-writer.test.ts +0 -464
  258. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
  259. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  260. package/src/execution/__tests__/timeout-integration.test.ts +0 -616
  261. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  262. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  263. package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
  264. package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
  265. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
  266. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  267. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
  268. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  269. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
  270. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  271. package/src/execution/engine/common/schema-emulation.ts +0 -189
  272. package/src/execution/engine/common/session-view-projection.ts +0 -51
  273. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
  274. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  275. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
  276. package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
  277. package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
  278. package/src/execution/engine/engines/pi/output-collector.ts +0 -317
  279. package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
  280. package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
  281. package/src/execution/engine/engines/pi/reader.ts +0 -59
  282. package/src/execution/engine/engines/pi/registration.ts +0 -37
  283. package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
  284. package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
  285. package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
  286. package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
  287. package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
  288. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
  289. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
  290. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
  291. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
  292. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
  293. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
  294. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  295. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
  296. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
  297. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
  298. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
  299. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
  300. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
  301. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
  302. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
  303. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
  304. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
  305. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
  306. package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
  307. package/src/execution/engine/engines/zcode/connection.ts +0 -598
  308. package/src/execution/engine/engines/zcode/constants.ts +0 -176
  309. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
  310. package/src/execution/engine/engines/zcode/parser.ts +0 -102
  311. package/src/execution/engine/engines/zcode/preparer.ts +0 -235
  312. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  313. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  314. package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
  315. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
  316. package/src/execution/round-settlement.ts +0 -93
  317. package/src/execution/ui-request-queue.ts +0 -215
  318. package/src/shared/schema-env.ts +0 -44
@@ -0,0 +1,255 @@
1
+ // src/execution/__tests__/chat-round-first-round-watchdog.test.ts
2
+ //
3
+ // [F-2 修复] 首轮/冷续轮 settled-watchdog arm 重接 + [F-5 修复] chat 轮路由终态化注销。
4
+ //
5
+ // 背景:armMidRoundNoProgress 此前唯一调用点在 deliverChatMessage(热路径续聊轮);
6
+ // kickOffChatRound(spawn 首轮 + 冷续 resume 轮)内无任何 arm——被删的 inproc
7
+ // stdout-pump 是首轮唯一中段守护,协议化后引擎侧 spawn-runner 仅 turn 计数无墙钟,
8
+ // 首轮 wedged(LC-1 场景①:无事件行输出)无任何熔断。修复 = run 派发前补 arm
9
+ //(refresh 源 = runId 键 ctx.onEvent 事件行 + ctx.onRoundLifecycle settled 交棒——
10
+ // 引擎侧 spawn-runner 对 text_delta 同时走 onEvent 与 onDelta,刷新面与热路径等效)。
11
+ //
12
+ // [F-5] chat 轮路由(chatRoundRoutes)此前仅 cancelBackground 注销;finalize 链终态化
13
+ // (closeChatIdle / closeAfterRoundSettled / finalizeRecord)不注销 → 路由闭包持
14
+ // record/stream/守护引用泄漏。修复 = doFinalizeRecord 的 onFinalized 钩子单一汇聚点。
15
+ //
16
+ // 替身形态与 subagent-service-recovery-bounds.test.ts 同源(fake-engine-port 协议 seam)。
17
+
18
+ import * as fs from "node:fs";
19
+ import * as os from "node:os";
20
+ import * as path from "node:path";
21
+
22
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
+
24
+ const { loggerMock } = vi.hoisted(() => ({
25
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
26
+ }));
27
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
28
+
29
+ const { killChildSpy } = vi.hoisted(() => ({ killChildSpy: vi.fn() }));
30
+ vi.mock("../engine/host/spawned-children.ts", async (importOriginal) => {
31
+ const actual = await importOriginal<typeof import("../engine/host/spawned-children.ts")>();
32
+ return { ...actual, killRecordChildWithEscalation: killChildSpy };
33
+ });
34
+
35
+ import { clearEngines } from "../engine/registry.ts";
36
+ import { registerFakePiEngine, type FakePiEnginePort } from "./helpers/fake-engine-port.ts";
37
+ import { createRecord } from "../execution-record.ts";
38
+ import { ModelConfigService } from "../model-config-service.ts";
39
+ import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
40
+ import type { RecordStore } from "../record-store.ts";
41
+ import { SubagentService } from "../subagent-service.ts";
42
+ import type { PiLike } from "../subagent-service.ts";
43
+ import {
44
+ getSettledWatchdogPhase,
45
+ hasSettledWatchdog,
46
+ SETTLED_MID_ROUND_NO_PROGRESS_MS,
47
+ SETTLED_WATCHDOG_TIMEOUT_MS,
48
+ _resetSettledWatchdogsForTest,
49
+ } from "../settled-watchdog.ts";
50
+ import { _resetLifecycleState } from "../lifecycle-manager.ts";
51
+ import { _resetCoreSpawnedChildrenMirrorForTest } from "../engine/host/spawned-children.ts";
52
+ import type { ExecutionRecord } from "../types.ts";
53
+
54
+ function makePi(): PiLike {
55
+ return {
56
+ appendEntry: vi.fn(),
57
+ events: { emit: vi.fn() },
58
+ sendMessage: vi.fn(),
59
+ } as unknown as PiLike;
60
+ }
61
+
62
+ interface ServiceInternals {
63
+ store: RecordStore;
64
+ }
65
+
66
+ const CTX_MODEL: ModelInfo = { id: "m", name: "M", provider: "p", reasoning: false };
67
+
68
+ function makeEmptyRegistry(): ModelRegistryLike {
69
+ return { getAvailable: () => [], find: () => undefined, hasConfiguredAuth: () => true };
70
+ }
71
+
72
+ function setup(): { agentDir: string; service: SubagentService; store: RecordStore; pi: PiLike; fake: FakePiEnginePort } {
73
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "chat-round-watchdog-"));
74
+ clearEngines();
75
+ const fake = registerFakePiEngine();
76
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
77
+ modelService.initModel({ modelRegistry: makeEmptyRegistry(), sessionId: "root-session", ctxModel: CTX_MODEL });
78
+ const service = new SubagentService({ cwd: agentDir, modelService });
79
+ const pi = makePi();
80
+ service.initSession({ pi, sessionId: "root-session" });
81
+ const store = (service as unknown as ServiceInternals).store;
82
+ return { agentDir, service, store, pi, fake };
83
+ }
84
+
85
+ /** 模拟 kickOffChatRound 的路由注册面(Service map + fake 引擎两端同挂)。 */
86
+ function registerRoute(service: SubagentService, fake: FakePiEnginePort, recordId: string): void {
87
+ const unregister = fake.registerChatRoundRoute(recordId, {
88
+ onStreamDelta: () => {},
89
+ onRoundLifecycle: () => {},
90
+ });
91
+ (
92
+ service as unknown as { chatRoundRoutes: Map<string, () => void> }
93
+ ).chatRoundRoutes.set(recordId, unregister);
94
+ }
95
+
96
+ function makeChatRecord(id: string): ExecutionRecord {
97
+ return createRecord(id, {
98
+ agent: "general-purpose",
99
+ model: "test/model",
100
+ mode: "background",
101
+ task: "test",
102
+ slug: "test",
103
+ startedAt: 1000,
104
+ rootSessionId: "root-session",
105
+ chatMode: true,
106
+ controller: new AbortController(),
107
+ });
108
+ }
109
+
110
+ describe("[F-2] 首轮/冷续轮 settled-watchdog arm(kickOffChatRound run 派发点)", () => {
111
+ let agentDir: string;
112
+ let service: SubagentService;
113
+ let store: RecordStore;
114
+ let pi: PiLike;
115
+ let fake: FakePiEnginePort;
116
+
117
+ beforeEach(() => {
118
+ vi.restoreAllMocks();
119
+ killChildSpy.mockClear();
120
+ ({ agentDir, service, store, pi, fake } = setup());
121
+ });
122
+
123
+ afterEach(() => {
124
+ service.dispose();
125
+ clearEngines();
126
+ vi.useRealTimers();
127
+ _resetLifecycleState();
128
+ _resetSettledWatchdogsForTest();
129
+ _resetCoreSpawnedChildrenMirrorForTest();
130
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
131
+ });
132
+
133
+ it("首轮 run 派发后中段守护 armed(修复前首轮无任何守护)", async () => {
134
+ const handle = await service.execute({ task: "first round", slug: "t", ctxModel: CTX_MODEL, conversation: true });
135
+ expect(fake.runs).toHaveLength(1);
136
+ expect(hasSettledWatchdog(handle.subagentId)).toBe(true);
137
+ expect(getSettledWatchdogPhase(handle.subagentId)).toBe("mid-round");
138
+ });
139
+
140
+ it("冷续轮进行中无产出 → 中段守护按既有超时语义触发:kill + 引擎 cancel + 回退 idle-resumable(fake timers)", async () => {
141
+ // fake timers 必须先于 arm 生效(useFakeTimers 不接管已存在的真实 timer)
142
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
143
+ const record = makeChatRecord("sa-first");
144
+ record.sessionFile = path.join(agentDir, "prior-session.jsonl");
145
+ fs.writeFileSync(record.sessionFile, "{}\n", "utf-8");
146
+ store.register(record);
147
+ await (
148
+ service as unknown as { resumeColdRound: (r: ExecutionRecord, t: string) => void }
149
+ ).resumeColdRound(record, "cold resume text");
150
+ expect(fake.runs).toHaveLength(1); // 冷续轮 run 已派发
151
+ expect(hasSettledWatchdog(record.id)).toBe(true);
152
+
153
+ // 冷续轮全程静默(无协议事件行)满中段窗长
154
+ await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS + 1);
155
+
156
+ expect(killChildSpy).toHaveBeenCalledWith(record.id, "settled watchdog (hot path)");
157
+ expect(hasSettledWatchdog(record.id)).toBe(false); // 到期自清
158
+ // chatMode MF-6:回退 idle-resumable(不销毁对话)
159
+ expect(record.resumable).toBe(true);
160
+ expect(record.status).toBe("running");
161
+ expect(record.result).toContain("settled watchdog");
162
+ expect(record.result).toContain("Recovery");
163
+ // 引擎侧终止意图(协议 interact cancel)
164
+ const cancel = fake.interacts.find((c) => c.action.kind === "cancel");
165
+ expect(cancel).toBeDefined();
166
+ void pi;
167
+ });
168
+
169
+ it("中段事件行(text_delta 经 onEvent)刷新静默计时:半窗 + 刷新 + 半窗不触发,再满窗才触发", async () => {
170
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
171
+ const handle = await service.execute({ task: "streaming round", slug: "t", ctxModel: CTX_MODEL, conversation: true });
172
+ const run = fake.runs[0]!;
173
+
174
+ await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS / 2);
175
+ // 首轮 runId 键协议事件行到达(text_delta——引擎侧同时走 onEvent 通道)
176
+ run.emitEvent({ type: "text_delta", delta: "chunk" });
177
+ await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS / 2 + 1);
178
+ expect(hasSettledWatchdog(handle.subagentId)).toBe(true); // 刷新后未到期
179
+ expect(killChildSpy).not.toHaveBeenCalled();
180
+
181
+ // 刷新后再满整个静默窗才触发
182
+ await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS + 1);
183
+ expect(killChildSpy).toHaveBeenCalledWith(handle.subagentId, "settled watchdog (hot path)");
184
+ });
185
+
186
+ it("首轮 settled 相位交棒:中段窗内相位到达 → 切收尾段(中段窗不再触发,收尾段硬顶触发)", async () => {
187
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
188
+ const handle = await service.execute({ task: "stuck after settle", slug: "t", ctxModel: CTX_MODEL, conversation: true });
189
+ const run = fake.runs[0]!;
190
+
191
+ await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS / 2);
192
+ // 首轮 runId 键 roundLifecycle settled 相位(W3 报告称已接——本用例核实链路生效)
193
+ run.emitLifecycle({ phase: "settled", usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 } });
194
+ expect(getSettledWatchdogPhase(handle.subagentId)).toBe("settled"); // 已交棒收尾段
195
+
196
+ // 中段不再计时(交棒清掉,不继承——30min 静默判据对收尾段失效);收尾段未满(<600s)不触发
197
+ await vi.advanceTimersByTimeAsync(SETTLED_WATCHDOG_TIMEOUT_MS - 1);
198
+ expect(killChildSpy).not.toHaveBeenCalled();
199
+ expect(hasSettledWatchdog(handle.subagentId)).toBe(true); // 收尾段仍在计时
200
+
201
+ // 收尾段硬顶(600s 默认)到期触发
202
+ await vi.advanceTimersByTimeAsync(2);
203
+ expect(killChildSpy).toHaveBeenCalledWith(handle.subagentId, "settled watchdog (hot path)");
204
+ });
205
+ });
206
+
207
+ describe("[F-5] chat 轮路由终态化注销(doFinalizeRecord onFinalized 汇聚点)", () => {
208
+ let agentDir: string;
209
+ let service: SubagentService;
210
+ let store: RecordStore;
211
+ let fake: FakePiEnginePort;
212
+
213
+ beforeEach(() => {
214
+ vi.restoreAllMocks();
215
+ killChildSpy.mockClear();
216
+ ({ agentDir, service, store, fake } = setup());
217
+ });
218
+
219
+ afterEach(() => {
220
+ service.dispose();
221
+ clearEngines();
222
+ _resetLifecycleState();
223
+ _resetSettledWatchdogsForTest();
224
+ _resetCoreSpawnedChildrenMirrorForTest();
225
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
226
+ });
227
+
228
+ it("closeChatIdle 终态化路径注销 chat 轮路由(修复前仅 cancelBackground 注销 → 泄漏)", async () => {
229
+ const record = makeChatRecord("sa-route-leak");
230
+ store.register(record);
231
+ // 模拟首轮已注册的 recordId 键路由(kickOffChatRound 注册面:Service map + fake 引擎两端)
232
+ registerRoute(service, fake, record.id);
233
+ // Path A:idle timer armed(进程保活等待续聊)→ closeSubagent(false) 走 closeChatIdle
234
+ const { armIdleTimer } = (await import("../lifecycle-manager.ts")) as typeof import("../lifecycle-manager.ts");
235
+ armIdleTimer(record.id, () => {}, 60_000);
236
+
237
+ await service.chatActions.closeSubagent(record, false);
238
+
239
+ expect(fake.chatRouteUnregisters).toContain(record.id);
240
+ });
241
+
242
+ it("closeAfterRoundSettled(优雅关闭消费)路径注销 chat 轮路由", async () => {
243
+ const record = makeChatRecord("sa-route-close-round");
244
+ store.register(record);
245
+ registerRoute(service, fake, record.id);
246
+ record.closeAfterRound = true;
247
+
248
+ await (
249
+ service as unknown as { closeAfterRoundSettled: (r: ExecutionRecord) => Promise<void> }
250
+ ).closeAfterRoundSettled(record);
251
+
252
+ expect(fake.chatRouteUnregisters).toContain(record.id);
253
+ expect(record.status).toBe("closed");
254
+ });
255
+ });
@@ -0,0 +1,291 @@
1
+ // src/execution/__tests__/collect-budget.test.ts
2
+ //
3
+ // U4 预算截断 + 指针 纯函数规格锁(subagent-sync-collect 设计 §3.1.2):
4
+ //
5
+ // 两段式预算(确定性,无瀑布分配):
6
+ // ① per-item 截断:每条目正文 ≤ perItemChars(默认 4000);
7
+ // ② 总量再压缩:Σ(截断后正文) > totalChars(默认 24000)时统一收紧
8
+ // effectivePerItem = clamp(floor(totalChars / n), 200, perItemChars);
9
+ // floor < 200(n > totalChars/200)→ 纯清单退化(头行 + 指针行,正文 0)。
10
+ //
11
+ // 指针行:[truncated {omitted} of {total} chars — full result: session_read
12
+ // {"action":"result","session":"{id}"}](omitted = total − kept;千位分隔)。
13
+ // [D6 #9] fixture id 统一 `sa-` 前缀——与产线 record id(`sa-<uuid>`,
14
+ // subprocess-agent-runner.ts / subagent-service.ts 同款)同形,session_read 指针行
15
+ // 断言的是产线真实可达的 id 形态。
16
+ // [C3 limit 随附] 该成员预算(effectivePerItem)> session_read 默认 8000 时 JSON 附
17
+ // "limit":N(N = effectivePerItem);≤8000 不附(默认已覆盖)。
18
+ //
19
+ // totalChars 口径:仅计条目正文(截断后)之和;批头/头行/指针行/分隔符等
20
+ // 包装开销有界常量不入预算。
21
+ //
22
+ // 纯内存零 IO;不依赖产线 mock(只 import 两个纯函数 + BgNotifyRecord 类型)。
23
+
24
+ import { describe, expect, it } from "vitest";
25
+ import type { BgNotifyRecord } from "../notifier.ts";
26
+ import { buildBatchLlmContent, computeBatchBudget } from "../notifier.ts";
27
+
28
+ // ─── fixture ──
29
+
30
+ /** 成功完成的批成员;result = ch 重复 bodyLen 次(缺省 7 字符,永不触预算)。 */
31
+ function member(id: string, over: Partial<BgNotifyRecord> = {}): BgNotifyRecord {
32
+ return {
33
+ id,
34
+ status: "closed",
35
+ outcome: "completed",
36
+ agent: `worker-${id}`,
37
+ result: `result of ${id}`,
38
+ startedAt: 1000,
39
+ endedAt: 2000,
40
+ ...over,
41
+ };
42
+ }
43
+
44
+ function filled(id: string, bodyLen: number, ch = "A"): BgNotifyRecord {
45
+ return member(id, { result: ch.repeat(bodyLen) });
46
+ }
47
+
48
+ /** 设计默认预算(config 热读接线前 U4 以默认常量锁规格)。 */
49
+ const PER_ITEM = 4000;
50
+ const TOTAL = 24000;
51
+
52
+ /** 期望指针行(千位分隔与设计样例 11,234 对齐)。perItemLimit > 8000 时附 "limit":N(C3)。 */
53
+ function pointer(id: string, kept: number, total: number, perItemLimit: number): string {
54
+ const fmt = (n: number) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
55
+ const limitPart = perItemLimit > 8000 ? `,"limit":${perItemLimit}` : "";
56
+ return `[truncated ${fmt(total - kept)} of ${fmt(total)} chars — full result: session_read {"action":"result","session":"${id}"${limitPart}}]`;
57
+ }
58
+
59
+ /** 期望条目:Result: 头行 + kept 正文 + (截断时)… + 指针行。 */
60
+ function expectedEntry(
61
+ rec: { id: string; agent: string },
62
+ fullBody: string,
63
+ kept: number,
64
+ perItemLimit: number,
65
+ ): string {
66
+ const head = `Subagent "${rec.agent}" (${rec.id}) completed. Result:\n`;
67
+ if (fullBody.length <= kept) return head + fullBody;
68
+ return `${head}${fullBody.slice(0, kept)}…\n${pointer(rec.id, kept, fullBody.length, perItemLimit)}`;
69
+ }
70
+
71
+ // ─── computeBatchBudget 纯函数公式 ──
72
+
73
+ describe("computeBatchBudget(两段式预算公式)", () => {
74
+ it("7 成员各 6000:effectivePerItem = clamp(floor(24000/7)=3428, 200, 4000) = 3428", () => {
75
+ const plan = computeBatchBudget(Array.from({ length: 7 }, () => 6000), PER_ITEM, TOTAL);
76
+ expect(plan.tightened).toBe(true);
77
+ expect(plan.listOnly).toBe(false);
78
+ expect(plan.effectivePerItem).toBe(3428);
79
+ });
80
+
81
+ it("6 成员各 3000(Σ=18000 ≤ totalChars):不触发第二段收紧", () => {
82
+ const plan = computeBatchBudget(Array.from({ length: 6 }, () => 3000), PER_ITEM, TOTAL);
83
+ expect(plan.tightened).toBe(false);
84
+ expect(plan.listOnly).toBe(false);
85
+ expect(plan.effectivePerItem).toBe(PER_ITEM);
86
+ });
87
+
88
+ it("n > totalChars/200(n=125 → floor=192 < 200):纯清单退化 effectivePerItem = 0", () => {
89
+ expect(TOTAL / 200).toBe(120);
90
+ const plan = computeBatchBudget(Array.from({ length: 125 }, () => 1000), PER_ITEM, TOTAL);
91
+ expect(plan.tightened).toBe(true);
92
+ expect(plan.listOnly).toBe(true);
93
+ expect(plan.effectivePerItem).toBe(0);
94
+ });
95
+
96
+ it("边界 n=120(floor(24000/120)=200 恰达下限):不退化,effectivePerItem = 200", () => {
97
+ const plan = computeBatchBudget(Array.from({ length: 120 }, () => 1000), PER_ITEM, TOTAL);
98
+ expect(plan.listOnly).toBe(false);
99
+ expect(plan.effectivePerItem).toBe(200);
100
+ });
101
+
102
+ it("口径:bodyLengths 仅收正文长度,Σ 恰等于 totalChars(=24000)不算超、不收紧", () => {
103
+ const plan = computeBatchBudget(Array.from({ length: 6 }, () => 4000), PER_ITEM, TOTAL);
104
+ expect(plan.tightened).toBe(false);
105
+ });
106
+ });
107
+
108
+ // ─── buildBatchLlmContent 组装链 ──
109
+
110
+ describe("buildBatchLlmContent 预算截断组装(设计 §3.1.2)", () => {
111
+ it("7 成员各 6000:每条收紧至 3428 字符 + 指针行,总量回到预算内", () => {
112
+ const records = Array.from({ length: 7 }, (_, i) => filled(`sa-${i}`, 6000));
113
+ const content = buildBatchLlmContent(records);
114
+ const parts = content.split("\n\n---\n\n");
115
+
116
+ expect(parts[0]).toBe("Subagent batch completed: 7 finished, 0 failed, 0 cancelled.");
117
+ expect(parts).toHaveLength(8);
118
+ for (let i = 0; i < 7; i++) {
119
+ expect(parts[i + 1]).toBe(expectedEntry(records[i], "A".repeat(6000), 3428, 3428));
120
+ expect(parts[i + 1]).toContain(
121
+ pointer(`sa-${i}`, 3428, 6000, 3428), // omitted = 6000 − 3428 = 2,572
122
+ );
123
+ }
124
+ });
125
+
126
+ it("6 成员各 3000(Σ=18000):不触发第二段——每条全量在场、零指针行", () => {
127
+ const records = Array.from({ length: 6 }, (_, i) => filled(`sa-${i}`, 3000));
128
+ const content = buildBatchLlmContent(records);
129
+ const parts = content.split("\n\n---\n\n");
130
+
131
+ expect(parts[0]).toBe("Subagent batch completed: 6 finished, 0 failed, 0 cancelled.");
132
+ expect(content).not.toContain("[truncated");
133
+ for (let i = 0; i < 6; i++) {
134
+ expect(parts[i + 1]).toBe(expectedEntry(records[i], "A".repeat(3000), 3000, PER_ITEM));
135
+ }
136
+ });
137
+
138
+ it("n=125:纯清单退化——每条只剩头行 + 指针行,正文零残留", () => {
139
+ const records = Array.from({ length: 125 }, (_, i) => filled(`sa-${i}`, 1000, "B"));
140
+ const content = buildBatchLlmContent(records);
141
+ const parts = content.split("\n\n---\n\n");
142
+
143
+ expect(parts[0]).toBe("Subagent batch completed: 125 finished, 0 failed, 0 cancelled.");
144
+ expect(parts).toHaveLength(126);
145
+ for (let i = 0; i < 125; i++) {
146
+ // 头行 + 指针行紧邻(kept=0 → 无正文、无省略号)
147
+ expect(parts[i + 1]).toBe(
148
+ `Subagent "worker-sa-${i}" (sa-${i}) completed. Result:\n${pointer(`sa-${i}`, 0, 1000, 0)}`,
149
+ );
150
+ }
151
+ // 正文零残留(kept=0)
152
+ expect(content).not.toContain("BBBB");
153
+ expect(content).not.toContain("…");
154
+ });
155
+
156
+ it("指针行格式(第一段 per-item 截断同样接指针):omitted = total − kept", () => {
157
+ // 2 成员各 5000:Σ=8000 ≤ 24000 不收紧,per-item 截 4000
158
+ const records = [filled("sa-aaa", 5000), filled("sa-bbb", 5000)];
159
+ const parts = buildBatchLlmContent(records).split("\n\n---\n\n");
160
+ for (const rec of records) {
161
+ expect(parts).toContain(expectedEntry(rec, "A".repeat(5000), PER_ITEM, PER_ITEM));
162
+ }
163
+ expect(parts[1]).toContain(pointer("sa-aaa", 4000, 5000, PER_ITEM)); // 1,000 of 5,000
164
+ });
165
+
166
+ it("长短参差(1 短 500 + 6 长 6000):统一收紧 3428,短条目足额保留——非瀑布分配", () => {
167
+ // Σ(per-item 后) = 500 + 6×4000 = 24500 > 24000 → 触发收紧
168
+ const shortOne = filled("sa-short", 500, "S");
169
+ const longs = Array.from({ length: 6 }, (_, i) => filled(`sa-long-${i}`, 6000));
170
+ const records = [shortOne, ...longs];
171
+ const content = buildBatchLlmContent(records);
172
+ const parts = content.split("\n\n---\n\n");
173
+
174
+ expect(parts[0]).toBe("Subagent batch completed: 7 finished, 0 failed, 0 cancelled.");
175
+ // 短条目:500 < 3428 足额全文,无截断
176
+ expect(parts[1]).toBe(expectedEntry(shortOne, "S".repeat(500), 500, 3428));
177
+ expect(parts[1]).not.toContain("[truncated");
178
+ // 长条目:统一收紧到同一 effectivePerItem = 3428
179
+ for (let i = 0; i < 6; i++) {
180
+ expect(parts[i + 2]).toBe(expectedEntry(longs[i], "A".repeat(6000), 3428, 3428));
181
+ }
182
+ });
183
+
184
+ it("确定性:同输入同输出;打乱条目顺序不改变各条目自身截断结果", () => {
185
+ const records = [
186
+ filled("sa-a", 6000),
187
+ filled("sa-b", 500), // 短
188
+ filled("sa-c", 6000),
189
+ filled("sa-d", 6000),
190
+ filled("sa-e", 3000),
191
+ filled("sa-f", 6000),
192
+ filled("sa-g", 6000),
193
+ ];
194
+ const run1 = buildBatchLlmContent(records);
195
+ const run2 = buildBatchLlmContent(records);
196
+ expect(run1).toBe(run2); // 同输入同输出
197
+
198
+ const entryOf = (content: string, id: string): string =>
199
+ content.split("\n\n---\n\n").find((p) => p.includes(`(${id})`)) ?? "";
200
+ const reversed = buildBatchLlmContent([...records].reverse());
201
+ for (const rec of records) {
202
+ // 统一收紧与顺序无关:每条目内容在两种排布下逐字节一致
203
+ expect(entryOf(reversed, rec.id)).toBe(entryOf(run1, rec.id));
204
+ }
205
+ });
206
+
207
+ it("totalChars 口径:仅计条目正文之和——Σ=23898 < 24000 时包装开销(批头/头行/指针行/分隔符)不触发收紧", () => {
208
+ // 整条通知实际字节数 ≈ 23898 + 批头 ~60 + 头行 7×~45 + 分隔符 ~80 > 24000,
209
+ // 但预算判定只看正文和 → 不收紧
210
+ const records = Array.from({ length: 6 }, (_, i) => filled(`sa-${i}`, 3983));
211
+ const content = buildBatchLlmContent(records);
212
+ expect(content.length).toBeGreaterThan(TOTAL); // 整体确实超了 totalChars
213
+ expect(content).not.toContain("[truncated"); // 却不收紧
214
+ for (let i = 0; i < 6; i++) {
215
+ expect(content).toContain("A".repeat(3983)); // 全量正文在场
216
+ }
217
+ });
218
+
219
+ it("Σ=24000 恰好等于 totalChars:不算超(> 才收紧),6×4000 全量在场零指针行", () => {
220
+ const records = Array.from({ length: 6 }, (_, i) => filled(`sa-${i}`, 4000));
221
+ const content = buildBatchLlmContent(records);
222
+ expect(content).not.toContain("[truncated");
223
+ const parts = content.split("\n\n---\n\n");
224
+ for (let i = 0; i < 6; i++) {
225
+ expect(parts[i + 1]).toBe(expectedEntry(records[i], "A".repeat(4000), 4000, PER_ITEM));
226
+ }
227
+ });
228
+
229
+ it("失败成员不入正文预算:failed error 全文原样,且不计入 Σ——成功条目 kept=4000 而非收紧 3428", () => {
230
+ const failedOne = member("sa-bad", { outcome: "failed", error: "boom".repeat(2000) });
231
+ const longs = Array.from({ length: 6 }, (_, i) => filled(`sa-${i}`, 6000));
232
+ const records = [failedOne, ...longs];
233
+ // 成功正文 Σ(per-item 后) = 6×4000 = 24000 ≤ 24000 → 不收紧;若把 failed error
234
+ //(8000)计入 Σ = 32000 > 24000 → 会收紧至 3428。断言 kept=4000 即锁住口径。
235
+ const content = buildBatchLlmContent(records);
236
+ const parts = content.split("\n\n---\n\n");
237
+ expect(parts[0]).toBe("Subagent batch completed: 6 finished, 1 failed, 0 cancelled.");
238
+ // failed 条目:error 全文原样、无截断无指针
239
+ expect(content).toContain(`Subagent "worker-sa-bad" (sa-bad) failed: ${"boom".repeat(2000)}`);
240
+ const failedPart = parts.find((p) => p.includes("(sa-bad)")) ?? "";
241
+ expect(failedPart).not.toContain("[truncated");
242
+ // 成功条目:仅第一段 per-item 截断(kept=4000),未被第二段收紧
243
+ for (let i = 0; i < 6; i++) {
244
+ expect(parts).toContain(expectedEntry(longs[i], "A".repeat(6000), 4000, PER_ITEM));
245
+ expect(content).toContain(pointer(`sa-${i}`, 4000, 6000, PER_ITEM)); // 2,000 of 6,000
246
+ }
247
+ expect(content).not.toContain("2,572 of 6,000"); // 3428 收紧形态未出现
248
+ });
249
+
250
+ it("极短 result 的既有形态零回归:不触预算时输出与 U3 基线逐字节一致", () => {
251
+ const records = [member("sa-1"), member("sa-2")];
252
+ const content = buildBatchLlmContent(records);
253
+ expect(content).toBe(
254
+ [
255
+ "Subagent batch completed: 2 finished, 0 failed, 0 cancelled.",
256
+ 'Subagent "worker-sa-1" (sa-1) completed. Result:\nresult of sa-1',
257
+ 'Subagent "worker-sa-2" (sa-2) completed. Result:\nresult of sa-2',
258
+ ].join("\n\n---\n\n"),
259
+ );
260
+ });
261
+ });
262
+
263
+ // ─── C3 指针行 limit 随附(adversarial-review-fixes §3.4 C3)───
264
+
265
+ describe("buildBatchLlmContent 指针行 limit 随附(C3:预算 > session_read 默认 8000 时附,≤ 不附)", () => {
266
+ it("perItemChars=12000(>8000):指针行 JSON 附 \"limit\":12000——模型照抄即取回 ≥ 默认值的有效正文", () => {
267
+ // 2 成员各 15000:Σ=24000 ≤ totalChars=48000 不收紧 → effectivePerItem = 12000
268
+ const records = [filled("sa-big", 15000), filled("sa-big2", 15000)];
269
+ const content = buildBatchLlmContent(records, { perItemChars: 12000, totalChars: 48000 });
270
+ expect(content).toContain(
271
+ `[truncated 3,000 of 15,000 chars — full result: session_read {"action":"result","session":"sa-big","limit":12000}]`,
272
+ );
273
+ expect(content).toContain(`"session":"sa-big2","limit":12000`);
274
+ });
275
+
276
+ it("perItemChars=4000(默认,≤8000):指针行无 limit 字段(默认已覆盖,避免噪音)", () => {
277
+ const records = [filled("sa-a", 5000)];
278
+ const content = buildBatchLlmContent(records);
279
+ expect(content).toContain(
280
+ `[truncated 1,000 of 5,000 chars — full result: session_read {"action":"result","session":"sa-a"}]`,
281
+ );
282
+ expect(content).not.toContain(`"limit"`);
283
+ });
284
+
285
+ it("收紧后 effectivePerItem=3428(≤8000):同样不附 limit——判定看收紧后的实际预算", () => {
286
+ const records = Array.from({ length: 7 }, (_, i) => filled(`sa-${i}`, 6000));
287
+ const content = buildBatchLlmContent(records);
288
+ expect(content).toContain(`"session":"sa-0"}`);
289
+ expect(content).not.toContain(`"limit"`);
290
+ });
291
+ });