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,495 @@
1
+ import { describe, test, expect, afterEach, beforeEach, mock, spyOn } from "bun:test"
2
+ import { mkdtempSync, rmSync } from "node:fs"
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 { jetstreamManager, RetentionPolicy, StorageType } from "@nats-io/jetstream"
8
+ import { Kvm } from "@nats-io/kv"
9
+ import { RunnerNatsHandler, connectRunner, shutdownConnection, probeProviders } from "./runner-nats"
10
+ import { RunnerAgent, type TurnFactory } from "./runner-agent"
11
+ import type { ResolveClaudeBinaryResult } from "../server/claude-pty/resolve-binary.adapter"
12
+ import {
13
+ runnerCmdSubject,
14
+ runnerHeartbeatSubject,
15
+ RUNNER_EVENTS_STREAM,
16
+ ALL_RUNNER_EVENTS,
17
+ RUNNER_REGISTRY_BUCKET,
18
+ type StartTurnCommand,
19
+ type CancelTurnCommand,
20
+ type RunnerRegistration,
21
+ } from "../shared/runner-protocol"
22
+ import type { HarnessEvent, HarnessTurn } from "../shared/harness-types"
23
+ import type { TranscriptEntry } from "../shared/types"
24
+
25
+ const encoder = new TextEncoder()
26
+ const decoder = new TextDecoder()
27
+
28
+ function ts<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(entry: T): TranscriptEntry {
29
+ return { _id: crypto.randomUUID(), createdAt: Date.now(), ...entry } as TranscriptEntry
30
+ }
31
+
32
+ function createMockTurn(events: HarnessEvent[]): HarnessTurn {
33
+ let interrupted = false
34
+ return {
35
+ provider: "claude",
36
+ stream: (async function* () {
37
+ for (const event of events) {
38
+ if (interrupted) return
39
+ yield event
40
+ }
41
+ })(),
42
+ interrupt: async () => { interrupted = true },
43
+ close: () => {},
44
+ }
45
+ }
46
+
47
+ describe("RunnerNatsHandler", () => {
48
+ let server: NatsServer
49
+ let nc: NatsConnection
50
+ let handlerNc: NatsConnection
51
+ let tmpDir: string | null = null
52
+
53
+ beforeEach(async () => {
54
+ tmpDir = mkdtempSync(join(tmpdir(), "runner-test-"))
55
+ server = await NatsServer.start({ jetstream: true, storeDir: tmpDir })
56
+ nc = await connect({ servers: server.url })
57
+ handlerNc = await connect({ servers: server.url })
58
+ const jsm = await jetstreamManager(nc)
59
+ await jsm.streams.add({
60
+ name: RUNNER_EVENTS_STREAM,
61
+ subjects: [ALL_RUNNER_EVENTS],
62
+ retention: RetentionPolicy.Limits,
63
+ storage: StorageType.File,
64
+ max_age: 5 * 60 * 1_000_000_000,
65
+ max_msgs: 10_000,
66
+ max_bytes: 64 * 1024 * 1024,
67
+ })
68
+ })
69
+
70
+ afterEach(async () => {
71
+ await nc?.drain()
72
+ await handlerNc?.drain()
73
+ await server?.stop()
74
+ if (tmpDir) {
75
+ rmSync(tmpDir, { recursive: true, force: true })
76
+ tmpDir = null
77
+ }
78
+ })
79
+
80
+ test("subscribes to start_turn and dispatches to RunnerAgent", async () => {
81
+ let turnStarted = false
82
+ const turnFactory: TurnFactory = async () => {
83
+ turnStarted = true
84
+ return createMockTurn([
85
+ { type: "transcript", entry: ts({ kind: "result", subtype: "success", isError: false, durationMs: 10, result: "ok" }) },
86
+ ])
87
+ }
88
+
89
+ const agent = new RunnerAgent({ nc: handlerNc, createTurn: turnFactory })
90
+ const handler = new RunnerNatsHandler({ nc: handlerNc, agent, runnerId: "r1" })
91
+ await handler.start()
92
+
93
+ const cmd: StartTurnCommand = {
94
+ chatId: "chat-1",
95
+ provider: "claude",
96
+ content: "hello",
97
+ model: "test-model",
98
+ planMode: false,
99
+ appendUserPrompt: true,
100
+ workspaceLocalPath: "/tmp",
101
+ sessionToken: null,
102
+ chatTitle: "New Chat",
103
+ existingMessageCount: 0,
104
+ workspaceId: "p1",
105
+ }
106
+
107
+ const reply = await nc.request(
108
+ runnerCmdSubject("r1", "start_turn"),
109
+ encoder.encode(JSON.stringify(cmd)),
110
+ { timeout: 2000 }
111
+ )
112
+ const response = JSON.parse(decoder.decode(reply.data))
113
+ expect(response.ok).toBe(true)
114
+
115
+ await new Promise((r) => setTimeout(r, 200))
116
+ expect(turnStarted).toBe(true)
117
+
118
+ handler.dispose()
119
+ })
120
+
121
+ test("subscribes to cancel_turn and dispatches to RunnerAgent", async () => {
122
+ let unblock: (() => void) | null = null
123
+ const turnFactory: TurnFactory = async () => ({
124
+ provider: "claude",
125
+ stream: (async function* () {
126
+ yield { type: "transcript" as const, entry: ts({ kind: "system_init", provider: "claude", model: "t", tools: [], agents: [], slashCommands: [], mcpServers: [] }) }
127
+ await new Promise<void>((r) => { unblock = r })
128
+ })(),
129
+ interrupt: async () => { unblock?.() },
130
+ close: () => {},
131
+ })
132
+
133
+ const agent = new RunnerAgent({ nc: handlerNc, createTurn: turnFactory })
134
+ const handler = new RunnerNatsHandler({ nc: handlerNc, agent, runnerId: "r1" })
135
+ await handler.start()
136
+
137
+ const startCmd: StartTurnCommand = {
138
+ chatId: "chat-1", provider: "claude", content: "hello", model: "m",
139
+ planMode: false, appendUserPrompt: true, workspaceLocalPath: "/tmp",
140
+ sessionToken: null, chatTitle: "New Chat", existingMessageCount: 0, workspaceId: "p1",
141
+ }
142
+ await nc.request(runnerCmdSubject("r1", "start_turn"), encoder.encode(JSON.stringify(startCmd)), { timeout: 2000 })
143
+ await new Promise((r) => setTimeout(r, 100))
144
+
145
+ const cancelCmd: CancelTurnCommand = { chatId: "chat-1" }
146
+ const reply = await nc.request(
147
+ runnerCmdSubject("r1", "cancel_turn"),
148
+ encoder.encode(JSON.stringify(cancelCmd)),
149
+ { timeout: 2000 }
150
+ )
151
+ const response = JSON.parse(decoder.decode(reply.data))
152
+ expect(response.ok).toBe(true)
153
+
154
+ await new Promise((r) => setTimeout(r, 200))
155
+ expect(agent.activeTurns.has("chat-1")).toBe(false)
156
+
157
+ handler.dispose()
158
+ })
159
+
160
+ test("publishes heartbeat periodically", async () => {
161
+ const agent = new RunnerAgent({ nc: handlerNc, createTurn: async () => createMockTurn([]) })
162
+ const handler = new RunnerNatsHandler({ nc: handlerNc, agent, runnerId: "r1", heartbeatIntervalMs: 100 })
163
+ await handler.start()
164
+
165
+ const heartbeats: unknown[] = []
166
+ const sub = nc.subscribe(runnerHeartbeatSubject("r1"))
167
+ void (async () => {
168
+ for await (const msg of sub) {
169
+ heartbeats.push(JSON.parse(decoder.decode(msg.data)))
170
+ }
171
+ })()
172
+
173
+ await new Promise((r) => setTimeout(r, 350))
174
+ expect(heartbeats.length).toBeGreaterThanOrEqual(2)
175
+
176
+ sub.unsubscribe()
177
+ handler.dispose()
178
+ })
179
+
180
+ test("registers in KV bucket on start", async () => {
181
+ const agent = new RunnerAgent({ nc: handlerNc, createTurn: async () => createMockTurn([]) })
182
+ const handler = new RunnerNatsHandler({ nc: handlerNc, agent, runnerId: "r1" })
183
+ await handler.start()
184
+
185
+ // Read from KV using the client connection
186
+ const kvm = new Kvm(nc)
187
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
188
+ const entry = await kvStore.get("r1")
189
+ expect(entry).toBeDefined()
190
+ const registration = JSON.parse(decoder.decode(entry!.value)) as RunnerRegistration
191
+ expect(registration.runnerId).toBe("r1")
192
+ expect(registration.pid).toBe(process.pid)
193
+
194
+ handler.dispose()
195
+ })
196
+ })
197
+
198
+ describe("runner NATS reconnect resilience", () => {
199
+ test("connectRunner is called with explicit reconnect options", async () => {
200
+ const capturedOpts: Array<Record<string, unknown>> = []
201
+ const fakeNc = { isFakeNc: true } as unknown as NatsConnection
202
+ const fakeConnect = mock(async (opts: Record<string, unknown>) => {
203
+ capturedOpts.push(opts)
204
+ return fakeNc
205
+ })
206
+
207
+ const result = await connectRunner({
208
+ natsUrl: "nats://test:4222",
209
+ token: "test-token",
210
+ connectFn: fakeConnect as unknown as typeof connect,
211
+ })
212
+
213
+ expect(result).toBe(fakeNc)
214
+ expect(fakeConnect).toHaveBeenCalledTimes(1)
215
+ expect(capturedOpts.length).toBe(1)
216
+ const opts = capturedOpts[0]!
217
+ expect(opts.servers).toBe("nats://test:4222")
218
+ expect(opts.token).toBe("test-token")
219
+ expect(opts.maxReconnectAttempts).toBe(-1)
220
+ expect(opts.reconnectTimeWait).toBe(750)
221
+ expect(opts.pingInterval).toBe(15_000)
222
+ expect(opts.maxPingOut).toBe(3)
223
+ })
224
+
225
+ test("connectRunner omits token when undefined but still passes reconnect options", async () => {
226
+ const capturedOpts: Array<Record<string, unknown>> = []
227
+ const fakeNc = {} as unknown as NatsConnection
228
+ const fakeConnect = mock(async (opts: Record<string, unknown>) => {
229
+ capturedOpts.push(opts)
230
+ return fakeNc
231
+ })
232
+
233
+ await connectRunner({
234
+ natsUrl: "nats://test:4222",
235
+ token: undefined,
236
+ connectFn: fakeConnect as unknown as typeof connect,
237
+ })
238
+
239
+ const opts = capturedOpts[0]!
240
+ expect(opts.token).toBeUndefined()
241
+ expect(opts.maxReconnectAttempts).toBe(-1)
242
+ expect(opts.reconnectTimeWait).toBe(750)
243
+ expect(opts.pingInterval).toBe(15_000)
244
+ expect(opts.maxPingOut).toBe(3)
245
+ })
246
+
247
+ test("shutdownConnection times out drain and falls back to close", async () => {
248
+ let closeCalled = false
249
+ const fakeNc = {
250
+ drain: mock(() => new Promise<void>(() => { /* never resolves */ })),
251
+ close: mock(async () => { closeCalled = true }),
252
+ } as unknown as NatsConnection
253
+
254
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {})
255
+ try {
256
+ const started = Date.now()
257
+ await shutdownConnection(fakeNc, { drainTimeoutMs: 200 })
258
+ const elapsed = Date.now() - started
259
+ expect(closeCalled).toBe(true)
260
+ expect(elapsed).toBeLessThan(1_000)
261
+ expect(warnSpy).toHaveBeenCalled()
262
+ } finally {
263
+ warnSpy.mockRestore()
264
+ }
265
+ })
266
+
267
+ test("shutdownConnection completes normally when drain resolves fast", async () => {
268
+ let drainCalled = false
269
+ let closeCalled = false
270
+ const fakeNc = {
271
+ drain: mock(async () => { drainCalled = true }),
272
+ close: mock(async () => { closeCalled = true }),
273
+ } as unknown as NatsConnection
274
+
275
+ await shutdownConnection(fakeNc, { drainTimeoutMs: 500 })
276
+ expect(drainCalled).toBe(true)
277
+ expect(closeCalled).toBe(false)
278
+ })
279
+
280
+ test("shutdownConnection warns if close() also fails after drain timeout", async () => {
281
+ const fakeNc = {
282
+ drain: mock(() => new Promise<void>(() => { /* never resolves */ })),
283
+ close: mock(async () => { throw new Error("close exploded") }),
284
+ } as unknown as NatsConnection
285
+
286
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {})
287
+ try {
288
+ // Should not throw even though close() errors.
289
+ await shutdownConnection(fakeNc, { drainTimeoutMs: 100 })
290
+ expect(warnSpy).toHaveBeenCalled()
291
+ const warnCalls = warnSpy.mock.calls.map((c) => c.join(" "))
292
+ const mentionsCloseFailure = warnCalls.some((line) => line.includes("close() also failed"))
293
+ expect(mentionsCloseFailure).toBe(true)
294
+ } finally {
295
+ warnSpy.mockRestore()
296
+ }
297
+ })
298
+
299
+ test("publishHeartbeat swallows and logs publish errors", async () => {
300
+
301
+ const fakeNc = {
302
+ publish: mock(() => { throw new Error("nats closed") }),
303
+ flush: mock(async () => {}),
304
+ subscribe: mock(() => ({
305
+ unsubscribe: () => {},
306
+ [Symbol.asyncIterator]: async function* () { /* no-op */ },
307
+ })),
308
+ } as unknown as NatsConnection
309
+
310
+ const agent = {
311
+ activeTurns: new Map<string, unknown>(),
312
+ } as unknown as RunnerAgent
313
+
314
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {})
315
+ try {
316
+ const handler = new RunnerNatsHandler({ nc: fakeNc, agent, runnerId: "r-fake" })
317
+ // Call the (now public-for-test) publishHeartbeat directly via a type-safe shim.
318
+ const publishAccessor = (handler as unknown as {
319
+ publishHeartbeatForTest: () => void
320
+ }).publishHeartbeatForTest
321
+ expect(typeof publishAccessor).toBe("function")
322
+ expect(() => publishAccessor.call(handler)).not.toThrow()
323
+
324
+ const warnCalls = warnSpy.mock.calls.map((c) => c.join(" "))
325
+ const mentionsHeartbeatFailure = warnCalls.some((line) =>
326
+ line.includes("heartbeat publish failed")
327
+ )
328
+ expect(mentionsHeartbeatFailure).toBe(true)
329
+ } finally {
330
+ warnSpy.mockRestore()
331
+ }
332
+ })
333
+ })
334
+
335
+ // ── PR4 Stage 2: probeProviders + capability-advertise ────────────────
336
+
337
+ describe("probeProviders", () => {
338
+ test("includes claude when resolver succeeds", async () => {
339
+ const fakeResolver = async (): Promise<ResolveClaudeBinaryResult> =>
340
+ ({ path: "/usr/local/bin/claude", source: "PATH", triedPaths: [] })
341
+
342
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {})
343
+ try {
344
+ const caps = await probeProviders(fakeResolver)
345
+ expect(caps.providers).toContain("claude")
346
+ // claude-pty rides on the same claude binary, so it must be advertised
347
+ // alongside claude — otherwise the server capability gate refuses it.
348
+ expect(caps.providers).toContain("claude-pty")
349
+ } finally {
350
+ warnSpy.mockRestore()
351
+ }
352
+ })
353
+
354
+ test("excludes claude and claude-pty when resolver throws", async () => {
355
+ const failingResolver = async (): Promise<ResolveClaudeBinaryResult> => {
356
+ throw new Error("claude not found")
357
+ }
358
+
359
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {})
360
+ try {
361
+ const caps = await probeProviders(failingResolver)
362
+ expect(caps.providers).not.toContain("claude")
363
+ expect(caps.providers).not.toContain("claude-pty")
364
+ } finally {
365
+ warnSpy.mockRestore()
366
+ }
367
+ })
368
+
369
+ test("probe error does not crash — returns partial providers list", async () => {
370
+ // Simulate a resolver that always throws — probeProviders must return gracefully.
371
+ const failingResolver = async (): Promise<ResolveClaudeBinaryResult> => {
372
+ throw new Error("unexpected probe failure")
373
+ }
374
+
375
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {})
376
+ try {
377
+ const caps = await probeProviders(failingResolver)
378
+ expect(Array.isArray(caps.providers)).toBe(true)
379
+ // May or may not include codex depending on the test env; no throw is the key assertion.
380
+ } finally {
381
+ warnSpy.mockRestore()
382
+ }
383
+ })
384
+ })
385
+
386
+ describe("RunnerNatsHandler — capability probe advertised in KV registration", () => {
387
+ let server: NatsServer
388
+ let nc: NatsConnection
389
+ let handlerNc: NatsConnection
390
+ let tmpDir: string | null = null
391
+
392
+ beforeEach(async () => {
393
+ tmpDir = mkdtempSync(join(tmpdir(), "runner-probe-test-"))
394
+ server = await NatsServer.start({ jetstream: true, storeDir: tmpDir })
395
+ nc = await connect({ servers: server.url })
396
+ handlerNc = await connect({ servers: server.url })
397
+ const jsm = await jetstreamManager(nc)
398
+ await jsm.streams.add({
399
+ name: RUNNER_EVENTS_STREAM,
400
+ subjects: [ALL_RUNNER_EVENTS],
401
+ retention: RetentionPolicy.Limits,
402
+ storage: StorageType.File,
403
+ max_age: 5 * 60 * 1_000_000_000,
404
+ max_msgs: 10_000,
405
+ max_bytes: 64 * 1024 * 1024,
406
+ })
407
+ })
408
+
409
+ afterEach(async () => {
410
+ await nc?.drain()
411
+ await handlerNc?.drain()
412
+ await server?.stop()
413
+ if (tmpDir) {
414
+ rmSync(tmpDir, { recursive: true, force: true })
415
+ tmpDir = null
416
+ }
417
+ })
418
+
419
+ test("claude resolves, codex does not → providers=[claude] in KV registration", async () => {
420
+ // Mock resolver: claude resolves, codex absent (spawnSync("which","codex") returns non-zero
421
+ // when run in the probe; we control only the claude resolver here, and codex is probed
422
+ // via the real spawnSync — but we override the full _probeProviders to isolate the test).
423
+ const fakeProbeFn = async () => ({ providers: ["claude" as const] })
424
+
425
+ const agent = new RunnerAgent({ nc: handlerNc, createTurn: async () => createMockTurn([]) })
426
+ const handler = new RunnerNatsHandler({
427
+ nc: handlerNc,
428
+ agent,
429
+ runnerId: "r-probe",
430
+ _probeProviders: fakeProbeFn,
431
+ })
432
+ await handler.start()
433
+
434
+ const kvm = new Kvm(nc)
435
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
436
+ const entry = await kvStore.get("r-probe")
437
+ expect(entry).toBeDefined()
438
+ const registration = JSON.parse(decoder.decode(entry!.value)) as RunnerRegistration
439
+ expect(registration.capabilities?.providers).toEqual(["claude"])
440
+ expect(registration.providers).toEqual(["claude"])
441
+
442
+ handler.dispose()
443
+ })
444
+
445
+ test("both providers installed → providers=[claude, codex] in KV registration", async () => {
446
+ const fakeProbeFn = async () => ({ providers: ["claude" as const, "codex" as const] })
447
+
448
+ const agent = new RunnerAgent({ nc: handlerNc, createTurn: async () => createMockTurn([]) })
449
+ const handler = new RunnerNatsHandler({
450
+ nc: handlerNc,
451
+ agent,
452
+ runnerId: "r-both",
453
+ _probeProviders: fakeProbeFn,
454
+ })
455
+ await handler.start()
456
+
457
+ const kvm = new Kvm(nc)
458
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
459
+ const entry = await kvStore.get("r-both")
460
+ const registration = JSON.parse(decoder.decode(entry!.value)) as RunnerRegistration
461
+ expect(registration.capabilities?.providers).toEqual(["claude", "codex"])
462
+
463
+ handler.dispose()
464
+ })
465
+
466
+ test("probe throws → registration proceeds with empty providers", async () => {
467
+ const failProbeFn = async (): Promise<{ providers: ("claude" | "codex")[] }> => {
468
+ throw new Error("probe catastrophically failed")
469
+ }
470
+
471
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {})
472
+ try {
473
+ const agent = new RunnerAgent({ nc: handlerNc, createTurn: async () => createMockTurn([]) })
474
+ const handler = new RunnerNatsHandler({
475
+ nc: handlerNc,
476
+ agent,
477
+ runnerId: "r-probe-fail",
478
+ _probeProviders: failProbeFn,
479
+ })
480
+ await handler.start()
481
+
482
+ const kvm = new Kvm(nc)
483
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
484
+ const entry = await kvStore.get("r-probe-fail")
485
+ expect(entry).toBeDefined()
486
+ const registration = JSON.parse(decoder.decode(entry!.value)) as RunnerRegistration
487
+ // Empty list — probe failed but registration didn't crash.
488
+ expect(registration.capabilities?.providers).toEqual([])
489
+
490
+ handler.dispose()
491
+ } finally {
492
+ warnSpy.mockRestore()
493
+ }
494
+ })
495
+ })