@zhushanwen/subagent-core 0.2.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 (330) hide show
  1. package/README.md +114 -0
  2. package/dist/chunk-3VOERJPJ.js +22 -0
  3. package/dist/chunk-A4IVZWVX.js +31 -0
  4. package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
  5. package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
  6. package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
  7. package/dist/execution/engine/engines/zcode/constants.js +22 -0
  8. package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
  9. package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
  10. package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
  11. package/dist/execution/engine/engines/zcode/reader.js +239 -0
  12. package/dist/execution/engine/paths.cjs +55 -0
  13. package/dist/execution/engine/paths.d.cts +8 -0
  14. package/dist/execution/engine/paths.d.ts +8 -0
  15. package/dist/execution/engine/paths.js +26 -0
  16. package/dist/execution/relay-env.cjs +62 -0
  17. package/dist/execution/relay-env.d.cts +33 -0
  18. package/dist/execution/relay-env.d.ts +33 -0
  19. package/dist/execution/relay-env.js +20 -0
  20. package/dist/index.cjs +1852 -0
  21. package/dist/index.d.cts +1184 -0
  22. package/dist/index.d.ts +1184 -0
  23. package/dist/index.js +1810 -0
  24. package/dist/types-BxyAidGf.d.cts +875 -0
  25. package/dist/types-BxyAidGf.d.ts +875 -0
  26. package/package.json +99 -0
  27. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
  28. package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
  29. package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
  30. package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
  31. package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
  32. package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
  33. package/src/__tests__/m2-append-content-probe.test.ts +73 -0
  34. package/src/__tests__/manifest-store.test.ts +368 -0
  35. package/src/__tests__/record-store-cache.test.ts +335 -0
  36. package/src/__tests__/record-store-index.test.ts +560 -0
  37. package/src/__tests__/record-store-last-line.test.ts +129 -0
  38. package/src/__tests__/review-fix-loop-script.test.ts +288 -0
  39. package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
  40. package/src/__tests__/robustness-low-batch1.test.ts +286 -0
  41. package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
  42. package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
  43. package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
  44. package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
  45. package/src/__tests__/session-runner.test.ts +69 -0
  46. package/src/__tests__/smoke.test.ts +9 -0
  47. package/src/__tests__/stream-sink.test.ts +203 -0
  48. package/src/core/__tests__/host-services.test.ts +144 -0
  49. package/src/core/__tests__/logger.test.ts +78 -0
  50. package/src/core/__tests__/notify-ports.test.ts +104 -0
  51. package/src/core/host-services.ts +97 -0
  52. package/src/core/logger.ts +47 -0
  53. package/src/core/notify-ports.ts +126 -0
  54. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
  55. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
  56. package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
  57. package/src/execution/__tests__/alive-store.test.ts +147 -0
  58. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
  59. package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
  60. package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
  61. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
  62. package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
  63. package/src/execution/__tests__/config.test.ts +302 -0
  64. package/src/execution/__tests__/delivery-methods.test.ts +424 -0
  65. package/src/execution/__tests__/dialog-queue.test.ts +299 -0
  66. package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
  67. package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
  68. package/src/execution/__tests__/execute-nesting.test.ts +323 -0
  69. package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
  70. package/src/execution/__tests__/execution-record.test.ts +1394 -0
  71. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
  72. package/src/execution/__tests__/finalize-record.test.ts +367 -0
  73. package/src/execution/__tests__/finalized-marker.test.ts +104 -0
  74. package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
  75. package/src/execution/__tests__/gc-timer.test.ts +184 -0
  76. package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
  77. package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
  78. package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
  79. package/src/execution/__tests__/host-mode.test.ts +87 -0
  80. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
  83. package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
  84. package/src/execution/__tests__/model-resolver.test.ts +465 -0
  85. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
  86. package/src/execution/__tests__/nested-visibility.test.ts +325 -0
  87. package/src/execution/__tests__/output-collector.test.ts +358 -0
  88. package/src/execution/__tests__/path-encoding.test.ts +104 -0
  89. package/src/execution/__tests__/pi-invocation.test.ts +134 -0
  90. package/src/execution/__tests__/record-store.test.ts +1057 -0
  91. package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
  92. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
  93. package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
  94. package/src/execution/__tests__/relay-env.test.ts +42 -0
  95. package/src/execution/__tests__/resource-policy.test.ts +109 -0
  96. package/src/execution/__tests__/rpc-mode.test.ts +89 -0
  97. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
  98. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
  99. package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
  100. package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
  101. package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
  102. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
  103. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
  104. package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
  105. package/src/execution/__tests__/session-file-gc.test.ts +293 -0
  106. package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
  107. package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
  108. package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
  109. package/src/execution/__tests__/spawn-args.test.ts +445 -0
  110. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
  111. package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
  112. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
  113. package/src/execution/__tests__/spawned-children.test.ts +92 -0
  114. package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
  115. package/src/execution/__tests__/stdin-writer.test.ts +462 -0
  116. package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
  117. package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
  118. package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
  119. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
  120. package/src/execution/__tests__/subagent-service.test.ts +788 -0
  121. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
  122. package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
  123. package/src/execution/__tests__/temp-prompt.test.ts +53 -0
  124. package/src/execution/__tests__/timeout-integration.test.ts +615 -0
  125. package/src/execution/__tests__/tombstone-store.test.ts +73 -0
  126. package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
  127. package/src/execution/__tests__/turn-limiter.test.ts +65 -0
  128. package/src/execution/__tests__/ui-channels.test.ts +187 -0
  129. package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
  130. package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
  131. package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
  132. package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
  133. package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
  134. package/src/execution/__tests__/worktree-manager.test.ts +633 -0
  135. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
  136. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
  137. package/src/execution/__tests__/worktree-registry.test.ts +199 -0
  138. package/src/execution/agent-registry.ts +199 -0
  139. package/src/execution/agent-result-mapper.ts +90 -0
  140. package/src/execution/alive-store.ts +119 -0
  141. package/src/execution/argv-mirror.ts +113 -0
  142. package/src/execution/best-effort.ts +38 -0
  143. package/src/execution/channel-registry-access.ts +144 -0
  144. package/src/execution/concurrency-pool.ts +116 -0
  145. package/src/execution/config.ts +165 -0
  146. package/src/execution/dialog-queue.ts +330 -0
  147. package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
  148. package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
  149. package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
  150. package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
  151. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
  152. package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
  153. package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
  154. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
  155. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
  156. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
  157. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
  158. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
  159. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
  160. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
  161. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
  162. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
  163. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
  164. package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
  165. package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
  166. package/src/execution/engine/__tests__/paths.test.ts +39 -0
  167. package/src/execution/engine/__tests__/registry.test.ts +120 -0
  168. package/src/execution/engine/__tests__/routing.test.ts +231 -0
  169. package/src/execution/engine/common/data-dir.ts +66 -0
  170. package/src/execution/engine/common/errors.ts +183 -0
  171. package/src/execution/engine/common/event-journal.ts +254 -0
  172. package/src/execution/engine/common/journal-replay.ts +62 -0
  173. package/src/execution/engine/common/kill-chain.ts +221 -0
  174. package/src/execution/engine/common/nesting-guard.ts +50 -0
  175. package/src/execution/engine/common/persona-router.ts +108 -0
  176. package/src/execution/engine/common/pool-manager.ts +226 -0
  177. package/src/execution/engine/common/schema-emulation.ts +189 -0
  178. package/src/execution/engine/common/session-view-projection.ts +51 -0
  179. package/src/execution/engine/engine-discovery.ts +65 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
  181. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
  182. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
  183. package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
  184. package/src/execution/engine/engines/pi/reader.ts +48 -0
  185. package/src/execution/engine/engines/pi/registration.ts +35 -0
  186. package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
  187. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
  188. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
  189. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
  190. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
  191. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
  192. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
  193. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
  194. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
  195. package/src/execution/engine/engines/zcode/constants.ts +43 -0
  196. package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
  197. package/src/execution/engine/engines/zcode/launcher.ts +161 -0
  198. package/src/execution/engine/engines/zcode/parser.ts +436 -0
  199. package/src/execution/engine/engines/zcode/preparer.ts +363 -0
  200. package/src/execution/engine/engines/zcode/reader.ts +381 -0
  201. package/src/execution/engine/engines/zcode/registration.ts +37 -0
  202. package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
  203. package/src/execution/engine/host-task-spec.ts +47 -0
  204. package/src/execution/engine/model-prompt.ts +158 -0
  205. package/src/execution/engine/paths.ts +42 -0
  206. package/src/execution/engine/port.ts +153 -0
  207. package/src/execution/engine/registry.ts +133 -0
  208. package/src/execution/engine/routing.ts +218 -0
  209. package/src/execution/engine/types.ts +309 -0
  210. package/src/execution/execute-options-mapper.ts +115 -0
  211. package/src/execution/execution-record.ts +984 -0
  212. package/src/execution/finalize-record.ts +254 -0
  213. package/src/execution/finalized-marker.ts +70 -0
  214. package/src/execution/get-state-handshake.ts +104 -0
  215. package/src/execution/host-mode.ts +56 -0
  216. package/src/execution/idle-gc.ts +47 -0
  217. package/src/execution/lifecycle-manager.ts +513 -0
  218. package/src/execution/lifecycle-predicates.ts +65 -0
  219. package/src/execution/manifest-store.ts +256 -0
  220. package/src/execution/model-config-service.ts +250 -0
  221. package/src/execution/model-resolver.ts +248 -0
  222. package/src/execution/notifier.ts +372 -0
  223. package/src/execution/notify-ledger.ts +580 -0
  224. package/src/execution/output-collector.ts +228 -0
  225. package/src/execution/path-encoding.ts +60 -0
  226. package/src/execution/pi-invocation.ts +120 -0
  227. package/src/execution/record-entry.ts +132 -0
  228. package/src/execution/record-store.ts +1265 -0
  229. package/src/execution/relay-env.ts +37 -0
  230. package/src/execution/session-context-resolver.ts +64 -0
  231. package/src/execution/session-file-gc.ts +120 -0
  232. package/src/execution/session-pending.ts +170 -0
  233. package/src/execution/session-reconstructor.ts +678 -0
  234. package/src/execution/session-runner.ts +1789 -0
  235. package/src/execution/sessions-index.ts +304 -0
  236. package/src/execution/spawn-event-adapter.ts +363 -0
  237. package/src/execution/stdin-writer.ts +198 -0
  238. package/src/execution/stream-sink.ts +126 -0
  239. package/src/execution/subagent-service.ts +2268 -0
  240. package/src/execution/subprocess-agent-runner.ts +317 -0
  241. package/src/execution/temp-prompt.ts +62 -0
  242. package/src/execution/tombstone-store.ts +72 -0
  243. package/src/execution/turn-limiter.ts +102 -0
  244. package/src/execution/types.ts +1023 -0
  245. package/src/execution/ui-channels.ts +216 -0
  246. package/src/execution/ui-interaction-model.ts +48 -0
  247. package/src/execution/ui-request-handler-factory.ts +243 -0
  248. package/src/execution/ui-request-observability.ts +81 -0
  249. package/src/execution/ui-request-queue.ts +184 -0
  250. package/src/execution/worktree-manager.ts +688 -0
  251. package/src/execution/worktree-registry.ts +279 -0
  252. package/src/index.ts +87 -0
  253. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
  254. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
  255. package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +143 -0
  257. package/src/orchestration/__tests__/config-loader.test.ts +503 -0
  258. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
  259. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
  260. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
  261. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
  262. package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
  263. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
  264. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
  265. package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
  266. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
  267. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
  268. package/src/orchestration/__tests__/script-lint.test.ts +831 -0
  269. package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
  270. package/src/orchestration/__tests__/test-mocks.ts +197 -0
  271. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
  272. package/src/orchestration/__tests__/worker-host.test.ts +120 -0
  273. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
  274. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
  275. package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
  276. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
  277. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
  278. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
  279. package/src/orchestration/agent-opts-resolver.ts +174 -0
  280. package/src/orchestration/args-validator.ts +127 -0
  281. package/src/orchestration/config-loader.ts +316 -0
  282. package/src/orchestration/error-recovery.ts +1018 -0
  283. package/src/orchestration/execute-agent-call.ts +256 -0
  284. package/src/orchestration/launcher.ts +455 -0
  285. package/src/orchestration/lifecycle.ts +399 -0
  286. package/src/orchestration/models/__tests__/budget.test.ts +307 -0
  287. package/src/orchestration/models/agent-call.ts +83 -0
  288. package/src/orchestration/models/budget.ts +118 -0
  289. package/src/orchestration/models/ports.ts +164 -0
  290. package/src/orchestration/models/run-runtime.ts +104 -0
  291. package/src/orchestration/models/run-spec.ts +83 -0
  292. package/src/orchestration/models/run-state.ts +44 -0
  293. package/src/orchestration/models/trace.ts +183 -0
  294. package/src/orchestration/models/types.ts +301 -0
  295. package/src/orchestration/models/workflow-run.ts +254 -0
  296. package/src/orchestration/models/workflow-script-registry.ts +35 -0
  297. package/src/orchestration/models/workflow-script.ts +117 -0
  298. package/src/orchestration/script-lint.ts +766 -0
  299. package/src/orchestration/skill-discovery.ts +135 -0
  300. package/src/orchestration/worker-handle.ts +115 -0
  301. package/src/orchestration/worker-host.ts +98 -0
  302. package/src/orchestration/worker-script-builder.ts +421 -0
  303. package/src/orchestration/workflow-files.ts +85 -0
  304. package/src/orchestration/workflow-script-registry-impl.ts +133 -0
  305. package/src/shared/__tests__/agent-ref.test.ts +34 -0
  306. package/src/shared/__tests__/meta-parser.test.ts +304 -0
  307. package/src/shared/__tests__/model-ref.test.ts +306 -0
  308. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
  309. package/src/shared/__tests__/resource-discovery.test.ts +749 -0
  310. package/src/shared/__tests__/resource-meta.test.ts +51 -0
  311. package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
  312. package/src/shared/__tests__/timer-delay.test.ts +61 -0
  313. package/src/shared/agent-event.ts +13 -0
  314. package/src/shared/agent-ref.ts +57 -0
  315. package/src/shared/meta-parser.ts +261 -0
  316. package/src/shared/model-ref.ts +286 -0
  317. package/src/shared/resource-discovery.ts +835 -0
  318. package/src/shared/resource-meta.ts +65 -0
  319. package/src/shared/schema-env.ts +44 -0
  320. package/src/shared/schema-jsonify.ts +58 -0
  321. package/src/shared/timer-delay.ts +54 -0
  322. package/src/shared/xml-injection.ts +35 -0
  323. package/workflows/README.md +81 -0
  324. package/workflows/_shared/agent-refs.cjs +40 -0
  325. package/workflows/chain.js +137 -0
  326. package/workflows/map-reduce.js +180 -0
  327. package/workflows/parallel.js +154 -0
  328. package/workflows/review-fix-loop-utils.cjs +1542 -0
  329. package/workflows/review-fix-loop.js +1605 -0
  330. package/workflows/scatter-gather.js +184 -0
