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,252 @@
1
+ /**
2
+ * PR3 Stage 2 integration tests — heartbeat-TTL liveness states.
3
+ *
4
+ * Covers:
5
+ * 1. /health exposes state/protocolVersion/incompatible (boot a real server,
6
+ * assert the shape once a runner is healthy).
7
+ * 2. Discover path: KV entry with stale lastSeenAt → not adoptable; fresh → adoptable.
8
+ * 3. getReadiness() derives state from runnerLivenessState (unit, no NATS required).
9
+ */
10
+
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test"
12
+ import { mkdtempSync, rmSync } from "node:fs"
13
+ import { join } from "node:path"
14
+ import { tmpdir } from "node:os"
15
+ import { NatsServer } from "@lagz0ne/nats-embedded"
16
+ import { connect, type NatsConnection } from "@nats-io/transport-node"
17
+ import { Kvm } from "@nats-io/kv"
18
+ import { startServer } from "./server"
19
+ import { RunnerManager } from "./runner-manager"
20
+ import { ensureRunnerEventsStream, ensureRunnerRegistryBucket } from "./nats-streams"
21
+ import {
22
+ RUNNER_REGISTRY_BUCKET,
23
+ LIVENESS_OFFLINE_MS,
24
+ type RunnerRegistration,
25
+ } from "../shared/runner-protocol"
26
+
27
+ const encoder = new TextEncoder()
28
+
29
+ // ── Unit: getReadiness derives liveness state from lastHeartbeatAt ────────────
30
+
31
+ describe("RunnerManager.getReadiness — liveness state derivation", () => {
32
+ let server: NatsServer
33
+ let nc: NatsConnection
34
+
35
+ beforeEach(async () => {
36
+ server = await NatsServer.start({ jetstream: true })
37
+ nc = await connect({ servers: server.url })
38
+ await ensureRunnerEventsStream(nc)
39
+ })
40
+
41
+ afterEach(async () => {
42
+ await nc?.drain()
43
+ await server?.stop()
44
+ })
45
+
46
+ test("state is 'offline' when no heartbeat has been received", () => {
47
+ const mgr = new RunnerManager({ nc, natsUrl: server.url })
48
+ const r = mgr.getReadiness()
49
+ expect(r.state).toBe("offline")
50
+ expect(r.heartbeatFresh).toBe(false)
51
+ expect(r.ok).toBe(false)
52
+ })
53
+
54
+ test("state is 'online' when heartbeat was just received (simulated via injected clock)", async () => {
55
+ const mgr = new RunnerManager({ nc, natsUrl: server.url })
56
+ // Access private field to inject a heartbeat timestamp directly.
57
+ const now = Date.now()
58
+ ;(mgr as unknown as { lastHeartbeatAt: number }).lastHeartbeatAt = now
59
+ const r = mgr.getReadiness(now)
60
+ expect(r.state).toBe("online")
61
+ expect(r.heartbeatFresh).toBe(true)
62
+ })
63
+
64
+ test("state is 'degraded' when heartbeat age is between 25s and 60s", () => {
65
+ const mgr = new RunnerManager({ nc, natsUrl: server.url })
66
+ const now = Date.now()
67
+ ;(mgr as unknown as { lastHeartbeatAt: number }).lastHeartbeatAt = now - 30_000
68
+ const r = mgr.getReadiness(now)
69
+ expect(r.state).toBe("degraded")
70
+ expect(r.heartbeatFresh).toBe(false) // heartbeatFresh === (state === "online")
71
+ })
72
+
73
+ test("state is 'offline' when heartbeat is older than LIVENESS_OFFLINE_MS", () => {
74
+ const mgr = new RunnerManager({ nc, natsUrl: server.url })
75
+ const now = Date.now()
76
+ ;(mgr as unknown as { lastHeartbeatAt: number }).lastHeartbeatAt = now - LIVENESS_OFFLINE_MS
77
+ const r = mgr.getReadiness(now)
78
+ expect(r.state).toBe("offline")
79
+ expect(r.heartbeatFresh).toBe(false)
80
+ })
81
+
82
+ test("heartbeatFresh === (state === 'online') invariant holds for all states", () => {
83
+ const mgr = new RunnerManager({ nc, natsUrl: server.url })
84
+ const now = Date.now()
85
+
86
+ for (const [age, expectedState] of [
87
+ [0, "online"],
88
+ [30_000, "degraded"],
89
+ [LIVENESS_OFFLINE_MS, "offline"],
90
+ [null, "offline"],
91
+ ] as [number | null, string][]) {
92
+ ;(mgr as unknown as { lastHeartbeatAt: number | null }).lastHeartbeatAt =
93
+ age === null ? null : now - age
94
+ const r = mgr.getReadiness(now)
95
+ expect(r.state as string).toBe(expectedState)
96
+ expect(r.heartbeatFresh).toBe(r.state === "online")
97
+ }
98
+ })
99
+ })
100
+
101
+ // ── Integration: discover path uses lastSeenAt TTL, not process.kill ─────────
102
+
103
+ describe("discover path — lastSeenAt TTL liveness", () => {
104
+ let server: NatsServer
105
+ let nc: NatsConnection
106
+ let tmpDir: string
107
+
108
+ beforeEach(async () => {
109
+ tmpDir = mkdtempSync(join(tmpdir(), "pr3-discover-"))
110
+ server = await NatsServer.start({ jetstream: true, storeDir: tmpDir })
111
+ nc = await connect({ servers: server.url })
112
+ await ensureRunnerEventsStream(nc)
113
+ await ensureRunnerRegistryBucket(nc)
114
+ })
115
+
116
+ afterEach(async () => {
117
+ await nc?.drain()
118
+ await server?.stop()
119
+ rmSync(tmpDir, { recursive: true, force: true })
120
+ })
121
+
122
+ test("stale lastSeenAt (>60s ago) → discover treats entry as offline, not adoptable", async () => {
123
+ // Write a KV entry whose lastSeenAt is well past the offline threshold.
124
+ const kvm = new Kvm(nc)
125
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
126
+ const staleReg: RunnerRegistration = {
127
+ runnerId: "stale-runner",
128
+ pid: 99999,
129
+ startedAt: Date.now() - 120_000,
130
+ providers: ["claude", "codex"],
131
+ protocolVersion: 1,
132
+ lastSeenAt: Date.now() - (LIVENESS_OFFLINE_MS + 5_000), // 65s ago — offline
133
+ }
134
+ await kvStore.put("stale-runner", encoder.encode(JSON.stringify(staleReg)))
135
+
136
+ // Discover mode should poll, find stale-runner, skip it (offline), and
137
+ // eventually time out (no live runner). We cap the timeout to 1s for speed.
138
+ const mgr = new RunnerManager({ nc, natsUrl: server.url, mode: "discover" })
139
+ await expect(
140
+ // Override the 15s deadline: wrap in a 1.5s race so the test completes fast.
141
+ Promise.race([
142
+ mgr.ensureRunner(),
143
+ new Promise<never>((_, reject) =>
144
+ setTimeout(() => reject(new Error("no live runner — stale skipped")), 1_500)
145
+ ),
146
+ ])
147
+ ).rejects.toThrow(/no live runner|No external runner/)
148
+ }, 10_000)
149
+
150
+ test("fresh lastSeenAt (just now) → discover adopts the entry", async () => {
151
+ // Write a KV entry with a fresh lastSeenAt so discover considers it live.
152
+ const kvm = new Kvm(nc)
153
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
154
+ const freshRunnerId = "fresh-runner"
155
+ const freshReg: RunnerRegistration = {
156
+ runnerId: freshRunnerId,
157
+ pid: process.pid, // real pid so adopt doesn't crash on heartbeat wait
158
+ startedAt: Date.now(),
159
+ providers: ["claude", "codex"],
160
+ protocolVersion: 1,
161
+ lastSeenAt: Date.now(), // fresh
162
+ }
163
+ await kvStore.put(freshRunnerId, encoder.encode(JSON.stringify(freshReg)))
164
+
165
+ // Discover picks the runner as adoptable. It then subscribes to heartbeats
166
+ // and waits 5s for one — which won't arrive (no actual runner process here).
167
+ // That's expected: what we're testing is that the *entry is picked at all*.
168
+ const mgr = new RunnerManager({ nc, natsUrl: server.url, mode: "discover" })
169
+ await expect(
170
+ Promise.race([
171
+ mgr.ensureRunner(),
172
+ new Promise<never>((_, reject) =>
173
+ setTimeout(() => reject(new Error("heartbeat timeout — runner adopted but no process")), 6_000)
174
+ ),
175
+ ])
176
+ ).rejects.toThrow(/heartbeat|no process/)
177
+ // The key assertion: runnerId was set (entry was adopted before the heartbeat wait)
178
+ expect((mgr as unknown as { runnerId: string | null }).runnerId).toBe(freshRunnerId)
179
+ }, 15_000)
180
+
181
+ test("incompatible lastSeenAt-fresh entry → discover skips it", async () => {
182
+ const kvm = new Kvm(nc)
183
+ const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
184
+ const incompatReg: RunnerRegistration = {
185
+ runnerId: "incompat-runner",
186
+ pid: 88888,
187
+ startedAt: Date.now(),
188
+ providers: ["claude"],
189
+ protocolVersion: 999, // outside SUPPORTED_RANGE
190
+ lastSeenAt: Date.now(), // fresh — but incompatible
191
+ }
192
+ await kvStore.put("incompat-runner", encoder.encode(JSON.stringify(incompatReg)))
193
+
194
+ const mgr = new RunnerManager({ nc, natsUrl: server.url, mode: "discover" })
195
+ await expect(
196
+ Promise.race([
197
+ mgr.ensureRunner(),
198
+ new Promise<never>((_, reject) =>
199
+ setTimeout(() => reject(new Error("incompatible runner skipped")), 1_500)
200
+ ),
201
+ ])
202
+ ).rejects.toThrow(/incompatible|No external runner/)
203
+ }, 10_000)
204
+ })
205
+
206
+ // ── Integration: /health exposes state/protocolVersion/incompatible ───────────
207
+
208
+ describe("/health runner shape (integration)", () => {
209
+ let started: Awaited<ReturnType<typeof startServer>> | null = null
210
+
211
+ afterEach(async () => {
212
+ await started?.stop()
213
+ started = null
214
+ delete process.env.NATS_DATA_DIR
215
+ delete process.env.RUNNER_PROTOCOL_VERSION
216
+ })
217
+
218
+ test("/health runner object includes state, protocolVersion, incompatible", async () => {
219
+ const natsDataDir = mkdtempSync(join(tmpdir(), "pr3-health-"))
220
+ process.env.NATS_DATA_DIR = natsDataDir
221
+ try {
222
+ started = await startServer({ port: 4371, host: "127.0.0.1", strictPort: true })
223
+ const port = started.port
224
+
225
+ // Wait up to 15s for the runner to become healthy.
226
+ let health: Record<string, unknown> | null = null
227
+ const deadline = Date.now() + 15_000
228
+ while (Date.now() < deadline) {
229
+ const res = await fetch(`http://127.0.0.1:${port}/health`)
230
+ const body = await res.json() as { ok: boolean; runner: Record<string, unknown> }
231
+ if (body.ok) {
232
+ health = body.runner
233
+ break
234
+ }
235
+ await new Promise((r) => setTimeout(r, 200))
236
+ }
237
+
238
+ expect(health).not.toBeNull()
239
+ // state must be present and one of the liveness values
240
+ expect(["online", "degraded", "offline"]).toContain(health!.state as string)
241
+ expect(health!.state).toBe("online") // healthy runner → online
242
+ // heartbeatFresh back-compat: must equal (state === "online")
243
+ expect(health!.heartbeatFresh).toBe(health!.state === "online")
244
+ // version fields
245
+ expect(typeof health!.protocolVersion).toBe("number")
246
+ expect(health!.protocolVersion).toBe(1)
247
+ expect(health!.incompatible).toBe(false)
248
+ } finally {
249
+ rmSync(natsDataDir, { recursive: true, force: true })
250
+ }
251
+ }, 30_000)
252
+ })
@@ -0,0 +1,10 @@
1
+ import { spawn, spawnSync } from "node:child_process"
2
+
3
+ export function spawnDetached(command: string, args: string[]) {
4
+ spawn(command, args, { stdio: "ignore", detached: true }).unref()
5
+ }
6
+
7
+ export function hasCommand(command: string) {
8
+ const result = spawnSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" })
9
+ return result.status === 0
10
+ }
@@ -0,0 +1,180 @@
1
+ // src/server/project-cli.ts
2
+
3
+ export interface CliCommand {
4
+ command: string
5
+ args: Record<string, string | undefined>
6
+ }
7
+
8
+ export function parseProjectCliArgs(argv: string[]): CliCommand {
9
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
10
+ return { command: "help", args: {} }
11
+ }
12
+
13
+ const flags: Record<string, string> = {}
14
+ const positional: string[] = []
15
+
16
+ for (let i = 0; i < argv.length; i++) {
17
+ if (argv[i].startsWith("--") && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
18
+ flags[argv[i].slice(2)] = argv[i + 1]
19
+ i++
20
+ } else if (!argv[i].startsWith("--")) {
21
+ positional.push(argv[i])
22
+ }
23
+ }
24
+
25
+ const cmd = positional[0]
26
+ const rest = positional.slice(1)
27
+ // Normalize flag names to match API expectations
28
+ const normalized: Record<string, string> = {}
29
+ for (const [key, value] of Object.entries(flags)) {
30
+ if (key === "project") normalized["workspaceId"] = value
31
+ else normalized[key] = value
32
+ }
33
+ const args: Record<string, string | undefined> = { ...normalized }
34
+
35
+ switch (cmd) {
36
+ case "sessions":
37
+ if (rest.length > 0) return { command: "session-detail", args: { ...args, chatId: rest[0] } }
38
+ return { command: "sessions", args }
39
+ case "search":
40
+ return { command: "search", args: { ...args, query: rest.join(" ") } }
41
+ case "tasks":
42
+ if (rest.length > 0) return { command: "task-detail", args: { ...args, taskId: rest[0] } }
43
+ return { command: "tasks", args }
44
+ case "claim":
45
+ return { command: "claim", args: { ...args, description: rest.join(" ") } }
46
+ case "complete":
47
+ return { command: "complete", args: { ...args, taskId: rest[0] } }
48
+ case "delegate":
49
+ return { command: "delegate", args: { ...args, request: rest.join(" ") } }
50
+ default:
51
+ return { command: "help", args: {} }
52
+ }
53
+ }
54
+
55
+ export function formatOutput(command: string, data: unknown, json: boolean): string {
56
+ if (json) return JSON.stringify(data, null, 2)
57
+
58
+ if (command === "sessions" && Array.isArray(data)) {
59
+ if (data.length === 0) return "No sessions found."
60
+ const rows = data.map((s: Record<string, unknown>) =>
61
+ `${s.chatId} ${String(s.status).padEnd(8)} ${String(s.provider).padEnd(6)} ${String(s.intent ?? "").slice(0, 50)}`
62
+ )
63
+ return ["CHAT_ID STATUS PROVIDER INTENT", ...rows].join("\n")
64
+ }
65
+
66
+ if (command === "tasks" && Array.isArray(data)) {
67
+ if (data.length === 0) return "No tasks tracked."
68
+ const rows = data.map((t: Record<string, unknown>) =>
69
+ `${t.id} ${String(t.status).padEnd(12)} ${t.claimedBy ?? t.createdBy ?? "-"} ${String(t.description).slice(0, 50)}`
70
+ )
71
+ return ["ID STATUS CLAIMED_BY DESCRIPTION", ...rows].join("\n")
72
+ }
73
+
74
+ return JSON.stringify(data, null, 2)
75
+ }
76
+
77
+ export function getHelpText(): string {
78
+ return `tinkaria-project — Cross-session project agent CLI
79
+
80
+ Commands:
81
+ sessions List active/recent sessions with summaries
82
+ sessions <chat-id> Detailed summary of a specific session
83
+ search <query> Lexical search over project transcripts
84
+ tasks List all project tasks
85
+ tasks <task-id> Get task details
86
+ claim <description> Claim a new task for the current session
87
+ complete <task-id> Mark a task as complete
88
+ delegate <request> Submit a delegation request to the project agent
89
+
90
+ Flags:
91
+ --json Output as JSON (default when stdout is not a TTY)
92
+ --project <id> Target project (default: current)
93
+ --session <chat-id> Identify calling session (for claim/complete)
94
+ --port <port> Tinkaria server port (default: 3210)
95
+ --version CLI version
96
+ --help Show this help`
97
+ }
98
+
99
+ export async function executeCommand(
100
+ parsed: CliCommand,
101
+ baseUrl: string,
102
+ ): Promise<{ output: string; exitCode: number }> {
103
+ const args = parsed.args
104
+ const json = "json" in args || !process.stdout.isTTY
105
+
106
+ try {
107
+ let data: unknown
108
+ switch (parsed.command) {
109
+ case "help":
110
+ return { output: getHelpText(), exitCode: 0 }
111
+ case "sessions": {
112
+ const workspaceId = args.workspaceId ?? ""
113
+ const res = await fetch(`${baseUrl}/api/workspace/sessions?workspaceId=${encodeURIComponent(workspaceId)}`)
114
+ data = await res.json()
115
+ break
116
+ }
117
+ case "session-detail": {
118
+ const res = await fetch(`${baseUrl}/api/workspace/sessions/${args.chatId}`)
119
+ if (res.status === 404) return { output: formatOutput("error", { error: "Session not found" }, json), exitCode: 1 }
120
+ data = await res.json()
121
+ break
122
+ }
123
+ case "search": {
124
+ const res = await fetch(`${baseUrl}/api/workspace/search`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({ query: args.query, limit: 10 }),
128
+ })
129
+ data = await res.json()
130
+ break
131
+ }
132
+ case "tasks": {
133
+ const res = await fetch(`${baseUrl}/api/workspace/tasks`)
134
+ data = await res.json()
135
+ break
136
+ }
137
+ case "task-detail": {
138
+ const res = await fetch(`${baseUrl}/api/workspace/tasks/${args.taskId}`)
139
+ if (res.status === 404) return { output: formatOutput("error", { error: "Task not found" }, json), exitCode: 1 }
140
+ data = await res.json()
141
+ break
142
+ }
143
+ case "claim": {
144
+ const res = await fetch(`${baseUrl}/api/workspace/claim`, {
145
+ method: "POST",
146
+ headers: { "Content-Type": "application/json" },
147
+ body: JSON.stringify({ description: args.description, session: args.session, branch: args.branch ?? null }),
148
+ })
149
+ if (res.status === 400) return { output: formatOutput("error", await res.json(), json), exitCode: 1 }
150
+ data = await res.json()
151
+ break
152
+ }
153
+ case "complete": {
154
+ const res = await fetch(`${baseUrl}/api/workspace/complete`, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({ taskId: args.taskId, outputs: [] }),
158
+ })
159
+ if (res.status === 404) return { output: formatOutput("error", { error: "Task not found" }, json), exitCode: 1 }
160
+ data = await res.json()
161
+ break
162
+ }
163
+ case "delegate": {
164
+ const res = await fetch(`${baseUrl}/api/workspace/delegate`, {
165
+ method: "POST",
166
+ headers: { "Content-Type": "application/json" },
167
+ body: JSON.stringify({ request: args.request, workspaceId: args.workspaceId ?? "" }),
168
+ })
169
+ data = await res.json()
170
+ break
171
+ }
172
+ default:
173
+ return { output: getHelpText(), exitCode: 1 }
174
+ }
175
+ return { output: formatOutput(parsed.command, data, json), exitCode: 0 }
176
+ } catch (err) {
177
+ const message = err instanceof Error ? err.message : String(err)
178
+ return { output: JSON.stringify({ error: message, code: 2 }), exitCode: 2 }
179
+ }
180
+ }
@@ -0,0 +1,177 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import {
3
+ codexServiceTierFromModelOptions,
4
+ normalizeClaudeModelOptions,
5
+ normalizeCodexModelOptions,
6
+ normalizeServerModel,
7
+ deriveServerProviderCatalog,
8
+ SERVER_PROVIDERS,
9
+ } from "./provider-catalog"
10
+ import { resolveClaudeApiModelId } from "../shared/types"
11
+ import type { RuntimeCapabilities, DiscoveredModel } from "../shared/runtime-types"
12
+
13
+ describe("provider catalog normalization", () => {
14
+ test("maps legacy Claude effort into shared model options", () => {
15
+ expect(normalizeClaudeModelOptions("opus", undefined, "max")).toEqual({
16
+ reasoningEffort: "max",
17
+ contextWindow: "200k",
18
+ })
19
+ })
20
+
21
+ test("normalizes Claude context window only for supported models", () => {
22
+ expect(normalizeClaudeModelOptions("sonnet", {
23
+ claude: {
24
+ reasoningEffort: "medium",
25
+ contextWindow: "1m",
26
+ },
27
+ })).toEqual({
28
+ reasoningEffort: "medium",
29
+ contextWindow: "1m",
30
+ })
31
+
32
+ expect(normalizeClaudeModelOptions("haiku", {
33
+ claude: {
34
+ reasoningEffort: "medium",
35
+ contextWindow: "1m",
36
+ },
37
+ })).toMatchObject({
38
+ reasoningEffort: "medium",
39
+ })
40
+ })
41
+
42
+ test("normalizes Codex model options and fast mode defaults", () => {
43
+ expect(normalizeCodexModelOptions(undefined)).toEqual({
44
+ reasoningEffort: "high",
45
+ fastMode: false,
46
+ })
47
+
48
+ const normalized = normalizeCodexModelOptions({
49
+ codex: {
50
+ reasoningEffort: "xhigh",
51
+ fastMode: true,
52
+ },
53
+ })
54
+
55
+ expect(normalized).toEqual({
56
+ reasoningEffort: "xhigh",
57
+ fastMode: true,
58
+ })
59
+ expect(codexServiceTierFromModelOptions(normalized)).toBe("fast")
60
+ })
61
+
62
+ test("resolves Claude API model ids for 1m context window", () => {
63
+ expect(resolveClaudeApiModelId("opus", "1m")).toBe("opus[1m]")
64
+ expect(resolveClaudeApiModelId("sonnet", "200k")).toBe("sonnet")
65
+ })
66
+ })
67
+
68
+ const DISCOVERED_MODELS: DiscoveredModel[] = [
69
+ {
70
+ value: "default",
71
+ displayName: "Default (recommended)",
72
+ description: "Opus 4.6 with 1M context",
73
+ supportsEffort: true,
74
+ supportedEffortLevels: ["low", "medium", "high", "max"],
75
+ supportsFastMode: true,
76
+ supportsAutoMode: true,
77
+ },
78
+ {
79
+ value: "sonnet",
80
+ displayName: "Sonnet",
81
+ description: "Sonnet 4.6 · Best for everyday tasks",
82
+ supportsEffort: true,
83
+ supportedEffortLevels: ["low", "medium", "high"],
84
+ supportsAutoMode: true,
85
+ },
86
+ {
87
+ value: "haiku",
88
+ displayName: "Haiku",
89
+ description: "Haiku 4.5 · Fastest for quick answers",
90
+ },
91
+ ]
92
+
93
+ function makeCapabilities(models: DiscoveredModel[] = DISCOVERED_MODELS): RuntimeCapabilities {
94
+ return { models, probedAt: Date.now(), runtimeVersion: "1.0.0" }
95
+ }
96
+
97
+ describe("deriveServerProviderCatalog", () => {
98
+ test("returns static SERVER_PROVIDERS when no capabilities", () => {
99
+ const result = deriveServerProviderCatalog()
100
+ expect(result).toEqual(SERVER_PROVIDERS)
101
+ })
102
+
103
+ test("returns static SERVER_PROVIDERS when capabilities have empty models", () => {
104
+ const result = deriveServerProviderCatalog(makeCapabilities([]))
105
+ expect(result).toEqual(SERVER_PROVIDERS)
106
+ })
107
+
108
+ test("enriches static Claude models with discovered description and effort levels", () => {
109
+ const result = deriveServerProviderCatalog(makeCapabilities())
110
+ const claude = result.find((p) => p.id === "claude")!
111
+ const sonnet = claude.models.find((m) => m.id === "sonnet")!
112
+ expect(sonnet.description).toBe("Sonnet 4.6 · Best for everyday tasks")
113
+ expect(sonnet.supportedEffortLevels).toEqual(["low", "medium", "high"])
114
+ })
115
+
116
+ test("maps 'default' discovered model to static 'opus' entry", () => {
117
+ const result = deriveServerProviderCatalog(makeCapabilities())
118
+ const claude = result.find((p) => p.id === "claude")!
119
+ const opus = claude.models.find((m) => m.id === "opus")!
120
+ expect(opus.description).toBe("Opus 4.6 with 1M context")
121
+ expect(opus.supportedEffortLevels).toEqual(["low", "medium", "high", "max"])
122
+ })
123
+
124
+ test("appends discovered models not in static list", () => {
125
+ const extraModel: DiscoveredModel = {
126
+ value: "claude-opus-4-7",
127
+ displayName: "Opus 4",
128
+ description: "Newer version available",
129
+ }
130
+ const result = deriveServerProviderCatalog(makeCapabilities([...DISCOVERED_MODELS, extraModel]))
131
+ const claude = result.find((p) => p.id === "claude")!
132
+ const extra = claude.models.find((m) => m.id === "claude-opus-4-7")
133
+ expect(extra).toBeDefined()
134
+ expect(extra!.description).toBe("Newer version available")
135
+ expect(extra!.label).toBe("Opus 4")
136
+ })
137
+
138
+ test("preserves static model order — static first, discovered extras appended", () => {
139
+ const extraModel: DiscoveredModel = {
140
+ value: "claude-opus-4-7",
141
+ displayName: "Opus 4",
142
+ description: "Newer version",
143
+ }
144
+ const result = deriveServerProviderCatalog(makeCapabilities([...DISCOVERED_MODELS, extraModel]))
145
+ const claude = result.find((p) => p.id === "claude")!
146
+ const ids = claude.models.map((m) => m.id)
147
+ expect(ids).toEqual(["opus", "sonnet", "haiku", "claude-opus-4-7"])
148
+ })
149
+
150
+ test("codex is always static regardless of capabilities", () => {
151
+ const result = deriveServerProviderCatalog(makeCapabilities())
152
+ const codex = result.find((p) => p.id === "codex")!
153
+ const staticCodex = SERVER_PROVIDERS.find((p) => p.id === "codex")!
154
+ expect(codex).toEqual(staticCodex)
155
+ })
156
+ })
157
+
158
+ describe("normalizeServerModel with dynamic catalog", () => {
159
+ test("accepts full model id when present in dynamic catalog", () => {
160
+ const extraModel: DiscoveredModel = {
161
+ value: "claude-opus-4-7",
162
+ displayName: "Opus 4",
163
+ description: "Newer version",
164
+ }
165
+ const catalog = deriveServerProviderCatalog(makeCapabilities([...DISCOVERED_MODELS, extraModel]))
166
+ expect(normalizeServerModel("claude", "claude-opus-4-7", catalog)).toBe("claude-opus-4-7")
167
+ })
168
+
169
+ test("falls back to default model when model not in catalog", () => {
170
+ const catalog = deriveServerProviderCatalog(makeCapabilities())
171
+ expect(normalizeServerModel("claude", "nonexistent", catalog)).toBe("sonnet")
172
+ })
173
+
174
+ test("uses static catalog by default (backward compat)", () => {
175
+ expect(normalizeServerModel("claude", "sonnet")).toBe("sonnet")
176
+ })
177
+ })