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,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn factory wrappers for the runner process.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports startClaudeTurn from claude-harness.ts and provides
|
|
5
|
+
* startCodexTurn / startClaudePtyTurn so the runner can create harness turns
|
|
6
|
+
* for all providers.
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle ownership for claude-pty:
|
|
9
|
+
* • `claudePtySessions` — module-level Map<chatId, ClaudePtySession>.
|
|
10
|
+
* Mirrors kanna's `claudeSessions` map. PTY handle persists across
|
|
11
|
+
* turns so the live `claude` CLI keeps in-memory context.
|
|
12
|
+
* • `OAuthTokenPool` — single instance constructed in `runner.ts` and
|
|
13
|
+
* handed to the factory at boot. `pickActive(chatId)` reserves a token
|
|
14
|
+
* for the chat; `release(chatId)` drops it on turn end / session close.
|
|
15
|
+
* `markLimited` / `markError` rotate the token on rate_limit / stream
|
|
16
|
+
* error events. Mirrors kanna's `AgentCoordinator` rotation discipline.
|
|
17
|
+
* • `ClaudePtyRegistry` — on-disk pidfile registry. Driver writes its pid
|
|
18
|
+
* + runtimeDir before sending the first prompt and unregisters during
|
|
19
|
+
* cleanup. Boot's `reapStale()` SIGKILLs orphans from a previous crash.
|
|
20
|
+
* • Idle sweeper — `sweepIdleClaudePtySessions(now, idleMs)` closes any
|
|
21
|
+
* session whose `lastUsedAt` is past the TTL. Scheduled from runner.ts
|
|
22
|
+
* so the runner controls cadence + can `unref()` the timer.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export { startClaudeTurn } from "../server/claude-harness"
|
|
26
|
+
|
|
27
|
+
import { CodexAppServerManager } from "../server/codex-app-server"
|
|
28
|
+
import type { HarnessToolRequest, HarnessTurn } from "../shared/harness-types"
|
|
29
|
+
import type { CodexReasoningEffort, ServiceTier } from "../shared/types"
|
|
30
|
+
import { startClaudeSessionPTY, type PtyUsageSample } from "../server/claude-pty/driver"
|
|
31
|
+
import { ptyDeltaSubject } from "../shared/nats-subjects"
|
|
32
|
+
import { compressPayload } from "../shared/compression"
|
|
33
|
+
import type { NatsConnection } from "@nats-io/transport-node"
|
|
34
|
+
import type { PtyInstanceDelta, PtyInstanceState } from "../shared/pty-instance"
|
|
35
|
+
import type { HarnessEvent } from "../shared/harness-types"
|
|
36
|
+
import type { ClaudeSessionHandle } from "../server/claude-pty/agent-normalizers"
|
|
37
|
+
import type { OAuthTokenPool } from "../server/oauth-pool/oauth-token-pool"
|
|
38
|
+
import type { ClaudePtyRegistry } from "../server/claude-pty/pid-registry.adapter"
|
|
39
|
+
import { spawnSync } from "node:child_process"
|
|
40
|
+
|
|
41
|
+
const ptyDeltaEncoder = new TextEncoder()
|
|
42
|
+
function encodeDelta(delta: PtyInstanceDelta): Uint8Array {
|
|
43
|
+
return compressPayload(ptyDeltaEncoder.encode(JSON.stringify(delta)))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ClaudePtySession {
|
|
47
|
+
chatId: string
|
|
48
|
+
handle: ClaudeSessionHandle
|
|
49
|
+
baseInstance: PtyInstanceState
|
|
50
|
+
currentTurn: TurnDispatcher | null
|
|
51
|
+
turnCount: number
|
|
52
|
+
/** Pool reservation id; null when no pool was available (test/fake paths). */
|
|
53
|
+
activeTokenId: string | null
|
|
54
|
+
/** Timestamp of the last turn end (or session start). Used by idle sweeper. */
|
|
55
|
+
lastUsedAt: number
|
|
56
|
+
publishDelta: (delta: PtyInstanceDelta) => void
|
|
57
|
+
/** Stops further onUsageSample deltas (called once the session is torn down). */
|
|
58
|
+
stopUsage: () => void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface TurnDispatcher {
|
|
62
|
+
push(event: HarnessEvent): void
|
|
63
|
+
end(): void
|
|
64
|
+
stream: AsyncIterable<HarnessEvent>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createTurnDispatcher(): TurnDispatcher {
|
|
68
|
+
const queue: HarnessEvent[] = []
|
|
69
|
+
const waiters: Array<(r: IteratorResult<HarnessEvent>) => void> = []
|
|
70
|
+
let ended = false
|
|
71
|
+
|
|
72
|
+
function push(event: HarnessEvent): void {
|
|
73
|
+
if (ended) return
|
|
74
|
+
const waiter = waiters.shift()
|
|
75
|
+
if (waiter) waiter({ value: event, done: false })
|
|
76
|
+
else queue.push(event)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function end(): void {
|
|
80
|
+
if (ended) return
|
|
81
|
+
ended = true
|
|
82
|
+
while (waiters.length > 0) {
|
|
83
|
+
const waiter = waiters.shift()
|
|
84
|
+
if (waiter) waiter({ value: undefined as unknown as HarnessEvent, done: true })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const stream: AsyncIterable<HarnessEvent> = {
|
|
89
|
+
[Symbol.asyncIterator]() {
|
|
90
|
+
return {
|
|
91
|
+
next(): Promise<IteratorResult<HarnessEvent>> {
|
|
92
|
+
if (queue.length > 0) {
|
|
93
|
+
const value = queue.shift() as HarnessEvent
|
|
94
|
+
return Promise.resolve({ value, done: false })
|
|
95
|
+
}
|
|
96
|
+
if (ended) return Promise.resolve({ value: undefined as unknown as HarnessEvent, done: true })
|
|
97
|
+
return new Promise((resolve) => waiters.push(resolve))
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { push, end, stream }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const claudePtySessions = new Map<string, ClaudePtySession>()
|
|
107
|
+
|
|
108
|
+
// Single pool + registry instance shared across all turns. Set by runner.ts at boot.
|
|
109
|
+
let sharedPool: OAuthTokenPool | null = null
|
|
110
|
+
let sharedRegistry: ClaudePtyRegistry | null = null
|
|
111
|
+
|
|
112
|
+
export function configureClaudePtyFactory(args: {
|
|
113
|
+
pool: OAuthTokenPool | null
|
|
114
|
+
registry: ClaudePtyRegistry | null
|
|
115
|
+
}): void {
|
|
116
|
+
sharedPool = args.pool
|
|
117
|
+
sharedRegistry = args.registry
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function startSessionReader(session: ClaudePtySession): void {
|
|
121
|
+
void (async () => {
|
|
122
|
+
// `poolRotated` mirrors pty-responders semantics: when rate_limit /
|
|
123
|
+
// stream error marks the token limited/errored, the pool's
|
|
124
|
+
// `takeStaleOwners` already drops the reservation for this chat. The
|
|
125
|
+
// finally{} block must skip its plain `release(chatId)` in that case
|
|
126
|
+
// so it does not touch unrelated tokens (audit #9d).
|
|
127
|
+
let poolRotated = false
|
|
128
|
+
try {
|
|
129
|
+
for await (const event of session.handle.stream) {
|
|
130
|
+
if (event.type === "rate_limit" && event.rateLimit) {
|
|
131
|
+
console.log("[claude-pty/runner] rate_limit chatId=" + session.chatId, event.rateLimit)
|
|
132
|
+
if (session.activeTokenId && sharedPool) {
|
|
133
|
+
try {
|
|
134
|
+
const stale = sharedPool.takeStaleOwners(session.activeTokenId)
|
|
135
|
+
sharedPool.markLimited(session.activeTokenId, event.rateLimit.resetAt)
|
|
136
|
+
poolRotated = true
|
|
137
|
+
console.log(
|
|
138
|
+
"[claude-pty/runner] pool token " + session.activeTokenId
|
|
139
|
+
+ " marked limited until " + event.rateLimit.resetAt
|
|
140
|
+
+ "; stale owners=" + stale.length,
|
|
141
|
+
)
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.warn(
|
|
144
|
+
"[claude-pty/runner] markLimited failed token=" + session.activeTokenId,
|
|
145
|
+
err instanceof Error ? err.message : String(err),
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
session.currentTurn?.push(event)
|
|
151
|
+
const entry = (event as { entry?: { kind?: string } }).entry
|
|
152
|
+
if (entry?.kind === "result") {
|
|
153
|
+
session.lastUsedAt = Date.now()
|
|
154
|
+
session.currentTurn?.end()
|
|
155
|
+
session.currentTurn = null
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
160
|
+
console.warn("[claude-pty/runner] session reader error:", message)
|
|
161
|
+
if (session.activeTokenId && sharedPool) {
|
|
162
|
+
try {
|
|
163
|
+
const stale = sharedPool.takeStaleOwners(session.activeTokenId)
|
|
164
|
+
sharedPool.markError(session.activeTokenId, message)
|
|
165
|
+
poolRotated = true
|
|
166
|
+
console.log(
|
|
167
|
+
"[claude-pty/runner] pool token " + session.activeTokenId
|
|
168
|
+
+ " marked error; stale owners=" + stale.length,
|
|
169
|
+
)
|
|
170
|
+
} catch (markErr) {
|
|
171
|
+
console.warn(
|
|
172
|
+
"[claude-pty/runner] markError failed token=" + session.activeTokenId,
|
|
173
|
+
markErr instanceof Error ? markErr.message : String(markErr),
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
session.stopUsage()
|
|
179
|
+
session.currentTurn?.end()
|
|
180
|
+
if (claudePtySessions.get(session.chatId) === session) {
|
|
181
|
+
claudePtySessions.delete(session.chatId)
|
|
182
|
+
}
|
|
183
|
+
if (!poolRotated && session.activeTokenId && sharedPool) {
|
|
184
|
+
try { sharedPool.release(session.chatId) } catch { /* swallow */ }
|
|
185
|
+
}
|
|
186
|
+
session.publishDelta({ type: "removed", chatId: session.chatId })
|
|
187
|
+
}
|
|
188
|
+
})()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function stopClaudePtySession(chatId: string): void {
|
|
192
|
+
const session = claudePtySessions.get(chatId)
|
|
193
|
+
if (!session) return
|
|
194
|
+
try { session.handle.close() } catch { /* swallow */ }
|
|
195
|
+
// Map entry + pool release will be cleared by the background reader's
|
|
196
|
+
// finally{} block once the stream observes the close. Delete eagerly so
|
|
197
|
+
// a racing `start_turn` for the same chatId does not reuse this handle.
|
|
198
|
+
claudePtySessions.delete(chatId)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function stopAllClaudePtySessions(): void {
|
|
202
|
+
for (const [, session] of claudePtySessions) {
|
|
203
|
+
try { session.handle.close() } catch { /* swallow */ }
|
|
204
|
+
}
|
|
205
|
+
claudePtySessions.clear()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Test-only inspector for the live session map. */
|
|
209
|
+
export function getClaudePtySessionChatIds(): string[] {
|
|
210
|
+
return [...claudePtySessions.keys()]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Close any session whose `lastUsedAt` is older than `idleMs` and has no
|
|
215
|
+
* active turn. Mirrors kanna's `sweepIdleClaudeSessions`. Runs from a
|
|
216
|
+
* setInterval in runner.ts; the runner owns timer cadence.
|
|
217
|
+
*/
|
|
218
|
+
export function sweepIdleClaudePtySessions(now: number, idleMs: number): number {
|
|
219
|
+
let closed = 0
|
|
220
|
+
for (const [chatId, session] of [...claudePtySessions.entries()]) {
|
|
221
|
+
if (session.currentTurn !== null) continue
|
|
222
|
+
if (now - session.lastUsedAt < idleMs) continue
|
|
223
|
+
console.log("[claude-pty/runner] idle TTL eviction chatId=" + chatId + " idleMs=" + (now - session.lastUsedAt))
|
|
224
|
+
try { session.handle.close() } catch { /* swallow */ }
|
|
225
|
+
claudePtySessions.delete(chatId)
|
|
226
|
+
closed += 1
|
|
227
|
+
}
|
|
228
|
+
return closed
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let codexManager: CodexAppServerManager | null = null
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Resolve the codex binary runner-side by checking PATH, then falling back to
|
|
235
|
+
* the well-known `codex` name (lets the shell find it via the runner's PATH).
|
|
236
|
+
* Always runner-local — never relies on a server-sent binaryPath.
|
|
237
|
+
*/
|
|
238
|
+
function resolveCodexBinary(extraEnv?: Record<string, string>): string {
|
|
239
|
+
// Runner-local resolution: NEVER let a server-supplied PATH redirect which
|
|
240
|
+
// binary we run (security M1) — strip PATH so the runner's own PATH decides.
|
|
241
|
+
const { PATH: _serverPath, ...safeEnv } = extraEnv ?? {}
|
|
242
|
+
const env = { ...process.env, ...safeEnv }
|
|
243
|
+
const which = spawnSync("which", ["codex"], { encoding: "utf-8", timeout: 3000, env })
|
|
244
|
+
if (which.status === 0) {
|
|
245
|
+
const p = which.stdout.trim()
|
|
246
|
+
if (p) return p
|
|
247
|
+
}
|
|
248
|
+
// Fall back: let the OS resolve it at spawn time
|
|
249
|
+
return "codex"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// NOTE: the codex manager is a once-per-runner-lifetime singleton — the first
|
|
253
|
+
// turn's (PATH-stripped) extraEnv fixes the manager. The binary is always
|
|
254
|
+
// runner-local (PATH stripped above), so this cannot be steered by the server.
|
|
255
|
+
function getCodexManager(extraEnv?: Record<string, string>): CodexAppServerManager {
|
|
256
|
+
if (!codexManager) {
|
|
257
|
+
// Strip server-supplied PATH from the spawned process env too (M1).
|
|
258
|
+
const { PATH: _serverPath, ...safeEnv } = extraEnv ?? {}
|
|
259
|
+
const binaryPath = resolveCodexBinary(safeEnv)
|
|
260
|
+
codexManager = new CodexAppServerManager({ binaryPath, extraEnv: safeEnv })
|
|
261
|
+
}
|
|
262
|
+
return codexManager
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function startCodexTurn(args: {
|
|
266
|
+
chatId: string
|
|
267
|
+
content: string
|
|
268
|
+
localPath: string
|
|
269
|
+
model: string
|
|
270
|
+
effort?: string
|
|
271
|
+
serviceTier?: "fast"
|
|
272
|
+
planMode: boolean
|
|
273
|
+
sessionToken: string | null
|
|
274
|
+
onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
|
|
275
|
+
extraEnv?: Record<string, string>
|
|
276
|
+
}): Promise<HarnessTurn> {
|
|
277
|
+
const manager = getCodexManager(args.extraEnv)
|
|
278
|
+
|
|
279
|
+
await manager.startSession({
|
|
280
|
+
chatId: args.chatId,
|
|
281
|
+
cwd: args.localPath,
|
|
282
|
+
model: args.model,
|
|
283
|
+
serviceTier: args.serviceTier as ServiceTier | undefined,
|
|
284
|
+
sessionToken: args.sessionToken,
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
return await manager.startTurn({
|
|
288
|
+
chatId: args.chatId,
|
|
289
|
+
content: args.content,
|
|
290
|
+
model: args.model,
|
|
291
|
+
effort: args.effort as CodexReasoningEffort | undefined,
|
|
292
|
+
serviceTier: args.serviceTier as ServiceTier | undefined,
|
|
293
|
+
planMode: args.planMode,
|
|
294
|
+
onToolRequest: args.onToolRequest,
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function buildPoolUnavailableMessage(reservedFor: string): string {
|
|
299
|
+
if (!sharedPool) return "OAuth pool is not configured on the runner."
|
|
300
|
+
if (!sharedPool.hasAnyToken()) {
|
|
301
|
+
return "No OAuth pool tokens configured. Add one under Settings → Claude PTY."
|
|
302
|
+
}
|
|
303
|
+
const summary = sharedPool
|
|
304
|
+
.describeUnavailability(reservedFor)
|
|
305
|
+
.filter((u) => u.reason !== "available")
|
|
306
|
+
.map((u) => (u.label || u.tokenId.slice(0, 8)) + ": " + u.reason)
|
|
307
|
+
.join("; ")
|
|
308
|
+
return "No usable OAuth pool token (" + (summary || "all tokens unavailable") + ")."
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function startClaudePtyTurn(args: {
|
|
312
|
+
chatId: string
|
|
313
|
+
content: string
|
|
314
|
+
projectId?: string
|
|
315
|
+
localPath: string
|
|
316
|
+
model: string
|
|
317
|
+
effort?: string
|
|
318
|
+
planMode: boolean
|
|
319
|
+
sessionToken: string | null
|
|
320
|
+
onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
|
|
321
|
+
nc?: NatsConnection
|
|
322
|
+
}): Promise<HarnessTurn> {
|
|
323
|
+
let session = claudePtySessions.get(args.chatId)
|
|
324
|
+
|
|
325
|
+
if (!session) {
|
|
326
|
+
if (!sharedPool) {
|
|
327
|
+
throw new Error("Claude PTY runner is not wired to an OAuthTokenPool. Configure via configureClaudePtyFactory().")
|
|
328
|
+
}
|
|
329
|
+
if (!sharedPool.hasAnyToken()) {
|
|
330
|
+
throw new Error("No OAuth pool tokens configured. Add one under Settings → Claude PTY.")
|
|
331
|
+
}
|
|
332
|
+
const picked = sharedPool.pickActive(args.chatId)
|
|
333
|
+
if (!picked) {
|
|
334
|
+
throw new Error(buildPoolUnavailableMessage(args.chatId))
|
|
335
|
+
}
|
|
336
|
+
sharedPool.markUsed(picked.id)
|
|
337
|
+
|
|
338
|
+
const now = Date.now()
|
|
339
|
+
const baseInstance: PtyInstanceState = {
|
|
340
|
+
chatId: args.chatId,
|
|
341
|
+
sessionId: null,
|
|
342
|
+
pid: null,
|
|
343
|
+
cwd: args.localPath,
|
|
344
|
+
model: args.model,
|
|
345
|
+
accountLabel: picked.label,
|
|
346
|
+
oauthMasked: null,
|
|
347
|
+
phase: "spawning",
|
|
348
|
+
startedAt: now,
|
|
349
|
+
lastEventAt: now,
|
|
350
|
+
turnCount: 0,
|
|
351
|
+
tokensIn: 0,
|
|
352
|
+
tokensOut: 0,
|
|
353
|
+
planMode: args.planMode,
|
|
354
|
+
smokeTest: null,
|
|
355
|
+
outputRingTail: null,
|
|
356
|
+
exitedAt: null,
|
|
357
|
+
exitCode: null,
|
|
358
|
+
rssBytes: null,
|
|
359
|
+
rssPeakBytes: null,
|
|
360
|
+
cpuPercent: null,
|
|
361
|
+
cpuPeakPercent: null,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function publishDelta(delta: PtyInstanceDelta): void {
|
|
365
|
+
if (!args.nc) return
|
|
366
|
+
try {
|
|
367
|
+
args.nc.publish(ptyDeltaSubject(), encodeDelta(delta))
|
|
368
|
+
} catch (err) {
|
|
369
|
+
console.warn("[claude-pty/runner] pty.delta publish failed:", err instanceof Error ? err.message : String(err))
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
publishDelta({ type: "added", instance: baseInstance })
|
|
374
|
+
|
|
375
|
+
// `baseInstance` is the canonical, mutable record for this session: phase
|
|
376
|
+
// transitions and usage samples both write into it so every published
|
|
377
|
+
// delta stays internally coherent. `usageStopped` suppresses late sampler
|
|
378
|
+
// ticks (spawn-failure or post-teardown) that would otherwise resurrect a
|
|
379
|
+
// removed instance in the client store.
|
|
380
|
+
let usageStopped = false
|
|
381
|
+
const onUsageSample = (usage: PtyUsageSample): void => {
|
|
382
|
+
if (usageStopped) return
|
|
383
|
+
baseInstance.rssBytes = usage.rssBytes
|
|
384
|
+
baseInstance.rssPeakBytes = usage.rssPeakBytes
|
|
385
|
+
baseInstance.cpuPercent = usage.cpuPercent
|
|
386
|
+
baseInstance.cpuPeakPercent = usage.cpuPeakPercent
|
|
387
|
+
baseInstance.lastEventAt = Date.now()
|
|
388
|
+
publishDelta({ type: "updated", instance: { ...baseInstance } })
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let handle: ClaudeSessionHandle
|
|
392
|
+
try {
|
|
393
|
+
handle = await startClaudeSessionPTY({
|
|
394
|
+
chatId: args.chatId,
|
|
395
|
+
projectId: args.projectId ?? args.chatId,
|
|
396
|
+
localPath: args.localPath,
|
|
397
|
+
model: args.model,
|
|
398
|
+
effort: args.effort,
|
|
399
|
+
planMode: args.planMode,
|
|
400
|
+
sessionToken: args.sessionToken,
|
|
401
|
+
forkSession: false,
|
|
402
|
+
oauthToken: picked.token,
|
|
403
|
+
oauthLabel: picked.label,
|
|
404
|
+
ptyRegistry: sharedRegistry ?? undefined,
|
|
405
|
+
onUsageSample,
|
|
406
|
+
onToolRequest: args.onToolRequest as never,
|
|
407
|
+
})
|
|
408
|
+
} catch (err) {
|
|
409
|
+
usageStopped = true
|
|
410
|
+
publishDelta({ type: "removed", chatId: args.chatId })
|
|
411
|
+
try { sharedPool.release(args.chatId) } catch { /* swallow */ }
|
|
412
|
+
throw err
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
baseInstance.phase = "ready"
|
|
416
|
+
baseInstance.lastEventAt = Date.now()
|
|
417
|
+
publishDelta({ type: "updated", instance: { ...baseInstance } })
|
|
418
|
+
|
|
419
|
+
session = {
|
|
420
|
+
chatId: args.chatId,
|
|
421
|
+
handle,
|
|
422
|
+
baseInstance,
|
|
423
|
+
currentTurn: null,
|
|
424
|
+
turnCount: 0,
|
|
425
|
+
activeTokenId: picked.id,
|
|
426
|
+
lastUsedAt: Date.now(),
|
|
427
|
+
publishDelta,
|
|
428
|
+
stopUsage: () => { usageStopped = true },
|
|
429
|
+
}
|
|
430
|
+
claudePtySessions.set(args.chatId, session)
|
|
431
|
+
startSessionReader(session)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const dispatcher = createTurnDispatcher()
|
|
435
|
+
session.currentTurn = dispatcher
|
|
436
|
+
session.turnCount += 1
|
|
437
|
+
session.lastUsedAt = Date.now()
|
|
438
|
+
session.baseInstance.phase = "streaming"
|
|
439
|
+
session.baseInstance.lastEventAt = Date.now()
|
|
440
|
+
session.baseInstance.turnCount = session.turnCount
|
|
441
|
+
session.publishDelta({ type: "updated", instance: { ...session.baseInstance } })
|
|
442
|
+
|
|
443
|
+
await session.handle.sendPrompt(args.content)
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
provider: "claude-pty",
|
|
447
|
+
stream: dispatcher.stream,
|
|
448
|
+
interrupt: async () => {
|
|
449
|
+
try {
|
|
450
|
+
await session!.handle.interrupt()
|
|
451
|
+
} finally {
|
|
452
|
+
dispatcher.end()
|
|
453
|
+
if (session!.currentTurn === dispatcher) {
|
|
454
|
+
session!.currentTurn = null
|
|
455
|
+
session!.lastUsedAt = Date.now()
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
close: () => {
|
|
460
|
+
dispatcher.end()
|
|
461
|
+
if (session!.currentTurn === dispatcher) {
|
|
462
|
+
session!.currentTurn = null
|
|
463
|
+
session!.lastUsedAt = Date.now()
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function stopCodexSession(chatId: string): void {
|
|
470
|
+
codexManager?.stopSession(chatId)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function stopAllCodexSessions(): void {
|
|
474
|
+
codexManager?.stopAll()
|
|
475
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, test, expect, afterEach } from "bun:test"
|
|
2
|
+
import { EventStore } from "./event-store"
|
|
3
|
+
import { deriveAgentConfigSnapshot } from "./read-models"
|
|
4
|
+
import type { AgentConfig } from "../shared/agent-config-types"
|
|
5
|
+
import { rmSync, mkdirSync } from "node:fs"
|
|
6
|
+
import { join } from "node:path"
|
|
7
|
+
|
|
8
|
+
const TEST_DIR = join(import.meta.dir, ".test-agent-config-journey")
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
rmSync(TEST_DIR, { recursive: true, force: true })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
async function createStoreWithProject() {
|
|
15
|
+
mkdirSync(TEST_DIR, { recursive: true })
|
|
16
|
+
const store = new EventStore(TEST_DIR)
|
|
17
|
+
await store.initialize()
|
|
18
|
+
const project = await store.openProject("/tmp/agent-config-journey-test", "AgentConfigJourney")
|
|
19
|
+
return { store, workspaceId: project.id }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeConfig(overrides: Partial<AgentConfig> = {}): AgentConfig {
|
|
23
|
+
return {
|
|
24
|
+
id: "cfg-1",
|
|
25
|
+
name: "Test Agent",
|
|
26
|
+
description: "A test agent config",
|
|
27
|
+
provider: "claude",
|
|
28
|
+
model: "claude-sonnet-4-20250514",
|
|
29
|
+
...overrides,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("agent config journey", () => {
|
|
34
|
+
test("stage 3: save agent config appears in snapshot", async () => {
|
|
35
|
+
const { store, workspaceId } = await createStoreWithProject()
|
|
36
|
+
const config = makeConfig()
|
|
37
|
+
|
|
38
|
+
await store.saveAgentConfig(workspaceId, config.id, config)
|
|
39
|
+
|
|
40
|
+
const snapshot = deriveAgentConfigSnapshot(store.state, workspaceId)
|
|
41
|
+
expect(snapshot.workspaceId).toBe(workspaceId)
|
|
42
|
+
expect(snapshot.configs).toHaveLength(1)
|
|
43
|
+
expect(snapshot.configs[0].id).toBe(config.id)
|
|
44
|
+
expect(snapshot.configs[0].config.name).toBe("Test Agent")
|
|
45
|
+
expect(snapshot.configs[0].config.provider).toBe("claude")
|
|
46
|
+
expect(snapshot.configs[0].workspaceId).toBe(workspaceId)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test("stage 4: update agent config (save again) updates in snapshot", async () => {
|
|
50
|
+
const { store, workspaceId } = await createStoreWithProject()
|
|
51
|
+
const config = makeConfig()
|
|
52
|
+
|
|
53
|
+
await store.saveAgentConfig(workspaceId, config.id, config)
|
|
54
|
+
await new Promise((r) => setTimeout(r, 5))
|
|
55
|
+
const updated = makeConfig({ name: "Updated Agent", model: "claude-opus-4-20250514" })
|
|
56
|
+
await store.saveAgentConfig(workspaceId, config.id, updated)
|
|
57
|
+
|
|
58
|
+
const snapshot = deriveAgentConfigSnapshot(store.state, workspaceId)
|
|
59
|
+
expect(snapshot.configs).toHaveLength(1)
|
|
60
|
+
expect(snapshot.configs[0].config.name).toBe("Updated Agent")
|
|
61
|
+
expect(snapshot.configs[0].config.model).toBe("claude-opus-4-20250514")
|
|
62
|
+
expect(snapshot.configs[0].updatedAt).toBeGreaterThan(snapshot.configs[0].createdAt)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("stage 5: remove agent config disappears from snapshot", async () => {
|
|
66
|
+
const { store, workspaceId } = await createStoreWithProject()
|
|
67
|
+
const config = makeConfig()
|
|
68
|
+
|
|
69
|
+
await store.saveAgentConfig(workspaceId, config.id, config)
|
|
70
|
+
expect(deriveAgentConfigSnapshot(store.state, workspaceId).configs).toHaveLength(1)
|
|
71
|
+
|
|
72
|
+
await store.removeAgentConfig(workspaceId, config.id)
|
|
73
|
+
const snapshot = deriveAgentConfigSnapshot(store.state, workspaceId)
|
|
74
|
+
expect(snapshot.configs).toHaveLength(0)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("agent config survives compact and replay", async () => {
|
|
78
|
+
const { store, workspaceId } = await createStoreWithProject()
|
|
79
|
+
const config = makeConfig({ id: "cfg-persist", name: "Persistent Agent" })
|
|
80
|
+
|
|
81
|
+
await store.saveAgentConfig(workspaceId, config.id, config)
|
|
82
|
+
const before = deriveAgentConfigSnapshot(store.state, workspaceId)
|
|
83
|
+
expect(before.configs).toHaveLength(1)
|
|
84
|
+
|
|
85
|
+
await store.compact()
|
|
86
|
+
|
|
87
|
+
const store2 = new EventStore(TEST_DIR)
|
|
88
|
+
await store2.initialize()
|
|
89
|
+
|
|
90
|
+
const after = deriveAgentConfigSnapshot(store2.state, workspaceId)
|
|
91
|
+
expect(after.configs).toHaveLength(1)
|
|
92
|
+
expect(after.configs[0].id).toBe("cfg-persist")
|
|
93
|
+
expect(after.configs[0].config.name).toBe("Persistent Agent")
|
|
94
|
+
expect(after.configs[0].config.provider).toBe("claude")
|
|
95
|
+
expect(after.configs[0].workspaceId).toBe(workspaceId)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("empty workspace returns empty configs", async () => {
|
|
99
|
+
const { store, workspaceId } = await createStoreWithProject()
|
|
100
|
+
|
|
101
|
+
const snapshot = deriveAgentConfigSnapshot(store.state, workspaceId)
|
|
102
|
+
expect(snapshot.workspaceId).toBe(workspaceId)
|
|
103
|
+
expect(snapshot.configs).toHaveLength(0)
|
|
104
|
+
expect(snapshot.lastUpdated).toBe(new Date(0).toISOString())
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backward-compatible re-exports.
|
|
3
|
+
*
|
|
4
|
+
* AgentCoordinator was removed — all turn execution is delegated to the
|
|
5
|
+
* runner process via RunnerProxy. Only the shared transcript helpers
|
|
6
|
+
* survive, now canonical in src/shared/transcript-entries.ts.
|
|
7
|
+
*/
|
|
8
|
+
export { timestamped, discardedToolResult } from "../shared/transcript-entries"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface AuthErrorDetection {
|
|
2
|
+
chatId: string
|
|
3
|
+
reason: string
|
|
4
|
+
raw: unknown
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ErrorLike {
|
|
8
|
+
message?: string
|
|
9
|
+
status?: number
|
|
10
|
+
api_error_status?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Strings the Claude CLI / Anthropic API emit when an OAuth token is
|
|
14
|
+
// rejected. Covers both the JSON error envelope (`authentication_error`)
|
|
15
|
+
// and the CLI's surfaced result text (`Failed to authenticate.`). The
|
|
16
|
+
// `api_error_status: 401` form appears in JSONL `result` entries from
|
|
17
|
+
// the CLI when subscription auth fails.
|
|
18
|
+
const AUTH_ERROR_PATTERNS = [
|
|
19
|
+
/api_error_status[^,}]*\s*:\s*401/i,
|
|
20
|
+
/401\s+Invalid authentication credentials/i,
|
|
21
|
+
/Failed to authenticate\.\s*API Error:\s*401/i,
|
|
22
|
+
/"type"\s*:\s*"authentication_error"/i,
|
|
23
|
+
/"error"\s*:\s*"authentication_failed"/i,
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
function isAuthErrorText(text: string): boolean {
|
|
27
|
+
return AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(text))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ClaudeAuthErrorDetector {
|
|
31
|
+
/**
|
|
32
|
+
* Inspect a thrown error (from the SDK `query()` stream) for OAuth/auth
|
|
33
|
+
* failure signals. Returns a detection when the token used for the
|
|
34
|
+
* spawn has been rejected by the API — caller should mark the token as
|
|
35
|
+
* errored and rotate.
|
|
36
|
+
*/
|
|
37
|
+
detect(chatId: string, error: unknown): AuthErrorDetection | null {
|
|
38
|
+
if (!error) return null
|
|
39
|
+
const e = error as ErrorLike
|
|
40
|
+
if (e.status === 401 || e.api_error_status === 401) {
|
|
41
|
+
return { chatId, reason: this.summarize(e.message), raw: error }
|
|
42
|
+
}
|
|
43
|
+
const message = typeof e.message === "string" ? e.message : null
|
|
44
|
+
if (message && isAuthErrorText(message)) {
|
|
45
|
+
return { chatId, reason: this.summarize(message), raw: error }
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Inspect the textual `result` field of a CLI JSONL `result` entry
|
|
52
|
+
* (Claude Code's subprocess-level error surface). The CLI emits
|
|
53
|
+
* `"api_error_status":401` and `"Failed to authenticate. API Error: 401
|
|
54
|
+
* Invalid authentication credentials"` for OAuth rejection.
|
|
55
|
+
*/
|
|
56
|
+
detectFromResultText(chatId: string, text: string): AuthErrorDetection | null {
|
|
57
|
+
if (typeof text !== "string" || text.length === 0) return null
|
|
58
|
+
if (!isAuthErrorText(text)) return null
|
|
59
|
+
return { chatId, reason: this.summarize(text), raw: text }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private summarize(message: string | undefined): string {
|
|
63
|
+
if (!message) return "401 authentication error"
|
|
64
|
+
return message.length > 200 ? `${message.slice(0, 200)}…` : message
|
|
65
|
+
}
|
|
66
|
+
}
|