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,61 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import { POLICY_DEFAULT } from "../../../shared/permission-policy"
6
+ import { createToolCallbackService } from "../../claude-pty/tool-callback"
7
+ import { createTestEventStore } from "../../storage/test-helpers"
8
+ import { createGlobTool } from "./glob.adapter"
9
+
10
+ async function newStore() {
11
+ const dir = await mkdtemp(path.join(tmpdir(), "kanna-mcp-glob-"))
12
+ const store = createTestEventStore(dir)
13
+ await store.initialize()
14
+ const cleanup = async () => {
15
+ await new Promise<void>((r) => setTimeout(r, 50))
16
+ await rm(dir, { recursive: true, force: true })
17
+ }
18
+ return { store, dir, cleanup }
19
+ }
20
+
21
+ const ctx = (cwd: string) => ({
22
+ chatId: "c",
23
+ sessionId: "s",
24
+ toolUseId: "tu",
25
+ cwd,
26
+ chatPolicy: { ...POLICY_DEFAULT, defaultAction: "auto-allow" as const },
27
+ })
28
+
29
+ describe("mcp__kanna__glob", () => {
30
+ test("*.ts pattern → returns matching files only", async () => {
31
+ const { store, dir, cleanup } = await newStore()
32
+ try {
33
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
34
+ const tool = createGlobTool({ toolCallback: svc })
35
+
36
+ // Create a mix of .ts and .txt files
37
+ await writeFile(path.join(dir, "foo.ts"), "")
38
+ await writeFile(path.join(dir, "bar.ts"), "")
39
+ await writeFile(path.join(dir, "baz.txt"), "")
40
+ await mkdir(path.join(dir, "sub"))
41
+ await writeFile(path.join(dir, "sub", "qux.ts"), "")
42
+
43
+ const result = await tool.handler({ path: dir, pattern: "**/*.ts" }, ctx(dir))
44
+ expect(result.isError).toBeFalsy()
45
+ const lines = result.content[0].text.split("\n").filter(Boolean)
46
+ expect(lines.every((l) => l.endsWith(".ts"))).toBe(true)
47
+ expect(lines.some((l) => l.includes("baz.txt"))).toBe(false)
48
+ expect(lines.length).toBe(3)
49
+ } finally { await cleanup() }
50
+ }, 30_000)
51
+
52
+ test("path in readPathDeny → isError true", async () => {
53
+ const { store, dir, cleanup } = await newStore()
54
+ try {
55
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
56
+ const tool = createGlobTool({ toolCallback: svc })
57
+ const result = await tool.handler({ path: "~/.ssh", pattern: "*.pem" }, ctx(dir))
58
+ expect(result.isError).toBe(true)
59
+ } finally { await cleanup() }
60
+ }, 30_000)
61
+ })
@@ -0,0 +1,126 @@
1
+ import { z } from "zod"
2
+ import { readdir, readFile, lstat, stat } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { homedir } from "node:os"
5
+ import type { ToolCallbackService } from "../../claude-pty/tool-callback"
6
+ import type { ToolHandlerContext, ToolHandlerResult } from "./tool-callback-shim"
7
+ import { gatedToolCall } from "./tool-callback-shim"
8
+
9
+ const InputSchema = z.object({
10
+ path: z.string(),
11
+ pattern: z.string(),
12
+ })
13
+
14
+ export type GrepInput = z.infer<typeof InputSchema>
15
+
16
+ export interface GrepTool {
17
+ name: "grep"
18
+ schema: typeof InputSchema
19
+ handler: (input: GrepInput, ctx: ToolHandlerContext) => Promise<ToolHandlerResult>
20
+ }
21
+
22
+ function resolvePath(p: string, cwd: string): string {
23
+ if (p.startsWith("~")) return path.join(homedir(), p.slice(1).replace(/^\//, ""))
24
+ return path.resolve(cwd, p)
25
+ }
26
+
27
+ const SKIP_DIRS = new Set(["node_modules", ".git"])
28
+ const MAX_LINES = 500
29
+ const MAX_FILE_SIZE = 1_000_000 // 1 MB
30
+
31
+ async function grepDir(root: string, re: RegExp, results: string[]): Promise<void> {
32
+ if (results.length >= MAX_LINES) return
33
+ let entries
34
+ try {
35
+ entries = await readdir(root, { withFileTypes: true })
36
+ } catch {
37
+ return
38
+ }
39
+ for (const entry of entries) {
40
+ if (results.length >= MAX_LINES) break
41
+ const fullPath = path.join(root, entry.name)
42
+ if (entry.isDirectory()) {
43
+ if (SKIP_DIRS.has(entry.name)) continue
44
+ // Symlink guard: skip symlinked directories to prevent traversal loops
45
+ try {
46
+ const st = await lstat(fullPath)
47
+ if (st.isSymbolicLink()) continue
48
+ } catch {
49
+ continue
50
+ }
51
+ await grepDir(fullPath, re, results)
52
+ } else {
53
+ // Skip large files to prevent memory issues
54
+ try {
55
+ const fileStat = await stat(fullPath)
56
+ if (fileStat.size > MAX_FILE_SIZE) continue
57
+ } catch {
58
+ continue
59
+ }
60
+ let content: string
61
+ try {
62
+ content = await readFile(fullPath, "utf8")
63
+ } catch {
64
+ continue
65
+ }
66
+ const lines = content.split("\n")
67
+ for (let i = 0; i < lines.length && results.length < MAX_LINES; i++) {
68
+ if (re.test(lines[i])) {
69
+ results.push(`${fullPath}:${i + 1}: ${lines[i]}`)
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ async function grepWithTimeout(root: string, re: RegExp, results: string[]): Promise<void> {
77
+ return await Promise.race([
78
+ grepDir(root, re, results),
79
+ new Promise<void>((_, reject) => setTimeout(() => reject(new Error("grep timeout 30s")), 30_000)),
80
+ ])
81
+ }
82
+
83
+ export function createGrepTool(deps: { toolCallback: ToolCallbackService }): GrepTool {
84
+ return {
85
+ name: "grep",
86
+ schema: InputSchema,
87
+ async handler(input, ctx) {
88
+ return gatedToolCall({
89
+ toolCallback: deps.toolCallback,
90
+ toolName: "mcp__kanna__grep",
91
+ ctx,
92
+ args: input as unknown as Record<string, unknown>,
93
+ formatAnswer: async () => {
94
+ let re: RegExp
95
+ try {
96
+ re = new RegExp(input.pattern)
97
+ } catch (err) {
98
+ const msg = err instanceof Error ? err.message : String(err)
99
+ return {
100
+ content: [{ type: "text" as const, text: `Invalid regex pattern: ${msg}` }],
101
+ isError: true,
102
+ }
103
+ }
104
+ const root = resolvePath(input.path, ctx.cwd)
105
+ const results: string[] = []
106
+ try {
107
+ await grepWithTimeout(root, re, results)
108
+ } catch (err) {
109
+ const msg = err instanceof Error ? err.message : String(err)
110
+ return {
111
+ content: [{ type: "text" as const, text: `grep error: ${msg}` }],
112
+ isError: true,
113
+ }
114
+ }
115
+ return {
116
+ content: [{ type: "text" as const, text: results.join("\n") }],
117
+ }
118
+ },
119
+ formatDeny: (reason) => ({
120
+ content: [{ type: "text" as const, text: `Denied: ${reason}` }],
121
+ isError: true,
122
+ }),
123
+ })
124
+ },
125
+ }
126
+ }
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import { POLICY_DEFAULT } from "../../../shared/permission-policy"
6
+ import { createToolCallbackService } from "../../claude-pty/tool-callback"
7
+ import { createTestEventStore } from "../../storage/test-helpers"
8
+ import { createGrepTool } from "./grep.adapter"
9
+
10
+ async function newStore() {
11
+ const dir = await mkdtemp(path.join(tmpdir(), "kanna-mcp-grep-"))
12
+ const store = createTestEventStore(dir)
13
+ await store.initialize()
14
+ const cleanup = async () => {
15
+ await new Promise<void>((r) => setTimeout(r, 50))
16
+ await rm(dir, { recursive: true, force: true })
17
+ }
18
+ return { store, dir, cleanup }
19
+ }
20
+
21
+ const ctx = (cwd: string) => ({
22
+ chatId: "c",
23
+ sessionId: "s",
24
+ toolUseId: "tu",
25
+ cwd,
26
+ chatPolicy: { ...POLICY_DEFAULT, defaultAction: "auto-allow" as const },
27
+ })
28
+
29
+ describe("mcp__kanna__grep", () => {
30
+ test("finds lines matching pattern across multiple files", async () => {
31
+ const { store, dir, cleanup } = await newStore()
32
+ try {
33
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
34
+ const tool = createGrepTool({ toolCallback: svc })
35
+
36
+ // Use a dedicated search dir separate from the event store dir
37
+ const searchDir = path.join(dir, "search")
38
+ await mkdir(searchDir)
39
+ await writeFile(path.join(searchDir, "alpha.txt"), "hello world\nfoo bar\nhello again")
40
+ await mkdir(path.join(searchDir, "sub"))
41
+ await writeFile(path.join(searchDir, "sub", "beta.txt"), "nothing here\nhello sub\n")
42
+ await writeFile(path.join(searchDir, "gamma.txt"), "no match")
43
+
44
+ const result = await tool.handler({ path: searchDir, pattern: "hello" }, ctx(dir))
45
+ expect(result.isError).toBeFalsy()
46
+ const lines = result.content[0].text.split("\n").filter(Boolean)
47
+ // Should find "hello world", "hello again", "hello sub"
48
+ expect(lines.length).toBe(3)
49
+ expect(lines.every((l) => l.includes("hello"))).toBe(true)
50
+ } finally { await cleanup() }
51
+ }, 30_000)
52
+
53
+ test("path in readPathDeny → isError true", async () => {
54
+ const { store, dir, cleanup } = await newStore()
55
+ try {
56
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
57
+ const tool = createGrepTool({ toolCallback: svc })
58
+ const result = await tool.handler({ path: "~/.ssh", pattern: "KEY" }, ctx(dir))
59
+ expect(result.isError).toBe(true)
60
+ } finally { await cleanup() }
61
+ }, 30_000)
62
+ })
@@ -0,0 +1,58 @@
1
+ import { z } from "zod"
2
+ import { readFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { homedir } from "node:os"
5
+ import type { ToolCallbackService } from "../../claude-pty/tool-callback"
6
+ import type { ToolHandlerContext, ToolHandlerResult } from "./tool-callback-shim"
7
+ import { gatedToolCall } from "./tool-callback-shim"
8
+
9
+ const InputSchema = z.object({
10
+ path: z.string(),
11
+ })
12
+
13
+ export type ReadInput = z.infer<typeof InputSchema>
14
+
15
+ export interface ReadTool {
16
+ name: "read"
17
+ schema: typeof InputSchema
18
+ handler: (input: ReadInput, ctx: ToolHandlerContext) => Promise<ToolHandlerResult>
19
+ }
20
+
21
+ function resolvePath(p: string, cwd: string): string {
22
+ if (p.startsWith("~")) return path.join(homedir(), p.slice(1).replace(/^\//, ""))
23
+ return path.resolve(cwd, p)
24
+ }
25
+
26
+ export function createReadTool(deps: { toolCallback: ToolCallbackService }): ReadTool {
27
+ return {
28
+ name: "read",
29
+ schema: InputSchema,
30
+ async handler(input, ctx) {
31
+ return gatedToolCall({
32
+ toolCallback: deps.toolCallback,
33
+ toolName: "mcp__kanna__read",
34
+ ctx,
35
+ args: input as unknown as Record<string, unknown>,
36
+ formatAnswer: async () => {
37
+ try {
38
+ const resolved = resolvePath(input.path, ctx.cwd)
39
+ const content = await readFile(resolved, "utf8")
40
+ return {
41
+ content: [{ type: "text" as const, text: content }],
42
+ }
43
+ } catch (err) {
44
+ const msg = err instanceof Error ? err.message : String(err)
45
+ return {
46
+ content: [{ type: "text" as const, text: `Error reading file: ${msg}` }],
47
+ isError: true,
48
+ }
49
+ }
50
+ },
51
+ formatDeny: (reason) => ({
52
+ content: [{ type: "text" as const, text: `Denied: ${reason}` }],
53
+ isError: true,
54
+ }),
55
+ })
56
+ },
57
+ }
58
+ }
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import { POLICY_DEFAULT } from "../../../shared/permission-policy"
6
+ import { createToolCallbackService } from "../../claude-pty/tool-callback"
7
+ import { createTestEventStore } from "../../storage/test-helpers"
8
+ import { createReadTool } from "./read.adapter"
9
+
10
+ async function newStore() {
11
+ const dir = await mkdtemp(path.join(tmpdir(), "kanna-mcp-read-"))
12
+ const store = createTestEventStore(dir)
13
+ await store.initialize()
14
+ const cleanup = async () => {
15
+ await new Promise<void>((r) => setTimeout(r, 50))
16
+ await rm(dir, { recursive: true, force: true })
17
+ }
18
+ return { store, dir, cleanup }
19
+ }
20
+
21
+ const ctx = (cwd: string) => ({
22
+ chatId: "c",
23
+ sessionId: "s",
24
+ toolUseId: "tu",
25
+ cwd,
26
+ chatPolicy: { ...POLICY_DEFAULT, defaultAction: "auto-allow" as const },
27
+ })
28
+
29
+ describe("mcp__kanna__read", () => {
30
+ test("reads existing file → content in result, no isError", async () => {
31
+ const { store, dir, cleanup } = await newStore()
32
+ try {
33
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
34
+ const tool = createReadTool({ toolCallback: svc })
35
+ const filePath = path.join(dir, "hello.txt")
36
+ await writeFile(filePath, "hello world")
37
+ const result = await tool.handler({ path: filePath }, ctx(dir))
38
+ expect(result.isError).toBeFalsy()
39
+ expect(result.content[0].text).toBe("hello world")
40
+ } finally { await cleanup() }
41
+ }, 30_000)
42
+
43
+ test("path in readPathDeny (~/.ssh/id_rsa) → isError true", async () => {
44
+ const { store, dir, cleanup } = await newStore()
45
+ try {
46
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
47
+ const tool = createReadTool({ toolCallback: svc })
48
+ const result = await tool.handler({ path: "~/.ssh/id_rsa" }, ctx(dir))
49
+ expect(result.isError).toBe(true)
50
+ } finally { await cleanup() }
51
+ }, 30_000)
52
+
53
+ test("missing file → isError true", async () => {
54
+ const { store, dir, cleanup } = await newStore()
55
+ try {
56
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
57
+ const tool = createReadTool({ toolCallback: svc })
58
+ const result = await tool.handler({ path: path.join(dir, "does-not-exist.txt") }, ctx(dir))
59
+ expect(result.isError).toBe(true)
60
+ } finally { await cleanup() }
61
+ }, 30_000)
62
+ })
@@ -0,0 +1,42 @@
1
+ import type { ToolCallbackService } from "../../claude-pty/tool-callback"
2
+ import type { ChatPermissionPolicy } from "../../../shared/permission-policy"
3
+
4
+ export interface ToolHandlerContext {
5
+ chatId: string
6
+ sessionId: string
7
+ toolUseId: string
8
+ cwd: string
9
+ chatPolicy: ChatPermissionPolicy
10
+ }
11
+
12
+ export interface ToolHandlerResult {
13
+ // Index signature required to satisfy MCP CallToolResult shape
14
+ [key: string]: unknown
15
+ content: { type: "text"; text: string }[]
16
+ isError?: boolean
17
+ }
18
+
19
+ export interface GatedToolCallArgs {
20
+ toolCallback: ToolCallbackService
21
+ toolName: string
22
+ ctx: ToolHandlerContext
23
+ args: Record<string, unknown>
24
+ formatAnswer: (payload: unknown) => ToolHandlerResult | Promise<ToolHandlerResult>
25
+ formatDeny: (reason: string) => ToolHandlerResult
26
+ }
27
+
28
+ export async function gatedToolCall(args: GatedToolCallArgs): Promise<ToolHandlerResult> {
29
+ const res = await args.toolCallback.submit({
30
+ chatId: args.ctx.chatId,
31
+ sessionId: args.ctx.sessionId,
32
+ toolUseId: args.ctx.toolUseId,
33
+ toolName: args.toolName,
34
+ args: args.args,
35
+ chatPolicy: args.ctx.chatPolicy,
36
+ cwd: args.ctx.cwd,
37
+ })
38
+ if (res.decision.kind === "allow" || res.decision.kind === "answer") {
39
+ return await args.formatAnswer(res.decision.payload)
40
+ }
41
+ return args.formatDeny(res.decision.reason ?? "denied")
42
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtemp, rm } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import { POLICY_DEFAULT } from "../../../shared/permission-policy"
6
+ import { createToolCallbackService } from "../../claude-pty/tool-callback"
7
+ import { createTestEventStore } from "../../storage/test-helpers"
8
+ import { createWebFetchTool } from "./webfetch"
9
+
10
+ async function newStore() {
11
+ const dir = await mkdtemp(path.join(tmpdir(), "kanna-mcp-webfetch-"))
12
+ const store = createTestEventStore(dir)
13
+ await store.initialize()
14
+ const cleanup = async () => {
15
+ await new Promise<void>((r) => setTimeout(r, 50))
16
+ await rm(dir, { recursive: true, force: true })
17
+ }
18
+ return { store, dir, cleanup }
19
+ }
20
+
21
+ const ctx = (cwd: string) => ({
22
+ chatId: "c",
23
+ sessionId: "s",
24
+ toolUseId: "tu",
25
+ cwd,
26
+ chatPolicy: { ...POLICY_DEFAULT, defaultAction: "auto-allow" as const },
27
+ })
28
+
29
+ describe("mcp__kanna__webfetch", () => {
30
+ test("fetches local server → result contains response body", async () => {
31
+ const { store, dir, cleanup } = await newStore()
32
+ const server = Bun.serve({
33
+ port: 0,
34
+ fetch() {
35
+ return new Response("hello from server", { status: 200 })
36
+ },
37
+ })
38
+ try {
39
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
40
+ const tool = createWebFetchTool({ toolCallback: svc })
41
+ const result = await tool.handler({ url: `http://localhost:${server.port}/` }, ctx(dir))
42
+ expect(result.isError).toBeFalsy()
43
+ expect(result.content[0].text).toContain("hello from server")
44
+ } finally {
45
+ server.stop()
46
+ await cleanup()
47
+ }
48
+ }, 30_000)
49
+
50
+ test("bad URL → isError true", async () => {
51
+ const { store, dir, cleanup } = await newStore()
52
+ try {
53
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
54
+ const tool = createWebFetchTool({ toolCallback: svc })
55
+ const result = await tool.handler({ url: "not-a-url" }, ctx(dir))
56
+ expect(result.isError).toBe(true)
57
+ } finally { await cleanup() }
58
+ }, 30_000)
59
+
60
+ test("rejects file:// URL → isError true", async () => {
61
+ const { store, dir, cleanup } = await newStore()
62
+ try {
63
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
64
+ const tool = createWebFetchTool({ toolCallback: svc })
65
+ const result = await tool.handler({ url: "file:///etc/passwd" }, ctx(dir))
66
+ expect(result.isError).toBe(true)
67
+ expect(result.content[0].text).toContain("scheme file: not allowed")
68
+ } finally { await cleanup() }
69
+ }, 30_000)
70
+
71
+ test("rejects cloud metadata URL → isError true", async () => {
72
+ const { store, dir, cleanup } = await newStore()
73
+ try {
74
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
75
+ const tool = createWebFetchTool({ toolCallback: svc })
76
+ const result = await tool.handler({ url: "http://169.254.169.254/latest/meta-data/" }, ctx(dir))
77
+ expect(result.isError).toBe(true)
78
+ expect(result.content[0].text).toContain("not externally reachable")
79
+ } finally { await cleanup() }
80
+ }, 30_000)
81
+ })
@@ -0,0 +1,82 @@
1
+ import { z } from "zod"
2
+ import type { ToolCallbackService } from "../../claude-pty/tool-callback"
3
+ import type { ToolHandlerContext, ToolHandlerResult } from "./tool-callback-shim"
4
+ import { gatedToolCall } from "./tool-callback-shim"
5
+
6
+ const InputSchema = z.object({
7
+ url: z.string(),
8
+ })
9
+
10
+ export type WebFetchInput = z.infer<typeof InputSchema>
11
+
12
+ export interface WebFetchTool {
13
+ name: "webfetch"
14
+ schema: typeof InputSchema
15
+ handler: (input: WebFetchInput, ctx: ToolHandlerContext) => Promise<ToolHandlerResult>
16
+ }
17
+
18
+ // Blocked patterns: cloud metadata endpoints and link-local ranges.
19
+ // Loopback/RFC1918 are intentionally allowed since Kanna runs locally and
20
+ // tests use localhost. Cloud metadata endpoints are the real SSRF risk.
21
+ const BLOCKED_HOST_PATTERNS = [
22
+ /^169\.254\./,
23
+ /^metadata\.google\.internal$/,
24
+ ]
25
+
26
+ function isSafeUrl(rawUrl: string): { ok: true; url: URL } | { ok: false; reason: string } {
27
+ let url: URL
28
+ try {
29
+ url = new URL(rawUrl)
30
+ } catch {
31
+ return { ok: false, reason: "invalid URL" }
32
+ }
33
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
34
+ return { ok: false, reason: `scheme ${url.protocol} not allowed` }
35
+ }
36
+ const host = url.hostname.toLowerCase()
37
+ for (const re of BLOCKED_HOST_PATTERNS) {
38
+ if (re.test(host)) return { ok: false, reason: `host ${host} is not externally reachable` }
39
+ }
40
+ return { ok: true, url }
41
+ }
42
+
43
+ export function createWebFetchTool(deps: { toolCallback: ToolCallbackService }): WebFetchTool {
44
+ return {
45
+ name: "webfetch",
46
+ schema: InputSchema,
47
+ async handler(input, ctx) {
48
+ return gatedToolCall({
49
+ toolCallback: deps.toolCallback,
50
+ toolName: "mcp__kanna__webfetch",
51
+ ctx,
52
+ args: input as unknown as Record<string, unknown>,
53
+ formatAnswer: async () => {
54
+ const check = isSafeUrl(input.url)
55
+ if (!check.ok) {
56
+ return {
57
+ content: [{ type: "text" as const, text: `Error: ${check.reason}` }],
58
+ isError: true,
59
+ }
60
+ }
61
+ try {
62
+ const res = await fetch(input.url)
63
+ const text = await res.text()
64
+ return {
65
+ content: [{ type: "text" as const, text: `Status: ${res.status}\n\n${text}` }],
66
+ }
67
+ } catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err)
69
+ return {
70
+ content: [{ type: "text" as const, text: `Error fetching URL: ${msg}` }],
71
+ isError: true,
72
+ }
73
+ }
74
+ },
75
+ formatDeny: (reason) => ({
76
+ content: [{ type: "text" as const, text: `Denied: ${reason}` }],
77
+ isError: true,
78
+ }),
79
+ })
80
+ },
81
+ }
82
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtemp, rm } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import { POLICY_DEFAULT } from "../../../shared/permission-policy"
6
+ import { createToolCallbackService } from "../../claude-pty/tool-callback"
7
+ import { createTestEventStore } from "../../storage/test-helpers"
8
+ import { createWebSearchTool } from "./websearch"
9
+
10
+ async function newStore() {
11
+ const dir = await mkdtemp(path.join(tmpdir(), "kanna-mcp-websearch-"))
12
+ const store = createTestEventStore(dir)
13
+ await store.initialize()
14
+ const cleanup = async () => {
15
+ await new Promise<void>((r) => setTimeout(r, 50))
16
+ await rm(dir, { recursive: true, force: true })
17
+ }
18
+ return { store, dir, cleanup }
19
+ }
20
+
21
+ const ctx = (cwd: string) => ({
22
+ chatId: "c",
23
+ sessionId: "s",
24
+ toolUseId: "tu",
25
+ cwd,
26
+ chatPolicy: { ...POLICY_DEFAULT, defaultAction: "auto-allow" as const },
27
+ })
28
+
29
+ describe("mcp__kanna__websearch", () => {
30
+ test("always returns isError + message contains 'unavailable'", async () => {
31
+ const { store, dir, cleanup } = await newStore()
32
+ try {
33
+ const svc = createToolCallbackService({ store, serverSecret: "k", now: () => 1, timeoutMs: 600_000 })
34
+ const tool = createWebSearchTool({ toolCallback: svc })
35
+ const result = await tool.handler({ query: "some search query" }, ctx(dir))
36
+ expect(result.isError).toBe(true)
37
+ expect(result.content[0].text).toContain("unavailable")
38
+ } finally { await cleanup() }
39
+ }, 30_000)
40
+ })
@@ -0,0 +1,42 @@
1
+ import { z } from "zod"
2
+ import type { ToolCallbackService } from "../../claude-pty/tool-callback"
3
+ import type { ToolHandlerContext, ToolHandlerResult } from "./tool-callback-shim"
4
+ import { gatedToolCall } from "./tool-callback-shim"
5
+
6
+ const InputSchema = z.object({
7
+ query: z.string(),
8
+ })
9
+
10
+ export type WebSearchInput = z.infer<typeof InputSchema>
11
+
12
+ export interface WebSearchTool {
13
+ name: "websearch"
14
+ schema: typeof InputSchema
15
+ handler: (input: WebSearchInput, ctx: ToolHandlerContext) => Promise<ToolHandlerResult>
16
+ }
17
+
18
+ export function createWebSearchTool(deps: { toolCallback: ToolCallbackService }): WebSearchTool {
19
+ return {
20
+ name: "websearch",
21
+ schema: InputSchema,
22
+ async handler(input, ctx) {
23
+ return gatedToolCall({
24
+ toolCallback: deps.toolCallback,
25
+ toolName: "mcp__kanna__websearch",
26
+ ctx,
27
+ args: input as unknown as Record<string, unknown>,
28
+ formatAnswer: async () => ({
29
+ content: [{
30
+ type: "text" as const,
31
+ text: "WebSearch unavailable in this environment. Use mcp__kanna__webfetch with a specific URL if you already know the target.",
32
+ }],
33
+ isError: true,
34
+ }),
35
+ formatDeny: (reason) => ({
36
+ content: [{ type: "text" as const, text: `Denied: ${reason}` }],
37
+ isError: true,
38
+ }),
39
+ })
40
+ },
41
+ }
42
+ }