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,194 @@
1
+ export interface LimitDetection {
2
+ chatId: string
3
+ resetAt: number
4
+ tz: string
5
+ raw: unknown
6
+ }
7
+
8
+ export interface LimitDetector {
9
+ detect(chatId: string, error: unknown): LimitDetection | null
10
+ detectFromResultText?(chatId: string, text: string, nowMs?: number): LimitDetection | null
11
+ detectFromSdkRateLimitInfo?(chatId: string, info: unknown): LimitDetection | null
12
+ }
13
+
14
+ interface ErrorLike {
15
+ message?: string
16
+ status?: number
17
+ headers?: Record<string, string>
18
+ }
19
+
20
+ function extractHeaders(error: unknown): Record<string, string> {
21
+ if (error && typeof error === "object" && "headers" in error) {
22
+ const headers = (error as ErrorLike).headers
23
+ if (headers && typeof headers === "object") return headers
24
+ }
25
+ return {}
26
+ }
27
+
28
+ function parseBody(error: unknown): Record<string, unknown> | null {
29
+ if (!error || typeof error !== "object") return null
30
+ const message = (error as ErrorLike).message
31
+ if (!message) return null
32
+ try {
33
+ const parsed = JSON.parse(message)
34
+ return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : null
35
+ } catch {
36
+ return null
37
+ }
38
+ }
39
+
40
+ function parseIsoMillis(value: unknown): number | null {
41
+ if (typeof value !== "string" || !value) return null
42
+ const millis = new Date(value).getTime()
43
+ return Number.isFinite(millis) ? millis : null
44
+ }
45
+
46
+ function zonedWallClockToUtcMs(
47
+ year: number, month: number, day: number, hour: number, minute: number, tz: string,
48
+ ): number {
49
+ const utcGuess = Date.UTC(year, month - 1, day, hour, minute)
50
+ const dtf = new Intl.DateTimeFormat("en-US", {
51
+ timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit",
52
+ hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false,
53
+ })
54
+ const parts = Object.fromEntries(
55
+ dtf.formatToParts(new Date(utcGuess))
56
+ .filter((part) => part.type !== "literal")
57
+ .map((part) => [part.type, part.value]),
58
+ )
59
+ const asLocal = Date.UTC(
60
+ Number(parts.year), Number(parts.month) - 1, Number(parts.day),
61
+ parts.hour === "24" ? 0 : Number(parts.hour), Number(parts.minute),
62
+ )
63
+ return utcGuess - (asLocal - utcGuess)
64
+ }
65
+
66
+ export function parseResetFromText(text: string, nowMs: number = Date.now()): { resetAt: number; tz: string } | null {
67
+ if (typeof text !== "string") return null
68
+ const match = text.match(/resets\s+(\d{1,2})(?::(\d{2}))?(am|pm)\s*\(([^)]+)\)/i)
69
+ if (!match) return null
70
+ const hour12 = Number(match[1])
71
+ const minute = match[2] ? Number(match[2]) : 0
72
+ const meridiem = match[3].toLowerCase()
73
+ const tz = match[4].trim()
74
+ if (!Number.isFinite(hour12) || hour12 < 1 || hour12 > 12) return null
75
+ if (!Number.isFinite(minute) || minute < 0 || minute > 59) return null
76
+ const hour24 = meridiem === "pm"
77
+ ? (hour12 === 12 ? 12 : hour12 + 12)
78
+ : (hour12 === 12 ? 0 : hour12)
79
+ let tzYear: number, tzMonth: number, tzDay: number
80
+ try {
81
+ const dtf = new Intl.DateTimeFormat("en-US", {
82
+ timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit",
83
+ })
84
+ const parts = Object.fromEntries(
85
+ dtf.formatToParts(new Date(nowMs))
86
+ .filter((part) => part.type !== "literal")
87
+ .map((part) => [part.type, part.value]),
88
+ )
89
+ tzYear = Number(parts.year)
90
+ tzMonth = Number(parts.month)
91
+ tzDay = Number(parts.day)
92
+ } catch {
93
+ return null
94
+ }
95
+ let resetAt = zonedWallClockToUtcMs(tzYear, tzMonth, tzDay, hour24, minute, tz)
96
+ if (resetAt <= nowMs) {
97
+ const next = new Date(Date.UTC(tzYear, tzMonth - 1, tzDay) + 24 * 3600_000)
98
+ resetAt = zonedWallClockToUtcMs(
99
+ next.getUTCFullYear(), next.getUTCMonth() + 1, next.getUTCDate(), hour24, minute, tz,
100
+ )
101
+ }
102
+ return { resetAt, tz }
103
+ }
104
+
105
+ export class ClaudeLimitDetector implements LimitDetector {
106
+ detect(chatId: string, error: unknown): LimitDetection | null {
107
+ const body = parseBody(error)
108
+ const inner = body && typeof body.error === "object" && body.error !== null
109
+ ? (body.error as Record<string, unknown>)
110
+ : null
111
+ const isRateLimit = inner?.type === "rate_limit_error"
112
+ || (error as ErrorLike | null)?.status === 429 && inner?.type === "rate_limit_error"
113
+
114
+ if (isRateLimit) {
115
+ const headers = extractHeaders(error)
116
+ const resetAt = parseIsoMillis(headers["anthropic-ratelimit-unified-reset"])
117
+ ?? parseIsoMillis(inner?.resets_at)
118
+ ?? parseIsoMillis(inner?.reset_at)
119
+ if (resetAt !== null) {
120
+ const tz = headers["x-anthropic-timezone"]
121
+ ?? (typeof inner?.timezone === "string" ? (inner.timezone as string) : null)
122
+ ?? "system"
123
+ return { chatId, resetAt, tz, raw: error }
124
+ }
125
+ }
126
+
127
+ // Fallback: the Claude Code SDK rethrows CLI result errors as
128
+ // `Error("Claude Code returned an error result: <text>")`. Parse the
129
+ // text directly for "You've hit your limit · resets ..." / "usage limit
130
+ // reached|<unix>" forms.
131
+ const message = (error as ErrorLike | null)?.message
132
+ if (typeof message === "string") {
133
+ return this.detectFromResultText(chatId, message)
134
+ }
135
+ return null
136
+ }
137
+
138
+ detectFromResultText(chatId: string, text: string, nowMs: number = Date.now()): LimitDetection | null {
139
+ const parsed = parseResetFromText(text, nowMs)
140
+ if (parsed) return { chatId, resetAt: parsed.resetAt, tz: parsed.tz, raw: text }
141
+ const pipe = parseClaudeUsageLimitPipe(text)
142
+ if (pipe !== null) return { chatId, resetAt: pipe, tz: "system", raw: text }
143
+ return null
144
+ }
145
+
146
+ detectFromSdkRateLimitInfo(chatId: string, info: unknown): LimitDetection | null {
147
+ if (!info || typeof info !== "object") return null
148
+ const rec = info as Record<string, unknown>
149
+ if (rec.status !== "rejected") return null
150
+ const raw = rec.resetsAt
151
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null
152
+ // SDK emits `resetsAt` as epoch seconds for claude.ai subscription limits;
153
+ // coerce to ms defensively (anything below year 5138 in ms is below 1e14).
154
+ const resetAt = raw < 1e12 ? Math.round(raw * 1000) : raw
155
+ return { chatId, resetAt, tz: "system", raw: info }
156
+ }
157
+ }
158
+
159
+ export function parseClaudeUsageLimitPipe(text: string): number | null {
160
+ // Claude CLI sometimes returns "Claude AI usage limit reached|<unix-seconds>".
161
+ if (typeof text !== "string") return null
162
+ const match = text.match(/usage limit reached\|(\d{9,13})/i)
163
+ if (!match) return null
164
+ const value = Number(match[1])
165
+ if (!Number.isFinite(value) || value <= 0) return null
166
+ return value < 1e12 ? value * 1000 : value
167
+ }
168
+
169
+ interface JsonRpcErrorLike {
170
+ code?: number
171
+ message?: string
172
+ data?: Record<string, unknown>
173
+ }
174
+
175
+ export class CodexLimitDetector implements LimitDetector {
176
+ detect(chatId: string, error: unknown): LimitDetection | null {
177
+ if (!error || typeof error !== "object") return null
178
+ const rpc = error as JsonRpcErrorLike
179
+ const data = rpc.data && typeof rpc.data === "object" ? rpc.data : null
180
+ const isRateLimit = data?.code === "rate_limit" || rpc.code === -32001
181
+ if (!isRateLimit) return null
182
+
183
+ let resetAt: number | null
184
+ if (typeof data?.resets_at_ms === "number" && Number.isFinite(data.resets_at_ms)) {
185
+ resetAt = data.resets_at_ms
186
+ } else {
187
+ resetAt = parseIsoMillis(data?.resets_at)
188
+ }
189
+ if (resetAt === null) return null
190
+
191
+ const tz = typeof data?.timezone === "string" ? (data.timezone as string) : "system"
192
+ return { chatId, resetAt, tz, raw: error }
193
+ }
194
+ }
@@ -0,0 +1,92 @@
1
+ // src/server/bm25.test.ts
2
+ import { describe, expect, test } from "bun:test"
3
+ import { BM25Index } from "./bm25"
4
+
5
+ describe("BM25Index", () => {
6
+ describe("tokenize", () => {
7
+ test("lowercases and splits on whitespace/punctuation", () => {
8
+ const index = new BM25Index<string>()
9
+ index.add("d1", "Hello, World! This is a test.")
10
+ const results = index.search("hello")
11
+ expect(results.length).toBe(1)
12
+ expect(results[0].id).toBe("d1")
13
+ })
14
+
15
+ test("filters stopwords", () => {
16
+ const index = new BM25Index<string>()
17
+ index.add("d1", "the quick brown fox")
18
+ index.add("d2", "a lazy dog")
19
+ // "the" and "a" are stopwords, should not dominate results
20
+ const results = index.search("quick")
21
+ expect(results.length).toBe(1)
22
+ expect(results[0].id).toBe("d1")
23
+ })
24
+ })
25
+
26
+ describe("search", () => {
27
+ test("ranks relevant documents higher", () => {
28
+ const index = new BM25Index<string>()
29
+ index.add("d1", "postgres database migration schema users table")
30
+ index.add("d2", "react component button styling CSS")
31
+ index.add("d3", "database connection pool postgres config")
32
+
33
+ const results = index.search("postgres database")
34
+ expect(results.length).toBeGreaterThanOrEqual(2)
35
+ // d1 and d3 both mention postgres+database terms
36
+ const ids = results.map((r) => r.id)
37
+ expect(ids).toContain("d1")
38
+ expect(ids).toContain("d3")
39
+ // d2 should not appear or rank very low
40
+ expect(ids.indexOf("d1")).toBeLessThan(ids.indexOf("d2") === -1 ? Infinity : ids.indexOf("d2"))
41
+ })
42
+
43
+ test("returns empty for no matches", () => {
44
+ const index = new BM25Index<string>()
45
+ index.add("d1", "hello world")
46
+ const results = index.search("nonexistent")
47
+ expect(results).toEqual([])
48
+ })
49
+
50
+ test("respects limit parameter", () => {
51
+ const index = new BM25Index<string>()
52
+ for (let i = 0; i < 20; i++) {
53
+ index.add(`d${i}`, `document ${i} about testing`)
54
+ }
55
+ const results = index.search("testing", 5)
56
+ expect(results.length).toBe(5)
57
+ })
58
+
59
+ test("handles multi-field documents via concatenation", () => {
60
+ const index = new BM25Index<string>()
61
+ index.add("d1", "auth middleware implementation error handling retry logic")
62
+ index.add("d2", "auth login form validation")
63
+ const results = index.search("auth error handling")
64
+ expect(results[0].id).toBe("d1")
65
+ })
66
+ })
67
+
68
+ describe("remove", () => {
69
+ test("removes document from index", () => {
70
+ const index = new BM25Index<string>()
71
+ index.add("d1", "hello world")
72
+ index.add("d2", "hello there")
73
+ index.remove("d1")
74
+ const results = index.search("hello")
75
+ expect(results.length).toBe(1)
76
+ expect(results[0].id).toBe("d2")
77
+ })
78
+ })
79
+
80
+ describe("size", () => {
81
+ test("tracks document count", () => {
82
+ const index = new BM25Index<string>()
83
+ expect(index.size).toBe(0)
84
+ index.add("d1", "hello")
85
+ expect(index.size).toBe(1)
86
+ index.add("d2", "world")
87
+ expect(index.size).toBe(2)
88
+ index.remove("d1")
89
+ expect(index.size).toBe(1)
90
+ })
91
+ })
92
+ })
@@ -0,0 +1,101 @@
1
+ // src/server/bm25.ts
2
+
3
+ const STOPWORDS = new Set([
4
+ "a", "an", "the", "is", "it", "of", "in", "to", "and", "or", "for",
5
+ "on", "at", "by", "with", "from", "as", "be", "was", "were", "been",
6
+ "are", "has", "had", "have", "do", "does", "did", "but", "not", "this",
7
+ "that", "these", "those", "i", "we", "you", "he", "she", "they",
8
+ ])
9
+
10
+ function tokenize(text: string): string[] {
11
+ return text
12
+ .toLowerCase()
13
+ .split(/[^a-z0-9_]+/)
14
+ .filter((t) => t.length > 1 && !STOPWORDS.has(t))
15
+ }
16
+
17
+ interface DocEntry {
18
+ tokens: string[]
19
+ length: number
20
+ }
21
+
22
+ export interface BM25Result<ID> {
23
+ id: ID
24
+ score: number
25
+ }
26
+
27
+ export class BM25Index<ID extends string = string> {
28
+ private readonly k1 = 1.2
29
+ private readonly b = 0.75
30
+ private readonly docs = new Map<ID, DocEntry>()
31
+ private readonly invertedIndex = new Map<string, Set<ID>>()
32
+ private totalLength = 0
33
+
34
+ get size(): number {
35
+ return this.docs.size
36
+ }
37
+
38
+ private get avgLength(): number {
39
+ return this.docs.size === 0 ? 0 : this.totalLength / this.docs.size
40
+ }
41
+
42
+ add(id: ID, text: string): void {
43
+ this.remove(id)
44
+ const tokens = tokenize(text)
45
+ this.docs.set(id, { tokens, length: tokens.length })
46
+ this.totalLength += tokens.length
47
+
48
+ for (const token of tokens) {
49
+ let set = this.invertedIndex.get(token)
50
+ if (!set) {
51
+ set = new Set()
52
+ this.invertedIndex.set(token, set)
53
+ }
54
+ set.add(id)
55
+ }
56
+ }
57
+
58
+ remove(id: ID): void {
59
+ const doc = this.docs.get(id)
60
+ if (!doc) return
61
+ this.totalLength -= doc.length
62
+ this.docs.delete(id)
63
+ for (const token of doc.tokens) {
64
+ const set = this.invertedIndex.get(token)
65
+ if (set) {
66
+ set.delete(id)
67
+ if (set.size === 0) this.invertedIndex.delete(token)
68
+ }
69
+ }
70
+ }
71
+
72
+ search(query: string, limit = 10): BM25Result<ID>[] {
73
+ const queryTokens = tokenize(query)
74
+ if (queryTokens.length === 0) return []
75
+
76
+ const scores = new Map<ID, number>()
77
+ const N = this.docs.size
78
+ const avgDl = this.avgLength
79
+
80
+ for (const qt of queryTokens) {
81
+ const postings = this.invertedIndex.get(qt)
82
+ if (!postings) continue
83
+
84
+ const df = postings.size
85
+ const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5))
86
+
87
+ for (const docId of postings) {
88
+ const doc = this.docs.get(docId)!
89
+ const tf = doc.tokens.filter((t) => t === qt).length
90
+ const tfNorm = (tf * (this.k1 + 1)) / (tf + this.k1 * (1 - this.b + this.b * (doc.length / avgDl)))
91
+ const score = idf * tfNorm
92
+ scores.set(docId, (scores.get(docId) ?? 0) + score)
93
+ }
94
+ }
95
+
96
+ return [...scores.entries()]
97
+ .sort((a, b) => b[1] - a[1])
98
+ .slice(0, limit)
99
+ .map(([id, score]) => ({ id, score }))
100
+ }
101
+ }
@@ -0,0 +1,135 @@
1
+ import { describe, test, expect, afterEach } from "bun:test"
2
+ import { connect, type NatsConnection } from "@nats-io/transport-node"
3
+ import { jetstream, DeliverPolicy } from "@nats-io/jetstream"
4
+ import { NatsBridge } from "./nats-bridge"
5
+ import { ensureChatMessageStream, CHAT_MESSAGE_EVENTS_STREAM } from "./nats-streams"
6
+ import { chatMessageSubject } from "../shared/nats-subjects"
7
+ import { compressPayload, decompressPayload } from "../shared/compression"
8
+
9
+ const encoder = new TextEncoder()
10
+ const decoder = new TextDecoder()
11
+
12
+ let bridge: NatsBridge | null = null
13
+ let nc: NatsConnection | null = null
14
+
15
+ afterEach(async () => {
16
+ await nc?.drain()
17
+ nc = null
18
+ if (bridge) {
19
+ await bridge.dispose()
20
+ bridge = null
21
+ }
22
+ })
23
+
24
+ async function setup() {
25
+ bridge = await NatsBridge.create()
26
+ nc = await connect({ servers: bridge.natsUrl })
27
+ await ensureChatMessageStream(nc)
28
+ return nc
29
+ }
30
+
31
+ describe("chat message events via JetStream ordered consumer", () => {
32
+ test("consumer receives events published to JetStream", async () => {
33
+ const conn = await setup()
34
+ const js = jetstream(conn)
35
+ const chatId = "test-chat-js-1"
36
+ const subject = chatMessageSubject(chatId)
37
+
38
+ // Create ordered consumer with DeliverPolicy.New
39
+ const consumer = await js.consumers.get(CHAT_MESSAGE_EVENTS_STREAM, {
40
+ filter_subjects: subject,
41
+ deliver_policy: DeliverPolicy.New,
42
+ })
43
+
44
+ // Publish events (mimicking server-side publishChatMessage)
45
+ const events = [
46
+ { chatId, entry: { id: "1", kind: "message", content: "hello" } },
47
+ { chatId, entry: { id: "2", kind: "message", content: "world" } },
48
+ ]
49
+
50
+ for (const event of events) {
51
+ const payload = compressPayload(encoder.encode(JSON.stringify(event)))
52
+ await js.publish(subject, payload)
53
+ }
54
+
55
+ // Consume and verify
56
+ const received: unknown[] = []
57
+ const messages = await consumer.consume()
58
+ for await (const msg of messages) {
59
+ const decoded = await decompressPayload(msg.data)
60
+ const data = JSON.parse(decoder.decode(decoded))
61
+ received.push(data)
62
+ if (received.length >= 2) break
63
+ }
64
+ await messages.close()
65
+
66
+ expect(received).toHaveLength(2)
67
+ expect(received[0]).toEqual(events[0])
68
+ expect(received[1]).toEqual(events[1])
69
+ })
70
+
71
+ test("consumer filters by chat subject", async () => {
72
+ const conn = await setup()
73
+ const js = jetstream(conn)
74
+
75
+ const chatId1 = "chat-js-filter-1"
76
+ const chatId2 = "chat-js-filter-2"
77
+
78
+ const consumer = await js.consumers.get(CHAT_MESSAGE_EVENTS_STREAM, {
79
+ filter_subjects: chatMessageSubject(chatId1),
80
+ deliver_policy: DeliverPolicy.New,
81
+ })
82
+
83
+ // Publish to chat 2 (should be filtered out)
84
+ await js.publish(
85
+ chatMessageSubject(chatId2),
86
+ encoder.encode(JSON.stringify({ chatId: chatId2, entry: { id: "x" } }))
87
+ )
88
+
89
+ // Publish to chat 1 (should be received)
90
+ await js.publish(
91
+ chatMessageSubject(chatId1),
92
+ encoder.encode(JSON.stringify({ chatId: chatId1, entry: { id: "1" } }))
93
+ )
94
+
95
+ const messages = await consumer.consume()
96
+ const received: unknown[] = []
97
+ for await (const msg of messages) {
98
+ const data = JSON.parse(decoder.decode(msg.data))
99
+ received.push(data)
100
+ if (received.length >= 1) break
101
+ }
102
+ await messages.close()
103
+
104
+ expect(received).toHaveLength(1)
105
+ expect(received[0]).toEqual({ chatId: chatId1, entry: { id: "1" } })
106
+ })
107
+
108
+ test("compressed events are decompressed correctly by consumer", async () => {
109
+ const conn = await setup()
110
+ const js = jetstream(conn)
111
+ const chatId = "test-chat-compress"
112
+ const subject = chatMessageSubject(chatId)
113
+
114
+ const consumer = await js.consumers.get(CHAT_MESSAGE_EVENTS_STREAM, {
115
+ filter_subjects: subject,
116
+ deliver_policy: DeliverPolicy.New,
117
+ })
118
+
119
+ // Create a large payload that will trigger compression (> 64KB threshold)
120
+ const largeContent = "x".repeat(100_000)
121
+ const event = { chatId, entry: { id: "big", content: largeContent } }
122
+ const payload = compressPayload(encoder.encode(JSON.stringify(event)))
123
+
124
+ await js.publish(subject, payload)
125
+
126
+ const messages = await consumer.consume()
127
+ for await (const msg of messages) {
128
+ const decoded = await decompressPayload(msg.data)
129
+ const data = JSON.parse(decoder.decode(decoded)) as typeof event
130
+ expect(data.entry.content).toBe(largeContent)
131
+ break
132
+ }
133
+ await messages.close()
134
+ })
135
+ })