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,343 @@
1
+ import { readdir, stat, open } from "node:fs/promises"
2
+ import { existsSync, watch } from "node:fs"
3
+ import path from "node:path"
4
+ import { awaitClaudeSessionForPid } from "./claude-session-registry.adapter"
5
+ import { computeJsonlPath } from "./jsonl-path.adapter"
6
+
7
+ export async function findLatestTranscript(
8
+ projectDir: string,
9
+ opts: { minMtimeMs?: number } = {},
10
+ ): Promise<string | null> {
11
+ if (!existsSync(projectDir)) return null
12
+ let entries: string[]
13
+ try {
14
+ entries = await readdir(projectDir)
15
+ } catch {
16
+ return null
17
+ }
18
+ const jsonlNames = entries.filter((n) => n.endsWith(".jsonl"))
19
+ if (jsonlNames.length === 0) return null
20
+ const floor = opts.minMtimeMs ?? 0
21
+ let bestPath: string | null = null
22
+ let bestMtime = 0
23
+ for (const name of jsonlNames) {
24
+ const full = path.join(projectDir, name)
25
+ try {
26
+ const s = await stat(full)
27
+ // Skip stale JSONLs from prior sessions in the same project dir.
28
+ // Without this floor, kanna's watcher locks onto the most-recently-
29
+ // touched OLD transcript while claude is still in the middle of
30
+ // creating its new one — events from the new session are lost.
31
+ if (s.mtimeMs < floor) continue
32
+ if (s.mtimeMs > bestMtime) {
33
+ bestMtime = s.mtimeMs
34
+ bestPath = full
35
+ }
36
+ } catch {
37
+ /* skip */
38
+ }
39
+ }
40
+ return bestPath
41
+ }
42
+
43
+ export interface TranscriptStream {
44
+ lines: AsyncIterable<string>
45
+ filePath: Promise<string>
46
+ close(): void
47
+ }
48
+
49
+ export interface StartTranscriptStreamArgs {
50
+ projectDir: string
51
+ knownFilePath?: string
52
+ /**
53
+ * Mtime floor (ms) for JSONL discovery. When `knownFilePath` is unset
54
+ * AND the session-registry lookup (via `claudeChildPid`) fails,
55
+ * `findLatestTranscript` filters out files older than this — set to
56
+ * spawn-start time so stale JSONLs from prior sessions in the same
57
+ * project dir cannot win the race. The registry lookup, when it
58
+ * succeeds, makes this floor moot.
59
+ */
60
+ minMtimeMs?: number
61
+ pollMode?: boolean
62
+ pollIntervalMs?: number
63
+ firstFileTimeoutMs?: number
64
+ /**
65
+ * Live PID of the spawned `claude` child. When set together with
66
+ * `homeDir`, `locateFirstFile` first polls `${homeDir}/.claude/sessions/<pid>.json`
67
+ * (claude-code's own per-PID registry) to obtain the real session UUID,
68
+ * then computes the JSONL path directly. This is race-free under
69
+ * concurrent claude spawns sharing the same projectDir — the legacy
70
+ * mtime heuristic can pick the wrong file when other claude TUIs run
71
+ * in the same cwd. Falls back to `findLatestTranscript` if the
72
+ * registry file does not appear within `sessionRegistryTimeoutMs`.
73
+ */
74
+ claudeChildPid?: number
75
+ homeDir?: string
76
+ sessionRegistryTimeoutMs?: number
77
+ sessionRegistryPollMs?: number
78
+ }
79
+
80
+ const DEFAULT_FIRST_FILE_TIMEOUT_MS = 20_000
81
+ const DEFAULT_POLL_INTERVAL_MS = 50
82
+ const DEFAULT_SESSION_REGISTRY_TIMEOUT_MS = 2_000
83
+ const DEFAULT_SESSION_REGISTRY_POLL_MS = 20
84
+ const DEFAULT_SAFETY_POLL_MS = 500
85
+
86
+ export async function startTranscriptStream(args: StartTranscriptStreamArgs): Promise<TranscriptStream> {
87
+ const lineQueue: string[] = []
88
+ const lineWaiters: Array<(r: IteratorResult<string, undefined>) => void> = []
89
+ let buffer = ""
90
+ let position = 0
91
+ let closed = false
92
+ let watcher: ReturnType<typeof watch> | null = null
93
+ let pollTimer: ReturnType<typeof setInterval> | null = null
94
+
95
+ function pushLine(line: string) {
96
+ const w = lineWaiters.shift()
97
+ if (w) w({ value: line, done: false })
98
+ else lineQueue.push(line)
99
+ }
100
+
101
+ function endLines() {
102
+ const done: IteratorReturnResult<undefined> = { value: undefined, done: true }
103
+ while (lineWaiters.length > 0) {
104
+ const w = lineWaiters.shift()
105
+ if (w) w(done)
106
+ }
107
+ }
108
+
109
+ async function readNewBytes(filePath: string) {
110
+ try {
111
+ const s = await stat(filePath)
112
+ if (s.size <= position) return
113
+ const fd = await open(filePath, "r")
114
+ try {
115
+ const length = s.size - position
116
+ const buf = Buffer.alloc(length)
117
+ await fd.read(buf, 0, length, position)
118
+ position = s.size
119
+ buffer += buf.toString("utf8")
120
+ const parts = buffer.split("\n")
121
+ buffer = parts.pop() ?? ""
122
+ for (const line of parts) {
123
+ if (line.length === 0) continue
124
+ pushLine(line)
125
+ }
126
+ } finally {
127
+ await fd.close()
128
+ }
129
+ } catch {
130
+ /* file rotated / truncated mid-read; next tick recovers */
131
+ }
132
+ }
133
+
134
+ function startFollowing(filePath: string) {
135
+ if (args.pollMode) {
136
+ const interval = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS
137
+ pollTimer = setInterval(() => { void readNewBytes(filePath) }, interval)
138
+ } else {
139
+ try {
140
+ watcher = watch(filePath, () => { void readNewBytes(filePath) })
141
+ } catch {
142
+ const interval = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS
143
+ pollTimer = setInterval(() => { void readNewBytes(filePath) }, interval)
144
+ }
145
+ // Safety-net poll alongside fs.watch: macOS FSEvents was observed to
146
+ // coalesce or drop events when claude appended several lines in rapid
147
+ // succession at turn end (final `assistant` + `system/turn_duration`
148
+ // rows silently lost). The watcher handles low-latency streaming;
149
+ // this poll guarantees eventual delivery within DEFAULT_SAFETY_POLL_MS.
150
+ pollTimer = setInterval(() => { void readNewBytes(filePath) }, DEFAULT_SAFETY_POLL_MS)
151
+ }
152
+ void readNewBytes(filePath)
153
+ }
154
+
155
+ async function locateFirstFile(): Promise<string> {
156
+ if (args.knownFilePath) return args.knownFilePath
157
+ // Preferred path: read claude's per-PID session file to learn the real
158
+ // session UUID, then compute the JSONL path directly. Race-free.
159
+ if (args.claudeChildPid && args.homeDir) {
160
+ const entry = await awaitClaudeSessionForPid({
161
+ homeDir: args.homeDir,
162
+ pid: args.claudeChildPid,
163
+ timeoutMs: args.sessionRegistryTimeoutMs ?? DEFAULT_SESSION_REGISTRY_TIMEOUT_MS,
164
+ pollIntervalMs: args.sessionRegistryPollMs ?? DEFAULT_SESSION_REGISTRY_POLL_MS,
165
+ })
166
+ if (entry) {
167
+ const computed = computeJsonlPath({
168
+ homeDir: args.homeDir,
169
+ cwd: entry.cwd,
170
+ sessionId: entry.sessionId,
171
+ })
172
+ // Registry resolved: the JSONL path is AUTHORITATIVE for this
173
+ // child pid. Poll existsSync until close or timeout. Do NOT fall
174
+ // back to mtime — when the registry-resolved JSONL never appears
175
+ // (e.g. claude was spawned but no prompt was ever sent), mtime
176
+ // discovery silently picks the newest unrelated JSONL in the
177
+ // shared project dir, causing cross-session transcript bleed.
178
+ // Without a timeout, a missed first prompt wedges the driver
179
+ // forever; bound the wait by firstFileTimeoutMs so the caller
180
+ // can surface a failure event and the user can retry.
181
+ const jsonlPollMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS
182
+ const jsonlTimeoutMs = args.firstFileTimeoutMs ?? DEFAULT_FIRST_FILE_TIMEOUT_MS
183
+ const jsonlPollStart = Date.now()
184
+ while (!closed) {
185
+ if (existsSync(computed)) return computed
186
+ if (Date.now() - jsonlPollStart > jsonlTimeoutMs) {
187
+ throw new Error(
188
+ `registry-resolved JSONL ${computed} did not appear in ${jsonlTimeoutMs}ms (claude TUI likely missed first prompt)`,
189
+ )
190
+ }
191
+ await new Promise((r) => setTimeout(r, jsonlPollMs))
192
+ }
193
+ throw new Error("transcript stream closed before registry-resolved JSONL appeared")
194
+ }
195
+ }
196
+ const timeoutMs = args.firstFileTimeoutMs ?? DEFAULT_FIRST_FILE_TIMEOUT_MS
197
+ const findOpts = { minMtimeMs: args.minMtimeMs }
198
+ const existing = await findLatestTranscript(args.projectDir, findOpts)
199
+ if (existing) return existing
200
+ return new Promise<string>((resolve, reject) => {
201
+ const start = Date.now()
202
+ const pollMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS
203
+ const timer = setInterval(async () => {
204
+ if (closed) {
205
+ clearInterval(timer)
206
+ reject(new Error("transcript stream closed before first file appeared"))
207
+ return
208
+ }
209
+ if (Date.now() - start > timeoutMs) {
210
+ clearInterval(timer)
211
+ reject(new Error(`transcript file did not appear in ${timeoutMs}ms under ${args.projectDir}`))
212
+ return
213
+ }
214
+ const found = await findLatestTranscript(args.projectDir, findOpts)
215
+ if (found) {
216
+ clearInterval(timer)
217
+ resolve(found)
218
+ }
219
+ }, pollMs)
220
+ })
221
+ }
222
+
223
+ const filePathPromise = locateFirstFile()
224
+ void filePathPromise
225
+ .then((fp) => { if (!closed) startFollowing(fp) })
226
+ .catch(() => {
227
+ /* surfaced via filePath rejection */
228
+ })
229
+
230
+ const lines: AsyncIterable<string> = {
231
+ [Symbol.asyncIterator]() {
232
+ return {
233
+ next(): Promise<IteratorResult<string, undefined>> {
234
+ if (lineQueue.length > 0) {
235
+ const v = lineQueue.shift()
236
+ if (v !== undefined) return Promise.resolve({ value: v, done: false })
237
+ }
238
+ if (closed) return Promise.resolve({ value: undefined, done: true as const })
239
+ return new Promise((resolve) => lineWaiters.push(resolve))
240
+ },
241
+ }
242
+ },
243
+ }
244
+
245
+ return {
246
+ lines,
247
+ filePath: filePathPromise,
248
+ close() {
249
+ if (closed) return
250
+ closed = true
251
+ if (watcher) try { watcher.close() } catch { /* swallow */ }
252
+ if (pollTimer) clearInterval(pollTimer)
253
+ endLines()
254
+ },
255
+ }
256
+ }
257
+
258
+ export async function waitForResultEntry(
259
+ stream: TranscriptStream,
260
+ opts: { timeoutMs?: number; signal?: AbortSignal } = {},
261
+ ): Promise<{ rawLine: string; parsed: { type: string } }> {
262
+ const timeoutMs = opts.timeoutMs
263
+ return new Promise((resolve, reject) => {
264
+ let settled = false
265
+
266
+ const timer = timeoutMs !== undefined
267
+ ? setTimeout(() => {
268
+ if (settled) return
269
+ settled = true
270
+ stream.close()
271
+ reject(new Error(`waitForResultEntry timed out after ${timeoutMs}ms`))
272
+ }, timeoutMs)
273
+ : null
274
+
275
+ const onAbort = () => {
276
+ if (settled) return
277
+ settled = true
278
+ if (timer) clearTimeout(timer)
279
+ stream.close()
280
+ reject(new Error("aborted"))
281
+ }
282
+
283
+ if (opts.signal) {
284
+ if (opts.signal.aborted) {
285
+ onAbort()
286
+ return
287
+ }
288
+ opts.signal.addEventListener("abort", onAbort, { once: true })
289
+ }
290
+
291
+ async function consume() {
292
+ try {
293
+ for await (const line of stream.lines) {
294
+ if (settled) return
295
+ let parsed: { type?: string; subtype?: string; error?: string; isApiErrorMessage?: boolean; apiErrorStatus?: number }
296
+ try { parsed = JSON.parse(line) as typeof parsed } catch { continue }
297
+ // Two completion markers:
298
+ // - `type: "result"` — SDK / `claude -p` output (one-shot)
299
+ // - `type: "system", subtype: "turn_duration"` — interactive TUI
300
+ // turn end (interactive mode never writes a `result` row).
301
+ // Reference: canon/index.ts:711 turnDurationMsFromRows.
302
+ const isTurnEnd =
303
+ parsed.type === "result" ||
304
+ (parsed.type === "system" && parsed.subtype === "turn_duration")
305
+ if (isTurnEnd) {
306
+ settled = true
307
+ if (timer) clearTimeout(timer)
308
+ if (opts.signal) opts.signal.removeEventListener("abort", onAbort)
309
+ resolve({ rawLine: line, parsed: { type: parsed.type ?? "result" } })
310
+ return
311
+ }
312
+ // Rate-limit responses (HTTP 429) arrive as assistant messages rather
313
+ // than result entries — surface them immediately so callers can
314
+ // distinguish a transient limit from a structural probe failure.
315
+ if (parsed.type === "assistant" && parsed.isApiErrorMessage && parsed.apiErrorStatus === 429) {
316
+ settled = true
317
+ if (timer) clearTimeout(timer)
318
+ if (opts.signal) opts.signal.removeEventListener("abort", onAbort)
319
+ const rlErr = new Error("rate_limited") as Error & { code: string }
320
+ rlErr.code = "rate_limited"
321
+ reject(rlErr)
322
+ return
323
+ }
324
+ }
325
+ if (!settled) {
326
+ settled = true
327
+ if (timer) clearTimeout(timer)
328
+ if (opts.signal) opts.signal.removeEventListener("abort", onAbort)
329
+ reject(new Error("transcript stream ended before result entry"))
330
+ }
331
+ } catch (err) {
332
+ if (!settled) {
333
+ settled = true
334
+ if (timer) clearTimeout(timer)
335
+ if (opts.signal) opts.signal.removeEventListener("abort", onAbort)
336
+ reject(err)
337
+ }
338
+ }
339
+ }
340
+
341
+ void consume()
342
+ })
343
+ }
@@ -0,0 +1,12 @@
1
+ export type ProposeFromToolResult =
2
+ | { status: "ok"; url: string; tunnelId: string }
3
+ | { status: "invalid_port"; reason: string }
4
+ | { status: "no_tunnel"; reason: string }
5
+ | { status: "error"; reason: string }
6
+
7
+ export interface TunnelGateway {
8
+ getPublicUrl(): string | null
9
+ isActive(): boolean
10
+ getTokenForRequest?(): string | null
11
+ proposeFromTool(args: { chatId: string; port: number }): Promise<ProposeFromToolResult>
12
+ }
@@ -0,0 +1,15 @@
1
+ import { createHash } from "node:crypto"
2
+
3
+ function canonicalJson(value: unknown): string {
4
+ if (value === null || typeof value !== "object") return JSON.stringify(value)
5
+ if (Array.isArray(value)) {
6
+ return `[${value.map(canonicalJson).join(",")}]`
7
+ }
8
+ const obj = value as Record<string, unknown>
9
+ const keys = Object.keys(obj).sort()
10
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`
11
+ }
12
+
13
+ export function canonicalArgsHash(args: unknown): string {
14
+ return createHash("sha256").update(canonicalJson(args)).digest("hex")
15
+ }
@@ -0,0 +1,8 @@
1
+ import { stat } from "node:fs/promises"
2
+ import type { Stats } from "node:fs"
3
+
4
+ export type PathInfo = Stats
5
+
6
+ export async function statPathOrNull(p: string): Promise<PathInfo | null> {
7
+ return await stat(p).catch(() => null)
8
+ }
@@ -0,0 +1,90 @@
1
+ import type { AgentProvider, TranscriptEntry } from "../../shared/types"
2
+
3
+ // Policy: renderEntry handles message-shaped TranscriptEntry kinds only
4
+ // (user_prompt, assistant_text, tool_call). All other kinds — slash-command
5
+ // echoes, errors, autocontinue markers, status, etc. — are intentionally
6
+ // omitted. The primer is a context bridge, not a full transcript replay.
7
+ // TODO: PRIMER_MAX_CHARS is provider-blind; per-provider tuning + telemetry
8
+ // are phase-1 follow-ups.
9
+ export const PRIMER_MAX_CHARS = 60_000
10
+
11
+ export function shouldInjectPrimer(
12
+ sessionTokensByProvider: Partial<Record<AgentProvider, string | null>>,
13
+ targetProvider: AgentProvider,
14
+ userClearedContext: boolean,
15
+ ): boolean {
16
+ if (userClearedContext) return true
17
+ return sessionTokensByProvider[targetProvider] == null
18
+ }
19
+
20
+ interface RenderedEntry {
21
+ text: string
22
+ createdAt: number
23
+ }
24
+
25
+ function renderEntry(entry: TranscriptEntry): RenderedEntry | null {
26
+ const ts = new Date(entry.createdAt).toISOString().replace("T", " ").slice(0, 19)
27
+ if (entry.kind === "user_prompt") {
28
+ return { text: `[user, ${ts}]\n${entry.content}\n`, createdAt: entry.createdAt }
29
+ }
30
+ if (entry.kind === "assistant_text") {
31
+ return { text: `[assistant, ${ts}]\n${entry.text}\n`, createdAt: entry.createdAt }
32
+ }
33
+ if (entry.kind === "tool_call") {
34
+ return { text: `[tool, ${ts}] ${entry.tool.toolName}\n`, createdAt: entry.createdAt }
35
+ }
36
+ return null
37
+ }
38
+
39
+ export function buildHistoryPrimer(
40
+ entries: TranscriptEntry[],
41
+ _targetProvider: AgentProvider,
42
+ userText: string,
43
+ ): string | null {
44
+ const hasAssistant = entries.some((entry) => entry.kind === "assistant_text")
45
+ if (!hasAssistant) return null
46
+
47
+ const rendered = entries
48
+ .map(renderEntry)
49
+ .filter((entry): entry is RenderedEntry => entry !== null)
50
+
51
+ const header = "The following is the prior conversation in this chat. The first part is context only; the actual request follows after the marker line.\n\n--- BEGIN PRIOR CONVERSATION ---\n"
52
+ const footer = "--- END PRIOR CONVERSATION ---\n\n"
53
+ const overhead = header.length + footer.length + userText.length
54
+ const budget = Math.max(0, PRIMER_MAX_CHARS - overhead)
55
+
56
+ const selected: RenderedEntry[] = []
57
+ let used = 0
58
+ let truncated = false
59
+ for (let i = rendered.length - 1; i >= 0; i -= 1) {
60
+ const candidate = rendered[i]
61
+ if (used + candidate.text.length > budget) {
62
+ truncated = i > 0 || selected.length === 0 ? true : truncated
63
+ break
64
+ }
65
+ selected.unshift(candidate)
66
+ used += candidate.text.length
67
+ }
68
+
69
+ const truncMarker = truncated ? "[... earlier conversation omitted ...]\n" : ""
70
+ return `${header}${truncMarker}${selected.map((entry) => entry.text).join("")}${footer}${userText}`
71
+ }
72
+
73
+ export function extractPreviousAssistantReply(entries: TranscriptEntry[]): string | null {
74
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
75
+ const entry = entries[i]
76
+ if (entry.kind === "assistant_text") return entry.text
77
+ }
78
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
79
+ const entry = entries[i]
80
+ if (entry.kind !== "tool_call") continue
81
+ const tool = entry.tool
82
+ const input = (tool as { input?: unknown }).input
83
+ const cmd = input && typeof input === "object" && "command" in (input as Record<string, unknown>)
84
+ ? (input as { command?: string }).command ?? ""
85
+ : ""
86
+ const suffix = cmd ? `: ${cmd}` : ""
87
+ return `${tool.toolName}${suffix}`.trim()
88
+ }
89
+ return null
90
+ }
@@ -0,0 +1,33 @@
1
+ import http from "node:http"
2
+ import type { AddressInfo } from "node:net"
3
+
4
+ export type HttpRequestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void
5
+
6
+ export interface HttpServerHandle {
7
+ port: number
8
+ close: () => Promise<void>
9
+ }
10
+
11
+ export function createHttpServer(handler: HttpRequestHandler) {
12
+ return http.createServer(handler)
13
+ }
14
+
15
+ export function listen(server: http.Server, port: number, host: string): Promise<AddressInfo> {
16
+ return new Promise<AddressInfo>((resolve, reject) => {
17
+ server.once("error", reject)
18
+ server.listen(port, host, () => {
19
+ server.off("error", reject)
20
+ resolve(server.address() as AddressInfo)
21
+ })
22
+ })
23
+ }
24
+
25
+ export function closeHttpServer(server: http.Server): Promise<void> {
26
+ return new Promise<void>((resolve) => {
27
+ server.close(() => resolve())
28
+ })
29
+ }
30
+
31
+ export type HttpServer = http.Server
32
+ export type HttpIncomingMessage = http.IncomingMessage
33
+ export type HttpServerResponse = http.ServerResponse
@@ -0,0 +1,177 @@
1
+ import { randomBytes, randomUUID } from "node:crypto"
2
+ import { closeHttpServer, createHttpServer, listen, type HttpIncomingMessage } from "./http-server.adapter"
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
5
+ import type { SdkMcpToolDefinition } from "@anthropic-ai/claude-agent-sdk"
6
+ import { KANNA_MCP_SERVER_NAME } from "../../shared/tools"
7
+ import { buildKannaMcpTools, type KannaMcpArgs } from "./mcp"
8
+ import type { McpServerConfig } from "../../shared/types"
9
+
10
+ export interface KannaMcpHttpHandle {
11
+ /** Full URL including path the claude CLI must POST/GET against. */
12
+ url: string
13
+ /** Bearer token the CLI must present in Authorization header. */
14
+ bearerToken: string
15
+ /** Tear down HTTP listener + MCP transport. Idempotent. */
16
+ close: () => Promise<void>
17
+ }
18
+
19
+ export interface StartKannaMcpHttpServerOptions {
20
+ args: KannaMcpArgs
21
+ /** Override host. Defaults to 127.0.0.1 (loopback-only). */
22
+ host?: string
23
+ /** Optional fixed port for tests. 0 = pick ephemeral. Defaults to 0. */
24
+ port?: number
25
+ }
26
+
27
+ /**
28
+ * Starts an in-process HTTP MCP server bound to loopback. The claude CLI
29
+ * subprocess (PTY driver) reaches kanna's tool-callback / tunnel-gateway /
30
+ * permission-policy state by connecting over HTTP. Bearer token in
31
+ * Authorization header gates each request — random per spawn, never reused.
32
+ *
33
+ * Loopback-only bind by design: tokens live in process memory and the
34
+ * --mcp-config JSON passed to the CLI; both are scoped to this machine.
35
+ */
36
+ export async function startKannaMcpHttpServer(
37
+ opts: StartKannaMcpHttpServerOptions,
38
+ ): Promise<KannaMcpHttpHandle> {
39
+ const bearerToken = randomBytes(32).toString("hex")
40
+ const host = opts.host ?? "127.0.0.1"
41
+ const port = opts.port ?? 0
42
+
43
+ const mcp = new McpServer({
44
+ name: KANNA_MCP_SERVER_NAME,
45
+ version: "1.0.0",
46
+ })
47
+
48
+ const tools = buildKannaMcpTools(opts.args)
49
+ for (const def of tools) {
50
+ registerToolOnMcpServer(mcp, def)
51
+ }
52
+
53
+ const transport = new StreamableHTTPServerTransport({
54
+ sessionIdGenerator: () => randomUUID(),
55
+ })
56
+ await mcp.connect(transport)
57
+
58
+ const httpServer = createHttpServer((req, res) => {
59
+ if (!authorize(req, bearerToken)) {
60
+ res.statusCode = 401
61
+ res.setHeader("WWW-Authenticate", "Bearer")
62
+ res.end("unauthorized")
63
+ return
64
+ }
65
+ void transport.handleRequest(req, res).catch((err) => {
66
+ if (!res.headersSent) {
67
+ res.statusCode = 500
68
+ res.end(String(err))
69
+ }
70
+ })
71
+ })
72
+
73
+ let address
74
+ try {
75
+ address = await listen(httpServer, port, host)
76
+ } catch (err) {
77
+ try { await transport.close() } catch { /* swallow */ }
78
+ throw err
79
+ }
80
+
81
+ const url = `http://${host}:${address.port}/mcp`
82
+
83
+ let closed = false
84
+ const close = async (): Promise<void> => {
85
+ if (closed) return
86
+ closed = true
87
+ try {
88
+ await transport.close()
89
+ } catch {
90
+ /* swallow */
91
+ }
92
+ await closeHttpServer(httpServer)
93
+ }
94
+
95
+ return { url, bearerToken, close }
96
+ }
97
+
98
+ function authorize(req: HttpIncomingMessage, bearerToken: string): boolean {
99
+ const header = req.headers.authorization
100
+ if (!header || typeof header !== "string") return false
101
+ const prefix = "Bearer "
102
+ if (!header.startsWith(prefix)) return false
103
+ const supplied = header.slice(prefix.length).trim()
104
+ return constantTimeEqual(supplied, bearerToken)
105
+ }
106
+
107
+ function constantTimeEqual(a: string, b: string): boolean {
108
+ if (a.length !== b.length) return false
109
+ let mismatch = 0
110
+ for (let i = 0; i < a.length; i++) {
111
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i)
112
+ }
113
+ return mismatch === 0
114
+ }
115
+
116
+ function registerToolOnMcpServer(
117
+ mcp: McpServer,
118
+ def: SdkMcpToolDefinition,
119
+ ): void {
120
+ mcp.registerTool(
121
+ def.name,
122
+ {
123
+ description: def.description,
124
+ inputSchema: def.inputSchema,
125
+ },
126
+ async (input: unknown, extra: unknown) => {
127
+ return await def.handler(input as never, extra)
128
+ },
129
+ )
130
+ }
131
+
132
+ /**
133
+ * Builds the --mcp-config JSON string the PTY driver passes to the claude
134
+ * CLI. Encodes the HTTP MCP server URL + bearer token under the kanna
135
+ * server name so the model sees tools as `mcp__kanna__<name>`.
136
+ *
137
+ * Optional `userServers` merges enabled user-configured MCP entries into
138
+ * the JSON. Disabled entries and any whose name collides with
139
+ * KANNA_MCP_SERVER_NAME are silently dropped.
140
+ */
141
+ export function buildMcpConfigJson(
142
+ handle: { url: string; bearerToken: string },
143
+ userServers: readonly McpServerConfig[] = [],
144
+ ): string {
145
+ const mcpServers: Record<string, unknown> = {
146
+ [KANNA_MCP_SERVER_NAME]: {
147
+ type: "http",
148
+ url: handle.url,
149
+ headers: {
150
+ Authorization: `Bearer ${handle.bearerToken}`,
151
+ },
152
+ },
153
+ }
154
+ for (const s of userServers) {
155
+ if (!s.enabled) continue
156
+ if (s.name === KANNA_MCP_SERVER_NAME) continue
157
+ mcpServers[s.name] = toClaudeCliMcpEntry(s)
158
+ }
159
+ return JSON.stringify({ mcpServers })
160
+ }
161
+
162
+ function toClaudeCliMcpEntry(s: McpServerConfig): Record<string, unknown> {
163
+ if (s.transport === "stdio") {
164
+ return {
165
+ type: "stdio",
166
+ command: s.command,
167
+ args: s.args,
168
+ env: s.env,
169
+ ...(s.cwd ? { cwd: s.cwd } : {}),
170
+ }
171
+ }
172
+ return {
173
+ type: s.transport,
174
+ url: s.url,
175
+ headers: s.headers,
176
+ }
177
+ }