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,312 @@
|
|
|
1
|
+
import type { Msg, NatsConnection, Subscription } from "@nats-io/transport-node"
|
|
2
|
+
import { Kvm } from "@nats-io/kv"
|
|
3
|
+
import {
|
|
4
|
+
runnerCmdSubject,
|
|
5
|
+
runnerHeartbeatSubject,
|
|
6
|
+
RUNNER_REGISTRY_BUCKET,
|
|
7
|
+
isProtocolSupported,
|
|
8
|
+
runnerLivenessState,
|
|
9
|
+
SUPPORTED_RANGE,
|
|
10
|
+
type RunnerCapabilities,
|
|
11
|
+
type RunnerHeartbeat,
|
|
12
|
+
type RunnerLivenessState,
|
|
13
|
+
type RunnerRegistration,
|
|
14
|
+
} from "../shared/runner-protocol"
|
|
15
|
+
import { LOG_PREFIX } from "../shared/branding"
|
|
16
|
+
|
|
17
|
+
const encoder = new TextEncoder()
|
|
18
|
+
const decoder = new TextDecoder()
|
|
19
|
+
|
|
20
|
+
export interface RunnerManagerOptions {
|
|
21
|
+
nc: NatsConnection
|
|
22
|
+
natsUrl: string
|
|
23
|
+
authToken?: string
|
|
24
|
+
/**
|
|
25
|
+
* Called in callout mode to mint a scoped credential token for the given
|
|
26
|
+
* runnerId just before the runner process is spawned. When provided, it takes
|
|
27
|
+
* precedence over authToken for the spawned runner's NATS_TOKEN env var.
|
|
28
|
+
*/
|
|
29
|
+
mintToken?: (runnerId: string) => Promise<string>
|
|
30
|
+
/** 'spawn' (default): spawn runner if not found. 'discover': only discover existing runner from KV. */
|
|
31
|
+
mode?: "spawn" | "discover"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RunnerReadiness {
|
|
35
|
+
ok: boolean
|
|
36
|
+
runnerId: string | null
|
|
37
|
+
pid: number | null
|
|
38
|
+
registered: boolean
|
|
39
|
+
/** Derived liveness state from server-tracked lastHeartbeatAt. */
|
|
40
|
+
state: RunnerLivenessState
|
|
41
|
+
/** heartbeatFresh === (state === "online") — kept for back-compat. */
|
|
42
|
+
heartbeatFresh: boolean
|
|
43
|
+
lastHeartbeatAt: number | null
|
|
44
|
+
/** Protocol version reported by the runner registration, or null if not registered. */
|
|
45
|
+
protocolVersion: number | null
|
|
46
|
+
/** True when the runner's protocolVersion is outside the server's SUPPORTED_RANGE. */
|
|
47
|
+
incompatible: boolean
|
|
48
|
+
/** Capabilities advertised by the runner at registration time. Null if not yet registered. */
|
|
49
|
+
capabilities: RunnerCapabilities | null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class RunnerManager {
|
|
53
|
+
private readonly nc: NatsConnection
|
|
54
|
+
private readonly natsUrl: string
|
|
55
|
+
private readonly authToken: string | undefined
|
|
56
|
+
private readonly mintToken: ((runnerId: string) => Promise<string>) | undefined
|
|
57
|
+
private readonly mode: "spawn" | "discover"
|
|
58
|
+
private proc: ReturnType<typeof Bun.spawn> | null = null
|
|
59
|
+
private runnerId: string | null = null
|
|
60
|
+
private runnerRegistration: RunnerRegistration | null = null
|
|
61
|
+
private lastHeartbeatAt: number | null = null
|
|
62
|
+
private heartbeatSubscription: Subscription | null = null
|
|
63
|
+
|
|
64
|
+
constructor(options: RunnerManagerOptions) {
|
|
65
|
+
this.nc = options.nc
|
|
66
|
+
this.natsUrl = options.natsUrl
|
|
67
|
+
this.authToken = options.authToken
|
|
68
|
+
this.mintToken = options.mintToken
|
|
69
|
+
this.mode = options.mode ?? "spawn"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getRunnerId(): string {
|
|
73
|
+
if (!this.runnerId) throw new Error("Runner not started")
|
|
74
|
+
return this.runnerId
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getReadiness(now = Date.now()): RunnerReadiness {
|
|
78
|
+
const state = runnerLivenessState(this.lastHeartbeatAt, now)
|
|
79
|
+
const heartbeatFresh = state === "online"
|
|
80
|
+
const registered = this.runnerRegistration !== null
|
|
81
|
+
// A registration missing protocolVersion is treated as incompatible (defensive).
|
|
82
|
+
const protocolVersion = this.runnerRegistration?.protocolVersion ?? null
|
|
83
|
+
const incompatible =
|
|
84
|
+
protocolVersion === null
|
|
85
|
+
? registered // if registered but no version field, it's incompatible
|
|
86
|
+
: !isProtocolSupported(protocolVersion)
|
|
87
|
+
const capabilities = this.runnerRegistration?.capabilities ?? null
|
|
88
|
+
return {
|
|
89
|
+
ok: this.runnerId !== null && registered && heartbeatFresh && !incompatible,
|
|
90
|
+
runnerId: this.runnerId,
|
|
91
|
+
pid: this.runnerRegistration?.pid ?? this.proc?.pid ?? null,
|
|
92
|
+
registered,
|
|
93
|
+
state,
|
|
94
|
+
heartbeatFresh,
|
|
95
|
+
lastHeartbeatAt: this.lastHeartbeatAt,
|
|
96
|
+
protocolVersion,
|
|
97
|
+
incompatible,
|
|
98
|
+
capabilities,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async ensureRunner(): Promise<string> {
|
|
103
|
+
// Check if runner already exists in KV and is alive
|
|
104
|
+
if (this.runnerId) {
|
|
105
|
+
try {
|
|
106
|
+
const kvm = new Kvm(this.nc)
|
|
107
|
+
const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
|
|
108
|
+
const entry = await kvStore.get(this.runnerId)
|
|
109
|
+
if (entry) {
|
|
110
|
+
const reg = JSON.parse(decoder.decode(entry.value)) as RunnerRegistration
|
|
111
|
+
this.runnerRegistration = reg
|
|
112
|
+
// For the active (spawn-mode) runner, prefer the server-tracked
|
|
113
|
+
// lastHeartbeatAt for liveness rather than lastSeenAt from KV.
|
|
114
|
+
// If we have live heartbeat data, use that; otherwise fall back to lastSeenAt.
|
|
115
|
+
const freshnessSrc = this.lastHeartbeatAt ?? reg.lastSeenAt ?? null
|
|
116
|
+
if (runnerLivenessState(freshnessSrc, Date.now()) !== "offline") {
|
|
117
|
+
return this.runnerId
|
|
118
|
+
}
|
|
119
|
+
// Runner offline — fall through to respawn or discover
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// KV bucket might not exist yet, proceed to spawn or discover
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (this.mode === "discover") {
|
|
127
|
+
return this.discoverExternalRunner()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Spawn new runner
|
|
131
|
+
const runnerId = `runner-${Date.now()}-${process.pid}`
|
|
132
|
+
const runnerScript = new URL("../runner/runner.ts", import.meta.url).pathname
|
|
133
|
+
|
|
134
|
+
// In callout mode mintToken produces a scoped credential for this runnerId.
|
|
135
|
+
// In token mode authToken is the shared static token — behaviour unchanged.
|
|
136
|
+
const runnerToken = this.mintToken
|
|
137
|
+
? await this.mintToken(runnerId)
|
|
138
|
+
: this.authToken
|
|
139
|
+
|
|
140
|
+
this.subscribeToHeartbeat(runnerId)
|
|
141
|
+
|
|
142
|
+
this.proc = Bun.spawn(["bun", "run", runnerScript], {
|
|
143
|
+
env: {
|
|
144
|
+
...process.env,
|
|
145
|
+
NATS_URL: this.natsUrl,
|
|
146
|
+
...(runnerToken ? { NATS_TOKEN: runnerToken } : {}),
|
|
147
|
+
RUNNER_ID: runnerId,
|
|
148
|
+
},
|
|
149
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
this.runnerId = runnerId
|
|
153
|
+
|
|
154
|
+
// Wait for runner to register in KV
|
|
155
|
+
await this.waitForRegistration(runnerId, 15_000)
|
|
156
|
+
await this.waitForHeartbeat(5_000)
|
|
157
|
+
|
|
158
|
+
const { incompatible, protocolVersion } = this.getReadiness()
|
|
159
|
+
if (incompatible) {
|
|
160
|
+
console.warn(
|
|
161
|
+
LOG_PREFIX,
|
|
162
|
+
`Runner ${runnerId} is incompatible (protocol v${protocolVersion ?? "unknown"}, server supports v${SUPPORTED_RANGE.min}–${SUPPORTED_RANGE.max})`,
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
console.warn(LOG_PREFIX, `Runner ${runnerId} spawned (pid: ${this.proc.pid})`)
|
|
166
|
+
|
|
167
|
+
return runnerId
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async waitForRegistration(runnerId: string, timeoutMs: number): Promise<void> {
|
|
171
|
+
const deadline = Date.now() + timeoutMs
|
|
172
|
+
const kvm = new Kvm(this.nc)
|
|
173
|
+
let kvStore: Awaited<ReturnType<typeof kvm.open>> | null = null
|
|
174
|
+
while (Date.now() < deadline) {
|
|
175
|
+
try {
|
|
176
|
+
if (!kvStore) kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
|
|
177
|
+
const entry = await kvStore.get(runnerId)
|
|
178
|
+
if (entry) {
|
|
179
|
+
this.runnerRegistration = JSON.parse(decoder.decode(entry.value)) as RunnerRegistration
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
kvStore = null // KV not ready yet, retry open next iteration
|
|
184
|
+
}
|
|
185
|
+
await new Promise((r) => setTimeout(r, 200))
|
|
186
|
+
}
|
|
187
|
+
throw new Error(`Runner ${runnerId} did not register within ${timeoutMs}ms`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private subscribeToHeartbeat(runnerId: string): void {
|
|
191
|
+
this.heartbeatSubscription?.unsubscribe()
|
|
192
|
+
this.lastHeartbeatAt = null
|
|
193
|
+
const sub = this.nc.subscribe(runnerHeartbeatSubject(runnerId))
|
|
194
|
+
this.heartbeatSubscription = sub
|
|
195
|
+
void this.consumeHeartbeats(sub, runnerId)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async consumeHeartbeats(sub: Subscription, runnerId: string): Promise<void> {
|
|
199
|
+
for await (const msg of sub) {
|
|
200
|
+
if (sub !== this.heartbeatSubscription || runnerId !== this.runnerId) {
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
this.recordHeartbeat(msg)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private recordHeartbeat(msg: Msg): void {
|
|
208
|
+
try {
|
|
209
|
+
const heartbeat = JSON.parse(decoder.decode(msg.data)) as RunnerHeartbeat
|
|
210
|
+
if (heartbeat.runnerId !== this.runnerId) return
|
|
211
|
+
this.lastHeartbeatAt = heartbeat.ts
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
214
|
+
console.warn(LOG_PREFIX, `Runner heartbeat decode failed: ${message}`)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async waitForHeartbeat(timeoutMs: number): Promise<void> {
|
|
219
|
+
const deadline = Date.now() + timeoutMs
|
|
220
|
+
while (Date.now() < deadline) {
|
|
221
|
+
if (this.getReadiness().heartbeatFresh) return
|
|
222
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`Runner ${this.runnerId ?? "unknown"} did not publish heartbeat within ${timeoutMs}ms`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Discover mode: poll KV for any registered runner instead of spawning one. */
|
|
228
|
+
private async discoverExternalRunner(): Promise<string> {
|
|
229
|
+
const deadline = Date.now() + 15_000
|
|
230
|
+
const kvm = new Kvm(this.nc)
|
|
231
|
+
while (Date.now() < deadline) {
|
|
232
|
+
try {
|
|
233
|
+
const kvStore = await kvm.open(RUNNER_REGISTRY_BUCKET)
|
|
234
|
+
const keys = await kvStore.keys()
|
|
235
|
+
for await (const key of keys) {
|
|
236
|
+
const entry = await kvStore.get(key)
|
|
237
|
+
if (!entry) continue
|
|
238
|
+
const reg = JSON.parse(decoder.decode(entry.value)) as RunnerRegistration
|
|
239
|
+
const livenessNow = Date.now()
|
|
240
|
+
// Use KV lastSeenAt for discovery liveness — pid is meaningless for
|
|
241
|
+
// cross-machine runners. Skip any candidate that is offline.
|
|
242
|
+
if (runnerLivenessState(reg.lastSeenAt ?? null, livenessNow) === "offline") {
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
// Skip incompatible runners — adopting one would block every turn start.
|
|
246
|
+
// `?? -1` guards pre-PR3 KV entries that lack protocolVersion (would be
|
|
247
|
+
// undefined at runtime despite the typed field) → treated as incompatible.
|
|
248
|
+
if (!isProtocolSupported(reg.protocolVersion ?? -1)) {
|
|
249
|
+
console.warn(
|
|
250
|
+
LOG_PREFIX,
|
|
251
|
+
`Discover: skipping incompatible runner ${key} (protocol v${reg.protocolVersion}, server supports v${SUPPORTED_RANGE.min}–${SUPPORTED_RANGE.max})`,
|
|
252
|
+
)
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
this.runnerId = key
|
|
256
|
+
this.runnerRegistration = reg
|
|
257
|
+
this.subscribeToHeartbeat(key)
|
|
258
|
+
await this.waitForHeartbeat(5_000)
|
|
259
|
+
console.warn(LOG_PREFIX, `Discovered external runner ${key} (pid: ${reg.pid})`)
|
|
260
|
+
return key
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
// KV not ready yet
|
|
264
|
+
}
|
|
265
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
266
|
+
}
|
|
267
|
+
throw new Error("No external runner found in KV registry within 15s — is kanna-runner.service running?")
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async dispose(): Promise<void> {
|
|
271
|
+
this.heartbeatSubscription?.unsubscribe()
|
|
272
|
+
this.heartbeatSubscription = null
|
|
273
|
+
if (this.mode === "discover") {
|
|
274
|
+
// External runner — don't kill it, just disconnect
|
|
275
|
+
this.runnerId = null
|
|
276
|
+
this.runnerRegistration = null
|
|
277
|
+
this.lastHeartbeatAt = null
|
|
278
|
+
console.warn(LOG_PREFIX, "Runner manager disposed (external runner left running)")
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
if (this.runnerId && this.proc) {
|
|
282
|
+
// Try graceful shutdown via NATS command, then SIGTERM as fallback
|
|
283
|
+
try {
|
|
284
|
+
await this.nc.request(
|
|
285
|
+
runnerCmdSubject(this.runnerId, "shutdown"),
|
|
286
|
+
encoder.encode(JSON.stringify({ reason: "server shutdown" })),
|
|
287
|
+
{ timeout: 3000 },
|
|
288
|
+
)
|
|
289
|
+
} catch {
|
|
290
|
+
// NATS request failed or timed out
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Always send SIGTERM — the NATS shutdown command only cleans up
|
|
294
|
+
// subscriptions but doesn't exit the process
|
|
295
|
+
try {
|
|
296
|
+
this.proc.kill("SIGTERM")
|
|
297
|
+
} catch {
|
|
298
|
+
// Process may already be gone
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (this.proc) {
|
|
303
|
+
await this.proc.exited
|
|
304
|
+
this.proc = null
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.runnerId = null
|
|
308
|
+
this.runnerRegistration = null
|
|
309
|
+
this.lastHeartbeatAt = null
|
|
310
|
+
console.warn(LOG_PREFIX, "Runner manager disposed")
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for resolveRunnerPairingUrls — the pairing-exchange NATS URL
|
|
3
|
+
* advertiser. Regression target: a runner paired against a server bound to
|
|
4
|
+
* 0.0.0.0 must NOT receive nats://0.0.0.0:<port> (ECONNREFUSED on a remote box).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect } from "bun:test"
|
|
8
|
+
import { resolveRunnerPairingUrls } from "./runner-pairing-urls"
|
|
9
|
+
|
|
10
|
+
const wildcard = { url: "nats://0.0.0.0:50640", wsUrl: "ws://0.0.0.0:50641" }
|
|
11
|
+
|
|
12
|
+
describe("resolveRunnerPairingUrls", () => {
|
|
13
|
+
test("rewrites both hosts to NATS_ADVERTISED_HOST when set", () => {
|
|
14
|
+
const result = resolveRunnerPairingUrls(wildcard, {
|
|
15
|
+
NATS_ADVERTISED_HOST: "100.64.0.2",
|
|
16
|
+
})
|
|
17
|
+
expect(result).toEqual({
|
|
18
|
+
ok: true,
|
|
19
|
+
natsUrl: "nats://100.64.0.2:50640",
|
|
20
|
+
natsWsUrl: "ws://100.64.0.2:50641",
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("advertised host wins even over an already-concrete daemon host", () => {
|
|
25
|
+
const result = resolveRunnerPairingUrls(
|
|
26
|
+
{ url: "nats://127.0.0.1:4222", wsUrl: "ws://127.0.0.1:4223" },
|
|
27
|
+
{ NATS_ADVERTISED_HOST: "192.168.1.50" },
|
|
28
|
+
)
|
|
29
|
+
expect(result).toEqual({
|
|
30
|
+
ok: true,
|
|
31
|
+
natsUrl: "nats://192.168.1.50:4222",
|
|
32
|
+
natsWsUrl: "ws://192.168.1.50:4223",
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("rejects a 0.0.0.0 bind host when no advertised host is set", () => {
|
|
37
|
+
const result = resolveRunnerPairingUrls(wildcard, {})
|
|
38
|
+
expect(result.ok).toBe(false)
|
|
39
|
+
if (!result.ok) expect(result.error).toContain("NATS_ADVERTISED_HOST")
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("rejects IPv6 wildcard hosts (:: and [::])", () => {
|
|
43
|
+
for (const url of ["nats://[::]:50640"]) {
|
|
44
|
+
const result = resolveRunnerPairingUrls({ url, wsUrl: "ws://[::]:50641" }, {})
|
|
45
|
+
expect(result.ok).toBe(false)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test("passes a concrete host through unchanged", () => {
|
|
50
|
+
const info = { url: "nats://127.0.0.1:4222", wsUrl: "ws://127.0.0.1:4223" }
|
|
51
|
+
const result = resolveRunnerPairingUrls(info, {})
|
|
52
|
+
expect(result).toEqual({ ok: true, natsUrl: info.url, natsWsUrl: info.wsUrl })
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("blank NATS_ADVERTISED_HOST is treated as unset", () => {
|
|
56
|
+
const result = resolveRunnerPairingUrls(wildcard, { NATS_ADVERTISED_HOST: " " })
|
|
57
|
+
expect(result.ok).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the NATS URLs handed to a *paired* runner.
|
|
3
|
+
*
|
|
4
|
+
* A paired runner may run on a different machine, so the URL host must be
|
|
5
|
+
* routable *from that machine*. The embedded daemon's URL carries whatever host
|
|
6
|
+
* the server bound to — which is the wildcard `0.0.0.0` (or `::`) when started
|
|
7
|
+
* with `--remote` / `--host 0.0.0.0`. A wildcard host is never a valid client
|
|
8
|
+
* destination: a remote runner would dial its own loopback and get
|
|
9
|
+
* ECONNREFUSED.
|
|
10
|
+
*
|
|
11
|
+
* This mirrors the advertised-host rewrite that `/auth/token` already applies
|
|
12
|
+
* (server.ts) via `NATS_ADVERTISED_HOST`. Kept self-contained (local
|
|
13
|
+
* `rewriteUrlHost`) so the unit test does not pull in the native
|
|
14
|
+
* `@lagz0ne/nats-embedded` dependency through `nats-bridge.ts`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Hosts that are bind-all wildcards and therefore unroutable as a client target. */
|
|
18
|
+
function isWildcardHost(host: string): boolean {
|
|
19
|
+
return host === "" || host === "0.0.0.0" || host === "::" || host === "[::]"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Return `rawUrl` with its host replaced by `host`, trailing slash stripped. */
|
|
23
|
+
function rewriteUrlHost(rawUrl: string, host: string): string {
|
|
24
|
+
const url = new URL(rawUrl)
|
|
25
|
+
url.hostname = host
|
|
26
|
+
return url.toString().replace(/\/$/, "")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type RunnerPairingUrls =
|
|
30
|
+
| { ok: true; natsUrl: string; natsWsUrl: string }
|
|
31
|
+
| { ok: false; error: string }
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compute the `natsUrl` / `natsWsUrl` to return from the pairing exchange.
|
|
35
|
+
*
|
|
36
|
+
* - If `NATS_ADVERTISED_HOST` is set, rewrite both URL hosts to it.
|
|
37
|
+
* - Otherwise, if the daemon URL host is a wildcard, refuse (the operator must
|
|
38
|
+
* declare a routable host).
|
|
39
|
+
* - Otherwise pass the daemon URLs through unchanged (e.g. `127.0.0.1` for
|
|
40
|
+
* same-machine pairing, or a concrete LAN/tailnet IP).
|
|
41
|
+
*/
|
|
42
|
+
export function resolveRunnerPairingUrls(
|
|
43
|
+
daemonInfo: { url: string; wsUrl: string },
|
|
44
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
45
|
+
): RunnerPairingUrls {
|
|
46
|
+
const advertisedHost = env.NATS_ADVERTISED_HOST?.trim()
|
|
47
|
+
if (advertisedHost) {
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
natsUrl: rewriteUrlHost(daemonInfo.url, advertisedHost),
|
|
51
|
+
natsWsUrl: rewriteUrlHost(daemonInfo.wsUrl, advertisedHost),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const host = new URL(daemonInfo.url).hostname
|
|
56
|
+
if (isWildcardHost(host)) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error:
|
|
60
|
+
`NATS is bound to a wildcard host (${host || "unspecified"}), which a remote ` +
|
|
61
|
+
`runner cannot reach. Set NATS_ADVERTISED_HOST to the server's routable ` +
|
|
62
|
+
`address (LAN or tailnet IP) and re-pair.`,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { ok: true, natsUrl: daemonInfo.url, natsWsUrl: daemonInfo.wsUrl }
|
|
67
|
+
}
|