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,312 @@
1
+ import type { Msg, NatsConnection, Subscription } from "@nats-io/transport-node"
2
+ import { Kvm } from "@nats-io/kv"
3
+ import {
4
+ runnerCmdSubject,
5
+ runnerHeartbeatSubject,
6
+ RUNNER_REGISTRY_BUCKET,
7
+ isProtocolSupported,
8
+ runnerLivenessState,
9
+ SUPPORTED_RANGE,
10
+ type RunnerCapabilities,
11
+ type RunnerHeartbeat,
12
+ type RunnerLivenessState,
13
+ type RunnerRegistration,
14
+ } from "../shared/runner-protocol"
15
+ import { LOG_PREFIX } from "../shared/branding"
16
+
17
+ const encoder = new TextEncoder()
18
+ const decoder = new TextDecoder()
19
+
20
+ export interface RunnerManagerOptions {
21
+ nc: NatsConnection
22
+ natsUrl: string
23
+ authToken?: string
24
+ /**
25
+ * Called in callout mode to mint a scoped credential token for the given
26
+ * runnerId just before the runner process is spawned. When provided, it takes
27
+ * precedence over authToken for the spawned runner's NATS_TOKEN env var.
28
+ */
29
+ mintToken?: (runnerId: string) => Promise<string>
30
+ /** 'spawn' (default): spawn runner if not found. 'discover': only discover existing runner from KV. */
31
+ mode?: "spawn" | "discover"
32
+ }
33
+
34
+ export interface RunnerReadiness {
35
+ ok: boolean
36
+ runnerId: string | null
37
+ pid: number | null
38
+ registered: boolean
39
+ /** Derived liveness state from server-tracked lastHeartbeatAt. */
40
+ state: RunnerLivenessState
41
+ /** heartbeatFresh === (state === "online") — kept for back-compat. */
42
+ heartbeatFresh: boolean
43
+ lastHeartbeatAt: number | null
44
+ /** Protocol version reported by the runner registration, or null if not registered. */
45
+ protocolVersion: number | null
46
+ /** True when the runner's protocolVersion is outside the server's SUPPORTED_RANGE. */
47
+ incompatible: boolean
48
+ /** Capabilities advertised by the runner at registration time. Null if not yet registered. */
49
+ capabilities: RunnerCapabilities | null
50
+ }
51
+
52
+ export class RunnerManager {
53
+ private readonly nc: NatsConnection
54
+ private readonly natsUrl: string
55
+ private readonly authToken: string | undefined
56
+ private readonly mintToken: ((runnerId: string) => Promise<string>) | undefined
57
+ private readonly mode: "spawn" | "discover"
58
+ private proc: ReturnType<typeof Bun.spawn> | null = null
59
+ private runnerId: string | null = null
60
+ private runnerRegistration: RunnerRegistration | null = null
61
+ private lastHeartbeatAt: number | null = null
62
+ private heartbeatSubscription: Subscription | null = null
63
+
64
+ constructor(options: RunnerManagerOptions) {
65
+ this.nc = options.nc
66
+ this.natsUrl = options.natsUrl
67
+ this.authToken = options.authToken
68
+ this.mintToken = options.mintToken
69
+ this.mode = options.mode ?? "spawn"
70
+ }
71
+
72
+ getRunnerId(): string {
73
+ if (!this.runnerId) throw new Error("Runner not started")
74
+ return this.runnerId
75
+ }
76
+
77
+ getReadiness(now = Date.now()): RunnerReadiness {
78
+ const state = runnerLivenessState(this.lastHeartbeatAt, now)
79
+ const heartbeatFresh = state === "online"
80
+ const registered = this.runnerRegistration !== null
81
+ // A registration missing protocolVersion is treated as incompatible (defensive).
82
+ const protocolVersion = this.runnerRegistration?.protocolVersion ?? null
83
+ const incompatible =
84
+ protocolVersion === null
85
+ ? registered // if registered but no version field, it's incompatible
86
+ : !isProtocolSupported(protocolVersion)
87
+ const capabilities = this.runnerRegistration?.capabilities ?? null
88
+ return {
89
+ ok: this.runnerId !== null && registered && heartbeatFresh && !incompatible,
90
+ runnerId: this.runnerId,
91
+ pid: this.runnerRegistration?.pid ?? this.proc?.pid ?? null,
92
+ registered,
93
+ state,
94
+ heartbeatFresh,
95
+ lastHeartbeatAt: this.lastHeartbeatAt,
96
+ protocolVersion,
97
+ incompatible,
98
+ capabilities,
99
+ }
100
+ }
101
+
102
+ async ensureRunner(): Promise<string> {
103
+ // Check if runner already exists in KV and is alive
104
+ if (this.runnerId) {
105
+ try {
106
+ const kvm = new Kvm(this.nc)
107
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
108
+ const entry = await kvStore.get(this.runnerId)
109
+ if (entry) {
110
+ const reg = JSON.parse(decoder.decode(entry.value)) as RunnerRegistration
111
+ this.runnerRegistration = reg
112
+ // For the active (spawn-mode) runner, prefer the server-tracked
113
+ // lastHeartbeatAt for liveness rather than lastSeenAt from KV.
114
+ // If we have live heartbeat data, use that; otherwise fall back to lastSeenAt.
115
+ const freshnessSrc = this.lastHeartbeatAt ?? reg.lastSeenAt ?? null
116
+ if (runnerLivenessState(freshnessSrc, Date.now()) !== "offline") {
117
+ return this.runnerId
118
+ }
119
+ // Runner offline — fall through to respawn or discover
120
+ }
121
+ } catch {
122
+ // KV bucket might not exist yet, proceed to spawn or discover
123
+ }
124
+ }
125
+
126
+ if (this.mode === "discover") {
127
+ return this.discoverExternalRunner()
128
+ }
129
+
130
+ // Spawn new runner
131
+ const runnerId = `runner-${Date.now()}-${process.pid}`
132
+ const runnerScript = new URL("../runner/runner.ts", import.meta.url).pathname
133
+
134
+ // In callout mode mintToken produces a scoped credential for this runnerId.
135
+ // In token mode authToken is the shared static token — behaviour unchanged.
136
+ const runnerToken = this.mintToken
137
+ ? await this.mintToken(runnerId)
138
+ : this.authToken
139
+
140
+ this.subscribeToHeartbeat(runnerId)
141
+
142
+ this.proc = Bun.spawn(["bun", "run", runnerScript], {
143
+ env: {
144
+ ...process.env,
145
+ NATS_URL: this.natsUrl,
146
+ ...(runnerToken ? { NATS_TOKEN: runnerToken } : {}),
147
+ RUNNER_ID: runnerId,
148
+ },
149
+ stdio: ["ignore", "inherit", "inherit"],
150
+ })
151
+
152
+ this.runnerId = runnerId
153
+
154
+ // Wait for runner to register in KV
155
+ await this.waitForRegistration(runnerId, 15_000)
156
+ await this.waitForHeartbeat(5_000)
157
+
158
+ const { incompatible, protocolVersion } = this.getReadiness()
159
+ if (incompatible) {
160
+ console.warn(
161
+ LOG_PREFIX,
162
+ `Runner ${runnerId} is incompatible (protocol v${protocolVersion ?? "unknown"}, server supports v${SUPPORTED_RANGE.min}–${SUPPORTED_RANGE.max})`,
163
+ )
164
+ }
165
+ console.warn(LOG_PREFIX, `Runner ${runnerId} spawned (pid: ${this.proc.pid})`)
166
+
167
+ return runnerId
168
+ }
169
+
170
+ private async waitForRegistration(runnerId: string, timeoutMs: number): Promise<void> {
171
+ const deadline = Date.now() + timeoutMs
172
+ const kvm = new Kvm(this.nc)
173
+ let kvStore: Awaited<ReturnType<typeof kvm.open>> | null = null
174
+ while (Date.now() < deadline) {
175
+ try {
176
+ if (!kvStore) kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
177
+ const entry = await kvStore.get(runnerId)
178
+ if (entry) {
179
+ this.runnerRegistration = JSON.parse(decoder.decode(entry.value)) as RunnerRegistration
180
+ return
181
+ }
182
+ } catch {
183
+ kvStore = null // KV not ready yet, retry open next iteration
184
+ }
185
+ await new Promise((r) => setTimeout(r, 200))
186
+ }
187
+ throw new Error(`Runner ${runnerId} did not register within ${timeoutMs}ms`)
188
+ }
189
+
190
+ private subscribeToHeartbeat(runnerId: string): void {
191
+ this.heartbeatSubscription?.unsubscribe()
192
+ this.lastHeartbeatAt = null
193
+ const sub = this.nc.subscribe(runnerHeartbeatSubject(runnerId))
194
+ this.heartbeatSubscription = sub
195
+ void this.consumeHeartbeats(sub, runnerId)
196
+ }
197
+
198
+ private async consumeHeartbeats(sub: Subscription, runnerId: string): Promise<void> {
199
+ for await (const msg of sub) {
200
+ if (sub !== this.heartbeatSubscription || runnerId !== this.runnerId) {
201
+ continue
202
+ }
203
+ this.recordHeartbeat(msg)
204
+ }
205
+ }
206
+
207
+ private recordHeartbeat(msg: Msg): void {
208
+ try {
209
+ const heartbeat = JSON.parse(decoder.decode(msg.data)) as RunnerHeartbeat
210
+ if (heartbeat.runnerId !== this.runnerId) return
211
+ this.lastHeartbeatAt = heartbeat.ts
212
+ } catch (error) {
213
+ const message = error instanceof Error ? error.message : String(error)
214
+ console.warn(LOG_PREFIX, `Runner heartbeat decode failed: ${message}`)
215
+ }
216
+ }
217
+
218
+ private async waitForHeartbeat(timeoutMs: number): Promise<void> {
219
+ const deadline = Date.now() + timeoutMs
220
+ while (Date.now() < deadline) {
221
+ if (this.getReadiness().heartbeatFresh) return
222
+ await new Promise((r) => setTimeout(r, 50))
223
+ }
224
+ throw new Error(`Runner ${this.runnerId ?? "unknown"} did not publish heartbeat within ${timeoutMs}ms`)
225
+ }
226
+
227
+ /** Discover mode: poll KV for any registered runner instead of spawning one. */
228
+ private async discoverExternalRunner(): Promise<string> {
229
+ const deadline = Date.now() + 15_000
230
+ const kvm = new Kvm(this.nc)
231
+ while (Date.now() < deadline) {
232
+ try {
233
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
234
+ const keys = await kvStore.keys()
235
+ for await (const key of keys) {
236
+ const entry = await kvStore.get(key)
237
+ if (!entry) continue
238
+ const reg = JSON.parse(decoder.decode(entry.value)) as RunnerRegistration
239
+ const livenessNow = Date.now()
240
+ // Use KV lastSeenAt for discovery liveness — pid is meaningless for
241
+ // cross-machine runners. Skip any candidate that is offline.
242
+ if (runnerLivenessState(reg.lastSeenAt ?? null, livenessNow) === "offline") {
243
+ continue
244
+ }
245
+ // Skip incompatible runners — adopting one would block every turn start.
246
+ // `?? -1` guards pre-PR3 KV entries that lack protocolVersion (would be
247
+ // undefined at runtime despite the typed field) → treated as incompatible.
248
+ if (!isProtocolSupported(reg.protocolVersion ?? -1)) {
249
+ console.warn(
250
+ LOG_PREFIX,
251
+ `Discover: skipping incompatible runner ${key} (protocol v${reg.protocolVersion}, server supports v${SUPPORTED_RANGE.min}–${SUPPORTED_RANGE.max})`,
252
+ )
253
+ continue
254
+ }
255
+ this.runnerId = key
256
+ this.runnerRegistration = reg
257
+ this.subscribeToHeartbeat(key)
258
+ await this.waitForHeartbeat(5_000)
259
+ console.warn(LOG_PREFIX, `Discovered external runner ${key} (pid: ${reg.pid})`)
260
+ return key
261
+ }
262
+ } catch {
263
+ // KV not ready yet
264
+ }
265
+ await new Promise((r) => setTimeout(r, 500))
266
+ }
267
+ throw new Error("No external runner found in KV registry within 15s — is kanna-runner.service running?")
268
+ }
269
+
270
+ async dispose(): Promise<void> {
271
+ this.heartbeatSubscription?.unsubscribe()
272
+ this.heartbeatSubscription = null
273
+ if (this.mode === "discover") {
274
+ // External runner — don't kill it, just disconnect
275
+ this.runnerId = null
276
+ this.runnerRegistration = null
277
+ this.lastHeartbeatAt = null
278
+ console.warn(LOG_PREFIX, "Runner manager disposed (external runner left running)")
279
+ return
280
+ }
281
+ if (this.runnerId && this.proc) {
282
+ // Try graceful shutdown via NATS command, then SIGTERM as fallback
283
+ try {
284
+ await this.nc.request(
285
+ runnerCmdSubject(this.runnerId, "shutdown"),
286
+ encoder.encode(JSON.stringify({ reason: "server shutdown" })),
287
+ { timeout: 3000 },
288
+ )
289
+ } catch {
290
+ // NATS request failed or timed out
291
+ }
292
+
293
+ // Always send SIGTERM — the NATS shutdown command only cleans up
294
+ // subscriptions but doesn't exit the process
295
+ try {
296
+ this.proc.kill("SIGTERM")
297
+ } catch {
298
+ // Process may already be gone
299
+ }
300
+ }
301
+
302
+ if (this.proc) {
303
+ await this.proc.exited
304
+ this.proc = null
305
+ }
306
+
307
+ this.runnerId = null
308
+ this.runnerRegistration = null
309
+ this.lastHeartbeatAt = null
310
+ console.warn(LOG_PREFIX, "Runner manager disposed")
311
+ }
312
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Unit tests for resolveRunnerPairingUrls — the pairing-exchange NATS URL
3
+ * advertiser. Regression target: a runner paired against a server bound to
4
+ * 0.0.0.0 must NOT receive nats://0.0.0.0:<port> (ECONNREFUSED on a remote box).
5
+ */
6
+
7
+ import { describe, test, expect } from "bun:test"
8
+ import { resolveRunnerPairingUrls } from "./runner-pairing-urls"
9
+
10
+ const wildcard = { url: "nats://0.0.0.0:50640", wsUrl: "ws://0.0.0.0:50641" }
11
+
12
+ describe("resolveRunnerPairingUrls", () => {
13
+ test("rewrites both hosts to NATS_ADVERTISED_HOST when set", () => {
14
+ const result = resolveRunnerPairingUrls(wildcard, {
15
+ NATS_ADVERTISED_HOST: "100.64.0.2",
16
+ })
17
+ expect(result).toEqual({
18
+ ok: true,
19
+ natsUrl: "nats://100.64.0.2:50640",
20
+ natsWsUrl: "ws://100.64.0.2:50641",
21
+ })
22
+ })
23
+
24
+ test("advertised host wins even over an already-concrete daemon host", () => {
25
+ const result = resolveRunnerPairingUrls(
26
+ { url: "nats://127.0.0.1:4222", wsUrl: "ws://127.0.0.1:4223" },
27
+ { NATS_ADVERTISED_HOST: "192.168.1.50" },
28
+ )
29
+ expect(result).toEqual({
30
+ ok: true,
31
+ natsUrl: "nats://192.168.1.50:4222",
32
+ natsWsUrl: "ws://192.168.1.50:4223",
33
+ })
34
+ })
35
+
36
+ test("rejects a 0.0.0.0 bind host when no advertised host is set", () => {
37
+ const result = resolveRunnerPairingUrls(wildcard, {})
38
+ expect(result.ok).toBe(false)
39
+ if (!result.ok) expect(result.error).toContain("NATS_ADVERTISED_HOST")
40
+ })
41
+
42
+ test("rejects IPv6 wildcard hosts (:: and [::])", () => {
43
+ for (const url of ["nats://[::]:50640"]) {
44
+ const result = resolveRunnerPairingUrls({ url, wsUrl: "ws://[::]:50641" }, {})
45
+ expect(result.ok).toBe(false)
46
+ }
47
+ })
48
+
49
+ test("passes a concrete host through unchanged", () => {
50
+ const info = { url: "nats://127.0.0.1:4222", wsUrl: "ws://127.0.0.1:4223" }
51
+ const result = resolveRunnerPairingUrls(info, {})
52
+ expect(result).toEqual({ ok: true, natsUrl: info.url, natsWsUrl: info.wsUrl })
53
+ })
54
+
55
+ test("blank NATS_ADVERTISED_HOST is treated as unset", () => {
56
+ const result = resolveRunnerPairingUrls(wildcard, { NATS_ADVERTISED_HOST: " " })
57
+ expect(result.ok).toBe(false)
58
+ })
59
+ })
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Resolve the NATS URLs handed to a *paired* runner.
3
+ *
4
+ * A paired runner may run on a different machine, so the URL host must be
5
+ * routable *from that machine*. The embedded daemon's URL carries whatever host
6
+ * the server bound to — which is the wildcard `0.0.0.0` (or `::`) when started
7
+ * with `--remote` / `--host 0.0.0.0`. A wildcard host is never a valid client
8
+ * destination: a remote runner would dial its own loopback and get
9
+ * ECONNREFUSED.
10
+ *
11
+ * This mirrors the advertised-host rewrite that `/auth/token` already applies
12
+ * (server.ts) via `NATS_ADVERTISED_HOST`. Kept self-contained (local
13
+ * `rewriteUrlHost`) so the unit test does not pull in the native
14
+ * `@lagz0ne/nats-embedded` dependency through `nats-bridge.ts`.
15
+ */
16
+
17
+ /** Hosts that are bind-all wildcards and therefore unroutable as a client target. */
18
+ function isWildcardHost(host: string): boolean {
19
+ return host === "" || host === "0.0.0.0" || host === "::" || host === "[::]"
20
+ }
21
+
22
+ /** Return `rawUrl` with its host replaced by `host`, trailing slash stripped. */
23
+ function rewriteUrlHost(rawUrl: string, host: string): string {
24
+ const url = new URL(rawUrl)
25
+ url.hostname = host
26
+ return url.toString().replace(/\/$/, "")
27
+ }
28
+
29
+ export type RunnerPairingUrls =
30
+ | { ok: true; natsUrl: string; natsWsUrl: string }
31
+ | { ok: false; error: string }
32
+
33
+ /**
34
+ * Compute the `natsUrl` / `natsWsUrl` to return from the pairing exchange.
35
+ *
36
+ * - If `NATS_ADVERTISED_HOST` is set, rewrite both URL hosts to it.
37
+ * - Otherwise, if the daemon URL host is a wildcard, refuse (the operator must
38
+ * declare a routable host).
39
+ * - Otherwise pass the daemon URLs through unchanged (e.g. `127.0.0.1` for
40
+ * same-machine pairing, or a concrete LAN/tailnet IP).
41
+ */
42
+ export function resolveRunnerPairingUrls(
43
+ daemonInfo: { url: string; wsUrl: string },
44
+ env: NodeJS.ProcessEnv = process.env,
45
+ ): RunnerPairingUrls {
46
+ const advertisedHost = env.NATS_ADVERTISED_HOST?.trim()
47
+ if (advertisedHost) {
48
+ return {
49
+ ok: true,
50
+ natsUrl: rewriteUrlHost(daemonInfo.url, advertisedHost),
51
+ natsWsUrl: rewriteUrlHost(daemonInfo.wsUrl, advertisedHost),
52
+ }
53
+ }
54
+
55
+ const host = new URL(daemonInfo.url).hostname
56
+ if (isWildcardHost(host)) {
57
+ return {
58
+ ok: false,
59
+ error:
60
+ `NATS is bound to a wildcard host (${host || "unspecified"}), which a remote ` +
61
+ `runner cannot reach. Set NATS_ADVERTISED_HOST to the server's routable ` +
62
+ `address (LAN or tailnet IP) and re-pair.`,
63
+ }
64
+ }
65
+
66
+ return { ok: true, natsUrl: daemonInfo.url, natsWsUrl: daemonInfo.wsUrl }
67
+ }