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,171 @@
1
+ /**
2
+ * Integration tests for the pairing endpoints (PR2 Stage 1).
3
+ *
4
+ * Starts a real embedded NATS server on a dedicated port and exercises
5
+ * POST /api/pairing/code and POST /api/pairing/exchange over HTTP.
6
+ *
7
+ * Covers:
8
+ * - code → exchange round-trip; returned token verifies via verifyCredentialToken.
9
+ * - second exchange of same code → 410 (consumed).
10
+ * - unknown code → 400.
11
+ * - token mode (no callout tokenSecret) → 409 on code issue.
12
+ */
13
+
14
+ import { afterEach, describe, test, expect } from "bun:test"
15
+ import { startServer } from "./server"
16
+ import { verifyCredentialToken } from "../nats/auth-callout/token"
17
+ import { ensureCalloutKeys } from "../nats/auth-callout/keys"
18
+ import path from "node:path"
19
+ import os from "node:os"
20
+ import fs from "node:fs"
21
+
22
+ type StartedServer = Awaited<ReturnType<typeof startServer>>
23
+
24
+ // ── Helpers ───────────────────────────────────────────────────────────────────
25
+
26
+ async function post(port: number, pathname: string, body?: unknown): Promise<Response> {
27
+ return fetch(`http://127.0.0.1:${port}${pathname}`, {
28
+ method: "POST",
29
+ headers: body !== undefined ? { "Content-Type": "application/json" } : {},
30
+ body: body !== undefined ? JSON.stringify(body) : undefined,
31
+ })
32
+ }
33
+
34
+ // ── Callout-mode suite ────────────────────────────────────────────────────────
35
+
36
+ describe("pairing endpoints (callout mode)", () => {
37
+ let started: StartedServer | null = null
38
+ let natsDataDir: string
39
+
40
+ afterEach(async () => {
41
+ await started?.stop()
42
+ started = null
43
+ })
44
+
45
+ test("code → exchange round-trip; token verifies as runner credential", async () => {
46
+ natsDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "pr2-pair-test-"))
47
+ process.env.NATS_AUTH_MODE = "callout"
48
+ process.env.NATS_DATA_DIR = natsDataDir
49
+ started = await startServer({ port: 4381, host: "127.0.0.1", strictPort: true })
50
+ const port = started.port
51
+
52
+ // Issue a pairing code.
53
+ const issueRes = await post(port, "/api/pairing/code")
54
+ expect(issueRes.status).toBe(200)
55
+ const issued = await issueRes.json() as { code: string; expiresAt: number }
56
+ expect(typeof issued.code).toBe("string")
57
+ expect(issued.code).toMatch(/^[a-z2-7]{5}-[a-z2-7]{5}-[a-z2-7]{6}$/)
58
+ expect(typeof issued.expiresAt).toBe("number")
59
+ expect(issued.expiresAt).toBeGreaterThan(Date.now())
60
+
61
+ // Exchange the code.
62
+ const exchangeRes = await post(port, "/api/pairing/exchange", { code: issued.code })
63
+ expect(exchangeRes.status).toBe(200)
64
+ const exchanged = await exchangeRes.json() as {
65
+ runnerId: string; token: string; natsUrl: string; natsWsUrl: string
66
+ }
67
+ expect(typeof exchanged.runnerId).toBe("string")
68
+ expect(exchanged.runnerId).toMatch(/^runner-/)
69
+ expect(typeof exchanged.token).toBe("string")
70
+ expect(typeof exchanged.natsUrl).toBe("string")
71
+ expect(typeof exchanged.natsWsUrl).toBe("string")
72
+
73
+ // Verify the returned token decodes to the correct runner identity.
74
+ const keys = await ensureCalloutKeys(natsDataDir)
75
+ const identity = await verifyCredentialToken(exchanged.token, keys.tokenSecret)
76
+ expect(identity).toEqual({ class: "runner", runnerId: exchanged.runnerId })
77
+ }, 30_000)
78
+
79
+ test("second exchange of same code returns 410 (consumed)", async () => {
80
+ natsDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "pr2-pair-test-"))
81
+ process.env.NATS_AUTH_MODE = "callout"
82
+ process.env.NATS_DATA_DIR = natsDataDir
83
+ started = await startServer({ port: 4382, host: "127.0.0.1", strictPort: true })
84
+ const port = started.port
85
+
86
+ const { code } = await post(port, "/api/pairing/code").then((r) => r.json()) as { code: string }
87
+ // First exchange — succeeds.
88
+ const first = await post(port, "/api/pairing/exchange", { code })
89
+ expect(first.status).toBe(200)
90
+ // Second exchange — consumed.
91
+ const second = await post(port, "/api/pairing/exchange", { code })
92
+ expect(second.status).toBe(410)
93
+ const body = await second.json() as { error: string }
94
+ expect(body.error).toBe("consumed")
95
+ }, 30_000)
96
+
97
+ test("unknown code returns 400", async () => {
98
+ natsDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "pr2-pair-test-"))
99
+ process.env.NATS_AUTH_MODE = "callout"
100
+ process.env.NATS_DATA_DIR = natsDataDir
101
+ started = await startServer({ port: 4383, host: "127.0.0.1", strictPort: true })
102
+ const port = started.port
103
+
104
+ const res = await post(port, "/api/pairing/exchange", { code: "aaaaa-bbbbb-cccccc" })
105
+ expect(res.status).toBe(400)
106
+ const body = await res.json() as { error: string }
107
+ expect(body.error).toBe("unknown")
108
+ }, 30_000)
109
+
110
+ test("bound to 0.0.0.0 without NATS_ADVERTISED_HOST → exchange rejects with 409", async () => {
111
+ natsDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "pr2-pair-test-"))
112
+ process.env.NATS_AUTH_MODE = "callout"
113
+ process.env.NATS_DATA_DIR = natsDataDir
114
+ delete process.env.NATS_ADVERTISED_HOST
115
+ // Bind to 0.0.0.0 — the daemon URL becomes nats://0.0.0.0:<port>, which a
116
+ // remote runner cannot reach. The exchange must refuse, not hand it out.
117
+ started = await startServer({ port: 4385, host: "0.0.0.0", strictPort: true })
118
+ const port = started.port
119
+
120
+ const { code } = await post(port, "/api/pairing/code").then((r) => r.json()) as { code: string }
121
+ const res = await post(port, "/api/pairing/exchange", { code })
122
+ expect(res.status).toBe(409)
123
+ const body = await res.json() as { error: string }
124
+ expect(body.error).toContain("NATS_ADVERTISED_HOST")
125
+ }, 30_000)
126
+
127
+ test("bound to 0.0.0.0 with NATS_ADVERTISED_HOST → natsUrl carries the advertised host", async () => {
128
+ natsDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "pr2-pair-test-"))
129
+ process.env.NATS_AUTH_MODE = "callout"
130
+ process.env.NATS_DATA_DIR = natsDataDir
131
+ process.env.NATS_ADVERTISED_HOST = "100.64.0.2"
132
+ try {
133
+ started = await startServer({ port: 4386, host: "0.0.0.0", strictPort: true })
134
+ const port = started.port
135
+
136
+ const { code } = await post(port, "/api/pairing/code").then((r) => r.json()) as { code: string }
137
+ const res = await post(port, "/api/pairing/exchange", { code })
138
+ expect(res.status).toBe(200)
139
+ const body = await res.json() as { natsUrl: string; natsWsUrl: string }
140
+ expect(new URL(body.natsUrl).hostname).toBe("100.64.0.2")
141
+ expect(new URL(body.natsWsUrl).hostname).toBe("100.64.0.2")
142
+ } finally {
143
+ delete process.env.NATS_ADVERTISED_HOST
144
+ }
145
+ }, 30_000)
146
+ })
147
+
148
+ // ── Token-mode suite ──────────────────────────────────────────────────────────
149
+
150
+ describe("pairing endpoints (token mode)", () => {
151
+ let started: StartedServer | null = null
152
+
153
+ afterEach(async () => {
154
+ await started?.stop()
155
+ started = null
156
+ delete process.env.NATS_AUTH_MODE
157
+ delete process.env.NATS_DATA_DIR
158
+ })
159
+
160
+ test("POST /api/pairing/code returns 409 in token mode", async () => {
161
+ process.env.NATS_AUTH_MODE = "token"
162
+ delete process.env.NATS_DATA_DIR
163
+ started = await startServer({ port: 4384, host: "127.0.0.1", strictPort: true })
164
+ const port = started.port
165
+
166
+ const res = await post(port, "/api/pairing/code")
167
+ expect(res.status).toBe(409)
168
+ const body = await res.json() as { error: string }
169
+ expect(body.error).toContain("callout")
170
+ }, 30_000)
171
+ })
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Unit tests for PairingStore (PR2 Stage 1).
3
+ *
4
+ * Uses injectable clock and RNG so tests are deterministic and fast.
5
+ */
6
+
7
+ import { describe, test, expect } from "bun:test"
8
+ import { PairingStore } from "./pairing-store"
9
+
10
+ // ── Helpers ───────────────────────────────────────────────────────────────────
11
+
12
+ function makeEntry() {
13
+ return { runnerId: "runner-test-1", token: "tok.sig" }
14
+ }
15
+
16
+ /** A store with a controllable clock (ms) and a fixed-byte RNG. */
17
+ function makeStore(startMs = 1_000_000, rngByte = 0xab) {
18
+ let now = startMs
19
+ const tick = (ms: number) => { now += ms }
20
+ const store = new PairingStore({
21
+ now: () => now,
22
+ randomBytes: (n) => new Uint8Array(n).fill(rngByte),
23
+ ttlMs: 60_000, // 1 min for tests
24
+ })
25
+ return { store, tick }
26
+ }
27
+
28
+ /** A store with a controllable clock and a counter-based RNG (unique codes per issue). */
29
+ function makeCounterStore(startMs = 1_000_000) {
30
+ let now = startMs
31
+ let counter = 0
32
+ const tick = (ms: number) => { now += ms }
33
+ const store = new PairingStore({
34
+ now: () => now,
35
+ randomBytes: (n) => {
36
+ const buf = new Uint8Array(n)
37
+ // Fill with an incrementing byte so each call produces a distinct value.
38
+ buf.fill(counter++ & 0xff)
39
+ return buf
40
+ },
41
+ ttlMs: 60_000,
42
+ })
43
+ return { store, tick }
44
+ }
45
+
46
+ // ── Code shape ────────────────────────────────────────────────────────────────
47
+
48
+ describe("PairingStore.issue", () => {
49
+ test("returns a code and expiresAt", () => {
50
+ const startMs = 1_000_000
51
+ const { store } = makeStore(startMs)
52
+ const { code, expiresAt } = store.issue(makeEntry())
53
+ expect(typeof code).toBe("string")
54
+ expect(code.length).toBeGreaterThan(0)
55
+ // expiresAt should be in the future relative to our injected clock start
56
+ expect(expiresAt).toBeGreaterThan(startMs)
57
+ })
58
+
59
+ test("code has dashed base32 shape (xxxxx-xxxxx-xxxxxx)", () => {
60
+ const { store } = makeStore()
61
+ const { code } = store.issue(makeEntry())
62
+ // 5-5-6 groups separated by dashes (16 base32 chars), lowercase a-z and 2-7
63
+ expect(code).toMatch(/^[a-z2-7]{5}-[a-z2-7]{5}-[a-z2-7]{6}$/)
64
+ })
65
+
66
+ test("codes from different RNG bytes are unique", () => {
67
+ // Two stores with different RNG seeds produce different codes.
68
+ const { store: s1 } = makeStore(1_000_000, 0x11)
69
+ const { store: s2 } = makeStore(1_000_000, 0x22)
70
+ const { code: c1 } = s1.issue(makeEntry())
71
+ const { code: c2 } = s2.issue(makeEntry())
72
+ expect(c1).not.toBe(c2)
73
+ })
74
+
75
+ test("expiresAt equals now + ttlMs", () => {
76
+ const startMs = 5_000_000
77
+ const { store } = makeStore(startMs)
78
+ const { expiresAt } = store.issue(makeEntry())
79
+ expect(expiresAt).toBe(startMs + 60_000)
80
+ })
81
+ })
82
+
83
+ // ── Exchange — happy path ─────────────────────────────────────────────────────
84
+
85
+ describe("PairingStore.exchange", () => {
86
+ test("exchange returns runnerId and token on first use", () => {
87
+ const { store } = makeStore()
88
+ const entry = makeEntry()
89
+ const { code } = store.issue(entry)
90
+ const result = store.exchange(code)
91
+ expect(result).toEqual({ ok: true, runnerId: entry.runnerId, token: entry.token })
92
+ })
93
+
94
+ test("second exchange of same code returns consumed", () => {
95
+ const { store } = makeStore()
96
+ const { code } = store.issue(makeEntry())
97
+ store.exchange(code) // first — succeeds
98
+ const result = store.exchange(code) // second
99
+ expect(result).toEqual({ ok: false, error: "consumed" })
100
+ })
101
+
102
+ // ── Expiry ────────────────────────────────────────────────────────────────
103
+
104
+ test("expired code (clock advanced past expiresAt) returns expired", () => {
105
+ const { store, tick } = makeStore()
106
+ const { code } = store.issue(makeEntry())
107
+ tick(60_001) // just past the 1-min TTL
108
+ const result = store.exchange(code)
109
+ expect(result).toEqual({ ok: false, error: "expired" })
110
+ })
111
+
112
+ test("code exactly at expiresAt boundary is expired (>= check)", () => {
113
+ const { store, tick } = makeStore()
114
+ const { code, expiresAt } = store.issue(makeEntry())
115
+ // Advance so now === expiresAt exactly.
116
+ tick(expiresAt - 1_000_000) // start was 1_000_000
117
+ const result = store.exchange(code)
118
+ expect(result).toEqual({ ok: false, error: "expired" })
119
+ })
120
+
121
+ test("unknown code returns unknown", () => {
122
+ const { store } = makeStore()
123
+ const result = store.exchange("xxxxx-yyyyy-zzzz")
124
+ expect(result).toEqual({ ok: false, error: "unknown" })
125
+ })
126
+
127
+ // ── Sweep ─────────────────────────────────────────────────────────────────
128
+
129
+ test("expired entries are swept from internal map on next issue", () => {
130
+ // After an issue() triggers a sweep, previously-expired entries are deleted.
131
+ // A subsequent exchange() of the old code returns "unknown" (entry gone).
132
+ const { store, tick } = makeCounterStore()
133
+ const { code } = store.issue(makeEntry())
134
+ tick(60_001) // expire it
135
+ // Second issue produces a different code (counter RNG) and triggers sweep.
136
+ store.issue({ runnerId: "runner-2", token: "tok2.sig" })
137
+ // First entry was swept — exchange now reports "unknown".
138
+ const result = store.exchange(code)
139
+ expect(result).toEqual({ ok: false, error: "unknown" })
140
+ })
141
+
142
+ test("consumed entries are also evicted after TTL (no permanent consumed-oracle / memory leak)", () => {
143
+ // Within the TTL window a reused code reports "consumed"...
144
+ const { store, tick } = makeCounterStore()
145
+ const { code } = store.issue(makeEntry())
146
+ expect(store.exchange(code).ok).toBe(true) // consume it
147
+ expect(store.exchange(code)).toEqual({ ok: false, error: "consumed" }) // still in window
148
+ // ...but once past the TTL it is swept on the next activity and reads as
149
+ // "unknown" like any dead code — not "consumed" forever (MEDIUM-1 fix).
150
+ tick(60_001)
151
+ store.issue({ runnerId: "runner-2", token: "tok2.sig" }) // triggers sweep
152
+ expect(store.exchange(code)).toEqual({ ok: false, error: "unknown" })
153
+ })
154
+ })
@@ -0,0 +1,124 @@
1
+ /**
2
+ * In-memory single-use pairing code store (PR2 Stage 1).
3
+ *
4
+ * Issues short-lived, unguessable codes that each map once to a pre-minted
5
+ * runner credential. Codes are consumed atomically on first exchange; expired
6
+ * or unknown codes are rejected. Lost on server restart (the 10-min TTL window
7
+ * means the member can simply re-issue — EventStore persistence is deferred).
8
+ *
9
+ * Pure / injectable clock + RNG for unit tests.
10
+ */
11
+
12
+ /** Default pairing code TTL: 10 minutes. */
13
+ export const DEFAULT_CODE_TTL_MS = 10 * 60 * 1000
14
+
15
+ /** Base32 alphabet (RFC 4648, lowercase, human-legible, no padding). */
16
+ const BASE32_CHARS = "abcdefghijklmnopqrstuvwxyz234567"
17
+
18
+ /** Number of random bytes per code — gives ~80 bits of entropy. */
19
+ const CODE_RANDOM_BYTES = 10
20
+
21
+ /** Format: xxxxx-xxxxx-xxxxxx (5-5-6, 16 base32 chars total, dashed for readability). */
22
+ function formatCode(raw: string): string {
23
+ // 10 random bytes → 16 base32 chars; split 5-5-6 for human readability.
24
+ return `${raw.slice(0, 5)}-${raw.slice(5, 10)}-${raw.slice(10)}`
25
+ }
26
+
27
+ function encodeBase32(bytes: Uint8Array): string {
28
+ let bits = 0
29
+ let value = 0
30
+ let output = ""
31
+ for (const byte of bytes) {
32
+ value = (value << 8) | byte
33
+ bits += 8
34
+ while (bits >= 5) {
35
+ bits -= 5
36
+ output += BASE32_CHARS[(value >>> bits) & 0x1f]
37
+ }
38
+ }
39
+ return output
40
+ }
41
+
42
+ interface PairingEntry {
43
+ runnerId: string
44
+ /** Pre-minted durable runner credential token. Never logged in full. */
45
+ token: string
46
+ expiresAt: number
47
+ consumed: boolean
48
+ }
49
+
50
+ export type ExchangeResult =
51
+ | { ok: true; runnerId: string; token: string }
52
+ | { ok: false; error: "expired" | "consumed" | "unknown" }
53
+
54
+ export interface PairingStoreOptions {
55
+ /** Injected for tests; defaults to Date.now. */
56
+ now?: () => number
57
+ /** Injected for tests; defaults to crypto.getRandomValues. */
58
+ randomBytes?: (n: number) => Uint8Array
59
+ /** Code TTL in ms; defaults to DEFAULT_CODE_TTL_MS (10 min). */
60
+ ttlMs?: number
61
+ }
62
+
63
+ export class PairingStore {
64
+ private readonly codes = new Map<string, PairingEntry>()
65
+ private readonly now: () => number
66
+ private readonly randomBytes: (n: number) => Uint8Array
67
+ private readonly ttlMs: number
68
+
69
+ constructor(options: PairingStoreOptions = {}) {
70
+ this.now = options.now ?? (() => Date.now())
71
+ this.randomBytes = options.randomBytes ?? ((n) => {
72
+ const buf = new Uint8Array(n)
73
+ crypto.getRandomValues(buf)
74
+ return buf
75
+ })
76
+ this.ttlMs = options.ttlMs ?? DEFAULT_CODE_TTL_MS
77
+ }
78
+
79
+ /**
80
+ * Issue a new pairing code for the given runner credential.
81
+ * Returns the code and the timestamp (ms) when it expires.
82
+ */
83
+ issue(params: { runnerId: string; token: string }): { code: string; expiresAt: number } {
84
+ this.sweep()
85
+ const raw = encodeBase32(this.randomBytes(CODE_RANDOM_BYTES))
86
+ const code = formatCode(raw)
87
+ const expiresAt = this.now() + this.ttlMs
88
+ this.codes.set(code, { runnerId: params.runnerId, token: params.token, expiresAt, consumed: false })
89
+ return { code, expiresAt }
90
+ }
91
+
92
+ /**
93
+ * Exchange a code for its runner credential, atomically consuming it.
94
+ * A second call with the same code returns { error: "consumed" }.
95
+ * An expired code returns { error: "expired" }.
96
+ * An unknown code returns { error: "unknown" }.
97
+ */
98
+ exchange(code: string): ExchangeResult {
99
+ // Look up the entry BEFORE sweeping so we can distinguish "expired"
100
+ // (we know the code, it just timed out) from "unknown" (never issued).
101
+ const entry = this.codes.get(code)
102
+ this.sweep()
103
+ if (!entry) return { ok: false, error: "unknown" }
104
+ if (entry.consumed) return { ok: false, error: "consumed" }
105
+ if (this.now() >= entry.expiresAt) return { ok: false, error: "expired" }
106
+ // Atomically mark consumed before returning.
107
+ entry.consumed = true
108
+ return { ok: true, runnerId: entry.runnerId, token: entry.token }
109
+ }
110
+
111
+ /**
112
+ * Remove expired entries (consumed OR not) to bound memory growth.
113
+ * Sweeping consumed-and-expired entries also avoids a permanent
114
+ * "consumed vs. unknown" distinction for long-dead codes — within the TTL
115
+ * window a reused code still reports "consumed" (the entry has not expired);
116
+ * after the window it is evicted and reads as "unknown" like any other.
117
+ */
118
+ private sweep(): void {
119
+ const now = this.now()
120
+ for (const [code, entry] of this.codes) {
121
+ if (now >= entry.expiresAt) this.codes.delete(code)
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,27 @@
1
+ import { mkdir, stat } from "node:fs/promises"
2
+ import { homedir } from "node:os"
3
+ import path from "node:path"
4
+
5
+ export function resolveLocalPath(localPath: string) {
6
+ const trimmed = localPath.trim()
7
+ if (!trimmed) {
8
+ throw new Error("Project path is required")
9
+ }
10
+ if (trimmed === "~") {
11
+ return homedir()
12
+ }
13
+ if (trimmed.startsWith("~/")) {
14
+ return path.join(homedir(), trimmed.slice(2))
15
+ }
16
+ return path.resolve(trimmed)
17
+ }
18
+
19
+ export async function ensureProjectDirectory(localPath: string) {
20
+ const resolvedPath = resolveLocalPath(localPath)
21
+
22
+ await mkdir(resolvedPath, { recursive: true })
23
+ const info = await stat(resolvedPath)
24
+ if (!info.isDirectory()) {
25
+ throw new Error("Project path must be a directory")
26
+ }
27
+ }