airaknit 1.1.2-rc.9

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 (440) hide show
  1. package/LICENSE +84 -0
  2. package/README.md +202 -0
  3. package/bin/airaknit +9 -0
  4. package/bin/airaknit-project +14 -0
  5. package/bin/kanna +9 -0
  6. package/dist/client/assets/CompactSummaryMessage-Yw0BDWEJ.js +1 -0
  7. package/dist/client/assets/ExitPlanModeMessage-DIdkQ4uF.js +1 -0
  8. package/dist/client/assets/LocalFilePreviewDialog-DQx2eiCc.js +3 -0
  9. package/dist/client/assets/LocalProjectsSection-C4xlWkgS.js +1 -0
  10. package/dist/client/assets/TextMessage-B5G39DEJ.js +1 -0
  11. package/dist/client/assets/UserMessage-CIkWk-0L.js +1 -0
  12. package/dist/client/assets/_basePickBy-CVrAFfnZ.js +1 -0
  13. package/dist/client/assets/_baseUniq-JL-aaF4P.js +1 -0
  14. package/dist/client/assets/arc-B07zg7ol.js +1 -0
  15. package/dist/client/assets/architecture-YZFGNWBL-PSLVJL3p.js +1 -0
  16. package/dist/client/assets/architectureDiagram-Q4EWVU46-DfWIF1G_.js +36 -0
  17. package/dist/client/assets/array-BGFCBI0e.js +1 -0
  18. package/dist/client/assets/blockDiagram-DXYQGD6D-CzwIeo_B.js +132 -0
  19. package/dist/client/assets/bundle-mjs-BDE2gWbQ.js +1 -0
  20. package/dist/client/assets/button-DO50qOGv.js +1 -0
  21. package/dist/client/assets/c4Diagram-AHTNJAMY-CR8DCQRE.js +10 -0
  22. package/dist/client/assets/channel-Dj-UUfaF.js +1 -0
  23. package/dist/client/assets/chunk-2KRD3SAO-dznP-cn8.js +1 -0
  24. package/dist/client/assets/chunk-336JU56O-Dqss5Vu6.js +2 -0
  25. package/dist/client/assets/chunk-426QAEUC-CEKis_0_.js +1 -0
  26. package/dist/client/assets/chunk-4BX2VUAB-BYOv0Gm1.js +1 -0
  27. package/dist/client/assets/chunk-4TB4RGXK-BxzubH5S.js +206 -0
  28. package/dist/client/assets/chunk-55IACEB6-BSlTj03a.js +1 -0
  29. package/dist/client/assets/chunk-5FUZZQ4R-9Au93Bi1.js +62 -0
  30. package/dist/client/assets/chunk-5PVQY5BW-BhksHFEZ.js +2 -0
  31. package/dist/client/assets/chunk-67CJDMHE-BFwhz-8t.js +1 -0
  32. package/dist/client/assets/chunk-7N4EOEYR-BDUPds87.js +1 -0
  33. package/dist/client/assets/chunk-AA7GKIK3-CEWTdyXO.js +1 -0
  34. package/dist/client/assets/chunk-BO2N2NFS-D0LvxnhU.js +103 -0
  35. package/dist/client/assets/chunk-BSJP7CBP-BNJnK6sq.js +1 -0
  36. package/dist/client/assets/chunk-Bj-mKKzh.js +1 -0
  37. package/dist/client/assets/chunk-CIAEETIT-CYhfoCeN.js +1 -0
  38. package/dist/client/assets/chunk-EDXVE4YY-C5ovJLc0.js +1 -0
  39. package/dist/client/assets/chunk-ENJZ2VHE-HOhYaeGr.js +10 -0
  40. package/dist/client/assets/chunk-FMBD7UC4-BLCiKcAQ.js +15 -0
  41. package/dist/client/assets/chunk-FOC6F5B3-B6GtY2ek.js +1 -0
  42. package/dist/client/assets/chunk-ICPOFSXX-DPoIZoC5.js +122 -0
  43. package/dist/client/assets/chunk-K5T4RW27-BsKN63rv.js +94 -0
  44. package/dist/client/assets/chunk-KGLVRYIC-BUGn9uuY.js +1 -0
  45. package/dist/client/assets/chunk-LIHQZDEY-DhaZyo03.js +1 -0
  46. package/dist/client/assets/chunk-ORNJ4GCN-DlpeeJyi.js +1 -0
  47. package/dist/client/assets/chunk-OYMX7WX6-Dc9q7aYA.js +231 -0
  48. package/dist/client/assets/chunk-QZHKN3VN-BEdrPoSb.js +1 -0
  49. package/dist/client/assets/chunk-U2HBQHQK-CIB3Bjjd.js +70 -0
  50. package/dist/client/assets/chunk-X2U36JSP-CtB-o8Yp.js +1 -0
  51. package/dist/client/assets/chunk-XPW4576I-C6iHhX_8.js +32 -0
  52. package/dist/client/assets/chunk-YZCP3GAM-CTmKr6ZH.js +1 -0
  53. package/dist/client/assets/chunk-ZZ45TVLE-BgU8A2RF.js +1 -0
  54. package/dist/client/assets/classDiagram-6PBFFD2Q-Bqk5e679.js +1 -0
  55. package/dist/client/assets/classDiagram-v2-HSJHXN6E-6pSaZOkC.js +1 -0
  56. package/dist/client/assets/client-BrKWI4CM.js +1 -0
  57. package/dist/client/assets/client-CGgNRU9w.js +1 -0
  58. package/dist/client/assets/client-DMSLRzg9.js +6 -0
  59. package/dist/client/assets/clone-DWcL7whJ.js +1 -0
  60. package/dist/client/assets/cose-bilkent-S5V4N54A-CrV5wsV_.js +1 -0
  61. package/dist/client/assets/cytoscape.esm--aLzKuep.js +321 -0
  62. package/dist/client/assets/dagre-CuRxWcrj.js +1 -0
  63. package/dist/client/assets/dagre-KV5264BT-BIDiVnkA.js +4 -0
  64. package/dist/client/assets/defaultLocale-CRZydyG6.js +1 -0
  65. package/dist/client/assets/diagram-5BDNPKRD-i1kjKRCB.js +10 -0
  66. package/dist/client/assets/diagram-G4DWMVQ6-9ZSLuhbl.js +24 -0
  67. package/dist/client/assets/diagram-MMDJMWI5-B4_CUjgv.js +43 -0
  68. package/dist/client/assets/diagram-TYMM5635-Ct5eTGS8.js +24 -0
  69. package/dist/client/assets/dist-CuB4kiSK.js +1 -0
  70. package/dist/client/assets/erDiagram-SMLLAGMA-Cy38ercc.js +85 -0
  71. package/dist/client/assets/flowDiagram-DWJPFMVM-CZKuYl0V.js +162 -0
  72. package/dist/client/assets/ganttDiagram-T4ZO3ILL-DLPjCh7a.js +292 -0
  73. package/dist/client/assets/gitGraph-7Q5UKJZL-DqbrtEp9.js +1 -0
  74. package/dist/client/assets/gitGraphDiagram-UUTBAWPF-BoRBkDhQ.js +106 -0
  75. package/dist/client/assets/graphlib-BcQ6qlQh.js +1 -0
  76. package/dist/client/assets/highlighted-body-OFNGDK62-BEpBVDTX.js +1 -0
  77. package/dist/client/assets/index-CetCiuqP.js +105 -0
  78. package/dist/client/assets/index-N29Mip7A.css +1 -0
  79. package/dist/client/assets/info-OMHHGYJF-D98DRBJX.js +1 -0
  80. package/dist/client/assets/infoDiagram-42DDH7IO-BAcdTWbt.js +2 -0
  81. package/dist/client/assets/init-B8gtcn7T.js +1 -0
  82. package/dist/client/assets/isArrayLikeObject-D8SJFmkN.js +1 -0
  83. package/dist/client/assets/isEmpty-BF3YX5Jk.js +1 -0
  84. package/dist/client/assets/ishikawaDiagram-UXIWVN3A-Ynu2VKdC.js +70 -0
  85. package/dist/client/assets/journeyDiagram-VCZTEJTY-BjfhQaN3.js +139 -0
  86. package/dist/client/assets/jsx-runtime-CyI9ICYU.js +1 -0
  87. package/dist/client/assets/kanban-definition-6JOO6SKY-JLXH9zUJ.js +89 -0
  88. package/dist/client/assets/katex-B94qP8b6.js +265 -0
  89. package/dist/client/assets/lib--QVjyxmL.js +29 -0
  90. package/dist/client/assets/lib-B6rgJiZ9.js +1 -0
  91. package/dist/client/assets/line-DCrYfLBn.js +1 -0
  92. package/dist/client/assets/linear-_4upLmeo.js +1 -0
  93. package/dist/client/assets/mermaid-GHXKKRXX-rwJHYUmW.js +1 -0
  94. package/dist/client/assets/mermaid-parser.core-KZinfW8o.js +4 -0
  95. package/dist/client/assets/mermaid.core-QqY9gSNe.js +11 -0
  96. package/dist/client/assets/mindmap-definition-QFDTVHPH-TWgHDAzp.js +96 -0
  97. package/dist/client/assets/ordinal-CCj7PWgZ.js +1 -0
  98. package/dist/client/assets/packet-4T2RLAQJ-DEvfkn3F.js +1 -0
  99. package/dist/client/assets/path-DZF-JdEe.js +1 -0
  100. package/dist/client/assets/pie-ZZUOXDRM-72e6WVjb.js +1 -0
  101. package/dist/client/assets/pieDiagram-DEJITSTG-Cl8PCsoj.js +30 -0
  102. package/dist/client/assets/preload-helper-rov5CBGT.js +1 -0
  103. package/dist/client/assets/pty-client-DZ27IS00.js +1 -0
  104. package/dist/client/assets/ptyInstancesStore-D9ag7SYd.js +1 -0
  105. package/dist/client/assets/quadrantDiagram-34T5L4WZ-CHyVGp9E.js +7 -0
  106. package/dist/client/assets/radar-PYXPWWZC-Cp7xd_EY.js +1 -0
  107. package/dist/client/assets/react-CClhXMB2.js +1 -0
  108. package/dist/client/assets/react-Dd6D81m0.js +1 -0
  109. package/dist/client/assets/react-dom--G6_6fQ_.js +1 -0
  110. package/dist/client/assets/requirementDiagram-MS252O5E-DaanG2iM.js +84 -0
  111. package/dist/client/assets/rough.esm-BsmKo2S5.js +1 -0
  112. package/dist/client/assets/sankeyDiagram-XADWPNL6-B_fhLY36.js +10 -0
  113. package/dist/client/assets/sequenceDiagram-FGHM5R23-C5FNrveI.js +157 -0
  114. package/dist/client/assets/src-DeTlMJAU.js +1 -0
  115. package/dist/client/assets/stateDiagram-FHFEXIEX-nTTcdjjQ.js +1 -0
  116. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Dw0632j_.js +1 -0
  117. package/dist/client/assets/timeline-definition-GMOUNBTQ-DkQV1yP8.js +120 -0
  118. package/dist/client/assets/treeView-SZITEDCU-ZLIgC7_K.js +1 -0
  119. package/dist/client/assets/treemap-W4RFUUIX-BqaMbB6N.js +1 -0
  120. package/dist/client/assets/uiIdentityOverlay-Ba7GNj7m.js +1 -0
  121. package/dist/client/assets/vennDiagram-DHZGUBPP-DbZ2xgs6.js +34 -0
  122. package/dist/client/assets/wardley-RL74JXVD-DXQS8zf4.js +1 -0
  123. package/dist/client/assets/wardleyDiagram-NUSXRM2D-BzCJ6MAu.js +20 -0
  124. package/dist/client/assets/xychartDiagram-5P7HB3ND-BSlFecop.js +7 -0
  125. package/dist/client/favicon.png +0 -0
  126. package/dist/client/favicon.svg +15 -0
  127. package/dist/client/fonts/body-medium.woff2 +0 -0
  128. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  129. package/dist/client/fonts/body-regular.woff2 +0 -0
  130. package/dist/client/fonts/body-semibold.woff2 +0 -0
  131. package/dist/client/index.html +31 -0
  132. package/dist/client/manifest.webmanifest +12 -0
  133. package/dist/client/sw.js +32 -0
  134. package/package.json +122 -0
  135. package/src/nats/auth-callout/callout-config.test.ts +93 -0
  136. package/src/nats/auth-callout/callout-config.ts +109 -0
  137. package/src/nats/auth-callout/callout.integration.test.ts +332 -0
  138. package/src/nats/auth-callout/keys.ts +103 -0
  139. package/src/nats/auth-callout/responder.ts +241 -0
  140. package/src/nats/auth-callout/scope-policy.test.ts +159 -0
  141. package/src/nats/auth-callout/scope-policy.ts +210 -0
  142. package/src/nats/auth-callout/token.test.ts +163 -0
  143. package/src/nats/auth-callout/token.ts +157 -0
  144. package/src/nats/nats-daemon-callout.ts +194 -0
  145. package/src/nats/nats-daemon.test.ts +77 -0
  146. package/src/nats/nats-daemon.ts +50 -0
  147. package/src/nats/nats-token.test.ts +61 -0
  148. package/src/nats/nats-token.ts +59 -0
  149. package/src/runner/coordination-mcp-integration.test.ts +134 -0
  150. package/src/runner/nats-coordination-client.test.ts +49 -0
  151. package/src/runner/nats-coordination-client.ts +94 -0
  152. package/src/runner/runner-agent.test.ts +469 -0
  153. package/src/runner/runner-agent.ts +453 -0
  154. package/src/runner/runner-credential.test.ts +93 -0
  155. package/src/runner/runner-credential.ts +82 -0
  156. package/src/runner/runner-nats.test.ts +495 -0
  157. package/src/runner/runner-nats.ts +323 -0
  158. package/src/runner/runner-pair.test.ts +107 -0
  159. package/src/runner/runner-pair.ts +81 -0
  160. package/src/runner/runner.test.ts +135 -0
  161. package/src/runner/runner.ts +212 -0
  162. package/src/runner/turn-factories.test.ts +97 -0
  163. package/src/runner/turn-factories.ts +475 -0
  164. package/src/server/agent-config-journey.test.ts +106 -0
  165. package/src/server/agent.ts +8 -0
  166. package/src/server/auto-continue/auth-error-detector.ts +66 -0
  167. package/src/server/auto-continue/limit-detector.ts +194 -0
  168. package/src/server/bm25.test.ts +92 -0
  169. package/src/server/bm25.ts +101 -0
  170. package/src/server/chat-events-jetstream.test.ts +135 -0
  171. package/src/server/claude-harness.ts +360 -0
  172. package/src/server/claude-pty/agent-normalizers.ts +309 -0
  173. package/src/server/claude-pty/auth.test.ts +38 -0
  174. package/src/server/claude-pty/auth.ts +32 -0
  175. package/src/server/claude-pty/claude-session-registry.adapter.ts +81 -0
  176. package/src/server/claude-pty/claude-session-registry.test.ts +149 -0
  177. package/src/server/claude-pty/driver.test.ts +902 -0
  178. package/src/server/claude-pty/driver.ts +807 -0
  179. package/src/server/claude-pty/jsonl-path.adapter.ts +57 -0
  180. package/src/server/claude-pty/jsonl-path.test.ts +114 -0
  181. package/src/server/claude-pty/jsonl-to-event.test.ts +241 -0
  182. package/src/server/claude-pty/jsonl-to-event.ts +174 -0
  183. package/src/server/claude-pty/output-ring.test.ts +35 -0
  184. package/src/server/claude-pty/output-ring.ts +25 -0
  185. package/src/server/claude-pty/parity-matrix.test.ts +227 -0
  186. package/src/server/claude-pty/pid-registry.adapter.ts +135 -0
  187. package/src/server/claude-pty/pid-registry.test.ts +122 -0
  188. package/src/server/claude-pty/preflight/binary-fingerprint.adapter.ts +20 -0
  189. package/src/server/claude-pty/preflight/binary-fingerprint.test.ts +32 -0
  190. package/src/server/claude-pty/pty-instance-registry.test.ts +177 -0
  191. package/src/server/claude-pty/pty-instance-registry.ts +166 -0
  192. package/src/server/claude-pty/pty-memory-sampler.adapter.test.ts +103 -0
  193. package/src/server/claude-pty/pty-memory-sampler.adapter.ts +85 -0
  194. package/src/server/claude-pty/pty-process.adapter.ts +66 -0
  195. package/src/server/claude-pty/pty-process.test.ts +49 -0
  196. package/src/server/claude-pty/resolve-binary.adapter.ts +106 -0
  197. package/src/server/claude-pty/resolve-binary.test.ts +118 -0
  198. package/src/server/claude-pty/runtime-dir.adapter.ts +19 -0
  199. package/src/server/claude-pty/settings-writer.adapter.ts +27 -0
  200. package/src/server/claude-pty/settings-writer.test.ts +22 -0
  201. package/src/server/claude-pty/smoke-test-io.adapter.ts +28 -0
  202. package/src/server/claude-pty/smoke-test.test.ts +191 -0
  203. package/src/server/claude-pty/smoke-test.ts +185 -0
  204. package/src/server/claude-pty/subagent-orchestrator.ts +887 -0
  205. package/src/server/claude-pty/tool-callback.ts +274 -0
  206. package/src/server/claude-pty/tui-control.test.ts +272 -0
  207. package/src/server/claude-pty/tui-control.ts +182 -0
  208. package/src/server/claude-pty/tui-source.adapter.test.ts +360 -0
  209. package/src/server/claude-pty/tui-source.adapter.ts +343 -0
  210. package/src/server/claude-pty/tunnel-gateway.ts +12 -0
  211. package/src/server/claude-pty-mcp/canonical-args.ts +15 -0
  212. package/src/server/claude-pty-mcp/fs-stat.adapter.ts +8 -0
  213. package/src/server/claude-pty-mcp/history-primer.ts +90 -0
  214. package/src/server/claude-pty-mcp/http-server.adapter.ts +33 -0
  215. package/src/server/claude-pty-mcp/mcp-http.ts +177 -0
  216. package/src/server/claude-pty-mcp/mcp.ts +412 -0
  217. package/src/server/claude-pty-mcp/mention-parser.ts +25 -0
  218. package/src/server/claude-pty-mcp/paths.ts +24 -0
  219. package/src/server/claude-pty-mcp/permission-gate.ts +243 -0
  220. package/src/server/claude-pty-mcp/terminal-pid-registry.adapter.ts +107 -0
  221. package/src/server/claude-pty-mcp/tools/ask-user-question.test.ts +119 -0
  222. package/src/server/claude-pty-mcp/tools/ask-user-question.ts +61 -0
  223. package/src/server/claude-pty-mcp/tools/bash.adapter.ts +76 -0
  224. package/src/server/claude-pty-mcp/tools/bash.test.ts +56 -0
  225. package/src/server/claude-pty-mcp/tools/delegate-subagent.test.ts +155 -0
  226. package/src/server/claude-pty-mcp/tools/delegate-subagent.ts +111 -0
  227. package/src/server/claude-pty-mcp/tools/edit.adapter.ts +95 -0
  228. package/src/server/claude-pty-mcp/tools/edit.test.ts +93 -0
  229. package/src/server/claude-pty-mcp/tools/exit-plan-mode.test.ts +61 -0
  230. package/src/server/claude-pty-mcp/tools/exit-plan-mode.ts +50 -0
  231. package/src/server/claude-pty-mcp/tools/glob.adapter.ts +86 -0
  232. package/src/server/claude-pty-mcp/tools/glob.test.ts +61 -0
  233. package/src/server/claude-pty-mcp/tools/grep.adapter.ts +126 -0
  234. package/src/server/claude-pty-mcp/tools/grep.test.ts +62 -0
  235. package/src/server/claude-pty-mcp/tools/read.adapter.ts +58 -0
  236. package/src/server/claude-pty-mcp/tools/read.test.ts +62 -0
  237. package/src/server/claude-pty-mcp/tools/tool-callback-shim.ts +42 -0
  238. package/src/server/claude-pty-mcp/tools/webfetch.test.ts +81 -0
  239. package/src/server/claude-pty-mcp/tools/webfetch.ts +82 -0
  240. package/src/server/claude-pty-mcp/tools/websearch.test.ts +40 -0
  241. package/src/server/claude-pty-mcp/tools/websearch.ts +42 -0
  242. package/src/server/claude-pty-mcp/tools/write.adapter.ts +60 -0
  243. package/src/server/claude-pty-mcp/tools/write.test.ts +52 -0
  244. package/src/server/claude-pty-mcp/uploads.adapter.ts +98 -0
  245. package/src/server/claude-pty-mcp/uploads.ts +38 -0
  246. package/src/server/claude-turn.test.ts +176 -0
  247. package/src/server/cli-runtime.test.ts +456 -0
  248. package/src/server/cli-runtime.ts +374 -0
  249. package/src/server/cli-supervisor.ts +81 -0
  250. package/src/server/cli.ts +78 -0
  251. package/src/server/client-log-forwarder.test.ts +74 -0
  252. package/src/server/client-log-forwarder.ts +75 -0
  253. package/src/server/codex-app-server-protocol.ts +449 -0
  254. package/src/server/codex-app-server.test.ts +2990 -0
  255. package/src/server/codex-app-server.ts +1713 -0
  256. package/src/server/coordination-integration.test.ts +63 -0
  257. package/src/server/coordination-mcp.test.ts +149 -0
  258. package/src/server/coordination-mcp.ts +197 -0
  259. package/src/server/delegation-coordinator.test.ts +675 -0
  260. package/src/server/delegation-coordinator.ts +454 -0
  261. package/src/server/discovery.test.ts +211 -0
  262. package/src/server/discovery.ts +301 -0
  263. package/src/server/event-store-agent-config.test.ts +124 -0
  264. package/src/server/event-store-coordination.test.ts +149 -0
  265. package/src/server/event-store-profile.test.ts +132 -0
  266. package/src/server/event-store-repo.test.ts +154 -0
  267. package/src/server/event-store-runner-team.test.ts +104 -0
  268. package/src/server/event-store.test.ts +342 -0
  269. package/src/server/event-store.ts +2208 -0
  270. package/src/server/events.ts +379 -0
  271. package/src/server/extension-router.test.ts +183 -0
  272. package/src/server/extension-router.ts +114 -0
  273. package/src/server/extensions/agents/server.test.ts +191 -0
  274. package/src/server/extensions/agents/server.ts +108 -0
  275. package/src/server/extensions/c3/server.test.ts +284 -0
  276. package/src/server/extensions/c3/server.ts +212 -0
  277. package/src/server/extensions/code/server.test.ts +200 -0
  278. package/src/server/extensions/code/server.ts +150 -0
  279. package/src/server/extensions.config.ts +10 -0
  280. package/src/server/external-open.ts +69 -0
  281. package/src/server/generate-fork-context.ts +58 -0
  282. package/src/server/generate-merge-context.test.ts +290 -0
  283. package/src/server/generate-merge-context.ts +141 -0
  284. package/src/server/generate-title.ts +36 -0
  285. package/src/server/git-clone-policy.test.ts +138 -0
  286. package/src/server/git-clone-policy.ts +27 -0
  287. package/src/server/harness-types.ts +1 -0
  288. package/src/server/journey-verification.test.ts +640 -0
  289. package/src/server/journey-verification.ts +195 -0
  290. package/src/server/machine-name.ts +22 -0
  291. package/src/server/nats-auth.test.ts +92 -0
  292. package/src/server/nats-auth.ts +6 -0
  293. package/src/server/nats-bind-guard.test.ts +71 -0
  294. package/src/server/nats-bind-guard.ts +42 -0
  295. package/src/server/nats-bridge.test.ts +141 -0
  296. package/src/server/nats-bridge.ts +111 -0
  297. package/src/server/nats-connector.test.ts +56 -0
  298. package/src/server/nats-connector.ts +71 -0
  299. package/src/server/nats-daemon-manager.test.ts +76 -0
  300. package/src/server/nats-daemon-manager.ts +174 -0
  301. package/src/server/nats-publisher.test.ts +356 -0
  302. package/src/server/nats-publisher.ts +271 -0
  303. package/src/server/nats-responders.test.ts +1018 -0
  304. package/src/server/nats-responders.ts +925 -0
  305. package/src/server/nats-streams.test.ts +112 -0
  306. package/src/server/nats-streams.ts +129 -0
  307. package/src/server/oauth-pool/oauth-responders.ts +185 -0
  308. package/src/server/oauth-pool/oauth-settings-store.ts +173 -0
  309. package/src/server/oauth-pool/oauth-token-pool.ts +303 -0
  310. package/src/server/orchestration.test.ts +1063 -0
  311. package/src/server/orchestration.ts +716 -0
  312. package/src/server/pairing-endpoints.test.ts +171 -0
  313. package/src/server/pairing-store.test.ts +154 -0
  314. package/src/server/pairing-store.ts +124 -0
  315. package/src/server/paths.ts +27 -0
  316. package/src/server/pr3-liveness.test.ts +252 -0
  317. package/src/server/process-utils.ts +10 -0
  318. package/src/server/project-cli.ts +180 -0
  319. package/src/server/provider-catalog.test.ts +177 -0
  320. package/src/server/provider-catalog.ts +146 -0
  321. package/src/server/pty-responders.ts +345 -0
  322. package/src/server/push-notifications.test.ts +234 -0
  323. package/src/server/push-notifications.ts +188 -0
  324. package/src/server/quick-response.test.ts +196 -0
  325. package/src/server/quick-response.ts +154 -0
  326. package/src/server/read-models-agent-config.test.ts +59 -0
  327. package/src/server/read-models-coordination.test.ts +69 -0
  328. package/src/server/read-models.test.ts +332 -0
  329. package/src/server/read-models.ts +258 -0
  330. package/src/server/repo-journey.test.ts +96 -0
  331. package/src/server/repo-manager.test.ts +118 -0
  332. package/src/server/repo-manager.ts +97 -0
  333. package/src/server/repo-status.test.ts +54 -0
  334. package/src/server/repo-status.ts +82 -0
  335. package/src/server/restart.test.ts +27 -0
  336. package/src/server/restart.ts +30 -0
  337. package/src/server/runner-incompatible-gate.test.ts +383 -0
  338. package/src/server/runner-manager.test.ts +72 -0
  339. package/src/server/runner-manager.ts +312 -0
  340. package/src/server/runner-pairing-urls.test.ts +59 -0
  341. package/src/server/runner-pairing-urls.ts +67 -0
  342. package/src/server/runner-proxy.test.ts +456 -0
  343. package/src/server/runner-proxy.ts +494 -0
  344. package/src/server/runner-router.test.ts +429 -0
  345. package/src/server/runner-router.ts +212 -0
  346. package/src/server/runner-routing.test.ts +584 -0
  347. package/src/server/runtime-registry.test.ts +436 -0
  348. package/src/server/runtime-registry.ts +307 -0
  349. package/src/server/sandbox-health.test.ts +127 -0
  350. package/src/server/sandbox-health.ts +94 -0
  351. package/src/server/sandbox-journey.test.ts +232 -0
  352. package/src/server/sandbox-manager.test.ts +146 -0
  353. package/src/server/sandbox-manager.ts +159 -0
  354. package/src/server/server.test.ts +287 -0
  355. package/src/server/server.ts +1108 -0
  356. package/src/server/session-discovery.test.ts +129 -0
  357. package/src/server/session-discovery.ts +475 -0
  358. package/src/server/session-index.test.ts +362 -0
  359. package/src/server/session-index.ts +119 -0
  360. package/src/server/session-seed.ts +288 -0
  361. package/src/server/share.test.ts +108 -0
  362. package/src/server/share.ts +113 -0
  363. package/src/server/skill-discovery.test.ts +201 -0
  364. package/src/server/skill-discovery.ts +77 -0
  365. package/src/server/storage/test-helpers.ts +67 -0
  366. package/src/server/terminal-manager.test.ts +309 -0
  367. package/src/server/terminal-manager.ts +354 -0
  368. package/src/server/transcript-consumer.test.ts +339 -0
  369. package/src/server/transcript-consumer.ts +162 -0
  370. package/src/server/transcript-search.test.ts +193 -0
  371. package/src/server/transcript-search.ts +83 -0
  372. package/src/server/transcript-utils.ts +52 -0
  373. package/src/server/update-manager.test.ts +107 -0
  374. package/src/server/update-manager.ts +230 -0
  375. package/src/server/workflow-engine.test.ts +251 -0
  376. package/src/server/workflow-engine.ts +169 -0
  377. package/src/server/workflow-mcp.ts +49 -0
  378. package/src/server/workflow-store.test.ts +155 -0
  379. package/src/server/workflow-store.ts +139 -0
  380. package/src/server/workspace-agent-integration.test.ts +167 -0
  381. package/src/server/workspace-agent-routes.test.ts +127 -0
  382. package/src/server/workspace-agent-routes.ts +89 -0
  383. package/src/server/workspace-agent.test.ts +103 -0
  384. package/src/server/workspace-agent.ts +102 -0
  385. package/src/server/workspace-cli.test.ts +79 -0
  386. package/src/server/workspace-config-manager.test.ts +109 -0
  387. package/src/server/workspace-config-manager.ts +83 -0
  388. package/src/server/workspace-directory-policy.test.ts +109 -0
  389. package/src/server/workspace-directory-policy.ts +56 -0
  390. package/src/shared/agent-config-types.ts +25 -0
  391. package/src/shared/branding.test.ts +42 -0
  392. package/src/shared/branding.ts +54 -0
  393. package/src/shared/compression.test.ts +85 -0
  394. package/src/shared/compression.ts +42 -0
  395. package/src/shared/coordination-store.test.ts +24 -0
  396. package/src/shared/coordination-store.ts +26 -0
  397. package/src/shared/dev-ports.test.ts +84 -0
  398. package/src/shared/dev-ports.ts +100 -0
  399. package/src/shared/extension-types.ts +45 -0
  400. package/src/shared/fork-presets.ts +54 -0
  401. package/src/shared/harness-types.test.ts +15 -0
  402. package/src/shared/harness-types.ts +21 -0
  403. package/src/shared/log-sink.test.ts +112 -0
  404. package/src/shared/log-sink.ts +185 -0
  405. package/src/shared/mention-pattern.ts +7 -0
  406. package/src/shared/merge-presets.ts +41 -0
  407. package/src/shared/nats-subjects.test.ts +61 -0
  408. package/src/shared/nats-subjects.ts +131 -0
  409. package/src/shared/permission-policy.ts +136 -0
  410. package/src/shared/ports.ts +3 -0
  411. package/src/shared/preset-types.ts +15 -0
  412. package/src/shared/profile-types.ts +49 -0
  413. package/src/shared/projectFileUrl.ts +36 -0
  414. package/src/shared/protocol.ts +153 -0
  415. package/src/shared/pty-instance.ts +43 -0
  416. package/src/shared/puggy/diagnostics/result.ts +18 -0
  417. package/src/shared/puggy/expressions/evaluate.ts +292 -0
  418. package/src/shared/puggy/index.test.ts +101 -0
  419. package/src/shared/puggy/index.ts +69 -0
  420. package/src/shared/puggy/parser/ast.ts +110 -0
  421. package/src/shared/puggy/parser/parser.ts +624 -0
  422. package/src/shared/puggy/renderer/html.ts +447 -0
  423. package/src/shared/runner-protocol.test.ts +277 -0
  424. package/src/shared/runner-protocol.ts +210 -0
  425. package/src/shared/runner-team-types.ts +73 -0
  426. package/src/shared/runtime-types.ts +48 -0
  427. package/src/shared/sandbox-types.ts +53 -0
  428. package/src/shared/tailwind-build.test.ts +12 -0
  429. package/src/shared/tinkaria-system-prompt.ts +101 -0
  430. package/src/shared/tools.test.ts +335 -0
  431. package/src/shared/tools.ts +397 -0
  432. package/src/shared/transcript-entries.ts +27 -0
  433. package/src/shared/transcript-render.test.ts +225 -0
  434. package/src/shared/transcript-render.ts +467 -0
  435. package/src/shared/types.ts +1031 -0
  436. package/src/shared/vite-config.test.ts +47 -0
  437. package/src/shared/web-context.test.ts +110 -0
  438. package/src/shared/web-context.ts +76 -0
  439. package/src/shared/workflow-types.ts +51 -0
  440. package/src/shared/workspace-types.ts +100 -0
