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