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,453 @@
|
|
|
1
|
+
import { jetstream, type JetStream } from "@nats-io/jetstream"
|
|
2
|
+
import type { NatsConnection } from "@nats-io/transport-node"
|
|
3
|
+
import { existsSync } from "node:fs"
|
|
4
|
+
import { LOG_PREFIX } from "../shared/branding"
|
|
5
|
+
import type { HarnessToolRequest, HarnessTurn } from "../shared/harness-types"
|
|
6
|
+
import {
|
|
7
|
+
runnerEventsSubject,
|
|
8
|
+
type RunnerTurnEvent,
|
|
9
|
+
type StartTurnCommand,
|
|
10
|
+
} from "../shared/runner-protocol"
|
|
11
|
+
import type {
|
|
12
|
+
AgentProvider,
|
|
13
|
+
NormalizedToolCall,
|
|
14
|
+
PendingToolSnapshot,
|
|
15
|
+
SessionStatus,
|
|
16
|
+
TranscriptEntry,
|
|
17
|
+
} from "../shared/types"
|
|
18
|
+
import { timestamped, discardedToolResult } from "../shared/transcript-entries"
|
|
19
|
+
import type { CoordinationStore } from "../shared/coordination-store"
|
|
20
|
+
|
|
21
|
+
const encoder = new TextEncoder()
|
|
22
|
+
const CANCEL_INTERRUPT_TIMEOUT_MS = 2_000
|
|
23
|
+
|
|
24
|
+
// ── Types ───────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export type TurnFactory = (args: {
|
|
27
|
+
provider: AgentProvider
|
|
28
|
+
content: string
|
|
29
|
+
localPath: string
|
|
30
|
+
model: string
|
|
31
|
+
effort?: string
|
|
32
|
+
serviceTier?: "fast"
|
|
33
|
+
planMode: boolean
|
|
34
|
+
sessionToken: string | null
|
|
35
|
+
onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
|
|
36
|
+
chatId: string
|
|
37
|
+
store?: CoordinationStore
|
|
38
|
+
extraEnv?: Record<string, string>
|
|
39
|
+
}) => Promise<HarnessTurn>
|
|
40
|
+
|
|
41
|
+
function buildHarnessInput(cmd: StartTurnCommand): string {
|
|
42
|
+
if (!cmd.delegatedContext?.trim()) {
|
|
43
|
+
return cmd.content
|
|
44
|
+
}
|
|
45
|
+
return `${cmd.delegatedContext}\n\nNew task:\n${cmd.content}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface PendingToolRequest {
|
|
49
|
+
toolUseId: string
|
|
50
|
+
tool: NormalizedToolCall & { toolKind: "ask_user_question" | "exit_plan_mode" }
|
|
51
|
+
resolve: (result: unknown) => void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ActiveTurn {
|
|
55
|
+
chatId: string
|
|
56
|
+
provider: AgentProvider
|
|
57
|
+
turn: HarnessTurn
|
|
58
|
+
model: string
|
|
59
|
+
effort?: string
|
|
60
|
+
serviceTier?: "fast"
|
|
61
|
+
planMode: boolean
|
|
62
|
+
status: SessionStatus
|
|
63
|
+
pendingTool: PendingToolRequest | null
|
|
64
|
+
postToolFollowUp: { content: string; planMode: boolean } | null
|
|
65
|
+
hasFinalResult: boolean
|
|
66
|
+
cancelRequested: boolean
|
|
67
|
+
cancelRecorded: boolean
|
|
68
|
+
/** Preserved from the original StartTurnCommand for follow-up turns */
|
|
69
|
+
originalCmd: StartTurnCommand
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RunnerAgentOptions {
|
|
73
|
+
nc: NatsConnection
|
|
74
|
+
createTurn: TurnFactory
|
|
75
|
+
generateTitle?: (content: string, cwd: string) => Promise<string | null>
|
|
76
|
+
coordinationStore?: CoordinationStore
|
|
77
|
+
/**
|
|
78
|
+
* Hook the runner calls when a chat is being torn down (cancel without
|
|
79
|
+
* active turn, chat.delete). Implementation closes any live claude-pty
|
|
80
|
+
* session for the chat. Wired from runner.ts to turn-factories.ts.
|
|
81
|
+
*/
|
|
82
|
+
stopClaudePtySession?: (chatId: string) => void
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── RunnerAgent ─────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
export class RunnerAgent {
|
|
88
|
+
private readonly js: JetStream
|
|
89
|
+
private readonly nc: NatsConnection
|
|
90
|
+
private readonly createTurn: TurnFactory
|
|
91
|
+
private readonly generateTitle: ((content: string, cwd: string) => Promise<string | null>) | undefined
|
|
92
|
+
private readonly coordinationStore: CoordinationStore | undefined
|
|
93
|
+
private readonly stopClaudePtySessionFn: ((chatId: string) => void) | undefined
|
|
94
|
+
readonly activeTurns = new Map<string, ActiveTurn>()
|
|
95
|
+
|
|
96
|
+
constructor(options: RunnerAgentOptions) {
|
|
97
|
+
this.nc = options.nc
|
|
98
|
+
this.js = jetstream(options.nc)
|
|
99
|
+
this.createTurn = options.createTurn
|
|
100
|
+
this.generateTitle = options.generateTitle
|
|
101
|
+
this.coordinationStore = options.coordinationStore
|
|
102
|
+
this.stopClaudePtySessionFn = options.stopClaudePtySession
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Publishing ──────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
private publishEvent(chatId: string, event: RunnerTurnEvent): void {
|
|
108
|
+
const subject = runnerEventsSubject(chatId)
|
|
109
|
+
void this.js.publish(subject, encoder.encode(JSON.stringify(event))).catch((error) => {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
111
|
+
console.warn(LOG_PREFIX, `JetStream publish failed on ${subject}: ${message}`)
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private publishTranscript(chatId: string, entry: TranscriptEntry): void {
|
|
116
|
+
this.publishEvent(chatId, { type: "transcript", chatId, entry })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Public API ────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
getActiveStatuses(): Map<string, SessionStatus> {
|
|
122
|
+
const statuses = new Map<string, SessionStatus>()
|
|
123
|
+
for (const [chatId, turn] of this.activeTurns.entries()) {
|
|
124
|
+
statuses.set(chatId, turn.status)
|
|
125
|
+
}
|
|
126
|
+
return statuses
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getPendingTool(chatId: string): PendingToolSnapshot | null {
|
|
130
|
+
const pending = this.activeTurns.get(chatId)?.pendingTool
|
|
131
|
+
if (!pending) return null
|
|
132
|
+
return { toolUseId: pending.toolUseId, toolKind: pending.tool.toolKind }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async startTurn(cmd: StartTurnCommand): Promise<void> {
|
|
136
|
+
if (this.activeTurns.has(cmd.chatId)) {
|
|
137
|
+
throw new Error("Chat is already running")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fail fast (instead of a silent 10s timeout) when the chat's workspace does
|
|
141
|
+
// not exist on THIS runner — e.g. a remote runner handed a server-only path.
|
|
142
|
+
// Launching the agent with a nonexistent cwd hangs the spawn, so the start_turn
|
|
143
|
+
// request never gets a reply. Surface a clear, actionable error instead.
|
|
144
|
+
if (cmd.workspaceLocalPath && !existsSync(cmd.workspaceLocalPath)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Workspace "${cmd.workspaceLocalPath}" does not exist on this runner machine. ` +
|
|
147
|
+
`The workspace must exist on the runner to run a turn here — pick a runner that ` +
|
|
148
|
+
`has this path, or make the path available on the runner.`,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const shouldGenerateTitle =
|
|
153
|
+
cmd.appendUserPrompt &&
|
|
154
|
+
cmd.chatTitle === "New Chat" &&
|
|
155
|
+
cmd.existingMessageCount === 0
|
|
156
|
+
|
|
157
|
+
// Publish user prompt
|
|
158
|
+
if (cmd.appendUserPrompt) {
|
|
159
|
+
this.publishTranscript(
|
|
160
|
+
cmd.chatId,
|
|
161
|
+
timestamped({ kind: "user_prompt", content: cmd.content })
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create tool request handler
|
|
166
|
+
const onToolRequest = async (request: HarnessToolRequest): Promise<unknown> => {
|
|
167
|
+
const active = this.activeTurns.get(cmd.chatId)
|
|
168
|
+
if (!active) throw new Error("Chat turn ended unexpectedly")
|
|
169
|
+
|
|
170
|
+
active.status = "waiting_for_user"
|
|
171
|
+
this.publishEvent(cmd.chatId, {
|
|
172
|
+
type: "status_change",
|
|
173
|
+
chatId: cmd.chatId,
|
|
174
|
+
status: "waiting_for_user",
|
|
175
|
+
})
|
|
176
|
+
this.publishEvent(cmd.chatId, {
|
|
177
|
+
type: "pending_tool",
|
|
178
|
+
chatId: cmd.chatId,
|
|
179
|
+
tool: { toolUseId: request.tool.toolId, toolKind: request.tool.toolKind },
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
return new Promise<unknown>((resolve) => {
|
|
183
|
+
active.pendingTool = {
|
|
184
|
+
toolUseId: request.tool.toolId,
|
|
185
|
+
tool: request.tool,
|
|
186
|
+
resolve,
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Start the harness turn — binary resolution is done inside each factory
|
|
192
|
+
const turn = await this.createTurn({
|
|
193
|
+
provider: cmd.provider,
|
|
194
|
+
content: buildHarnessInput(cmd),
|
|
195
|
+
localPath: cmd.workspaceLocalPath,
|
|
196
|
+
model: cmd.model,
|
|
197
|
+
planMode: cmd.planMode,
|
|
198
|
+
sessionToken: cmd.sessionToken,
|
|
199
|
+
onToolRequest,
|
|
200
|
+
chatId: cmd.chatId,
|
|
201
|
+
store: this.coordinationStore,
|
|
202
|
+
extraEnv: cmd.extraEnv,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const active: ActiveTurn = {
|
|
206
|
+
chatId: cmd.chatId,
|
|
207
|
+
provider: cmd.provider,
|
|
208
|
+
turn,
|
|
209
|
+
model: cmd.model,
|
|
210
|
+
planMode: cmd.planMode,
|
|
211
|
+
status: "starting",
|
|
212
|
+
pendingTool: null,
|
|
213
|
+
postToolFollowUp: null,
|
|
214
|
+
hasFinalResult: false,
|
|
215
|
+
cancelRequested: false,
|
|
216
|
+
cancelRecorded: false,
|
|
217
|
+
originalCmd: cmd,
|
|
218
|
+
}
|
|
219
|
+
this.activeTurns.set(cmd.chatId, active)
|
|
220
|
+
|
|
221
|
+
this.publishEvent(cmd.chatId, {
|
|
222
|
+
type: "status_change",
|
|
223
|
+
chatId: cmd.chatId,
|
|
224
|
+
status: "starting",
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// Background: title generation
|
|
228
|
+
if (shouldGenerateTitle) {
|
|
229
|
+
void this.generateTitleInBackground(cmd.chatId, cmd.content, cmd.workspaceLocalPath)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Run the turn asynchronously
|
|
233
|
+
void this.runTurn(active)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async cancel(chatId: string): Promise<void> {
|
|
237
|
+
const active = this.activeTurns.get(chatId)
|
|
238
|
+
if (!active) {
|
|
239
|
+
// No active turn but a long-lived claude-pty session may still own a
|
|
240
|
+
// claude CLI child, MCP HTTP server, file watcher, sampler interval,
|
|
241
|
+
// and an OAuth pool reservation. Closing the session here matches
|
|
242
|
+
// kanna's `closeChat` discipline — cancel of an idle chat must release
|
|
243
|
+
// the PTY, not silently no-op.
|
|
244
|
+
this.stopClaudePtySessionFn?.(chatId)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
active.cancelRequested = true
|
|
249
|
+
|
|
250
|
+
const pendingTool = active.pendingTool
|
|
251
|
+
active.pendingTool = null
|
|
252
|
+
|
|
253
|
+
if (pendingTool) {
|
|
254
|
+
const result = discardedToolResult(pendingTool.tool)
|
|
255
|
+
this.publishTranscript(
|
|
256
|
+
chatId,
|
|
257
|
+
timestamped({ kind: "tool_result", toolId: pendingTool.toolUseId, content: result })
|
|
258
|
+
)
|
|
259
|
+
if (active.provider === "codex" && pendingTool.tool.toolKind === "exit_plan_mode") {
|
|
260
|
+
pendingTool.resolve(result)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.publishTranscript(chatId, timestamped({ kind: "interrupted" }))
|
|
265
|
+
this.publishEvent(chatId, { type: "turn_cancelled", chatId })
|
|
266
|
+
active.cancelRecorded = true
|
|
267
|
+
active.hasFinalResult = true
|
|
268
|
+
this.activeTurns.delete(chatId)
|
|
269
|
+
|
|
270
|
+
void this.interruptTurnAfterCancel(active).catch(() => {})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Tear down any live claude-pty session for the chat unconditionally.
|
|
275
|
+
* Called from RunnerProxy.disposeChat after a separate `cancel_turn`
|
|
276
|
+
* has interrupted any in-flight turn. Always safe — no-op when the chat
|
|
277
|
+
* has no session.
|
|
278
|
+
*/
|
|
279
|
+
stopChatPty(chatId: string): void {
|
|
280
|
+
this.stopClaudePtySessionFn?.(chatId)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async respondTool(chatId: string, toolUseId: string, result: unknown): Promise<void> {
|
|
284
|
+
const active = this.activeTurns.get(chatId)
|
|
285
|
+
if (!active?.pendingTool) throw new Error("No pending tool request")
|
|
286
|
+
if (active.pendingTool.toolUseId !== toolUseId) throw new Error("Tool response does not match active request")
|
|
287
|
+
|
|
288
|
+
const pending = active.pendingTool
|
|
289
|
+
this.publishTranscript(
|
|
290
|
+
chatId,
|
|
291
|
+
timestamped({ kind: "tool_result", toolId: toolUseId, content: result })
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
active.pendingTool = null
|
|
295
|
+
active.status = "running"
|
|
296
|
+
|
|
297
|
+
this.publishEvent(chatId, { type: "pending_tool", chatId, tool: null })
|
|
298
|
+
this.publishEvent(chatId, { type: "status_change", chatId, status: "running" })
|
|
299
|
+
|
|
300
|
+
// Handle exit_plan_mode follow-up for Codex
|
|
301
|
+
if (pending.tool.toolKind === "exit_plan_mode") {
|
|
302
|
+
const res = (result ?? {}) as { confirmed?: boolean; clearContext?: boolean; message?: string }
|
|
303
|
+
if (res.confirmed && res.clearContext) {
|
|
304
|
+
this.publishEvent(chatId, { type: "session_token", chatId, sessionToken: "" })
|
|
305
|
+
this.publishTranscript(chatId, timestamped({ kind: "context_cleared" }))
|
|
306
|
+
}
|
|
307
|
+
if (active.provider === "codex") {
|
|
308
|
+
active.postToolFollowUp = res.confirmed
|
|
309
|
+
? {
|
|
310
|
+
content: res.message
|
|
311
|
+
? `Proceed with the approved plan. Additional guidance: ${res.message}`
|
|
312
|
+
: "Proceed with the approved plan.",
|
|
313
|
+
planMode: false,
|
|
314
|
+
}
|
|
315
|
+
: {
|
|
316
|
+
content: res.message
|
|
317
|
+
? `Revise the plan using this feedback: ${res.message}`
|
|
318
|
+
: "Revise the plan using this feedback.",
|
|
319
|
+
planMode: true,
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
pending.resolve(result)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Private ───────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
private async generateTitleInBackground(chatId: string, content: string, cwd: string): Promise<void> {
|
|
330
|
+
if (!this.generateTitle) return
|
|
331
|
+
try {
|
|
332
|
+
const title = await this.generateTitle(content, cwd)
|
|
333
|
+
if (!title) return
|
|
334
|
+
this.publishEvent(chatId, { type: "title_generated", chatId, title })
|
|
335
|
+
} catch (_error) {
|
|
336
|
+
// Ignore background title generation failures
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async interruptTurnAfterCancel(active: ActiveTurn): Promise<void> {
|
|
341
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
342
|
+
let timedOut = false
|
|
343
|
+
let failed = false
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
await Promise.race([
|
|
347
|
+
active.turn.interrupt(),
|
|
348
|
+
new Promise<void>((resolve) => {
|
|
349
|
+
timeoutId = setTimeout(() => {
|
|
350
|
+
timedOut = true
|
|
351
|
+
resolve()
|
|
352
|
+
}, CANCEL_INTERRUPT_TIMEOUT_MS)
|
|
353
|
+
}),
|
|
354
|
+
])
|
|
355
|
+
} catch (_error) {
|
|
356
|
+
failed = true
|
|
357
|
+
} finally {
|
|
358
|
+
if (timeoutId !== null) clearTimeout(timeoutId)
|
|
359
|
+
if (timedOut || failed) {
|
|
360
|
+
active.turn.close()
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private async runTurn(active: ActiveTurn): Promise<void> {
|
|
366
|
+
try {
|
|
367
|
+
for await (const event of active.turn.stream) {
|
|
368
|
+
if (active.cancelRequested) {
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (event.type === "session_token" && event.sessionToken) {
|
|
373
|
+
this.publishEvent(active.chatId, {
|
|
374
|
+
type: "session_token",
|
|
375
|
+
chatId: active.chatId,
|
|
376
|
+
sessionToken: event.sessionToken,
|
|
377
|
+
})
|
|
378
|
+
continue
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!event.entry) continue
|
|
382
|
+
|
|
383
|
+
this.publishTranscript(active.chatId, event.entry)
|
|
384
|
+
|
|
385
|
+
if (event.entry.kind === "system_init") {
|
|
386
|
+
active.status = "running"
|
|
387
|
+
this.publishEvent(active.chatId, {
|
|
388
|
+
type: "status_change",
|
|
389
|
+
chatId: active.chatId,
|
|
390
|
+
status: "running",
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (event.entry.kind === "result") {
|
|
395
|
+
active.hasFinalResult = true
|
|
396
|
+
if (event.entry.isError) {
|
|
397
|
+
this.publishEvent(active.chatId, {
|
|
398
|
+
type: "turn_failed",
|
|
399
|
+
chatId: active.chatId,
|
|
400
|
+
error: event.entry.result || "Turn failed",
|
|
401
|
+
})
|
|
402
|
+
} else if (!active.cancelRequested) {
|
|
403
|
+
this.publishEvent(active.chatId, {
|
|
404
|
+
type: "turn_finished",
|
|
405
|
+
chatId: active.chatId,
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (!active.cancelRequested) {
|
|
412
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
413
|
+
this.publishTranscript(
|
|
414
|
+
active.chatId,
|
|
415
|
+
timestamped({ kind: "result", subtype: "error", isError: true, durationMs: 0, result: message })
|
|
416
|
+
)
|
|
417
|
+
this.publishEvent(active.chatId, {
|
|
418
|
+
type: "turn_failed",
|
|
419
|
+
chatId: active.chatId,
|
|
420
|
+
error: message,
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
} finally {
|
|
424
|
+
if (active.cancelRequested && !active.cancelRecorded) {
|
|
425
|
+
this.publishEvent(active.chatId, { type: "turn_cancelled", chatId: active.chatId })
|
|
426
|
+
}
|
|
427
|
+
this.activeTurns.delete(active.chatId)
|
|
428
|
+
|
|
429
|
+
// Handle follow-up turn (Codex exit_plan_mode) — close() deferred so
|
|
430
|
+
// the follow-up can reuse the live session (avoids re-spawn cost).
|
|
431
|
+
if (active.postToolFollowUp && !active.cancelRequested) {
|
|
432
|
+
try {
|
|
433
|
+
await this.startTurn({
|
|
434
|
+
...active.originalCmd,
|
|
435
|
+
content: active.postToolFollowUp.content,
|
|
436
|
+
planMode: active.postToolFollowUp.planMode,
|
|
437
|
+
appendUserPrompt: false,
|
|
438
|
+
})
|
|
439
|
+
} catch (error) {
|
|
440
|
+
active.turn.close()
|
|
441
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
442
|
+
this.publishTranscript(
|
|
443
|
+
active.chatId,
|
|
444
|
+
timestamped({ kind: "result", subtype: "error", isError: true, durationMs: 0, result: message })
|
|
445
|
+
)
|
|
446
|
+
this.publishEvent(active.chatId, { type: "turn_failed", chatId: active.chatId, error: message })
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
active.turn.close()
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the runner credential store (PR2 Stage 2).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - write→read round-trip; shape is preserved.
|
|
6
|
+
* - credential file is written with mode 0600.
|
|
7
|
+
* - TINKARIA_RUNNER_HOME overrides the default ~/.tinkaria directory.
|
|
8
|
+
* - missing file returns null (not throw).
|
|
9
|
+
* - second write overwrites cleanly.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
13
|
+
import { mkdtempSync, rmSync, statSync } from "node:fs"
|
|
14
|
+
import { join } from "node:path"
|
|
15
|
+
import { tmpdir } from "node:os"
|
|
16
|
+
import { writeRunnerCredential, readRunnerCredential, type RunnerCredential } from "./runner-credential"
|
|
17
|
+
|
|
18
|
+
const SAMPLE: RunnerCredential = {
|
|
19
|
+
runnerId: "runner-1234567890-999",
|
|
20
|
+
token: "eytest.token.value",
|
|
21
|
+
natsUrl: "nats://127.0.0.1:4222",
|
|
22
|
+
natsWsUrl: "ws://127.0.0.1:8222",
|
|
23
|
+
pairedAt: 1700000000000,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let tmpDir: string
|
|
27
|
+
let savedRunnerHome: string | undefined
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
tmpDir = mkdtempSync(join(tmpdir(), "pr2-cred-test-"))
|
|
31
|
+
savedRunnerHome = process.env.TINKARIA_RUNNER_HOME
|
|
32
|
+
process.env.TINKARIA_RUNNER_HOME = tmpDir
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
37
|
+
if (savedRunnerHome === undefined) {
|
|
38
|
+
delete process.env.TINKARIA_RUNNER_HOME
|
|
39
|
+
} else {
|
|
40
|
+
process.env.TINKARIA_RUNNER_HOME = savedRunnerHome
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe("runner-credential", () => {
|
|
45
|
+
test("write then read round-trips all fields", async () => {
|
|
46
|
+
await writeRunnerCredential(SAMPLE)
|
|
47
|
+
const got = await readRunnerCredential()
|
|
48
|
+
expect(got).toEqual(SAMPLE)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("credential file is written with mode 0600", async () => {
|
|
52
|
+
await writeRunnerCredential(SAMPLE)
|
|
53
|
+
// The file lives at <TINKARIA_RUNNER_HOME>/runner-secret.json
|
|
54
|
+
const filePath = join(tmpDir, "runner-secret.json")
|
|
55
|
+
const mode = statSync(filePath).mode & 0o777
|
|
56
|
+
expect(mode).toBe(0o600)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("TINKARIA_RUNNER_HOME is honoured", async () => {
|
|
60
|
+
const altDir = mkdtempSync(join(tmpdir(), "pr2-cred-alt-"))
|
|
61
|
+
try {
|
|
62
|
+
process.env.TINKARIA_RUNNER_HOME = altDir
|
|
63
|
+
await writeRunnerCredential(SAMPLE)
|
|
64
|
+
// Original tmpDir should have nothing.
|
|
65
|
+
const inOriginal = await readRunnerCredential()
|
|
66
|
+
// readRunnerCredential also reads from TINKARIA_RUNNER_HOME (already set to altDir)
|
|
67
|
+
expect(inOriginal).toEqual(SAMPLE)
|
|
68
|
+
const filePath = join(altDir, "runner-secret.json")
|
|
69
|
+
const mode = statSync(filePath).mode & 0o777
|
|
70
|
+
expect(mode).toBe(0o600)
|
|
71
|
+
} finally {
|
|
72
|
+
process.env.TINKARIA_RUNNER_HOME = tmpDir
|
|
73
|
+
rmSync(altDir, { recursive: true, force: true })
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("missing file returns null", async () => {
|
|
78
|
+
const result = await readRunnerCredential()
|
|
79
|
+
expect(result).toBeNull()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("second write overwrites the first", async () => {
|
|
83
|
+
await writeRunnerCredential(SAMPLE)
|
|
84
|
+
const updated: RunnerCredential = { ...SAMPLE, runnerId: "runner-updated-42", pairedAt: 9999999999999 }
|
|
85
|
+
await writeRunnerCredential(updated)
|
|
86
|
+
const got = await readRunnerCredential()
|
|
87
|
+
expect(got).toEqual(updated)
|
|
88
|
+
// Mode preserved after overwrite
|
|
89
|
+
const filePath = join(tmpDir, "runner-secret.json")
|
|
90
|
+
const mode = statSync(filePath).mode & 0o777
|
|
91
|
+
expect(mode).toBe(0o600)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runner credential store (PR2 Stage 2).
|
|
3
|
+
*
|
|
4
|
+
* Reads and writes the durable runner credential to
|
|
5
|
+
* `<TINKARIA_RUNNER_HOME>/runner-secret.json` (default: `~/.tinkaria/`).
|
|
6
|
+
* The file is written atomically (tmp + rename) with mode 0600.
|
|
7
|
+
*
|
|
8
|
+
* The credential is a PR1 callout token minted at pairing time:
|
|
9
|
+
* { runnerId, token, natsUrl, natsWsUrl, pairedAt }
|
|
10
|
+
*
|
|
11
|
+
* Env-var TINKARIA_RUNNER_HOME overrides the default directory so that
|
|
12
|
+
* tests and multiple local runners can use isolated locations.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { join, resolve } from "node:path"
|
|
16
|
+
import { chmodSync, mkdirSync, renameSync } from "node:fs"
|
|
17
|
+
import { homedir } from "node:os"
|
|
18
|
+
|
|
19
|
+
export interface RunnerCredential {
|
|
20
|
+
runnerId: string
|
|
21
|
+
/** PR1 callout credential token (long-lived, self-verifying). Never log in full. */
|
|
22
|
+
token: string
|
|
23
|
+
natsUrl: string
|
|
24
|
+
natsWsUrl: string
|
|
25
|
+
/**
|
|
26
|
+
* WebSocket URL of the server's `/nats-ws` proxy (through the tunneled HTTP
|
|
27
|
+
* port), derived from the server URL the runner paired against. Preferred over
|
|
28
|
+
* the raw `natsUrl` TCP path: the raw NATS port is often not the tunneled one,
|
|
29
|
+
* and direct tailnet connections can fail to receive server-initiated pushes.
|
|
30
|
+
* Optional for backward compat — older credentials only have natsUrl (TCP).
|
|
31
|
+
*/
|
|
32
|
+
natsWsProxyUrl?: string
|
|
33
|
+
/** Unix epoch ms when the runner was paired. */
|
|
34
|
+
pairedAt: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const CREDENTIAL_FILE = "runner-secret.json"
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the runner home directory (overridable via TINKARIA_RUNNER_HOME).
|
|
41
|
+
* The env var is operator-controlled config (like HOME) — it may legitimately
|
|
42
|
+
* point anywhere, so we don't restrict it; we normalize it to an absolute path
|
|
43
|
+
* for predictability. The directory is created 0700 (see writeRunnerCredential)
|
|
44
|
+
* so the secret file's parent isn't world-traversable.
|
|
45
|
+
*/
|
|
46
|
+
function runnerHomeDir(): string {
|
|
47
|
+
const raw = process.env.TINKARIA_RUNNER_HOME ?? join(homedir(), ".tinkaria")
|
|
48
|
+
return resolve(raw)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function credentialPath(): string {
|
|
52
|
+
return join(runnerHomeDir(), CREDENTIAL_FILE)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Write the runner credential to disk atomically with mode 0600.
|
|
57
|
+
* Creates the directory if it does not exist.
|
|
58
|
+
*/
|
|
59
|
+
export async function writeRunnerCredential(cred: RunnerCredential): Promise<void> {
|
|
60
|
+
const dir = runnerHomeDir()
|
|
61
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 })
|
|
62
|
+
|
|
63
|
+
const dest = credentialPath()
|
|
64
|
+
const tmp = `${dest}.tmp.${process.pid}`
|
|
65
|
+
|
|
66
|
+
await Bun.write(tmp, JSON.stringify(cred, null, 2) + "\n")
|
|
67
|
+
chmodSync(tmp, 0o600)
|
|
68
|
+
renameSync(tmp, dest)
|
|
69
|
+
// chmod again after rename in case umask widened it on some platforms
|
|
70
|
+
chmodSync(dest, 0o600)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read the runner credential from disk.
|
|
75
|
+
* Returns null if the file does not exist (runner not yet paired).
|
|
76
|
+
*/
|
|
77
|
+
export async function readRunnerCredential(): Promise<RunnerCredential | null> {
|
|
78
|
+
const file = Bun.file(credentialPath())
|
|
79
|
+
if (!(await file.exists())) return null
|
|
80
|
+
const text = await file.text()
|
|
81
|
+
return JSON.parse(text) as RunnerCredential
|
|
82
|
+
}
|