@@ -0,0 +1,177 @@
1
+ // event-journal.test.ts —— JournalWriter 写入-重放往返一致 + 中立格式字段断言。
2
+ //
3
+ // 三视角:①构建者——行格式 v/ts/taskId/engineId/seq/event 逐字段、seq 单调;②使用者
4
+ // ——replayJournal 重放即得事件流(read 第②级),文件不存在/坏行不 throw;③观察者
5
+ // ——写失败 warn 留痕且 close 不抛(journal 是尽力而为的②级数据源)。
6
+
7
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { dirname, join } from "node:path";
10
+
11
+ import { afterEach, describe, expect, it } from "vitest";
12
+
13
+ import { JournalWriter, replayJournal } from "../../common/event-journal.ts";
14
+ import type { JournalFsDeps } from "../../common/event-journal.ts";
15
+ import type { AgentEvent } from "../../../types.ts";
16
+
17
+ let tmpRoot: string | undefined;
18
+
19
+ function tmpPath(name: string): string {
20
+ tmpRoot ??= mkdtempSync(join(tmpdir(), "engine-journal-test-"));
21
+ const path = join(tmpRoot, name);
22
+ mkdirSync(dirname(path), { recursive: true });
23
+ return path;
24
+ }
25
+
26
+ afterEach(() => {
27
+ if (tmpRoot !== undefined) {
28
+ rmSync(tmpRoot, { recursive: true, force: true });
29
+ tmpRoot = undefined;
30
+ }
31
+ });
32
+
33
+ const EVENTS: AgentEvent[] = [
34
+ { type: "text_delta", delta: "hello " },
35
+ { type: "text_delta", delta: "world" },
36
+ { type: "tool_start", toolName: "bash", args: { cmd: "ls" } },
37
+ { type: "tool_end", toolName: "bash", isError: false },
38
+ { type: "turn_end" },
39
+ ];
40
+
41
+ describe("JournalWriter 写入 + replayJournal 重放", () => {
42
+ it("写入-重放往返一致:append N 事件 → close → replay 深等事件流", async () => {
43
+ const path = tmpPath("roundtrip/journal-bg-1.jsonl");
44
+ const writer = new JournalWriter({ path, taskId: "bg-1", engineId: "zcode" });
45
+ for (const ev of EVENTS) writer.append(ev);
46
+ await writer.close();
47
+
48
+ expect(replayJournal(path)).toEqual(EVENTS);
49
+ });
50
+
51
+ it("中立格式字段断言:每行 {v:1, ts, taskId, engineId, seq, event},seq 单调递增", async () => {
52
+ const path = tmpPath("format/journal-bg-2.jsonl");
53
+ const writer = new JournalWriter({ path, taskId: "bg-2", engineId: "pi" });
54
+ const before = Date.now();
55
+ for (const ev of EVENTS) writer.append(ev);
56
+ await writer.close();
57
+
58
+ const lines = readFileSync(path, "utf8").trim().split("\n");
59
+ expect(lines.length).toBe(EVENTS.length);
60
+ const parsed = lines.map((l) => JSON.parse(l) as Record<string, unknown>);
61
+ let prevSeq = -1;
62
+ for (const [i, row] of parsed.entries()) {
63
+ expect(row.v).toBe(1);
64
+ expect(typeof row.ts).toBe("number");
65
+ expect(row.ts).toBeGreaterThanOrEqual(before);
66
+ expect(row.taskId).toBe("bg-2");
67
+ expect(row.engineId).toBe("pi");
68
+ expect(row.seq).toBe(i);
69
+ expect((row.seq as number) > prevSeq).toBe(true);
70
+ prevSeq = row.seq as number;
71
+ expect(row.event).toEqual(EVENTS[i]);
72
+ }
73
+ });
74
+
75
+ it("close 幂等(二次 close 不追加不抛错);close 后 append 丢弃", async () => {
76
+ const path = tmpPath("idempotent/journal-bg-3.jsonl");
77
+ const writer = new JournalWriter({ path, taskId: "bg-3", engineId: "pi" });
78
+ writer.append({ type: "turn_end" });
79
+ await writer.close();
80
+ writer.append({ type: "error", message: "late" });
81
+ await writer.close();
82
+ expect(replayJournal(path)).toEqual([{ type: "turn_end" }]);
83
+ });
84
+
85
+ it("无事件任务不产生空 journal 文件(惰性创建)", async () => {
86
+ const path = tmpPath("empty/journal-bg-4.jsonl");
87
+ const writer = new JournalWriter({ path, taskId: "bg-4", engineId: "pi" });
88
+ await writer.close();
89
+ expect(existsSync(path)).toBe(false);
90
+ expect(replayJournal(path)).toEqual([]);
91
+ });
92
+ });
93
+
94
+ describe("JournalWriter 写失败(尽力而为语义)", () => {
95
+ function failingFs(): { fs: JournalFsDeps; warnings: string[] } {
96
+ const warnings: string[] = [];
97
+ const fs: JournalFsDeps = {
98
+ mkdir: async () => undefined,
99
+ appendFile: async () => {
100
+ throw new Error("disk full");
101
+ },
102
+ open: async () => {
103
+ throw new Error("unreachable in failed path");
104
+ },
105
+ };
106
+ return { fs, warnings };
107
+ }
108
+
109
+ it("appendFile 失败 → warn 留痕 + failed,close 不抛,后续 append 丢弃", async () => {
110
+ const path = tmpPath("failing/journal-bg-5.jsonl");
111
+ const { fs } = failingFs();
112
+ const warnings: string[] = [];
113
+ const writer = new JournalWriter(
114
+ { path, taskId: "bg-5", engineId: "zcode" },
115
+ fs,
116
+ (msg) => warnings.push(msg),
117
+ );
118
+ writer.append({ type: "turn_end" });
119
+ await writer.flush();
120
+ expect(writer.isFailed).toBe(true);
121
+ expect(warnings.length).toBe(1);
122
+ expect(warnings[0]).toContain("bg-5");
123
+ expect(warnings[0]).toContain("disk full");
124
+
125
+ writer.append({ type: "error", message: "dropped" });
126
+ await writer.close(); // 不抛
127
+ expect(existsSync(path)).toBe(false);
128
+ });
129
+ });
130
+
131
+ describe("replayJournal 容错", () => {
132
+ it("文件不存在 → [](②级不可达不算错误,降级链语义)", () => {
133
+ expect(replayJournal(tmpPath("missing/journal-none.jsonl"))).toEqual([]);
134
+ });
135
+
136
+ it("损坏行跳过(追加写末行可能截断),好行保留", () => {
137
+ const path = tmpPath("corrupt/journal-bg-6.jsonl");
138
+ const good = JSON.stringify({
139
+ v: 1, ts: 1, taskId: "bg-6", engineId: "pi", seq: 0, event: { type: "turn_end" },
140
+ });
141
+ writeFileSync(path, `${good}\n{"v":1,"truncated...\n`, "utf8");
142
+ expect(replayJournal(path)).toEqual([{ type: "turn_end" }]);
143
+ });
144
+
145
+ it("行序乱 → 按 seq 稳定排序返回(重放顺序权威是 seq)", () => {
146
+ const path = tmpPath("unordered/journal-bg-7.jsonl");
147
+ const line = (seq: number, delta: string): string =>
148
+ JSON.stringify({ v: 1, ts: seq, taskId: "bg-7", engineId: "pi", seq, event: { type: "text_delta", delta } });
149
+ writeFileSync(path, `${line(2, "c")}\n${line(0, "a")}\n${line(1, "b")}\n`, "utf8");
150
+ expect(replayJournal(path)).toEqual([
151
+ { type: "text_delta", delta: "a" },
152
+ { type: "text_delta", delta: "b" },
153
+ { type: "text_delta", delta: "c" },
154
+ ]);
155
+ });
156
+
157
+ it("空文件 → []", () => {
158
+ const path = tmpPath("blank/journal-bg-8.jsonl");
159
+ writeFileSync(path, "\n\n", "utf8");
160
+ expect(replayJournal(path)).toEqual([]);
161
+ });
162
+
163
+ it("结构不符的行(缺 event / v≠1)跳过", () => {
164
+ const path = tmpPath("shape/journal-bg-9.jsonl");
165
+ writeFileSync(
166
+ path,
167
+ [
168
+ JSON.stringify({ v: 1, ts: 1, taskId: "t", engineId: "pi", seq: 0, event: { type: "compaction" } }),
169
+ JSON.stringify({ v: 1, ts: 2, taskId: "t", engineId: "pi", seq: 1 }), // 无 event
170
+ JSON.stringify({ v: 2, ts: 3, taskId: "t", engineId: "pi", seq: 2, event: { type: "turn_end" } }), // v≠1
171
+ JSON.stringify({ v: 1, ts: 4, taskId: "t", engineId: "pi", seq: 3, event: "not-an-object" }),
172
+ ].join("\n") + "\n",
173
+ "utf8",
174
+ );
175
+ expect(replayJournal(path)).toEqual([{ type: "compaction" }]);
176
+ });
177
+ });
@@ -0,0 +1,192 @@
1
+ // kill-chain.test.ts —— 杀链两分支 + 超时终态合成 + abort 两级编排(fake timers)。
2
+ //
3
+ // 三视角:①构建者——SIGTERM 优雅 / 超时 SIGKILL 两分支的信号序列正确;②使用者——
4
+ // abortWithFallback 对 CLI-only 与 native-interrupt 引擎都收敛不悬挂;③观察者——
5
+ // 合成终态的 error 含 stdout 尾部与 exitCode=null(被信号杀死判据)。
6
+
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+
9
+ import {
10
+ abortWithFallback,
11
+ killChain,
12
+ synthesizeTimeoutOutcome,
13
+ } from "../../common/kill-chain.ts";
14
+ import type { KillableChild } from "../../common/kill-chain.ts";
15
+
16
+ /** fake 子进程:记录 kill 信号序列,emitExit 模拟退出(置退出态 + 触发 exit listener)。 */
17
+ function makeFakeChild(): {
18
+ child: KillableChild;
19
+ signals: string[];
20
+ emitExit(code: number | null, signal: NodeJS.Signals | null): void;
21
+ } {
22
+ const listeners: Array<(code: number | null, signal: NodeJS.Signals | null) => void> = [];
23
+ const signals: string[] = [];
24
+ const child: KillableChild = {
25
+ exitCode: null,
26
+ signalCode: null,
27
+ kill(signal?: NodeJS.Signals | number): boolean {
28
+ signals.push(String(signal ?? "SIGTERM"));
29
+ return true;
30
+ },
31
+ once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): KillableChild {
32
+ if (event === "exit") listeners.push(listener);
33
+ return child;
34
+ },
35
+ };
36
+ return {
37
+ child,
38
+ signals,
39
+ emitExit(code, signal) {
40
+ child.exitCode = code;
41
+ child.signalCode = signal;
42
+ for (const l of listeners.splice(0)) l(code, signal);
43
+ },
44
+ };
45
+ }
46
+
47
+ beforeEach(() => {
48
+ vi.useFakeTimers();
49
+ });
50
+
51
+ afterEach(() => {
52
+ vi.useRealTimers();
53
+ });
54
+
55
+ describe("killChain", () => {
56
+ it("SIGTERM 后进程优雅退出 → 'terminated',无 SIGKILL", async () => {
57
+ const { child, signals, emitExit } = makeFakeChild();
58
+ const p = killChain(child, { graceMs: 5_000 });
59
+ emitExit(0, null); // SIGTERM 被 trap 后正常退出
60
+ expect(await p).toBe("terminated");
61
+ expect(signals).toEqual(["SIGTERM"]);
62
+ });
63
+
64
+ it("SIGTERM 超时未退 → SIGKILL 兜底 → 'killed'(fake timers 推进两级窗口)", async () => {
65
+ const { child, signals } = makeFakeChild();
66
+ const p = killChain(child, { graceMs: 5_000 });
67
+ expect(signals).toEqual(["SIGTERM"]); // kill 同步发出
68
+ await vi.advanceTimersByTimeAsync(5_000); // grace 窗口走完,进程仍活
69
+ expect(signals).toEqual(["SIGTERM", "SIGKILL"]);
70
+ await vi.advanceTimersByTimeAsync(10_000); // SIGKILL 收尸窗口(进程不退也返回)
71
+ expect(await p).toBe("killed");
72
+ });
73
+
74
+ it("进程已退出(调用前)→ 不发任何信号,'terminated'", async () => {
75
+ const { child, signals, emitExit } = makeFakeChild();
76
+ emitExit(1, null);
77
+ expect(await killChain(child, { graceMs: 5_000 })).toBe("terminated");
78
+ expect(signals).toEqual([]);
79
+ });
80
+
81
+ it("grace 窗口内恰在超时前一毫秒退出 → 复核退出态,不误发 SIGKILL", async () => {
82
+ const { child, signals, emitExit } = makeFakeChild();
83
+ const p = killChain(child, { graceMs: 5_000 });
84
+ emitExit(null, "SIGTERM"); // 被 SIGTERM 杀死(signalCode 形态)
85
+ await vi.advanceTimersByTimeAsync(5_000);
86
+ expect(await p).toBe("terminated");
87
+ expect(signals).toEqual(["SIGTERM"]);
88
+ });
89
+ });
90
+
91
+ describe("synthesizeTimeoutOutcome", () => {
92
+ it("合成 engine_timeout 终态:error 含 slug + stdout 尾部 + engine: pi 重跑建议;exitCode=null", () => {
93
+ const outcome = synthesizeTimeoutOutcome(
94
+ { task: "review files", slug: "review-files" },
95
+ "last stdout lines...",
96
+ );
97
+ expect(outcome.error).toContain("engine_timeout");
98
+ expect(outcome.error).toContain("review-files");
99
+ expect(outcome.error).toContain("last stdout lines...");
100
+ expect(outcome.error).toMatch(/`engine: pi`/);
101
+ expect(outcome.exitCode).toBe(null);
102
+ expect(outcome.content).toBe("");
103
+ expect(outcome.engineId).toBe("pi");
104
+ });
105
+
106
+ it("stdout 尾部超 2000 字截断;engineId 可指定", () => {
107
+ const outcome = synthesizeTimeoutOutcome(
108
+ { task: "t", slug: "s" },
109
+ "y".repeat(3_000),
110
+ "zcode",
111
+ );
112
+ expect(outcome.engineId).toBe("zcode");
113
+ expect(outcome.error).toContain("...");
114
+ expect(outcome.error.length).toBeLessThan(3_000);
115
+ });
116
+ });
117
+
118
+ describe("abortWithFallback", () => {
119
+ it("CLI-only 引擎(无原生中断):abort → 直接杀链,SIGTERM 退 → 'terminated'", async () => {
120
+ const { child, signals, emitExit } = makeFakeChild();
121
+ const controller = new AbortController();
122
+ const p = abortWithFallback(child, controller.signal);
123
+ controller.abort();
124
+ emitExit(0, null); // SIGTERM 生效
125
+ expect(await p).toBe("terminated");
126
+ expect(signals).toEqual(["SIGTERM"]);
127
+ });
128
+
129
+ it("原生中断生效:abort → native interrupt → 进程退出,不走杀链(零 kill 信号)", async () => {
130
+ const { child, signals, emitExit } = makeFakeChild();
131
+ const controller = new AbortController();
132
+ const interrupt = vi.fn(async () => {
133
+ emitExit(0, null); // 引擎原生中断让进程优雅退出
134
+ });
135
+ const p = abortWithFallback(child, controller.signal, interrupt);
136
+ controller.abort();
137
+ expect(await p).toBe("terminated");
138
+ expect(interrupt).toHaveBeenCalledTimes(1);
139
+ expect(signals).toEqual([]); // 从未发信号——原生中断足量
140
+ });
141
+
142
+ it("原生中断无效(进程未停)→ 宽限窗口后落杀链 SIGKILL → 'killed'", async () => {
143
+ const { child, signals } = makeFakeChild();
144
+ const controller = new AbortController();
145
+ const interrupt = vi.fn(async () => undefined); // 中断送达但进程不退
146
+ const p = abortWithFallback(child, controller.signal, interrupt, {
147
+ nativeGraceMs: 3_000,
148
+ graceMs: 5_000,
149
+ });
150
+ controller.abort();
151
+ expect(interrupt).toHaveBeenCalledTimes(1);
152
+ await vi.advanceTimersByTimeAsync(3_000); // native 宽限窗口走完
153
+ expect(signals).toEqual(["SIGTERM"]);
154
+ await vi.advanceTimersByTimeAsync(5_000); // 杀链 grace 窗口走完
155
+ expect(signals).toEqual(["SIGTERM", "SIGKILL"]);
156
+ await vi.advanceTimersByTimeAsync(10_000);
157
+ expect(await p).toBe("killed");
158
+ });
159
+
160
+ it("原生中断 throw(协议错)→ 不阻断兜底,继续杀链", async () => {
161
+ const { child, signals, emitExit } = makeFakeChild();
162
+ const controller = new AbortController();
163
+ const interrupt = vi.fn(async () => {
164
+ throw new Error("rpc broken");
165
+ });
166
+ const p = abortWithFallback(child, controller.signal, interrupt, { nativeGraceMs: 100, graceMs: 5_000 });
167
+ controller.abort();
168
+ await vi.advanceTimersByTimeAsync(100);
169
+ emitExit(0, null); // 后续 SIGTERM 生效
170
+ expect(await p).toBe("terminated");
171
+ expect(signals).toEqual(["SIGTERM"]);
172
+ });
173
+
174
+ it("进程自然退出(signal 从未 abort)→ 'terminated',promise 不悬挂", async () => {
175
+ const { child, signals, emitExit } = makeFakeChild();
176
+ const controller = new AbortController();
177
+ const p = abortWithFallback(child, controller.signal);
178
+ emitExit(0, null);
179
+ expect(await p).toBe("terminated");
180
+ expect(signals).toEqual([]);
181
+ });
182
+
183
+ it("signal 已 abort 的场景(先 abort 后接线)→ 立即执行两级中断", async () => {
184
+ const { child, signals, emitExit } = makeFakeChild();
185
+ const controller = new AbortController();
186
+ controller.abort(); // 先 abort
187
+ const p = abortWithFallback(child, controller.signal);
188
+ emitExit(0, null);
189
+ expect(await p).toBe("terminated");
190
+ expect(signals).toEqual(["SIGTERM"]);
191
+ });
192
+ });
@@ -0,0 +1,81 @@
1
+ // nesting-guard.test.ts —— 嵌套标记注入与原生标记剥离(D8 双层防护)。
2
+ //
3
+ // 三视角:①构建者——三引擎原生标记(PI_SUBAGENT_* / CLAUDECODE / ZSW_NESTED)全部
4
+ // 剥离且其余 env 保留;②使用者——子代理进程内 assertNotNestedSpawn 同步拒绝且文案
5
+ // 可操作;③观察者——产出的 env 是新对象(不污染入参)。
6
+
7
+ import { describe, expect, it } from "vitest";
8
+
9
+ import {
10
+ assertNotNestedSpawn,
11
+ buildNestedSpawnEnv,
12
+ NESTED_SPAWN_ENV,
13
+ } from "../../common/nesting-guard.ts";
14
+ import { EngineError } from "../../common/errors.ts";
15
+
16
+ describe("buildNestedSpawnEnv", () => {
17
+ it("注入统一标记 XYZ_AGENT_SUBAGENT=1", () => {
18
+ const env = buildNestedSpawnEnv({ PATH: "/usr/bin" });
19
+ expect(env[NESTED_SPAWN_ENV]).toBe("1");
20
+ expect(env.PATH).toBe("/usr/bin");
21
+ });
22
+
23
+ it("剥离 pi 原生标记 PI_SUBAGENT_*(任意后缀)", () => {
24
+ const env = buildNestedSpawnEnv({
25
+ PI_SUBAGENT_ID: "bg-1",
26
+ PI_SUBAGENT_CWD: "/tmp/x",
27
+ PI_SUBAGENT_SESSION: "abc",
28
+ KEEP_ME: "yes",
29
+ });
30
+ expect("PI_SUBAGENT_ID" in env).toBe(false);
31
+ expect("PI_SUBAGENT_CWD" in env).toBe(false);
32
+ expect("PI_SUBAGENT_SESSION" in env).toBe(false);
33
+ expect(env.KEEP_ME).toBe("yes");
34
+ });
35
+
36
+ it("剥离 CC 与 zsub 的原生标记(CLAUDECODE / ZSW_NESTED)", () => {
37
+ const env = buildNestedSpawnEnv({
38
+ CLAUDECODE: "1",
39
+ ZSW_NESTED: "1",
40
+ CLAUDE_UNRELATED: "keep", // 非嵌套标记的 CLAUDE_ 前缀不受影响
41
+ });
42
+ expect("CLAUDECODE" in env).toBe(false);
43
+ expect("ZSW_NESTED" in env).toBe(false);
44
+ expect(env.CLAUDE_UNRELATED).toBe("keep");
45
+ });
46
+
47
+ it("返回新对象,不 mutate 入参(spawn env 组装链安全)", () => {
48
+ const base = { PATH: "/usr/bin", PI_SUBAGENT_ID: "x" };
49
+ const env = buildNestedSpawnEnv(base);
50
+ expect(base.PI_SUBAGENT_ID).toBe("x");
51
+ expect("XYZ_AGENT_SUBAGENT" in base).toBe(false);
52
+ expect(env).not.toBe(base);
53
+ });
54
+
55
+ it("空白 env 也能产出仅含标记的最小 env", () => {
56
+ const env = buildNestedSpawnEnv({});
57
+ expect(env).toEqual({ [NESTED_SPAWN_ENV]: "1" });
58
+ });
59
+ });
60
+
61
+ describe("assertNotNestedSpawn", () => {
62
+ it("检测到统一标记(本进程已是 subagent)→ 抛 nested_spawn_rejected", () => {
63
+ expect(() => assertNotNestedSpawn({ [NESTED_SPAWN_ENV]: "1" })).toThrowError(EngineError);
64
+ try {
65
+ assertNotNestedSpawn({ [NESTED_SPAWN_ENV]: "1" });
66
+ expect.unreachable("should throw");
67
+ } catch (err) {
68
+ const e = err as EngineError;
69
+ expect(e.code).toBe("nested_spawn_rejected");
70
+ // 文案说明防护规则(标记名)+ 指向 task 内自行完成
71
+ expect(e.message).toContain("XYZ_AGENT_SUBAGENT");
72
+ expect(e.recovery).toMatch(/inside the current task/);
73
+ }
74
+ });
75
+
76
+ it("无标记 / 值非 '1' → 不抛(顶层进程正常派发)", () => {
77
+ expect(() => assertNotNestedSpawn({})).not.toThrow();
78
+ expect(() => assertNotNestedSpawn({ [NESTED_SPAWN_ENV]: "0" })).not.toThrow();
79
+ expect(() => assertNotNestedSpawn({ [NESTED_SPAWN_ENV]: undefined })).not.toThrow();
80
+ });
81
+ });
@@ -0,0 +1,123 @@
1
+ // persona-router.test.ts —— persona 三策略路由 + argv 预算前置拦截。
2
+ //
3
+ // 三视角:①构建者——三通道产出物各归其位;②使用者——argv 超限时报
4
+ // prompt_too_large 且文案含三条恢复建议;③观察者——估算口径含多字节字符与分隔符。
5
+
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ import {
9
+ applyPersona,
10
+ assertArgvBudget,
11
+ DEFAULT_ARGV_BUDGET_BYTES,
12
+ estimateArgvBytes,
13
+ } from "../../common/persona-router.ts";
14
+ import { EngineError } from "../../common/errors.ts";
15
+ import type { EngineCapabilities } from "../../types.ts";
16
+
17
+ const CAPS_FILE: EngineCapabilities = {
18
+ schemaEnforcement: "emulated", steer: "unsupported", conversation: "unsupported",
19
+ personaInjection: "file", eventGranularity: "coarse", sandbox: "none",
20
+ sessionRead: "partial", resume: "cold", interrupt: "kill-only", permissionMode: "native",
21
+ };
22
+ const CAPS_FLAG: EngineCapabilities = { ...CAPS_FILE, personaInjection: "flag" };
23
+ const CAPS_PROMPT: EngineCapabilities = { ...CAPS_FILE, personaInjection: "prompt" };
24
+
25
+ const persona = {
26
+ agentRef: "reviewer",
27
+ skillPath: "/skills/review.md",
28
+ appendSystemPrompt: ["You are a careful code reviewer.", "Be terse."],
29
+ };
30
+
31
+ describe("applyPersona 三策略路由", () => {
32
+ it("file 策略:人设全文进 fileCandidate(路径相对池目录),promptSegment 为空", () => {
33
+ const routing = applyPersona(persona, CAPS_FILE);
34
+ expect(routing.promptSegment).toBe("");
35
+ expect(routing.fileCandidate).toBeDefined();
36
+ expect(routing.fileCandidate?.suggestedPath).toBe("persona.md");
37
+ expect(routing.fileCandidate?.content).toContain("# Agent: reviewer");
38
+ expect(routing.fileCandidate?.content).toContain("/skills/review.md");
39
+ expect(routing.fileCandidate?.content).toContain("You are a careful code reviewer.");
40
+ });
41
+
42
+ it("flag 策略:promptSegment 为纯人设正文(launcher 组 flag 直传),无 fileCandidate", () => {
43
+ const routing = applyPersona(persona, CAPS_FLAG);
44
+ expect(routing.fileCandidate).toBeUndefined();
45
+ expect(routing.promptSegment).toContain("You are a careful code reviewer.");
46
+ expect(routing.promptSegment).toContain("Be terse.");
47
+ // 纯正文:不带 prompt 通道的结构头
48
+ expect(routing.promptSegment).not.toContain("## Persona");
49
+ });
50
+
51
+ it("prompt 策略:promptSegment 为带结构头的人设段(拼进最终 prompt)", () => {
52
+ const routing = applyPersona(persona, CAPS_PROMPT);
53
+ expect(routing.promptSegment.startsWith("## Persona")).toBe(true);
54
+ expect(routing.promptSegment).toContain("# Agent: reviewer");
55
+ expect(routing.promptSegment).toContain("You are a careful code reviewer.");
56
+ });
57
+
58
+ it("空 persona(全字段缺省):三通道都产出空载体(白占 argv/prompt 无意义)", () => {
59
+ expect(applyPersona({}, CAPS_FILE)).toEqual({ promptSegment: "" });
60
+ expect(applyPersona({}, CAPS_FLAG)).toEqual({ promptSegment: "" });
61
+ expect(applyPersona({}, CAPS_PROMPT)).toEqual({ promptSegment: "" });
62
+ });
63
+
64
+ it("仅 appendSystemPrompt:file 通道照常落文件(agentRef/skillPath 头部行缺省)", () => {
65
+ const routing = applyPersona({ appendSystemPrompt: ["only body"] }, CAPS_FILE);
66
+ expect(routing.fileCandidate?.content).toBe("only body");
67
+ });
68
+ });
69
+
70
+ describe("estimateArgvBytes", () => {
71
+ it("ASCII:字节数之和 + 每参数 1 个 NUL 分隔符", () => {
72
+ // "ab"(2) + "cde"(3) + 2 个 NUL = 7
73
+ expect(estimateArgvBytes(["ab", "cde"])).toBe(7);
74
+ expect(estimateArgvBytes([])).toBe(0);
75
+ });
76
+
77
+ it("多字节字符按 UTF-8 计(中文 3 字节/字)——字符数口径会低估", () => {
78
+ // "中"=3 字节 + 1 NUL = 4
79
+ expect(estimateArgvBytes(["中"])).toBe(4);
80
+ // "a中b" = 1+3+1 = 5 字节 + 1 = 6
81
+ expect(estimateArgvBytes(["a中b"])).toBe(6);
82
+ });
83
+ });
84
+
85
+ describe("assertArgvBudget", () => {
86
+ it(`默认上限 ${DEFAULT_ARGV_BUDGET_BYTES} 字节(128KB)且预算内通过`, () => {
87
+ expect(DEFAULT_ARGV_BUDGET_BYTES).toBe(128 * 1024);
88
+ expect(() => assertArgvBudget(["--prompt", "short"])).not.toThrow();
89
+ });
90
+
91
+ it("超限抛 prompt_too_large:detail 含实际/上限字节数", () => {
92
+ const bigArg = "x".repeat(1024);
93
+ const argv = [bigArg, bigArg]; // 2KB,limit 1KB
94
+ let caught: EngineError | undefined;
95
+ try {
96
+ assertArgvBudget(argv, 1024);
97
+ expect.unreachable("should throw");
98
+ } catch (err) {
99
+ caught = err as EngineError;
100
+ }
101
+ expect(caught).toBeInstanceOf(EngineError);
102
+ expect(caught?.code).toBe("prompt_too_large");
103
+ expect(caught?.message).toContain("2050"); // 1024*2 + 2 NUL
104
+ expect(caught?.message).toContain("1024");
105
+ });
106
+
107
+ it("错误恢复指引含三条建议:缩短 task / persona 移 file 通道 / 换 stdin 引擎", () => {
108
+ try {
109
+ assertArgvBudget(["y".repeat(3000)], 1024);
110
+ expect.unreachable("should throw");
111
+ } catch (err) {
112
+ const e = err as EngineError;
113
+ expect(e.recovery).toMatch(/shorten the task/i);
114
+ expect(e.recovery).toMatch(/file channel/);
115
+ expect(e.recovery).toMatch(/stdin/);
116
+ }
117
+ });
118
+
119
+ it("limit 可配(调用方收紧到引擎实测限额)", () => {
120
+ expect(() => assertArgvBudget(["a".repeat(100)], 50)).toThrowError(/prompt_too_large/);
121
+ expect(() => assertArgvBudget(["a".repeat(100)], 101)).not.toThrow();
122
+ });
123
+ });