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.
- package/LICENSE +84 -0
- package/README.md +202 -0
- package/bin/airaknit +9 -0
- package/bin/airaknit-project +14 -0
- package/bin/kanna +9 -0
- package/dist/client/assets/CompactSummaryMessage-Yw0BDWEJ.js +1 -0
- package/dist/client/assets/ExitPlanModeMessage-DIdkQ4uF.js +1 -0
- package/dist/client/assets/LocalFilePreviewDialog-DQx2eiCc.js +3 -0
- package/dist/client/assets/LocalProjectsSection-C4xlWkgS.js +1 -0
- package/dist/client/assets/TextMessage-B5G39DEJ.js +1 -0
- package/dist/client/assets/UserMessage-CIkWk-0L.js +1 -0
- package/dist/client/assets/_basePickBy-CVrAFfnZ.js +1 -0
- package/dist/client/assets/_baseUniq-JL-aaF4P.js +1 -0
- package/dist/client/assets/arc-B07zg7ol.js +1 -0
- package/dist/client/assets/architecture-YZFGNWBL-PSLVJL3p.js +1 -0
- package/dist/client/assets/architectureDiagram-Q4EWVU46-DfWIF1G_.js +36 -0
- package/dist/client/assets/array-BGFCBI0e.js +1 -0
- package/dist/client/assets/blockDiagram-DXYQGD6D-CzwIeo_B.js +132 -0
- package/dist/client/assets/bundle-mjs-BDE2gWbQ.js +1 -0
- package/dist/client/assets/button-DO50qOGv.js +1 -0
- package/dist/client/assets/c4Diagram-AHTNJAMY-CR8DCQRE.js +10 -0
- package/dist/client/assets/channel-Dj-UUfaF.js +1 -0
- package/dist/client/assets/chunk-2KRD3SAO-dznP-cn8.js +1 -0
- package/dist/client/assets/chunk-336JU56O-Dqss5Vu6.js +2 -0
- package/dist/client/assets/chunk-426QAEUC-CEKis_0_.js +1 -0
- package/dist/client/assets/chunk-4BX2VUAB-BYOv0Gm1.js +1 -0
- package/dist/client/assets/chunk-4TB4RGXK-BxzubH5S.js +206 -0
- package/dist/client/assets/chunk-55IACEB6-BSlTj03a.js +1 -0
- package/dist/client/assets/chunk-5FUZZQ4R-9Au93Bi1.js +62 -0
- package/dist/client/assets/chunk-5PVQY5BW-BhksHFEZ.js +2 -0
- package/dist/client/assets/chunk-67CJDMHE-BFwhz-8t.js +1 -0
- package/dist/client/assets/chunk-7N4EOEYR-BDUPds87.js +1 -0
- package/dist/client/assets/chunk-AA7GKIK3-CEWTdyXO.js +1 -0
- package/dist/client/assets/chunk-BO2N2NFS-D0LvxnhU.js +103 -0
- package/dist/client/assets/chunk-BSJP7CBP-BNJnK6sq.js +1 -0
- package/dist/client/assets/chunk-Bj-mKKzh.js +1 -0
- package/dist/client/assets/chunk-CIAEETIT-CYhfoCeN.js +1 -0
- package/dist/client/assets/chunk-EDXVE4YY-C5ovJLc0.js +1 -0
- package/dist/client/assets/chunk-ENJZ2VHE-HOhYaeGr.js +10 -0
- package/dist/client/assets/chunk-FMBD7UC4-BLCiKcAQ.js +15 -0
- package/dist/client/assets/chunk-FOC6F5B3-B6GtY2ek.js +1 -0
- package/dist/client/assets/chunk-ICPOFSXX-DPoIZoC5.js +122 -0
- package/dist/client/assets/chunk-K5T4RW27-BsKN63rv.js +94 -0
- package/dist/client/assets/chunk-KGLVRYIC-BUGn9uuY.js +1 -0
- package/dist/client/assets/chunk-LIHQZDEY-DhaZyo03.js +1 -0
- package/dist/client/assets/chunk-ORNJ4GCN-DlpeeJyi.js +1 -0
- package/dist/client/assets/chunk-OYMX7WX6-Dc9q7aYA.js +231 -0
- package/dist/client/assets/chunk-QZHKN3VN-BEdrPoSb.js +1 -0
- package/dist/client/assets/chunk-U2HBQHQK-CIB3Bjjd.js +70 -0
- package/dist/client/assets/chunk-X2U36JSP-CtB-o8Yp.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-C6iHhX_8.js +32 -0
- package/dist/client/assets/chunk-YZCP3GAM-CTmKr6ZH.js +1 -0
- package/dist/client/assets/chunk-ZZ45TVLE-BgU8A2RF.js +1 -0
- package/dist/client/assets/classDiagram-6PBFFD2Q-Bqk5e679.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-6pSaZOkC.js +1 -0
- package/dist/client/assets/client-BrKWI4CM.js +1 -0
- package/dist/client/assets/client-CGgNRU9w.js +1 -0
- package/dist/client/assets/client-DMSLRzg9.js +6 -0
- package/dist/client/assets/clone-DWcL7whJ.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-CrV5wsV_.js +1 -0
- package/dist/client/assets/cytoscape.esm--aLzKuep.js +321 -0
- package/dist/client/assets/dagre-CuRxWcrj.js +1 -0
- package/dist/client/assets/dagre-KV5264BT-BIDiVnkA.js +4 -0
- package/dist/client/assets/defaultLocale-CRZydyG6.js +1 -0
- package/dist/client/assets/diagram-5BDNPKRD-i1kjKRCB.js +10 -0
- package/dist/client/assets/diagram-G4DWMVQ6-9ZSLuhbl.js +24 -0
- package/dist/client/assets/diagram-MMDJMWI5-B4_CUjgv.js +43 -0
- package/dist/client/assets/diagram-TYMM5635-Ct5eTGS8.js +24 -0
- package/dist/client/assets/dist-CuB4kiSK.js +1 -0
- package/dist/client/assets/erDiagram-SMLLAGMA-Cy38ercc.js +85 -0
- package/dist/client/assets/flowDiagram-DWJPFMVM-CZKuYl0V.js +162 -0
- package/dist/client/assets/ganttDiagram-T4ZO3ILL-DLPjCh7a.js +292 -0
- package/dist/client/assets/gitGraph-7Q5UKJZL-DqbrtEp9.js +1 -0
- package/dist/client/assets/gitGraphDiagram-UUTBAWPF-BoRBkDhQ.js +106 -0
- package/dist/client/assets/graphlib-BcQ6qlQh.js +1 -0
- package/dist/client/assets/highlighted-body-OFNGDK62-BEpBVDTX.js +1 -0
- package/dist/client/assets/index-CetCiuqP.js +105 -0
- package/dist/client/assets/index-N29Mip7A.css +1 -0
- package/dist/client/assets/info-OMHHGYJF-D98DRBJX.js +1 -0
- package/dist/client/assets/infoDiagram-42DDH7IO-BAcdTWbt.js +2 -0
- package/dist/client/assets/init-B8gtcn7T.js +1 -0
- package/dist/client/assets/isArrayLikeObject-D8SJFmkN.js +1 -0
- package/dist/client/assets/isEmpty-BF3YX5Jk.js +1 -0
- package/dist/client/assets/ishikawaDiagram-UXIWVN3A-Ynu2VKdC.js +70 -0
- package/dist/client/assets/journeyDiagram-VCZTEJTY-BjfhQaN3.js +139 -0
- package/dist/client/assets/jsx-runtime-CyI9ICYU.js +1 -0
- package/dist/client/assets/kanban-definition-6JOO6SKY-JLXH9zUJ.js +89 -0
- package/dist/client/assets/katex-B94qP8b6.js +265 -0
- package/dist/client/assets/lib--QVjyxmL.js +29 -0
- package/dist/client/assets/lib-B6rgJiZ9.js +1 -0
- package/dist/client/assets/line-DCrYfLBn.js +1 -0
- package/dist/client/assets/linear-_4upLmeo.js +1 -0
- package/dist/client/assets/mermaid-GHXKKRXX-rwJHYUmW.js +1 -0
- package/dist/client/assets/mermaid-parser.core-KZinfW8o.js +4 -0
- package/dist/client/assets/mermaid.core-QqY9gSNe.js +11 -0
- package/dist/client/assets/mindmap-definition-QFDTVHPH-TWgHDAzp.js +96 -0
- package/dist/client/assets/ordinal-CCj7PWgZ.js +1 -0
- package/dist/client/assets/packet-4T2RLAQJ-DEvfkn3F.js +1 -0
- package/dist/client/assets/path-DZF-JdEe.js +1 -0
- package/dist/client/assets/pie-ZZUOXDRM-72e6WVjb.js +1 -0
- package/dist/client/assets/pieDiagram-DEJITSTG-Cl8PCsoj.js +30 -0
- package/dist/client/assets/preload-helper-rov5CBGT.js +1 -0
- package/dist/client/assets/pty-client-DZ27IS00.js +1 -0
- package/dist/client/assets/ptyInstancesStore-D9ag7SYd.js +1 -0
- package/dist/client/assets/quadrantDiagram-34T5L4WZ-CHyVGp9E.js +7 -0
- package/dist/client/assets/radar-PYXPWWZC-Cp7xd_EY.js +1 -0
- package/dist/client/assets/react-CClhXMB2.js +1 -0
- package/dist/client/assets/react-Dd6D81m0.js +1 -0
- package/dist/client/assets/react-dom--G6_6fQ_.js +1 -0
- package/dist/client/assets/requirementDiagram-MS252O5E-DaanG2iM.js +84 -0
- package/dist/client/assets/rough.esm-BsmKo2S5.js +1 -0
- package/dist/client/assets/sankeyDiagram-XADWPNL6-B_fhLY36.js +10 -0
- package/dist/client/assets/sequenceDiagram-FGHM5R23-C5FNrveI.js +157 -0
- package/dist/client/assets/src-DeTlMJAU.js +1 -0
- package/dist/client/assets/stateDiagram-FHFEXIEX-nTTcdjjQ.js +1 -0
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Dw0632j_.js +1 -0
- package/dist/client/assets/timeline-definition-GMOUNBTQ-DkQV1yP8.js +120 -0
- package/dist/client/assets/treeView-SZITEDCU-ZLIgC7_K.js +1 -0
- package/dist/client/assets/treemap-W4RFUUIX-BqaMbB6N.js +1 -0
- package/dist/client/assets/uiIdentityOverlay-Ba7GNj7m.js +1 -0
- package/dist/client/assets/vennDiagram-DHZGUBPP-DbZ2xgs6.js +34 -0
- package/dist/client/assets/wardley-RL74JXVD-DXQS8zf4.js +1 -0
- package/dist/client/assets/wardleyDiagram-NUSXRM2D-BzCJ6MAu.js +20 -0
- package/dist/client/assets/xychartDiagram-5P7HB3ND-BSlFecop.js +7 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/favicon.svg +15 -0
- package/dist/client/fonts/body-medium.woff2 +0 -0
- package/dist/client/fonts/body-regular-italic.woff2 +0 -0
- package/dist/client/fonts/body-regular.woff2 +0 -0
- package/dist/client/fonts/body-semibold.woff2 +0 -0
- package/dist/client/index.html +31 -0
- package/dist/client/manifest.webmanifest +12 -0
- package/dist/client/sw.js +32 -0
- package/package.json +122 -0
- package/src/nats/auth-callout/callout-config.test.ts +93 -0
- package/src/nats/auth-callout/callout-config.ts +109 -0
- package/src/nats/auth-callout/callout.integration.test.ts +332 -0
- package/src/nats/auth-callout/keys.ts +103 -0
- package/src/nats/auth-callout/responder.ts +241 -0
- package/src/nats/auth-callout/scope-policy.test.ts +159 -0
- package/src/nats/auth-callout/scope-policy.ts +210 -0
- package/src/nats/auth-callout/token.test.ts +163 -0
- package/src/nats/auth-callout/token.ts +157 -0
- package/src/nats/nats-daemon-callout.ts +194 -0
- package/src/nats/nats-daemon.test.ts +77 -0
- package/src/nats/nats-daemon.ts +50 -0
- package/src/nats/nats-token.test.ts +61 -0
- package/src/nats/nats-token.ts +59 -0
- package/src/runner/coordination-mcp-integration.test.ts +134 -0
- package/src/runner/nats-coordination-client.test.ts +49 -0
- package/src/runner/nats-coordination-client.ts +94 -0
- package/src/runner/runner-agent.test.ts +469 -0
- package/src/runner/runner-agent.ts +453 -0
- package/src/runner/runner-credential.test.ts +93 -0
- package/src/runner/runner-credential.ts +82 -0
- package/src/runner/runner-nats.test.ts +495 -0
- package/src/runner/runner-nats.ts +323 -0
- package/src/runner/runner-pair.test.ts +107 -0
- package/src/runner/runner-pair.ts +81 -0
- package/src/runner/runner.test.ts +135 -0
- package/src/runner/runner.ts +212 -0
- package/src/runner/turn-factories.test.ts +97 -0
- package/src/runner/turn-factories.ts +475 -0
- package/src/server/agent-config-journey.test.ts +106 -0
- package/src/server/agent.ts +8 -0
- package/src/server/auto-continue/auth-error-detector.ts +66 -0
- package/src/server/auto-continue/limit-detector.ts +194 -0
- package/src/server/bm25.test.ts +92 -0
- package/src/server/bm25.ts +101 -0
- package/src/server/chat-events-jetstream.test.ts +135 -0
- package/src/server/claude-harness.ts +360 -0
- package/src/server/claude-pty/agent-normalizers.ts +309 -0
- package/src/server/claude-pty/auth.test.ts +38 -0
- package/src/server/claude-pty/auth.ts +32 -0
- package/src/server/claude-pty/claude-session-registry.adapter.ts +81 -0
- package/src/server/claude-pty/claude-session-registry.test.ts +149 -0
- package/src/server/claude-pty/driver.test.ts +902 -0
- package/src/server/claude-pty/driver.ts +807 -0
- package/src/server/claude-pty/jsonl-path.adapter.ts +57 -0
- package/src/server/claude-pty/jsonl-path.test.ts +114 -0
- package/src/server/claude-pty/jsonl-to-event.test.ts +241 -0
- package/src/server/claude-pty/jsonl-to-event.ts +174 -0
- package/src/server/claude-pty/output-ring.test.ts +35 -0
- package/src/server/claude-pty/output-ring.ts +25 -0
- package/src/server/claude-pty/parity-matrix.test.ts +227 -0
- package/src/server/claude-pty/pid-registry.adapter.ts +135 -0
- package/src/server/claude-pty/pid-registry.test.ts +122 -0
- package/src/server/claude-pty/preflight/binary-fingerprint.adapter.ts +20 -0
- package/src/server/claude-pty/preflight/binary-fingerprint.test.ts +32 -0
- package/src/server/claude-pty/pty-instance-registry.test.ts +177 -0
- package/src/server/claude-pty/pty-instance-registry.ts +166 -0
- package/src/server/claude-pty/pty-memory-sampler.adapter.test.ts +103 -0
- package/src/server/claude-pty/pty-memory-sampler.adapter.ts +85 -0
- package/src/server/claude-pty/pty-process.adapter.ts +66 -0
- package/src/server/claude-pty/pty-process.test.ts +49 -0
- package/src/server/claude-pty/resolve-binary.adapter.ts +106 -0
- package/src/server/claude-pty/resolve-binary.test.ts +118 -0
- package/src/server/claude-pty/runtime-dir.adapter.ts +19 -0
- package/src/server/claude-pty/settings-writer.adapter.ts +27 -0
- package/src/server/claude-pty/settings-writer.test.ts +22 -0
- package/src/server/claude-pty/smoke-test-io.adapter.ts +28 -0
- package/src/server/claude-pty/smoke-test.test.ts +191 -0
- package/src/server/claude-pty/smoke-test.ts +185 -0
- package/src/server/claude-pty/subagent-orchestrator.ts +887 -0
- package/src/server/claude-pty/tool-callback.ts +274 -0
- package/src/server/claude-pty/tui-control.test.ts +272 -0
- package/src/server/claude-pty/tui-control.ts +182 -0
- package/src/server/claude-pty/tui-source.adapter.test.ts +360 -0
- package/src/server/claude-pty/tui-source.adapter.ts +343 -0
- package/src/server/claude-pty/tunnel-gateway.ts +12 -0
- package/src/server/claude-pty-mcp/canonical-args.ts +15 -0
- package/src/server/claude-pty-mcp/fs-stat.adapter.ts +8 -0
- package/src/server/claude-pty-mcp/history-primer.ts +90 -0
- package/src/server/claude-pty-mcp/http-server.adapter.ts +33 -0
- package/src/server/claude-pty-mcp/mcp-http.ts +177 -0
- package/src/server/claude-pty-mcp/mcp.ts +412 -0
- package/src/server/claude-pty-mcp/mention-parser.ts +25 -0
- package/src/server/claude-pty-mcp/paths.ts +24 -0
- package/src/server/claude-pty-mcp/permission-gate.ts +243 -0
- package/src/server/claude-pty-mcp/terminal-pid-registry.adapter.ts +107 -0
- package/src/server/claude-pty-mcp/tools/ask-user-question.test.ts +119 -0
- package/src/server/claude-pty-mcp/tools/ask-user-question.ts +61 -0
- package/src/server/claude-pty-mcp/tools/bash.adapter.ts +76 -0
- package/src/server/claude-pty-mcp/tools/bash.test.ts +56 -0
- package/src/server/claude-pty-mcp/tools/delegate-subagent.test.ts +155 -0
- package/src/server/claude-pty-mcp/tools/delegate-subagent.ts +111 -0
- package/src/server/claude-pty-mcp/tools/edit.adapter.ts +95 -0
- package/src/server/claude-pty-mcp/tools/edit.test.ts +93 -0
- package/src/server/claude-pty-mcp/tools/exit-plan-mode.test.ts +61 -0
- package/src/server/claude-pty-mcp/tools/exit-plan-mode.ts +50 -0
- package/src/server/claude-pty-mcp/tools/glob.adapter.ts +86 -0
- package/src/server/claude-pty-mcp/tools/glob.test.ts +61 -0
- package/src/server/claude-pty-mcp/tools/grep.adapter.ts +126 -0
- package/src/server/claude-pty-mcp/tools/grep.test.ts +62 -0
- package/src/server/claude-pty-mcp/tools/read.adapter.ts +58 -0
- package/src/server/claude-pty-mcp/tools/read.test.ts +62 -0
- package/src/server/claude-pty-mcp/tools/tool-callback-shim.ts +42 -0
- package/src/server/claude-pty-mcp/tools/webfetch.test.ts +81 -0
- package/src/server/claude-pty-mcp/tools/webfetch.ts +82 -0
- package/src/server/claude-pty-mcp/tools/websearch.test.ts +40 -0
- package/src/server/claude-pty-mcp/tools/websearch.ts +42 -0
- package/src/server/claude-pty-mcp/tools/write.adapter.ts +60 -0
- package/src/server/claude-pty-mcp/tools/write.test.ts +52 -0
- package/src/server/claude-pty-mcp/uploads.adapter.ts +98 -0
- package/src/server/claude-pty-mcp/uploads.ts +38 -0
- package/src/server/claude-turn.test.ts +176 -0
- package/src/server/cli-runtime.test.ts +456 -0
- package/src/server/cli-runtime.ts +374 -0
- package/src/server/cli-supervisor.ts +81 -0
- package/src/server/cli.ts +78 -0
- package/src/server/client-log-forwarder.test.ts +74 -0
- package/src/server/client-log-forwarder.ts +75 -0
- package/src/server/codex-app-server-protocol.ts +449 -0
- package/src/server/codex-app-server.test.ts +2990 -0
- package/src/server/codex-app-server.ts +1713 -0
- package/src/server/coordination-integration.test.ts +63 -0
- package/src/server/coordination-mcp.test.ts +149 -0
- package/src/server/coordination-mcp.ts +197 -0
- package/src/server/delegation-coordinator.test.ts +675 -0
- package/src/server/delegation-coordinator.ts +454 -0
- package/src/server/discovery.test.ts +211 -0
- package/src/server/discovery.ts +301 -0
- package/src/server/event-store-agent-config.test.ts +124 -0
- package/src/server/event-store-coordination.test.ts +149 -0
- package/src/server/event-store-profile.test.ts +132 -0
- package/src/server/event-store-repo.test.ts +154 -0
- package/src/server/event-store-runner-team.test.ts +104 -0
- package/src/server/event-store.test.ts +342 -0
- package/src/server/event-store.ts +2208 -0
- package/src/server/events.ts +379 -0
- package/src/server/extension-router.test.ts +183 -0
- package/src/server/extension-router.ts +114 -0
- package/src/server/extensions/agents/server.test.ts +191 -0
- package/src/server/extensions/agents/server.ts +108 -0
- package/src/server/extensions/c3/server.test.ts +284 -0
- package/src/server/extensions/c3/server.ts +212 -0
- package/src/server/extensions/code/server.test.ts +200 -0
- package/src/server/extensions/code/server.ts +150 -0
- package/src/server/extensions.config.ts +10 -0
- package/src/server/external-open.ts +69 -0
- package/src/server/generate-fork-context.ts +58 -0
- package/src/server/generate-merge-context.test.ts +290 -0
- package/src/server/generate-merge-context.ts +141 -0
- package/src/server/generate-title.ts +36 -0
- package/src/server/git-clone-policy.test.ts +138 -0
- package/src/server/git-clone-policy.ts +27 -0
- package/src/server/harness-types.ts +1 -0
- package/src/server/journey-verification.test.ts +640 -0
- package/src/server/journey-verification.ts +195 -0
- package/src/server/machine-name.ts +22 -0
- package/src/server/nats-auth.test.ts +92 -0
- package/src/server/nats-auth.ts +6 -0
- package/src/server/nats-bind-guard.test.ts +71 -0
- package/src/server/nats-bind-guard.ts +42 -0
- package/src/server/nats-bridge.test.ts +141 -0
- package/src/server/nats-bridge.ts +111 -0
- package/src/server/nats-connector.test.ts +56 -0
- package/src/server/nats-connector.ts +71 -0
- package/src/server/nats-daemon-manager.test.ts +76 -0
- package/src/server/nats-daemon-manager.ts +174 -0
- package/src/server/nats-publisher.test.ts +356 -0
- package/src/server/nats-publisher.ts +271 -0
- package/src/server/nats-responders.test.ts +1018 -0
- package/src/server/nats-responders.ts +925 -0
- package/src/server/nats-streams.test.ts +112 -0
- package/src/server/nats-streams.ts +129 -0
- package/src/server/oauth-pool/oauth-responders.ts +185 -0
- package/src/server/oauth-pool/oauth-settings-store.ts +173 -0
- package/src/server/oauth-pool/oauth-token-pool.ts +303 -0
- package/src/server/orchestration.test.ts +1063 -0
- package/src/server/orchestration.ts +716 -0
- package/src/server/pairing-endpoints.test.ts +171 -0
- package/src/server/pairing-store.test.ts +154 -0
- package/src/server/pairing-store.ts +124 -0
- package/src/server/paths.ts +27 -0
- package/src/server/pr3-liveness.test.ts +252 -0
- package/src/server/process-utils.ts +10 -0
- package/src/server/project-cli.ts +180 -0
- package/src/server/provider-catalog.test.ts +177 -0
- package/src/server/provider-catalog.ts +146 -0
- package/src/server/pty-responders.ts +345 -0
- package/src/server/push-notifications.test.ts +234 -0
- package/src/server/push-notifications.ts +188 -0
- package/src/server/quick-response.test.ts +196 -0
- package/src/server/quick-response.ts +154 -0
- package/src/server/read-models-agent-config.test.ts +59 -0
- package/src/server/read-models-coordination.test.ts +69 -0
- package/src/server/read-models.test.ts +332 -0
- package/src/server/read-models.ts +258 -0
- package/src/server/repo-journey.test.ts +96 -0
- package/src/server/repo-manager.test.ts +118 -0
- package/src/server/repo-manager.ts +97 -0
- package/src/server/repo-status.test.ts +54 -0
- package/src/server/repo-status.ts +82 -0
- package/src/server/restart.test.ts +27 -0
- package/src/server/restart.ts +30 -0
- package/src/server/runner-incompatible-gate.test.ts +383 -0
- package/src/server/runner-manager.test.ts +72 -0
- package/src/server/runner-manager.ts +312 -0
- package/src/server/runner-pairing-urls.test.ts +59 -0
- package/src/server/runner-pairing-urls.ts +67 -0
- package/src/server/runner-proxy.test.ts +456 -0
- package/src/server/runner-proxy.ts +494 -0
- package/src/server/runner-router.test.ts +429 -0
- package/src/server/runner-router.ts +212 -0
- package/src/server/runner-routing.test.ts +584 -0
- package/src/server/runtime-registry.test.ts +436 -0
- package/src/server/runtime-registry.ts +307 -0
- package/src/server/sandbox-health.test.ts +127 -0
- package/src/server/sandbox-health.ts +94 -0
- package/src/server/sandbox-journey.test.ts +232 -0
- package/src/server/sandbox-manager.test.ts +146 -0
- package/src/server/sandbox-manager.ts +159 -0
- package/src/server/server.test.ts +287 -0
- package/src/server/server.ts +1108 -0
- package/src/server/session-discovery.test.ts +129 -0
- package/src/server/session-discovery.ts +475 -0
- package/src/server/session-index.test.ts +362 -0
- package/src/server/session-index.ts +119 -0
- package/src/server/session-seed.ts +288 -0
- package/src/server/share.test.ts +108 -0
- package/src/server/share.ts +113 -0
- package/src/server/skill-discovery.test.ts +201 -0
- package/src/server/skill-discovery.ts +77 -0
- package/src/server/storage/test-helpers.ts +67 -0
- package/src/server/terminal-manager.test.ts +309 -0
- package/src/server/terminal-manager.ts +354 -0
- package/src/server/transcript-consumer.test.ts +339 -0
- package/src/server/transcript-consumer.ts +162 -0
- package/src/server/transcript-search.test.ts +193 -0
- package/src/server/transcript-search.ts +83 -0
- package/src/server/transcript-utils.ts +52 -0
- package/src/server/update-manager.test.ts +107 -0
- package/src/server/update-manager.ts +230 -0
- package/src/server/workflow-engine.test.ts +251 -0
- package/src/server/workflow-engine.ts +169 -0
- package/src/server/workflow-mcp.ts +49 -0
- package/src/server/workflow-store.test.ts +155 -0
- package/src/server/workflow-store.ts +139 -0
- package/src/server/workspace-agent-integration.test.ts +167 -0
- package/src/server/workspace-agent-routes.test.ts +127 -0
- package/src/server/workspace-agent-routes.ts +89 -0
- package/src/server/workspace-agent.test.ts +103 -0
- package/src/server/workspace-agent.ts +102 -0
- package/src/server/workspace-cli.test.ts +79 -0
- package/src/server/workspace-config-manager.test.ts +109 -0
- package/src/server/workspace-config-manager.ts +83 -0
- package/src/server/workspace-directory-policy.test.ts +109 -0
- package/src/server/workspace-directory-policy.ts +56 -0
- package/src/shared/agent-config-types.ts +25 -0
- package/src/shared/branding.test.ts +42 -0
- package/src/shared/branding.ts +54 -0
- package/src/shared/compression.test.ts +85 -0
- package/src/shared/compression.ts +42 -0
- package/src/shared/coordination-store.test.ts +24 -0
- package/src/shared/coordination-store.ts +26 -0
- package/src/shared/dev-ports.test.ts +84 -0
- package/src/shared/dev-ports.ts +100 -0
- package/src/shared/extension-types.ts +45 -0
- package/src/shared/fork-presets.ts +54 -0
- package/src/shared/harness-types.test.ts +15 -0
- package/src/shared/harness-types.ts +21 -0
- package/src/shared/log-sink.test.ts +112 -0
- package/src/shared/log-sink.ts +185 -0
- package/src/shared/mention-pattern.ts +7 -0
- package/src/shared/merge-presets.ts +41 -0
- package/src/shared/nats-subjects.test.ts +61 -0
- package/src/shared/nats-subjects.ts +131 -0
- package/src/shared/permission-policy.ts +136 -0
- package/src/shared/ports.ts +3 -0
- package/src/shared/preset-types.ts +15 -0
- package/src/shared/profile-types.ts +49 -0
- package/src/shared/projectFileUrl.ts +36 -0
- package/src/shared/protocol.ts +153 -0
- package/src/shared/pty-instance.ts +43 -0
- package/src/shared/puggy/diagnostics/result.ts +18 -0
- package/src/shared/puggy/expressions/evaluate.ts +292 -0
- package/src/shared/puggy/index.test.ts +101 -0
- package/src/shared/puggy/index.ts +69 -0
- package/src/shared/puggy/parser/ast.ts +110 -0
- package/src/shared/puggy/parser/parser.ts +624 -0
- package/src/shared/puggy/renderer/html.ts +447 -0
- package/src/shared/runner-protocol.test.ts +277 -0
- package/src/shared/runner-protocol.ts +210 -0
- package/src/shared/runner-team-types.ts +73 -0
- package/src/shared/runtime-types.ts +48 -0
- package/src/shared/sandbox-types.ts +53 -0
- package/src/shared/tailwind-build.test.ts +12 -0
- package/src/shared/tinkaria-system-prompt.ts +101 -0
- package/src/shared/tools.test.ts +335 -0
- package/src/shared/tools.ts +397 -0
- package/src/shared/transcript-entries.ts +27 -0
- package/src/shared/transcript-render.test.ts +225 -0
- package/src/shared/transcript-render.ts +467 -0
- package/src/shared/types.ts +1031 -0
- package/src/shared/vite-config.test.ts +47 -0
- package/src/shared/web-context.test.ts +110 -0
- package/src/shared/web-context.ts +76 -0
- package/src/shared/workflow-types.ts +51 -0
- 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
|
+
}
|