@@ -0,0 +1,675 @@
1
+ import { afterEach, describe, test, expect } from "bun:test"
2
+ import { mkdtemp, rm } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import { NatsServer } from "@lagz0ne/nats-embedded"
6
+ import { connect, type NatsConnection } from "@nats-io/transport-node"
7
+ import { Kvm } from "@nats-io/kv"
8
+ import {
9
+ DelegationCoordinator,
10
+ type DelegationStore,
11
+ type CreateDelegationArgs,
12
+ type TerminalOutcome,
13
+ } from "./delegation-coordinator"
14
+ import type { TranscriptEntry, AgentResultEntry, UserPromptEntry, AssistantTextEntry } from "../shared/types"
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function createFakeDelegationStore(overrides?: Partial<DelegationStore>): DelegationStore & {
21
+ appendedMessages: Array<{ chatId: string; entry: TranscriptEntry }>
22
+ existingChats: Set<string>
23
+ workspacesByChatId: Map<string, string>
24
+ lastTurnOutcomes: Map<string, string>
25
+ } {
26
+ const appendedMessages: Array<{ chatId: string; entry: TranscriptEntry }> = []
27
+ const existingChats = new Set<string>(["parent-1", "child-1", "child-2"])
28
+ const workspacesByChatId = new Map<string, string>([
29
+ ["parent-1", "ws-1"],
30
+ ["child-1", "ws-1"],
31
+ ["child-2", "ws-1"],
32
+ ])
33
+ const lastTurnOutcomes = new Map<string, string>()
34
+
35
+ return {
36
+ appendedMessages,
37
+ existingChats,
38
+ workspacesByChatId,
39
+ lastTurnOutcomes,
40
+ appendMessage(chatId, entry) {
41
+ appendedMessages.push({ chatId, entry })
42
+ overrides?.appendMessage?.(chatId, entry)
43
+ },
44
+ chatExists(chatId) {
45
+ return overrides?.chatExists?.(chatId) ?? existingChats.has(chatId)
46
+ },
47
+ getChatWorkspaceId(chatId) {
48
+ return overrides?.getChatWorkspaceId?.(chatId) ?? workspacesByChatId.get(chatId)
49
+ },
50
+ getLastTurnOutcome(chatId) {
51
+ return overrides?.getLastTurnOutcome?.(chatId) ?? lastTurnOutcomes.get(chatId)
52
+ },
53
+ }
54
+ }
55
+
56
+ function baseArgs(overrides?: Partial<CreateDelegationArgs>): CreateDelegationArgs {
57
+ return {
58
+ workspaceId: "ws-1",
59
+ parentChatId: "parent-1",
60
+ childChatId: "child-1",
61
+ childProvider: "claude",
62
+ instructionPreview: "Do the thing",
63
+ mode: "blocking",
64
+ resume: "gate",
65
+ depth: 1,
66
+ ...overrides,
67
+ }
68
+ }
69
+
70
+ function successOutcome(summary?: string): TerminalOutcome {
71
+ return { outcome: "success", resultSummary: summary ?? "All done" }
72
+ }
73
+
74
+ function failedOutcome(summary?: string): TerminalOutcome {
75
+ return { outcome: "failed", resultSummary: summary ?? "Something broke" }
76
+ }
77
+
78
+ function cancelledOutcome(): TerminalOutcome {
79
+ return { outcome: "cancelled" }
80
+ }
81
+
82
+ function makeUserEntry(content: string): UserPromptEntry {
83
+ return { _id: crypto.randomUUID(), createdAt: Date.now(), kind: "user_prompt", content }
84
+ }
85
+
86
+ function makeAssistantEntry(text: string): AssistantTextEntry {
87
+ return { _id: crypto.randomUUID(), createdAt: Date.now(), kind: "assistant_text", text }
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Tests
92
+ // ---------------------------------------------------------------------------
93
+
94
+ describe("DelegationCoordinator", () => {
95
+ let natsServer: NatsServer | null = null
96
+ let nc: NatsConnection | null = null
97
+ let coordinator: DelegationCoordinator | null = null
98
+ let store: ReturnType<typeof createFakeDelegationStore> | null = null
99
+ let storeDir: string | null = null
100
+
101
+ async function setup(storeOverrides?: Partial<DelegationStore>) {
102
+ storeDir = await mkdtemp(join(tmpdir(), "nats-delegation-test-"))
103
+ natsServer = await NatsServer.start({ jetstream: true, storeDir })
104
+ nc = await connect({ servers: natsServer.url })
105
+ store = createFakeDelegationStore(storeOverrides)
106
+ coordinator = new DelegationCoordinator(nc, store)
107
+ await coordinator.initialize()
108
+ return { coordinator: coordinator!, store: store!, nc: nc! }
109
+ }
110
+
111
+ afterEach(async () => {
112
+ coordinator = null
113
+ store = null
114
+ if (nc && !nc.isClosed()) await nc.drain()
115
+ nc = null
116
+ if (natsServer) await natsServer.stop()
117
+ natsServer = null
118
+ if (storeDir) {
119
+ await rm(storeDir, { recursive: true, force: true }).catch(() => {})
120
+ storeDir = null
121
+ }
122
+ })
123
+
124
+ // -----------------------------------------------------------------------
125
+ // Initialization
126
+ // -----------------------------------------------------------------------
127
+
128
+ test("creates KV bucket on init", async () => {
129
+ await setup()
130
+ const kvm = new Kvm(nc!)
131
+ const kv = await kvm.open("delegations")
132
+ const status = await kv.status()
133
+ expect(status.bucket).toBe("delegations")
134
+ })
135
+
136
+ // -----------------------------------------------------------------------
137
+ // createDelegation
138
+ // -----------------------------------------------------------------------
139
+
140
+ test("createDelegation persists record and returns delegationId", async () => {
141
+ const { coordinator } = await setup()
142
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
143
+
144
+ expect(typeof delegationId).toBe("string")
145
+ expect(delegationId.length).toBeGreaterThan(0)
146
+
147
+ const record = await coordinator.getDelegation("ws-1", delegationId)
148
+ expect(record).not.toBeNull()
149
+ expect(record!.status).toBe("active")
150
+ expect(record!.parentChatId).toBe("parent-1")
151
+ expect(record!.childChatId).toBe("child-1")
152
+ expect(record!.mode).toBe("blocking")
153
+ expect(record!.resume).toBe("gate")
154
+ expect(record!.depth).toBe(1)
155
+ })
156
+
157
+ test("createDelegation writes secondary index", async () => {
158
+ const { coordinator } = await setup()
159
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
160
+
161
+ const children = await coordinator.getDelegationsForChild("ws-1", "child-1")
162
+ expect(children).toHaveLength(1)
163
+ expect(children[0]!.delegationId).toBe(delegationId)
164
+ })
165
+
166
+ test("createDelegation stores resumeHint", async () => {
167
+ const { coordinator } = await setup()
168
+ const { delegationId } = await coordinator.createDelegation(
169
+ baseArgs({ resumeHint: "Context from parent" }),
170
+ )
171
+
172
+ const record = await coordinator.getDelegation("ws-1", delegationId)
173
+ expect(record!.resumeHint).toBe("Context from parent")
174
+ })
175
+
176
+ test("createDelegation truncates instructionPreview to 120 chars", async () => {
177
+ const { coordinator } = await setup()
178
+ const longInstruction = "A".repeat(200)
179
+ const { delegationId } = await coordinator.createDelegation(
180
+ baseArgs({ instructionPreview: longInstruction }),
181
+ )
182
+
183
+ const record = await coordinator.getDelegation("ws-1", delegationId)
184
+ expect(record!.instructionPreview.length).toBe(120)
185
+ })
186
+
187
+ test("createDelegation rejects depth > 2", async () => {
188
+ const { coordinator } = await setup()
189
+ await expect(
190
+ coordinator.createDelegation(baseArgs({ depth: 3 })),
191
+ ).rejects.toThrow(/depth 3 exceeds maximum 2/i)
192
+ })
193
+
194
+ // -----------------------------------------------------------------------
195
+ // getDelegation
196
+ // -----------------------------------------------------------------------
197
+
198
+ test("getDelegation returns null for missing record", async () => {
199
+ const { coordinator } = await setup()
200
+ const result = await coordinator.getDelegation("ws-1", "nonexistent")
201
+ expect(result).toBeNull()
202
+ })
203
+
204
+ test("getDelegation returns stored record", async () => {
205
+ const { coordinator } = await setup()
206
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
207
+ const record = await coordinator.getDelegation("ws-1", delegationId)
208
+ expect(record).not.toBeNull()
209
+ expect(record!.delegationId).toBe(delegationId)
210
+ })
211
+
212
+ // -----------------------------------------------------------------------
213
+ // getDelegationsForChild
214
+ // -----------------------------------------------------------------------
215
+
216
+ test("getDelegationsForChild returns all delegations for a child", async () => {
217
+ const { coordinator } = await setup()
218
+ await coordinator.createDelegation(baseArgs())
219
+ await coordinator.createDelegation(baseArgs({ parentChatId: "parent-1", mode: "background" }))
220
+
221
+ const children = await coordinator.getDelegationsForChild("ws-1", "child-1")
222
+ expect(children).toHaveLength(2)
223
+ })
224
+
225
+ test("getDelegationsForChild returns empty for unknown child", async () => {
226
+ const { coordinator } = await setup()
227
+ const children = await coordinator.getDelegationsForChild("ws-1", "no-such-child")
228
+ expect(children).toHaveLength(0)
229
+ })
230
+
231
+ // -----------------------------------------------------------------------
232
+ // getBlockingDelegationsForParent
233
+ // -----------------------------------------------------------------------
234
+
235
+ test("getBlockingDelegationsForParent returns only active blocking delegations", async () => {
236
+ const { coordinator } = await setup()
237
+ // blocking active
238
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-1" }))
239
+ // background active — should NOT appear
240
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-2", mode: "background" }))
241
+
242
+ const blocking = await coordinator.getBlockingDelegationsForParent("ws-1", "parent-1")
243
+ expect(blocking).toHaveLength(1)
244
+ expect(blocking[0]!.mode).toBe("blocking")
245
+ })
246
+
247
+ test("getBlockingDelegationsForParent excludes completed delegations", async () => {
248
+ const { coordinator } = await setup()
249
+ await coordinator.createDelegation(baseArgs())
250
+ // Complete it
251
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
252
+
253
+ const blocking = await coordinator.getBlockingDelegationsForParent("ws-1", "parent-1")
254
+ expect(blocking).toHaveLength(0)
255
+ })
256
+
257
+ // -----------------------------------------------------------------------
258
+ // reconcileChildTerminal
259
+ // -----------------------------------------------------------------------
260
+
261
+ test("reconcileChildTerminal transitions active→completed", async () => {
262
+ const { coordinator } = await setup()
263
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
264
+
265
+ const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome("Done!"))
266
+ expect(result).not.toBeNull()
267
+ expect("alreadyReconciled" in result!).toBe(false)
268
+
269
+ const record = await coordinator.getDelegation("ws-1", delegationId)
270
+ expect(record!.status).toBe("completed")
271
+ expect(record!.resultSummary).toBe("Done!")
272
+ expect(record!.isError).toBe(false)
273
+ })
274
+
275
+ test("reconcileChildTerminal on failed child sets status failed + isError", async () => {
276
+ const { coordinator } = await setup()
277
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
278
+
279
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", failedOutcome("Boom"))
280
+
281
+ const record = await coordinator.getDelegation("ws-1", delegationId)
282
+ expect(record!.status).toBe("failed")
283
+ expect(record!.isError).toBe(true)
284
+ expect(record!.resultSummary).toBe("Boom")
285
+ })
286
+
287
+ test("reconcileChildTerminal on cancelled child sets status failed", async () => {
288
+ const { coordinator } = await setup()
289
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
290
+
291
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", cancelledOutcome())
292
+
293
+ const record = await coordinator.getDelegation("ws-1", delegationId)
294
+ expect(record!.status).toBe("failed")
295
+ expect(record!.isError).toBe(true)
296
+ })
297
+
298
+ test("duplicate terminal calls are no-ops (CAS idempotency)", async () => {
299
+ const { coordinator, store } = await setup()
300
+ await coordinator.createDelegation(baseArgs())
301
+
302
+ const first = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
303
+ const second = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
304
+
305
+ expect("alreadyReconciled" in first!).toBe(false)
306
+ expect(second).toEqual({ alreadyReconciled: true })
307
+ // appendMessage called only once
308
+ expect(store.appendedMessages).toHaveLength(1)
309
+ })
310
+
311
+ test("reconcileChildTerminal injects agent_result into parent transcript", async () => {
312
+ const { coordinator, store } = await setup()
313
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
314
+
315
+ const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome("All good"))
316
+ expect("injectedEntryId" in result!).toBe(true)
317
+
318
+ expect(store.appendedMessages).toHaveLength(1)
319
+ const { chatId, entry } = store.appendedMessages[0]!
320
+ expect(chatId).toBe("parent-1")
321
+ expect(entry.kind).toBe("agent_result")
322
+ const agentResult = entry as AgentResultEntry
323
+ expect(agentResult.delegationId).toBe(delegationId)
324
+ expect(agentResult.resultSummary).toBe("All good")
325
+ expect(agentResult.childChatId).toBe("child-1")
326
+ expect(agentResult.childProvider).toBe("claude")
327
+ expect(agentResult.isError).toBe(false)
328
+ })
329
+
330
+ test("reconcileChildTerminal returns null for unknown child", async () => {
331
+ const { coordinator } = await setup()
332
+ const result = await coordinator.reconcileChildTerminal("ws-1", "unknown-child", successOutcome())
333
+ expect(result).toBeNull()
334
+ })
335
+
336
+ // -----------------------------------------------------------------------
337
+ // Resume eligibility
338
+ // -----------------------------------------------------------------------
339
+
340
+ test("gate: resumeEligible=false when one sibling still active", async () => {
341
+ const { coordinator } = await setup()
342
+ // Two blocking children under same parent
343
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-1" }))
344
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-2" }))
345
+
346
+ // Complete child-1 only
347
+ const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
348
+ expect("resumeEligible" in result!).toBe(true)
349
+ expect((result as { resumeEligible: boolean }).resumeEligible).toBe(false)
350
+ })
351
+
352
+ test("gate: resumeEligible=true when all children terminal", async () => {
353
+ const { coordinator } = await setup()
354
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-1" }))
355
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-2" }))
356
+
357
+ // Complete both
358
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
359
+ const result = await coordinator.reconcileChildTerminal("ws-1", "child-2", successOutcome())
360
+ expect((result as { resumeEligible: boolean }).resumeEligible).toBe(true)
361
+ })
362
+
363
+ test("immediate: resumeEligible=true per-child", async () => {
364
+ const { coordinator } = await setup()
365
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-1", resume: "immediate" }))
366
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-2", resume: "immediate" }))
367
+
368
+ // Complete child-1 only — should already be eligible
369
+ const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
370
+ expect((result as { resumeEligible: boolean }).resumeEligible).toBe(true)
371
+ })
372
+
373
+ test("background: never resumeEligible", async () => {
374
+ const { coordinator } = await setup()
375
+ await coordinator.createDelegation(baseArgs({ mode: "background", childChatId: "child-1" }))
376
+
377
+ const result = await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
378
+ expect((result as { resumeEligible: boolean }).resumeEligible).toBe(false)
379
+ })
380
+
381
+ // -----------------------------------------------------------------------
382
+ // generateResumeHint
383
+ // -----------------------------------------------------------------------
384
+
385
+ test("generateResumeHint returns undefined for empty entries", async () => {
386
+ const { coordinator } = await setup()
387
+ const hint = coordinator.generateResumeHint([])
388
+ expect(hint).toBeUndefined()
389
+ })
390
+
391
+ test("generateResumeHint returns undefined for non-displayable entries", async () => {
392
+ const { coordinator } = await setup()
393
+ const entries: TranscriptEntry[] = [
394
+ { _id: "1", createdAt: Date.now(), kind: "compact_boundary" },
395
+ ]
396
+ const hint = coordinator.generateResumeHint(entries)
397
+ expect(hint).toBeUndefined()
398
+ })
399
+
400
+ test("generateResumeHint includes user and assistant entries", async () => {
401
+ const { coordinator } = await setup()
402
+ const entries: TranscriptEntry[] = [
403
+ makeUserEntry("Build the feature"),
404
+ makeAssistantEntry("I will build it now"),
405
+ ]
406
+
407
+ const hint = coordinator.generateResumeHint(entries)
408
+ expect(hint).toBeDefined()
409
+ expect(hint).toContain("User: Build the feature")
410
+ expect(hint).toContain("Assistant: I will build it now")
411
+ expect(hint).toContain("Delegation context from parent chat:")
412
+ })
413
+
414
+ test("generateResumeHint truncates to MAX entries", async () => {
415
+ const { coordinator } = await setup()
416
+ // Generate 30 entries (MAX_RESUME_HINT_ENTRIES = 24)
417
+ const entries: TranscriptEntry[] = Array.from({ length: 30 }, (_, i) =>
418
+ makeUserEntry(`Message ${i}`),
419
+ )
420
+
421
+ const hint = coordinator.generateResumeHint(entries)!
422
+ expect(hint).toContain("Older transcript lines omitted:")
423
+ // Should only contain last 24 messages
424
+ expect(hint).toContain("Message 29")
425
+ expect(hint).toContain("Message 6")
426
+ expect(hint).not.toContain("Message 5")
427
+ })
428
+
429
+ test("generateResumeHint respects character budget", async () => {
430
+ const { coordinator } = await setup()
431
+ // Each line = ~500 chars, 24 entries = ~12000 chars → will hit MAX_RESUME_HINT_CHARS
432
+ const entries: TranscriptEntry[] = Array.from({ length: 24 }, (_, i) =>
433
+ makeUserEntry(`${"X".repeat(450)} msg-${i}`),
434
+ )
435
+
436
+ const hint = coordinator.generateResumeHint(entries)!
437
+ expect(hint.length).toBeLessThanOrEqual(12_000)
438
+ })
439
+
440
+ // -----------------------------------------------------------------------
441
+ // bootReconciliation
442
+ // -----------------------------------------------------------------------
443
+
444
+ test("bootReconciliation: parent missing → orphaned", async () => {
445
+ const { coordinator, store } = await setup()
446
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
447
+ // Remove parent from known chats
448
+ store.existingChats.delete("parent-1")
449
+
450
+ await coordinator.bootReconciliation()
451
+
452
+ const record = await coordinator.getDelegation("ws-1", delegationId)
453
+ expect(record!.status).toBe("orphaned")
454
+ })
455
+
456
+ test("bootReconciliation: child missing → orphaned", async () => {
457
+ const { coordinator, store } = await setup()
458
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
459
+ store.existingChats.delete("child-1")
460
+
461
+ await coordinator.bootReconciliation()
462
+
463
+ const record = await coordinator.getDelegation("ws-1", delegationId)
464
+ expect(record!.status).toBe("orphaned")
465
+ })
466
+
467
+ test("bootReconciliation: child already terminal (success) → complete + inject", async () => {
468
+ const { coordinator, store } = await setup()
469
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
470
+ store.lastTurnOutcomes.set("child-1", "success")
471
+
472
+ await coordinator.bootReconciliation()
473
+
474
+ const record = await coordinator.getDelegation("ws-1", delegationId)
475
+ expect(record!.status).toBe("completed")
476
+ // Should inject agent_result into parent
477
+ expect(store.appendedMessages).toHaveLength(1)
478
+ expect(store.appendedMessages[0]!.chatId).toBe("parent-1")
479
+ expect(store.appendedMessages[0]!.entry.kind).toBe("agent_result")
480
+ })
481
+
482
+ test("bootReconciliation: child terminal (failed) → failed", async () => {
483
+ const { coordinator, store } = await setup()
484
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
485
+ store.lastTurnOutcomes.set("child-1", "failed")
486
+
487
+ await coordinator.bootReconciliation()
488
+
489
+ const record = await coordinator.getDelegation("ws-1", delegationId)
490
+ expect(record!.status).toBe("failed")
491
+ })
492
+
493
+ test("bootReconciliation: child still running → keep active", async () => {
494
+ const { coordinator } = await setup()
495
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
496
+ // No lastTurnOutcome set → child still active
497
+
498
+ await coordinator.bootReconciliation()
499
+
500
+ const record = await coordinator.getDelegation("ws-1", delegationId)
501
+ expect(record!.status).toBe("active")
502
+ })
503
+
504
+ test("bootReconciliation: old record → stale", async () => {
505
+ const { coordinator } = await setup()
506
+ // Create delegation and manually backdate it via KV
507
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
508
+
509
+ // Directly update createdAt to 25h ago via KV
510
+ const kvm = new Kvm(nc!)
511
+ const kv = await kvm.open("delegations")
512
+ const key = `delegation.ws-1.${delegationId}`
513
+ const entry = await kv.get(key)
514
+ const record = JSON.parse(new TextDecoder().decode(entry!.value))
515
+ record.createdAt = Date.now() - 25 * 60 * 60 * 1000
516
+ await kv.update(key, new TextEncoder().encode(JSON.stringify(record)), entry!.revision)
517
+
518
+ await coordinator.bootReconciliation()
519
+
520
+ const result = await coordinator.getDelegation("ws-1", delegationId)
521
+ expect(result!.status).toBe("stale")
522
+ })
523
+
524
+ test("bootReconciliation: rebuild missing secondary index", async () => {
525
+ const { coordinator } = await setup()
526
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
527
+
528
+ // Delete the secondary index key manually
529
+ const kvm = new Kvm(nc!)
530
+ const kv = await kvm.open("delegations")
531
+ const secKey = `delegation_by_child.ws-1.child-1.${delegationId}`
532
+ await kv.delete(secKey)
533
+
534
+ // Verify it's gone
535
+ const beforeChildren = await coordinator.getDelegationsForChild("ws-1", "child-1")
536
+ expect(beforeChildren).toHaveLength(0)
537
+
538
+ await coordinator.bootReconciliation()
539
+
540
+ // Should be rebuilt
541
+ const afterChildren = await coordinator.getDelegationsForChild("ws-1", "child-1")
542
+ expect(afterChildren).toHaveLength(1)
543
+ expect(afterChildren[0]!.delegationId).toBe(delegationId)
544
+ })
545
+
546
+ test("bootReconciliation: stuck completing with entryId → completed", async () => {
547
+ const { coordinator } = await setup()
548
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
549
+
550
+ // Manually set to "completing" with agentResultEntryId via KV
551
+ const kvm = new Kvm(nc!)
552
+ const kv = await kvm.open("delegations")
553
+ const key = `delegation.ws-1.${delegationId}`
554
+ const entry = await kv.get(key)
555
+ const record = JSON.parse(new TextDecoder().decode(entry!.value))
556
+ record.status = "completing"
557
+ record.agentResultEntryId = "some-entry-id"
558
+ await kv.update(key, new TextEncoder().encode(JSON.stringify(record)), entry!.revision)
559
+
560
+ await coordinator.bootReconciliation()
561
+
562
+ const result = await coordinator.getDelegation("ws-1", delegationId)
563
+ expect(result!.status).toBe("completed")
564
+ })
565
+
566
+ test("bootReconciliation: stuck completing without entryId → failed", async () => {
567
+ const { coordinator } = await setup()
568
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
569
+
570
+ // Manually set to "completing" without agentResultEntryId
571
+ const kvm = new Kvm(nc!)
572
+ const kv = await kvm.open("delegations")
573
+ const key = `delegation.ws-1.${delegationId}`
574
+ const entry = await kv.get(key)
575
+ const record = JSON.parse(new TextDecoder().decode(entry!.value))
576
+ record.status = "completing"
577
+ await kv.update(key, new TextEncoder().encode(JSON.stringify(record)), entry!.revision)
578
+
579
+ await coordinator.bootReconciliation()
580
+
581
+ const result = await coordinator.getDelegation("ws-1", delegationId)
582
+ expect(result!.status).toBe("failed")
583
+ })
584
+
585
+ // -----------------------------------------------------------------------
586
+ // hasActiveBlockingDelegations (sync cache)
587
+ // -----------------------------------------------------------------------
588
+
589
+ test("hasActiveBlockingDelegations returns false with no delegations", async () => {
590
+ const { coordinator } = await setup()
591
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
592
+ })
593
+
594
+ test("hasActiveBlockingDelegations returns true after blocking delegation created", async () => {
595
+ const { coordinator } = await setup()
596
+ await coordinator.createDelegation(baseArgs({ mode: "blocking" }))
597
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
598
+ })
599
+
600
+ test("hasActiveBlockingDelegations returns false for background delegation", async () => {
601
+ const { coordinator } = await setup()
602
+ await coordinator.createDelegation(baseArgs({ mode: "background" }))
603
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
604
+ })
605
+
606
+ test("hasActiveBlockingDelegations returns false after delegation completes", async () => {
607
+ const { coordinator } = await setup()
608
+ await coordinator.createDelegation(baseArgs({ mode: "blocking" }))
609
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
610
+
611
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
612
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
613
+ })
614
+
615
+ test("hasActiveBlockingDelegations tracks multiple blocking delegations", async () => {
616
+ const { coordinator } = await setup()
617
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-1", mode: "blocking" }))
618
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-2", mode: "blocking" }))
619
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
620
+
621
+ // Complete one — still has active blocking
622
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
623
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
624
+
625
+ // Complete the other — now clear
626
+ await coordinator.reconcileChildTerminal("ws-1", "child-2", successOutcome())
627
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(false)
628
+ })
629
+
630
+ test("bootReconciliation rebuilds sync cache from KV", async () => {
631
+ const { coordinator } = await setup()
632
+ // Create two blocking delegations
633
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-1", mode: "blocking" }))
634
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-2", mode: "blocking" }))
635
+ expect(coordinator.hasActiveBlockingDelegations("parent-1")).toBe(true)
636
+
637
+ // Create a fresh coordinator sharing the same NATS + KV to simulate restart
638
+ const coordinator2 = new DelegationCoordinator(nc!, store!)
639
+ await coordinator2.initialize()
640
+ // Before boot reconciliation, cache is empty
641
+ expect(coordinator2.hasActiveBlockingDelegations("parent-1")).toBe(false)
642
+
643
+ await coordinator2.bootReconciliation()
644
+ // After boot, cache is rebuilt
645
+ expect(coordinator2.hasActiveBlockingDelegations("parent-1")).toBe(true)
646
+ })
647
+
648
+ test("bootReconciliation cache excludes completed delegations", async () => {
649
+ const { coordinator } = await setup()
650
+ await coordinator.createDelegation(baseArgs({ childChatId: "child-1", mode: "blocking" }))
651
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
652
+
653
+ // Fresh coordinator
654
+ const coordinator2 = new DelegationCoordinator(nc!, store!)
655
+ await coordinator2.initialize()
656
+ await coordinator2.bootReconciliation()
657
+ expect(coordinator2.hasActiveBlockingDelegations("parent-1")).toBe(false)
658
+ })
659
+
660
+ test("bootReconciliation skips already terminal records", async () => {
661
+ const { coordinator } = await setup()
662
+ const { delegationId } = await coordinator.createDelegation(baseArgs())
663
+
664
+ // Complete it first
665
+ await coordinator.reconcileChildTerminal("ws-1", "child-1", successOutcome())
666
+ const before = await coordinator.getDelegation("ws-1", delegationId)
667
+ expect(before!.status).toBe("completed")
668
+
669
+ // Boot reconciliation should not touch it
670
+ await coordinator.bootReconciliation()
671
+ const after = await coordinator.getDelegation("ws-1", delegationId)
672
+ expect(after!.status).toBe("completed")
673
+ expect(after!.updatedAt).toBe(before!.updatedAt)
674
+ })
675
+ })