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,106 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { promisify } from "node:util"
|
|
4
|
+
import { execFile } from "node:child_process"
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile)
|
|
7
|
+
|
|
8
|
+
export interface ResolveClaudeBinaryArgs {
|
|
9
|
+
env: NodeJS.ProcessEnv
|
|
10
|
+
homeDir: string
|
|
11
|
+
cwd?: string
|
|
12
|
+
platform?: NodeJS.Platform
|
|
13
|
+
arch?: NodeJS.Architecture
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ResolveClaudeBinaryResult {
|
|
17
|
+
path: string
|
|
18
|
+
source: "env-CLAUDE_EXECUTABLE" | "env-CLAUDE_CODE_EXECPATH" | "PATH" | "node_modules"
|
|
19
|
+
triedPaths: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function expandTilde(p: string, home: string): string {
|
|
23
|
+
return p.replace(/^~(?=\/|$)/, home)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function isExecutableFile(p: string): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
const s = await stat(p)
|
|
29
|
+
return s.isFile()
|
|
30
|
+
} catch {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function whichClaude(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): Promise<string | null> {
|
|
36
|
+
const cmd = platform === "win32" ? "where" : "which"
|
|
37
|
+
try {
|
|
38
|
+
const { stdout } = await execFileAsync(cmd, ["claude"], { env, timeout: 2000 })
|
|
39
|
+
const first = stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0)
|
|
40
|
+
if (!first) return null
|
|
41
|
+
if (!(await isExecutableFile(first))) return null
|
|
42
|
+
return first
|
|
43
|
+
} catch {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildNodeModulesCandidates(cwd: string, platform: NodeJS.Platform, arch: NodeJS.Architecture): string[] {
|
|
49
|
+
const tag = `${platform}-${arch}`
|
|
50
|
+
const pkgDir = `@anthropic-ai/claude-agent-sdk-${tag}`
|
|
51
|
+
const candidates: string[] = []
|
|
52
|
+
let dir = path.resolve(cwd)
|
|
53
|
+
while (true) {
|
|
54
|
+
candidates.push(path.join(dir, "node_modules", pkgDir, "claude"))
|
|
55
|
+
const parent = path.dirname(dir)
|
|
56
|
+
if (parent === dir) break
|
|
57
|
+
dir = parent
|
|
58
|
+
}
|
|
59
|
+
return candidates
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function resolveClaudeBinary(args: ResolveClaudeBinaryArgs): Promise<ResolveClaudeBinaryResult> {
|
|
63
|
+
const env = args.env
|
|
64
|
+
const home = args.homeDir
|
|
65
|
+
const cwd = args.cwd ?? process.cwd()
|
|
66
|
+
const platform = args.platform ?? process.platform
|
|
67
|
+
const arch = args.arch ?? process.arch
|
|
68
|
+
const tried: string[] = []
|
|
69
|
+
|
|
70
|
+
if (env.CLAUDE_EXECUTABLE) {
|
|
71
|
+
const candidate = expandTilde(env.CLAUDE_EXECUTABLE, home)
|
|
72
|
+
tried.push(`CLAUDE_EXECUTABLE=${candidate}`)
|
|
73
|
+
if (await isExecutableFile(candidate)) {
|
|
74
|
+
return { path: candidate, source: "env-CLAUDE_EXECUTABLE", triedPaths: tried }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (env.CLAUDE_CODE_EXECPATH) {
|
|
79
|
+
const candidate = expandTilde(env.CLAUDE_CODE_EXECPATH, home)
|
|
80
|
+
tried.push(`CLAUDE_CODE_EXECPATH=${candidate}`)
|
|
81
|
+
if (await isExecutableFile(candidate)) {
|
|
82
|
+
return { path: candidate, source: "env-CLAUDE_CODE_EXECPATH", triedPaths: tried }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const fromPath = await whichClaude(env, platform)
|
|
87
|
+
tried.push(`PATH lookup: ${fromPath ?? "<not found>"}`)
|
|
88
|
+
if (fromPath) {
|
|
89
|
+
return { path: fromPath, source: "PATH", triedPaths: tried }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const candidates = buildNodeModulesCandidates(cwd, platform, arch)
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
tried.push(candidate)
|
|
95
|
+
if (await isExecutableFile(candidate)) {
|
|
96
|
+
return { path: candidate, source: "node_modules", triedPaths: tried }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Unable to locate the \`claude\` CLI binary. Set CLAUDE_EXECUTABLE to an absolute path, `
|
|
102
|
+
+ `install \`@anthropic-ai/claude-code\` globally so \`claude\` is on PATH, or install `
|
|
103
|
+
+ `\`@anthropic-ai/claude-agent-sdk\` so the platform-bundled binary exists in node_modules. `
|
|
104
|
+
+ `Tried:\n - ${tried.join("\n - ")}`,
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { resolveClaudeBinary } from "./resolve-binary.adapter"
|
|
6
|
+
|
|
7
|
+
describe("resolveClaudeBinary", () => {
|
|
8
|
+
let workDir: string
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
workDir = await mkdtemp(path.join(tmpdir(), "kanna-resolve-binary-"))
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await rm(workDir, { recursive: true, force: true })
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
async function makeExec(p: string): Promise<void> {
|
|
19
|
+
await mkdir(path.dirname(p), { recursive: true })
|
|
20
|
+
await writeFile(p, "#!/bin/sh\necho fake\n", { encoding: "utf8" })
|
|
21
|
+
await chmod(p, 0o755)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test("returns CLAUDE_EXECUTABLE when set and exists", async () => {
|
|
25
|
+
const bin = path.join(workDir, "fake-claude")
|
|
26
|
+
await makeExec(bin)
|
|
27
|
+
const result = await resolveClaudeBinary({
|
|
28
|
+
env: { CLAUDE_EXECUTABLE: bin, PATH: "" },
|
|
29
|
+
homeDir: workDir,
|
|
30
|
+
cwd: workDir,
|
|
31
|
+
platform: "darwin",
|
|
32
|
+
arch: "arm64",
|
|
33
|
+
})
|
|
34
|
+
expect(result.source).toBe("env-CLAUDE_EXECUTABLE")
|
|
35
|
+
expect(result.path).toBe(bin)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test("expands tilde in CLAUDE_EXECUTABLE", async () => {
|
|
39
|
+
const bin = path.join(workDir, "tilde-claude")
|
|
40
|
+
await makeExec(bin)
|
|
41
|
+
const result = await resolveClaudeBinary({
|
|
42
|
+
env: { CLAUDE_EXECUTABLE: "~/tilde-claude", PATH: "" },
|
|
43
|
+
homeDir: workDir,
|
|
44
|
+
cwd: workDir,
|
|
45
|
+
platform: "darwin",
|
|
46
|
+
arch: "arm64",
|
|
47
|
+
})
|
|
48
|
+
expect(result.path).toBe(bin)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("falls back to CLAUDE_CODE_EXECPATH when CLAUDE_EXECUTABLE missing file", async () => {
|
|
52
|
+
const bin = path.join(workDir, "execpath-claude")
|
|
53
|
+
await makeExec(bin)
|
|
54
|
+
const result = await resolveClaudeBinary({
|
|
55
|
+
env: {
|
|
56
|
+
CLAUDE_EXECUTABLE: path.join(workDir, "does-not-exist"),
|
|
57
|
+
CLAUDE_CODE_EXECPATH: bin,
|
|
58
|
+
PATH: "",
|
|
59
|
+
},
|
|
60
|
+
homeDir: workDir,
|
|
61
|
+
cwd: workDir,
|
|
62
|
+
platform: "darwin",
|
|
63
|
+
arch: "arm64",
|
|
64
|
+
})
|
|
65
|
+
expect(result.source).toBe("env-CLAUDE_CODE_EXECPATH")
|
|
66
|
+
expect(result.path).toBe(bin)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("falls back to node_modules platform-bundled binary", async () => {
|
|
70
|
+
const inner = path.join(workDir, "nested", "deep")
|
|
71
|
+
await mkdir(inner, { recursive: true })
|
|
72
|
+
const bundled = path.join(workDir, "node_modules", "@anthropic-ai", "claude-agent-sdk-darwin-arm64", "claude")
|
|
73
|
+
await makeExec(bundled)
|
|
74
|
+
const result = await resolveClaudeBinary({
|
|
75
|
+
env: { PATH: "" },
|
|
76
|
+
homeDir: workDir,
|
|
77
|
+
cwd: inner,
|
|
78
|
+
platform: "darwin",
|
|
79
|
+
arch: "arm64",
|
|
80
|
+
})
|
|
81
|
+
expect(result.source).toBe("node_modules")
|
|
82
|
+
expect(result.path).toBe(bundled)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("walks parent dirs when looking up node_modules bundled binary", async () => {
|
|
86
|
+
const inner = path.join(workDir, "a", "b", "c")
|
|
87
|
+
await mkdir(inner, { recursive: true })
|
|
88
|
+
const bundled = path.join(workDir, "node_modules", "@anthropic-ai", "claude-agent-sdk-linux-x64", "claude")
|
|
89
|
+
await makeExec(bundled)
|
|
90
|
+
const result = await resolveClaudeBinary({
|
|
91
|
+
env: { PATH: "" },
|
|
92
|
+
homeDir: workDir,
|
|
93
|
+
cwd: inner,
|
|
94
|
+
platform: "linux",
|
|
95
|
+
arch: "x64",
|
|
96
|
+
})
|
|
97
|
+
expect(result.path).toBe(bundled)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("throws with all tried paths when nothing resolves", async () => {
|
|
101
|
+
let err: unknown
|
|
102
|
+
try {
|
|
103
|
+
await resolveClaudeBinary({
|
|
104
|
+
env: { CLAUDE_EXECUTABLE: path.join(workDir, "missing"), PATH: "" },
|
|
105
|
+
homeDir: workDir,
|
|
106
|
+
cwd: workDir,
|
|
107
|
+
platform: "darwin",
|
|
108
|
+
arch: "arm64",
|
|
109
|
+
})
|
|
110
|
+
} catch (caught) {
|
|
111
|
+
err = caught
|
|
112
|
+
}
|
|
113
|
+
expect(err).toBeInstanceOf(Error)
|
|
114
|
+
expect((err as Error).message).toContain("Unable to locate")
|
|
115
|
+
expect((err as Error).message).toContain("CLAUDE_EXECUTABLE")
|
|
116
|
+
expect((err as Error).message).toContain("node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64/claude")
|
|
117
|
+
}, 10_000)
|
|
118
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
|
|
5
|
+
export async function createRuntimeDir(prefix: string): Promise<string> {
|
|
6
|
+
return await mkdtemp(path.join(tmpdir(), prefix))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function writeRuntimeFile(
|
|
10
|
+
filePath: string,
|
|
11
|
+
contents: string,
|
|
12
|
+
options?: { encoding?: BufferEncoding; mode?: number },
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
await writeFile(filePath, contents, options)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function removeRuntimeDir(dir: string): Promise<void> {
|
|
18
|
+
await rm(dir, { recursive: true, force: true })
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
export interface WriteSpawnSettingsResult {
|
|
5
|
+
settingsPath: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function writeSpawnSettings(args: {
|
|
9
|
+
runtimeDir: string
|
|
10
|
+
}): Promise<WriteSpawnSettingsResult> {
|
|
11
|
+
await mkdir(args.runtimeDir, { recursive: true, mode: 0o700 })
|
|
12
|
+
const settingsPath = path.join(args.runtimeDir, "settings.local.json")
|
|
13
|
+
const body = {
|
|
14
|
+
spinnerTipsEnabled: false,
|
|
15
|
+
showTurnDuration: false,
|
|
16
|
+
syntaxHighlightingDisabled: true,
|
|
17
|
+
// Auto-allow every mcp__kanna__* tool at the claude CLI permission gate.
|
|
18
|
+
// Approval still flows through kanna's toolCallback (durable + auditable)
|
|
19
|
+
// when KANNA_MCP_TOOL_CALLBACKS=1; this just stops the CLI from blocking
|
|
20
|
+
// tool_call before our MCP server sees the request.
|
|
21
|
+
permissions: {
|
|
22
|
+
allow: ["mcp__kanna__*"],
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
await writeFile(settingsPath, JSON.stringify(body, null, 2), { encoding: "utf8", mode: 0o600 })
|
|
26
|
+
return { settingsPath }
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtemp, rm, readFile } from "node:fs/promises"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { writeSpawnSettings } from "./settings-writer.adapter"
|
|
6
|
+
|
|
7
|
+
describe("writeSpawnSettings", () => {
|
|
8
|
+
test("writes per-spawn settings with claimed keys", async () => {
|
|
9
|
+
const dir = await mkdtemp(path.join(tmpdir(), "kanna-settings-"))
|
|
10
|
+
try {
|
|
11
|
+
const result = await writeSpawnSettings({ runtimeDir: dir })
|
|
12
|
+
expect(result.settingsPath.startsWith(dir)).toBe(true)
|
|
13
|
+
const raw = await readFile(result.settingsPath, "utf8")
|
|
14
|
+
const parsed = JSON.parse(raw)
|
|
15
|
+
expect(parsed.spinnerTipsEnabled).toBe(false)
|
|
16
|
+
expect(parsed.showTurnDuration).toBe(false)
|
|
17
|
+
expect(parsed.permissions?.allow).toContain("mcp__kanna__*")
|
|
18
|
+
} finally {
|
|
19
|
+
await rm(dir, { recursive: true, force: true })
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, writeFile, rm } from "node:fs/promises"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
|
|
6
|
+
export function makeTempCwd(prefix: string): Promise<string> {
|
|
7
|
+
return mkdtemp(path.join(tmpdir(), prefix))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function readTextFile(p: string): Promise<string> {
|
|
11
|
+
return readFile(p, "utf8")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function rmDirRecursive(p: string): Promise<void> {
|
|
15
|
+
await rm(p, { recursive: true, force: true })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function fileExists(p: string): boolean {
|
|
19
|
+
return existsSync(p)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function mkdirRecursive(p: string): Promise<void> {
|
|
23
|
+
await mkdir(p, { recursive: true })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function writeFile0600(p: string, contents: string): Promise<void> {
|
|
27
|
+
await writeFile(p, contents, { encoding: "utf8", mode: 0o600 })
|
|
28
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } 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 { createSmokeTestGate, createFileSmokeTestCache, type SmokeTestProbeFn, type SmokeTestCache } from "./smoke-test"
|
|
6
|
+
|
|
7
|
+
let workHome: string
|
|
8
|
+
|
|
9
|
+
function inMemoryCache(): SmokeTestCache {
|
|
10
|
+
const store = new Map<string, { result: "pass" | "fail"; ts: number }>()
|
|
11
|
+
return {
|
|
12
|
+
async get(key) { return store.get(key) ?? null },
|
|
13
|
+
async set(key, entry) { store.set(key, entry) },
|
|
14
|
+
async invalidate() { store.clear() },
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
workHome = await mkdtemp(path.join(tmpdir(), "kanna-smoke-"))
|
|
20
|
+
await writeFile(path.join(workHome, "fake-claude"), "#!/bin/sh\necho fake\n", { mode: 0o755 })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await rm(workHome, { recursive: true, force: true })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe("createSmokeTestGate", () => {
|
|
28
|
+
test("cached PASS skips probe", async () => {
|
|
29
|
+
let probeRan = false
|
|
30
|
+
const probe: SmokeTestProbeFn = async () => { probeRan = true; return "pass" }
|
|
31
|
+
const cache = inMemoryCache()
|
|
32
|
+
await cache.set("aaa|claude-opus-4-7", { result: "pass", ts: Date.now() })
|
|
33
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
34
|
+
const result = await gate.canSpawn({ binarySha256: "aaa", model: "claude-opus-4-7" })
|
|
35
|
+
expect(result.ok).toBe(true)
|
|
36
|
+
expect(probeRan).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test("cached FAIL refuses spawn without running probe", async () => {
|
|
40
|
+
let probeRan = false
|
|
41
|
+
const probe: SmokeTestProbeFn = async () => { probeRan = true; return "pass" }
|
|
42
|
+
const cache = inMemoryCache()
|
|
43
|
+
await cache.set("bbb|claude-opus-4-7", { result: "fail", ts: Date.now() })
|
|
44
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
45
|
+
const result = await gate.canSpawn({ binarySha256: "bbb", model: "claude-opus-4-7" })
|
|
46
|
+
expect(result.ok).toBe(false)
|
|
47
|
+
if (!result.ok) expect(result.reason).toMatch(/disallowedTools/i)
|
|
48
|
+
expect(probeRan).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("cache miss runs probe and caches PASS", async () => {
|
|
52
|
+
let probeRan = false
|
|
53
|
+
const probe: SmokeTestProbeFn = async () => { probeRan = true; return "pass" }
|
|
54
|
+
const cache = inMemoryCache()
|
|
55
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
56
|
+
const result = await gate.canSpawn({ binarySha256: "ccc", model: "m1" })
|
|
57
|
+
expect(result.ok).toBe(true)
|
|
58
|
+
expect(probeRan).toBe(true)
|
|
59
|
+
const cached = await cache.get("ccc|m1")
|
|
60
|
+
expect(cached?.result).toBe("pass")
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("cache miss runs probe and refuses spawn on FAIL", async () => {
|
|
64
|
+
const probe: SmokeTestProbeFn = async () => "fail"
|
|
65
|
+
const cache = inMemoryCache()
|
|
66
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
67
|
+
const result = await gate.canSpawn({ binarySha256: "ddd", model: "m1" })
|
|
68
|
+
expect(result.ok).toBe(false)
|
|
69
|
+
const cached = await cache.get("ddd|m1")
|
|
70
|
+
expect(cached?.result).toBe("fail")
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("probe error is re-thrown and NOT cached (no 24h poisoning)", async () => {
|
|
74
|
+
let probeRuns = 0
|
|
75
|
+
const probe: SmokeTestProbeFn = async () => {
|
|
76
|
+
probeRuns += 1
|
|
77
|
+
throw new Error("TUI ready timeout")
|
|
78
|
+
}
|
|
79
|
+
const cache = inMemoryCache()
|
|
80
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
81
|
+
|
|
82
|
+
await expect(gate.canSpawn({ binarySha256: "transient", model: "m1" })).rejects.toThrow("TUI ready timeout")
|
|
83
|
+
// A transient probe error must leave the cache untouched so the next spawn
|
|
84
|
+
// re-probes instead of being refused for the full TTL.
|
|
85
|
+
expect(await cache.get("transient|m1")).toBeNull()
|
|
86
|
+
await expect(gate.canSpawn({ binarySha256: "transient", model: "m1" })).rejects.toThrow("TUI ready timeout")
|
|
87
|
+
expect(probeRuns).toBe(2)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("expired cache entry triggers re-probe", async () => {
|
|
91
|
+
let probeRan = 0
|
|
92
|
+
const probe: SmokeTestProbeFn = async () => { probeRan++; return "pass" }
|
|
93
|
+
const cache = inMemoryCache()
|
|
94
|
+
let nowMs = 1_000_000
|
|
95
|
+
await cache.set("eee|m1", { result: "pass", ts: nowMs })
|
|
96
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 1000, now: () => nowMs })
|
|
97
|
+
await gate.canSpawn({ binarySha256: "eee", model: "m1" })
|
|
98
|
+
expect(probeRan).toBe(0)
|
|
99
|
+
nowMs += 2000
|
|
100
|
+
await gate.canSpawn({ binarySha256: "eee", model: "m1" })
|
|
101
|
+
expect(probeRan).toBe(1)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe("createSmokeTestGate singleflight (adr-20260522-oauth-token-share-cap)", () => {
|
|
106
|
+
test("concurrent canSpawn calls on same (sha,model) collapse to one probe", async () => {
|
|
107
|
+
let probeStartCount = 0
|
|
108
|
+
const resolvers: Array<(r: "pass" | "fail") => void> = []
|
|
109
|
+
const probe: SmokeTestProbeFn = () => {
|
|
110
|
+
probeStartCount += 1
|
|
111
|
+
return new Promise<"pass" | "fail">((r) => { resolvers.push(r) })
|
|
112
|
+
}
|
|
113
|
+
const cache = inMemoryCache()
|
|
114
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
115
|
+
|
|
116
|
+
const results = [
|
|
117
|
+
gate.canSpawn({ binarySha256: "fff", model: "m1" }),
|
|
118
|
+
gate.canSpawn({ binarySha256: "fff", model: "m1" }),
|
|
119
|
+
gate.canSpawn({ binarySha256: "fff", model: "m1" }),
|
|
120
|
+
gate.canSpawn({ binarySha256: "fff", model: "m1" }),
|
|
121
|
+
gate.canSpawn({ binarySha256: "fff", model: "m1" }),
|
|
122
|
+
]
|
|
123
|
+
// Give the event loop one tick so each promise registers with inFlight.
|
|
124
|
+
await Promise.resolve()
|
|
125
|
+
expect(probeStartCount).toBe(1)
|
|
126
|
+
expect(resolvers).toHaveLength(1)
|
|
127
|
+
resolvers[0]("pass")
|
|
128
|
+
const resolved = await Promise.all(results)
|
|
129
|
+
for (const r of resolved) expect(r.ok).toBe(true)
|
|
130
|
+
expect(probeStartCount).toBe(1)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("after a probe resolves, future cache-miss callers run a fresh probe", async () => {
|
|
134
|
+
let probeStartCount = 0
|
|
135
|
+
const probe: SmokeTestProbeFn = async () => { probeStartCount += 1; return "pass" }
|
|
136
|
+
const cache: SmokeTestCache = {
|
|
137
|
+
// Read-only cache: every get returns null so the gate must probe each time.
|
|
138
|
+
async get() { return null },
|
|
139
|
+
async set() { /* discard */ },
|
|
140
|
+
async invalidate() { /* noop */ },
|
|
141
|
+
}
|
|
142
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
143
|
+
await gate.canSpawn({ binarySha256: "ggg", model: "m1" })
|
|
144
|
+
await gate.canSpawn({ binarySha256: "ggg", model: "m1" })
|
|
145
|
+
// Two sequential cache-miss calls → two probes (singleflight only collapses concurrent ones).
|
|
146
|
+
expect(probeStartCount).toBe(2)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test("singleflight is keyed by (sha,model) — different keys probe independently", async () => {
|
|
150
|
+
let probeStartCount = 0
|
|
151
|
+
const resolvers: Array<(r: "pass" | "fail") => void> = []
|
|
152
|
+
const probe: SmokeTestProbeFn = () => {
|
|
153
|
+
probeStartCount += 1
|
|
154
|
+
return new Promise<"pass" | "fail">((r) => { resolvers.push(r) })
|
|
155
|
+
}
|
|
156
|
+
const cache = inMemoryCache()
|
|
157
|
+
const gate = createSmokeTestGate({ probe, cache, ttlMs: 24 * 3600 * 1000, now: () => Date.now() })
|
|
158
|
+
const a = gate.canSpawn({ binarySha256: "hhh", model: "m1" })
|
|
159
|
+
const b = gate.canSpawn({ binarySha256: "hhh", model: "m2" })
|
|
160
|
+
await Promise.resolve()
|
|
161
|
+
expect(probeStartCount).toBe(2)
|
|
162
|
+
expect(resolvers).toHaveLength(2)
|
|
163
|
+
resolvers[0]("pass")
|
|
164
|
+
resolvers[1]("pass")
|
|
165
|
+
await Promise.all([a, b])
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe("createFileSmokeTestCache", () => {
|
|
170
|
+
test("round-trips an entry through disk", async () => {
|
|
171
|
+
const dir = path.join(workHome, "smoke-cache")
|
|
172
|
+
const cache = createFileSmokeTestCache({ cacheDir: dir })
|
|
173
|
+
await cache.set("abc|m1", { result: "pass", ts: 1234 })
|
|
174
|
+
const got = await cache.get("abc|m1")
|
|
175
|
+
expect(got).toEqual({ result: "pass", ts: 1234 })
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test("returns null on missing key", async () => {
|
|
179
|
+
const cache = createFileSmokeTestCache({ cacheDir: path.join(workHome, "smoke-cache-2") })
|
|
180
|
+
const got = await cache.get("missing|m1")
|
|
181
|
+
expect(got).toBeNull()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test("invalidate wipes the dir", async () => {
|
|
185
|
+
const dir = path.join(workHome, "smoke-cache-3")
|
|
186
|
+
const cache = createFileSmokeTestCache({ cacheDir: dir })
|
|
187
|
+
await cache.set("xxx|m", { result: "pass", ts: 1 })
|
|
188
|
+
await cache.invalidate()
|
|
189
|
+
expect(await cache.get("xxx|m")).toBeNull()
|
|
190
|
+
})
|
|
191
|
+
})
|