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,354 @@
1
+ import path from "node:path"
2
+ import process from "node:process"
3
+ import defaultShell, { detectDefaultShell } from "default-shell"
4
+ import { Terminal } from "@xterm/headless"
5
+ import { SerializeAddon } from "@xterm/addon-serialize"
6
+ import type { TerminalEvent, TerminalSnapshot } from "../shared/protocol"
7
+
8
+ const DEFAULT_COLS = 80
9
+ const DEFAULT_ROWS = 24
10
+ const DEFAULT_SCROLLBACK = 1_000
11
+ const MIN_SCROLLBACK = 500
12
+ const MAX_SCROLLBACK = 5_000
13
+ const FOCUS_IN_SEQUENCE = "\x1b[I"
14
+ const FOCUS_OUT_SEQUENCE = "\x1b[O"
15
+ const MODE_SEQUENCE_TAIL_LENGTH = 16
16
+
17
+ interface CreateTerminalArgs {
18
+ workspacePath: string
19
+ terminalId: string
20
+ cols: number
21
+ rows: number
22
+ scrollback: number
23
+ }
24
+
25
+ interface TerminalSession {
26
+ terminalId: string
27
+ title: string
28
+ cwd: string
29
+ shell: string
30
+ cols: number
31
+ rows: number
32
+ scrollback: number
33
+ status: "running" | "exited"
34
+ exitCode: number | null
35
+ process: Bun.Subprocess | null
36
+ terminal: Bun.Terminal
37
+ headless: Terminal
38
+ serializeAddon: SerializeAddon
39
+ focusReportingEnabled: boolean
40
+ modeSequenceTail: string
41
+ }
42
+
43
+ function clampScrollback(value: number) {
44
+ if (!Number.isFinite(value)) return DEFAULT_SCROLLBACK
45
+ return Math.min(MAX_SCROLLBACK, Math.max(MIN_SCROLLBACK, Math.round(value)))
46
+ }
47
+
48
+ function normalizeTerminalDimension(value: number, fallback: number) {
49
+ if (!Number.isFinite(value)) return fallback
50
+ return Math.max(1, Math.round(value))
51
+ }
52
+
53
+ function resolveShell() {
54
+ if (process.env.TINKARIA_SHELL) {
55
+ return process.env.TINKARIA_SHELL
56
+ }
57
+
58
+ try {
59
+ return detectDefaultShell()
60
+ } catch {
61
+ if (defaultShell) return defaultShell
62
+ if (process.platform === "win32") {
63
+ return process.env.ComSpec || "cmd.exe"
64
+ }
65
+ return process.env.SHELL || "/bin/sh"
66
+ }
67
+ }
68
+
69
+ function resolveShellArgs(shellPath: string) {
70
+ if (process.platform === "win32") {
71
+ return []
72
+ }
73
+
74
+ const shellName = path.basename(shellPath)
75
+ if (["bash", "zsh", "fish", "sh", "ksh"].includes(shellName)) {
76
+ return ["-l"]
77
+ }
78
+
79
+ return []
80
+ }
81
+
82
+ function createTerminalEnv() {
83
+ return {
84
+ ...process.env,
85
+ TERM: "xterm-256color",
86
+ COLORTERM: "truecolor",
87
+ }
88
+ }
89
+
90
+ function updateFocusReportingState(session: Pick<TerminalSession, "focusReportingEnabled" | "modeSequenceTail">, chunk: string) {
91
+ const combined = session.modeSequenceTail + chunk
92
+ const regex = /\x1b\[\?1004([hl])/g
93
+
94
+ for (const match of combined.matchAll(regex)) {
95
+ session.focusReportingEnabled = match[1] === "h"
96
+ }
97
+
98
+ session.modeSequenceTail = combined.slice(-MODE_SEQUENCE_TAIL_LENGTH)
99
+ }
100
+
101
+ function filterFocusReportInput(data: string, allowFocusReporting: boolean) {
102
+ if (allowFocusReporting) {
103
+ return data
104
+ }
105
+
106
+ return data.replaceAll(FOCUS_IN_SEQUENCE, "").replaceAll(FOCUS_OUT_SEQUENCE, "")
107
+ }
108
+
109
+ function killTerminalProcessTree(subprocess: Bun.Subprocess | null) {
110
+ if (!subprocess) return
111
+
112
+ const pid = subprocess.pid
113
+ if (typeof pid !== "number") return
114
+
115
+ if (process.platform !== "win32") {
116
+ try {
117
+ process.kill(-pid, "SIGKILL")
118
+ return
119
+ } catch {
120
+ // Fall back to killing only the shell process if group termination fails.
121
+ }
122
+ }
123
+
124
+ try {
125
+ subprocess.kill("SIGKILL")
126
+ } catch {
127
+ // Ignore subprocess shutdown errors during disposal.
128
+ }
129
+ }
130
+
131
+ function signalTerminalProcessGroup(subprocess: Bun.Subprocess | null, signal: NodeJS.Signals) {
132
+ if (!subprocess) return false
133
+
134
+ const pid = subprocess.pid
135
+ if (typeof pid !== "number") return false
136
+
137
+ if (process.platform !== "win32") {
138
+ try {
139
+ process.kill(-pid, signal)
140
+ return true
141
+ } catch {
142
+ // Fall back to signaling only the shell if group signaling fails.
143
+ }
144
+ }
145
+
146
+ try {
147
+ subprocess.kill(signal)
148
+ return true
149
+ } catch {
150
+ return false
151
+ }
152
+ }
153
+
154
+ export class TerminalManager {
155
+ private readonly sessions = new Map<string, TerminalSession>()
156
+ private readonly listeners = new Set<(event: TerminalEvent) => void>()
157
+
158
+ onEvent(listener: (event: TerminalEvent) => void) {
159
+ this.listeners.add(listener)
160
+ return () => {
161
+ this.listeners.delete(listener)
162
+ }
163
+ }
164
+
165
+ createTerminal(args: CreateTerminalArgs) {
166
+ if (process.platform === "win32") {
167
+ throw new Error("Embedded terminal is currently supported on macOS/Linux only.")
168
+ }
169
+ if (typeof Bun.Terminal !== "function") {
170
+ throw new Error("Embedded terminal requires Bun 1.3.5+ with Bun.Terminal support.")
171
+ }
172
+
173
+ const existing = this.sessions.get(args.terminalId)
174
+ if (existing) {
175
+ existing.scrollback = clampScrollback(args.scrollback)
176
+ existing.cols = normalizeTerminalDimension(args.cols, existing.cols)
177
+ existing.rows = normalizeTerminalDimension(args.rows, existing.rows)
178
+ existing.headless.options.scrollback = existing.scrollback
179
+ existing.headless.resize(existing.cols, existing.rows)
180
+ existing.terminal.resize(existing.cols, existing.rows)
181
+ signalTerminalProcessGroup(existing.process, "SIGWINCH")
182
+ return this.snapshotOf(existing)
183
+ }
184
+
185
+ const shell = resolveShell()
186
+ const cols = normalizeTerminalDimension(args.cols, DEFAULT_COLS)
187
+ const rows = normalizeTerminalDimension(args.rows, DEFAULT_ROWS)
188
+ const scrollback = clampScrollback(args.scrollback)
189
+ const title = path.basename(shell) || "shell"
190
+ const headless = new Terminal({ cols, rows, scrollback, allowProposedApi: true })
191
+ const serializeAddon = new SerializeAddon()
192
+ headless.loadAddon(serializeAddon)
193
+
194
+ const session: TerminalSession = {
195
+ terminalId: args.terminalId,
196
+ title,
197
+ cwd: args.workspacePath,
198
+ shell,
199
+ cols,
200
+ rows,
201
+ scrollback,
202
+ status: "running",
203
+ exitCode: null,
204
+ process: null,
205
+ terminal: new Bun.Terminal({
206
+ cols,
207
+ rows,
208
+ name: "xterm-256color",
209
+ data: (_terminal, data) => {
210
+ const chunk = Buffer.from(data).toString("utf8")
211
+ updateFocusReportingState(session, chunk)
212
+ headless.write(chunk)
213
+ this.emit({
214
+ type: "terminal.output",
215
+ terminalId: args.terminalId,
216
+ data: chunk,
217
+ })
218
+ },
219
+ }),
220
+ headless,
221
+ serializeAddon,
222
+ focusReportingEnabled: false,
223
+ modeSequenceTail: "",
224
+ }
225
+
226
+ try {
227
+ session.process = Bun.spawn([shell, ...resolveShellArgs(shell)], {
228
+ cwd: args.workspacePath,
229
+ env: createTerminalEnv(),
230
+ terminal: session.terminal,
231
+ })
232
+ } catch (error) {
233
+ session.terminal.close()
234
+ session.serializeAddon.dispose()
235
+ session.headless.dispose()
236
+ throw error
237
+ }
238
+ void session.process.exited.then((exitCode) => {
239
+ const active = this.sessions.get(args.terminalId)
240
+ if (!active) return
241
+ active.status = "exited"
242
+ active.exitCode = exitCode
243
+ this.emit({
244
+ type: "terminal.exit",
245
+ terminalId: args.terminalId,
246
+ exitCode,
247
+ })
248
+ }).catch((error) => {
249
+ const active = this.sessions.get(args.terminalId)
250
+ if (!active) return
251
+ active.status = "exited"
252
+ active.exitCode = 1
253
+ this.emit({
254
+ type: "terminal.output",
255
+ terminalId: args.terminalId,
256
+ data: `\r\n[terminal error] ${error instanceof Error ? error.message : String(error)}\r\n`,
257
+ })
258
+ this.emit({
259
+ type: "terminal.exit",
260
+ terminalId: args.terminalId,
261
+ exitCode: 1,
262
+ })
263
+ })
264
+
265
+ this.sessions.set(args.terminalId, session)
266
+ return this.snapshotOf(session)
267
+ }
268
+
269
+ getSnapshot(terminalId: string): TerminalSnapshot | null {
270
+ const session = this.sessions.get(terminalId)
271
+ return session ? this.snapshotOf(session) : null
272
+ }
273
+
274
+ write(terminalId: string, data: string) {
275
+ const session = this.sessions.get(terminalId)
276
+ if (!session || session.status === "exited") return
277
+
278
+ const filteredData = filterFocusReportInput(data, session.focusReportingEnabled)
279
+ if (!filteredData) return
280
+
281
+ let cursor = 0
282
+
283
+ while (cursor < filteredData.length) {
284
+ const ctrlCIndex = filteredData.indexOf("\x03", cursor)
285
+
286
+ if (ctrlCIndex === -1) {
287
+ session.terminal.write(filteredData.slice(cursor))
288
+ return
289
+ }
290
+
291
+ if (ctrlCIndex > cursor) {
292
+ session.terminal.write(filteredData.slice(cursor, ctrlCIndex))
293
+ }
294
+
295
+ signalTerminalProcessGroup(session.process, "SIGINT")
296
+ cursor = ctrlCIndex + 1
297
+ }
298
+ }
299
+
300
+ resize(terminalId: string, cols: number, rows: number) {
301
+ const session = this.sessions.get(terminalId)
302
+ if (!session) return
303
+ session.cols = normalizeTerminalDimension(cols, session.cols)
304
+ session.rows = normalizeTerminalDimension(rows, session.rows)
305
+ session.headless.resize(session.cols, session.rows)
306
+ session.terminal.resize(session.cols, session.rows)
307
+ signalTerminalProcessGroup(session.process, "SIGWINCH")
308
+ }
309
+
310
+ close(terminalId: string) {
311
+ const session = this.sessions.get(terminalId)
312
+ if (!session) return
313
+
314
+ this.sessions.delete(terminalId)
315
+ killTerminalProcessTree(session.process)
316
+ session.terminal.close()
317
+ session.serializeAddon.dispose()
318
+ session.headless.dispose()
319
+ }
320
+
321
+ closeByCwd(cwd: string) {
322
+ for (const [terminalId, session] of this.sessions.entries()) {
323
+ if (session.cwd !== cwd) continue
324
+ this.close(terminalId)
325
+ }
326
+ }
327
+
328
+ closeAll() {
329
+ for (const terminalId of this.sessions.keys()) {
330
+ this.close(terminalId)
331
+ }
332
+ }
333
+
334
+ private snapshotOf(session: TerminalSession): TerminalSnapshot {
335
+ return {
336
+ terminalId: session.terminalId,
337
+ title: session.title,
338
+ cwd: session.cwd,
339
+ shell: session.shell,
340
+ cols: session.cols,
341
+ rows: session.rows,
342
+ scrollback: session.scrollback,
343
+ serializedState: session.serializeAddon.serialize({ scrollback: session.scrollback }),
344
+ status: session.status,
345
+ exitCode: session.exitCode,
346
+ }
347
+ }
348
+
349
+ private emit(event: TerminalEvent) {
350
+ for (const listener of this.listeners) {
351
+ listener(event)
352
+ }
353
+ }
354
+ }
@@ -0,0 +1,339 @@
1
+ import { afterEach, describe, expect, test } from "bun:test"
2
+ import { NatsServer } from "@lagz0ne/nats-embedded"
3
+ import { connect, type NatsConnection } from "@nats-io/transport-node"
4
+ import { jetstream } from "@nats-io/jetstream"
5
+ import { ensureRunnerEventsStream } from "./nats-streams"
6
+ import { runnerEventsSubject } from "../shared/runner-protocol"
7
+ import { TranscriptConsumer, type TranscriptConsumerStore } from "./transcript-consumer"
8
+ import type { TranscriptEntry, SessionStatus, AgentProvider } from "../shared/types"
9
+
10
+ const encoder = new TextEncoder()
11
+
12
+ let server: NatsServer | null = null
13
+ let nc: NatsConnection | null = null
14
+ let consumer: TranscriptConsumer | null = null
15
+
16
+ afterEach(async () => {
17
+ if (consumer) {
18
+ consumer.stop()
19
+ consumer = null
20
+ }
21
+ if (nc) {
22
+ await nc.drain()
23
+ nc = null
24
+ }
25
+ if (server) {
26
+ await server.stop()
27
+ server = null
28
+ }
29
+ })
30
+
31
+ async function setup() {
32
+ server = await NatsServer.start({ jetstream: true })
33
+ nc = await connect({ servers: server.url })
34
+ await ensureRunnerEventsStream(nc)
35
+ return nc
36
+ }
37
+
38
+ function makeStore(): TranscriptConsumerStore & {
39
+ calls: { method: string; args: unknown[] }[]
40
+ } {
41
+ const calls: { method: string; args: unknown[] }[] = []
42
+ return {
43
+ calls,
44
+ async appendMessage(chatId: string, entry: TranscriptEntry) {
45
+ calls.push({ method: "appendMessage", args: [chatId, entry] })
46
+ },
47
+ async recordTurnFinished(chatId: string) {
48
+ calls.push({ method: "recordTurnFinished", args: [chatId] })
49
+ },
50
+ async recordTurnFailed(chatId: string, error: string) {
51
+ calls.push({ method: "recordTurnFailed", args: [chatId, error] })
52
+ },
53
+ async recordTurnCancelled(chatId: string) {
54
+ calls.push({ method: "recordTurnCancelled", args: [chatId] })
55
+ },
56
+ async setSessionToken(chatId: string, token: string | null) {
57
+ calls.push({ method: "setSessionToken", args: [chatId, token] })
58
+ },
59
+ async renameChat(chatId: string, title: string) {
60
+ calls.push({ method: "renameChat", args: [chatId, title] })
61
+ },
62
+ async setChatProvider(chatId: string, provider: AgentProvider) {
63
+ calls.push({ method: "setChatProvider", args: [chatId, provider] })
64
+ },
65
+ async setPlanMode(chatId: string, planMode: boolean) {
66
+ calls.push({ method: "setPlanMode", args: [chatId, planMode] })
67
+ },
68
+ }
69
+ }
70
+
71
+ async function publishEvent(conn: NatsConnection, chatId: string, event: Record<string, unknown>) {
72
+ const js = jetstream(conn)
73
+ const subject = runnerEventsSubject(chatId)
74
+ await js.publish(subject, encoder.encode(JSON.stringify({ ...event, chatId })))
75
+ }
76
+
77
+ /** Wait until the predicate returns true, polling every intervalMs. Throws after timeoutMs. */
78
+ async function waitFor(predicate: () => boolean, timeoutMs = 3000, intervalMs = 20): Promise<void> {
79
+ const deadline = Date.now() + timeoutMs
80
+ while (!predicate()) {
81
+ if (Date.now() > deadline) throw new Error("waitFor timeout")
82
+ await new Promise((r) => setTimeout(r, intervalMs))
83
+ }
84
+ }
85
+
86
+ describe("TranscriptConsumer", () => {
87
+ test("transcript event calls store.appendMessage and onMessageAppended", async () => {
88
+ const conn = await setup()
89
+ const store = makeStore()
90
+ const appended: { chatId: string; entry: TranscriptEntry }[] = []
91
+
92
+ consumer = new TranscriptConsumer({
93
+ nc: conn,
94
+ store,
95
+ onStateChange: () => {},
96
+ onMessageAppended: (chatId, entry) => appended.push({ chatId, entry }),
97
+ })
98
+ await consumer.start()
99
+
100
+ const entry: TranscriptEntry = { _id: "msg-1", createdAt: Date.now(), kind: "assistant_text", text: "hello" }
101
+ await publishEvent(conn, "chat-1", { type: "transcript", entry })
102
+
103
+ await waitFor(() => store.calls.length >= 1)
104
+ expect(store.calls[0]).toEqual({ method: "appendMessage", args: ["chat-1", entry] })
105
+ expect(appended).toHaveLength(1)
106
+ expect(appended[0]!.chatId).toBe("chat-1")
107
+ expect(appended[0]!.entry).toEqual(entry)
108
+ })
109
+
110
+ test("turn_finished calls store.recordTurnFinished and removes from activeStatuses", async () => {
111
+ const conn = await setup()
112
+ const store = makeStore()
113
+ let stateChanges = 0
114
+
115
+ consumer = new TranscriptConsumer({
116
+ nc: conn,
117
+ store,
118
+ onStateChange: () => { stateChanges++ },
119
+ })
120
+ await consumer.start()
121
+
122
+ // First set a status so there's something to remove
123
+ await publishEvent(conn, "chat-2", { type: "status_change", status: "running" satisfies SessionStatus })
124
+ await waitFor(() => stateChanges >= 1)
125
+ expect(consumer.getActiveStatuses().get("chat-2")).toBe("running")
126
+
127
+ // Now finish
128
+ await publishEvent(conn, "chat-2", { type: "turn_finished" })
129
+ await waitFor(() => store.calls.some((c) => c.method === "recordTurnFinished"))
130
+ expect(store.calls.find((c) => c.method === "recordTurnFinished")).toEqual({
131
+ method: "recordTurnFinished",
132
+ args: ["chat-2"],
133
+ })
134
+ expect(consumer.getActiveStatuses().has("chat-2")).toBe(false)
135
+ })
136
+
137
+ test("turn_failed calls store.recordTurnFailed and removes from activeStatuses", async () => {
138
+ const conn = await setup()
139
+ const store = makeStore()
140
+ let stateChanges = 0
141
+
142
+ consumer = new TranscriptConsumer({
143
+ nc: conn,
144
+ store,
145
+ onStateChange: () => { stateChanges++ },
146
+ })
147
+ await consumer.start()
148
+
149
+ await publishEvent(conn, "chat-3", { type: "status_change", status: "running" satisfies SessionStatus })
150
+ await waitFor(() => stateChanges >= 1)
151
+
152
+ await publishEvent(conn, "chat-3", { type: "turn_failed", error: "something broke" })
153
+ await waitFor(() => store.calls.some((c) => c.method === "recordTurnFailed"))
154
+ expect(store.calls.find((c) => c.method === "recordTurnFailed")).toEqual({
155
+ method: "recordTurnFailed",
156
+ args: ["chat-3", "something broke"],
157
+ })
158
+ expect(consumer.getActiveStatuses().has("chat-3")).toBe(false)
159
+ })
160
+
161
+ test("turn_cancelled calls store.recordTurnCancelled and removes from activeStatuses", async () => {
162
+ const conn = await setup()
163
+ const store = makeStore()
164
+ let stateChanges = 0
165
+
166
+ consumer = new TranscriptConsumer({
167
+ nc: conn,
168
+ store,
169
+ onStateChange: () => { stateChanges++ },
170
+ })
171
+ await consumer.start()
172
+
173
+ await publishEvent(conn, "chat-4", { type: "status_change", status: "running" satisfies SessionStatus })
174
+ await waitFor(() => stateChanges >= 1)
175
+
176
+ await publishEvent(conn, "chat-4", { type: "turn_cancelled" })
177
+ await waitFor(() => store.calls.some((c) => c.method === "recordTurnCancelled"))
178
+ expect(store.calls.find((c) => c.method === "recordTurnCancelled")).toEqual({
179
+ method: "recordTurnCancelled",
180
+ args: ["chat-4"],
181
+ })
182
+ expect(consumer.getActiveStatuses().has("chat-4")).toBe(false)
183
+ })
184
+
185
+ test("session_token calls store.setSessionToken", async () => {
186
+ const conn = await setup()
187
+ const store = makeStore()
188
+
189
+ consumer = new TranscriptConsumer({
190
+ nc: conn,
191
+ store,
192
+ onStateChange: () => {},
193
+ })
194
+ await consumer.start()
195
+
196
+ await publishEvent(conn, "chat-5", { type: "session_token", sessionToken: "tok-abc" })
197
+ await waitFor(() => store.calls.some((c) => c.method === "setSessionToken"))
198
+ expect(store.calls.find((c) => c.method === "setSessionToken")).toEqual({
199
+ method: "setSessionToken",
200
+ args: ["chat-5", "tok-abc"],
201
+ })
202
+ })
203
+
204
+ test("title_generated calls store.renameChat", async () => {
205
+ const conn = await setup()
206
+ const store = makeStore()
207
+
208
+ consumer = new TranscriptConsumer({
209
+ nc: conn,
210
+ store,
211
+ onStateChange: () => {},
212
+ })
213
+ await consumer.start()
214
+
215
+ await publishEvent(conn, "chat-6", { type: "title_generated", title: "My Chat Title" })
216
+ await waitFor(() => store.calls.some((c) => c.method === "renameChat"))
217
+ expect(store.calls.find((c) => c.method === "renameChat")).toEqual({
218
+ method: "renameChat",
219
+ args: ["chat-6", "My Chat Title"],
220
+ })
221
+ })
222
+
223
+ test("status_change updates activeStatuses", async () => {
224
+ const conn = await setup()
225
+ const store = makeStore()
226
+ let stateChanges = 0
227
+
228
+ consumer = new TranscriptConsumer({
229
+ nc: conn,
230
+ store,
231
+ onStateChange: () => { stateChanges++ },
232
+ })
233
+ await consumer.start()
234
+
235
+ await publishEvent(conn, "chat-7", { type: "status_change", status: "running" satisfies SessionStatus })
236
+ await waitFor(() => stateChanges >= 1)
237
+ expect(consumer.getActiveStatuses().get("chat-7")).toBe("running")
238
+
239
+ await publishEvent(conn, "chat-7", { type: "status_change", status: "waiting_for_user" satisfies SessionStatus })
240
+ await waitFor(() => stateChanges >= 2)
241
+ expect(consumer.getActiveStatuses().get("chat-7")).toBe("waiting_for_user")
242
+ })
243
+
244
+ test("provider_set calls store.setChatProvider", async () => {
245
+ const conn = await setup()
246
+ const store = makeStore()
247
+
248
+ consumer = new TranscriptConsumer({
249
+ nc: conn,
250
+ store,
251
+ onStateChange: () => {},
252
+ })
253
+ await consumer.start()
254
+
255
+ await publishEvent(conn, "chat-8", { type: "provider_set", provider: "claude" })
256
+ await waitFor(() => store.calls.some((c) => c.method === "setChatProvider"))
257
+ expect(store.calls.find((c) => c.method === "setChatProvider")).toEqual({
258
+ method: "setChatProvider",
259
+ args: ["chat-8", "claude"],
260
+ })
261
+ })
262
+
263
+ test("plan_mode_set calls store.setPlanMode", async () => {
264
+ const conn = await setup()
265
+ const store = makeStore()
266
+
267
+ consumer = new TranscriptConsumer({
268
+ nc: conn,
269
+ store,
270
+ onStateChange: () => {},
271
+ })
272
+ await consumer.start()
273
+
274
+ await publishEvent(conn, "chat-9", { type: "plan_mode_set", planMode: true })
275
+ await waitFor(() => store.calls.some((c) => c.method === "setPlanMode"))
276
+ expect(store.calls.find((c) => c.method === "setPlanMode")).toEqual({
277
+ method: "setPlanMode",
278
+ args: ["chat-9", true],
279
+ })
280
+ })
281
+
282
+ test("every event triggers onStateChange", async () => {
283
+ const conn = await setup()
284
+ const store = makeStore()
285
+ let stateChanges = 0
286
+
287
+ consumer = new TranscriptConsumer({
288
+ nc: conn,
289
+ store,
290
+ onStateChange: () => { stateChanges++ },
291
+ })
292
+ await consumer.start()
293
+
294
+ const entry: TranscriptEntry = { _id: "msg-x", createdAt: Date.now(), kind: "assistant_text", text: "hi" }
295
+ await publishEvent(conn, "chat-x", { type: "transcript", entry })
296
+ await publishEvent(conn, "chat-x", { type: "status_change", status: "running" satisfies SessionStatus })
297
+ await publishEvent(conn, "chat-x", { type: "turn_finished" })
298
+
299
+ await waitFor(() => stateChanges >= 3)
300
+ expect(stateChanges).toBeGreaterThanOrEqual(3)
301
+ })
302
+
303
+ test("pending_tool event triggers onStateChange without store call", async () => {
304
+ const conn = await setup()
305
+ const store = makeStore()
306
+ let stateChanges = 0
307
+
308
+ consumer = new TranscriptConsumer({
309
+ nc: conn,
310
+ store,
311
+ onStateChange: () => { stateChanges++ },
312
+ })
313
+ await consumer.start()
314
+
315
+ await publishEvent(conn, "chat-pt", { type: "pending_tool", tool: { toolUseId: "t1", toolKind: "ask_user_question" } })
316
+ await waitFor(() => stateChanges >= 1)
317
+ // No store method called for pending_tool
318
+ expect(store.calls).toHaveLength(0)
319
+ })
320
+
321
+ test("context_cleared event does not trigger onStateChange (handled by session_token)", async () => {
322
+ const conn = await setup()
323
+ const store = makeStore()
324
+ let stateChanges = 0
325
+
326
+ consumer = new TranscriptConsumer({
327
+ nc: conn,
328
+ store,
329
+ onStateChange: () => { stateChanges++ },
330
+ })
331
+ await consumer.start()
332
+
333
+ await publishEvent(conn, "chat-cc", { type: "context_cleared" })
334
+ // Give consumer time to process — but state should NOT change
335
+ await new Promise(r => setTimeout(r, 200))
336
+ expect(stateChanges).toBe(0)
337
+ expect(store.calls).toHaveLength(0)
338
+ })
339
+ })