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,354 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import process from "node:process"
|
|
3
|
+
import defaultShell, { detectDefaultShell } from "default-shell"
|
|
4
|
+
import { Terminal } from "@xterm/headless"
|
|
5
|
+
import { SerializeAddon } from "@xterm/addon-serialize"
|
|
6
|
+
import type { TerminalEvent, TerminalSnapshot } from "../shared/protocol"
|
|
7
|
+
|
|
8
|
+
const DEFAULT_COLS = 80
|
|
9
|
+
const DEFAULT_ROWS = 24
|
|
10
|
+
const DEFAULT_SCROLLBACK = 1_000
|
|
11
|
+
const MIN_SCROLLBACK = 500
|
|
12
|
+
const MAX_SCROLLBACK = 5_000
|
|
13
|
+
const FOCUS_IN_SEQUENCE = "\x1b[I"
|
|
14
|
+
const FOCUS_OUT_SEQUENCE = "\x1b[O"
|
|
15
|
+
const MODE_SEQUENCE_TAIL_LENGTH = 16
|
|
16
|
+
|
|
17
|
+
interface CreateTerminalArgs {
|
|
18
|
+
workspacePath: string
|
|
19
|
+
terminalId: string
|
|
20
|
+
cols: number
|
|
21
|
+
rows: number
|
|
22
|
+
scrollback: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TerminalSession {
|
|
26
|
+
terminalId: string
|
|
27
|
+
title: string
|
|
28
|
+
cwd: string
|
|
29
|
+
shell: string
|
|
30
|
+
cols: number
|
|
31
|
+
rows: number
|
|
32
|
+
scrollback: number
|
|
33
|
+
status: "running" | "exited"
|
|
34
|
+
exitCode: number | null
|
|
35
|
+
process: Bun.Subprocess | null
|
|
36
|
+
terminal: Bun.Terminal
|
|
37
|
+
headless: Terminal
|
|
38
|
+
serializeAddon: SerializeAddon
|
|
39
|
+
focusReportingEnabled: boolean
|
|
40
|
+
modeSequenceTail: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function clampScrollback(value: number) {
|
|
44
|
+
if (!Number.isFinite(value)) return DEFAULT_SCROLLBACK
|
|
45
|
+
return Math.min(MAX_SCROLLBACK, Math.max(MIN_SCROLLBACK, Math.round(value)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeTerminalDimension(value: number, fallback: number) {
|
|
49
|
+
if (!Number.isFinite(value)) return fallback
|
|
50
|
+
return Math.max(1, Math.round(value))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveShell() {
|
|
54
|
+
if (process.env.TINKARIA_SHELL) {
|
|
55
|
+
return process.env.TINKARIA_SHELL
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
return detectDefaultShell()
|
|
60
|
+
} catch {
|
|
61
|
+
if (defaultShell) return defaultShell
|
|
62
|
+
if (process.platform === "win32") {
|
|
63
|
+
return process.env.ComSpec || "cmd.exe"
|
|
64
|
+
}
|
|
65
|
+
return process.env.SHELL || "/bin/sh"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveShellArgs(shellPath: string) {
|
|
70
|
+
if (process.platform === "win32") {
|
|
71
|
+
return []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const shellName = path.basename(shellPath)
|
|
75
|
+
if (["bash", "zsh", "fish", "sh", "ksh"].includes(shellName)) {
|
|
76
|
+
return ["-l"]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function createTerminalEnv() {
|
|
83
|
+
return {
|
|
84
|
+
...process.env,
|
|
85
|
+
TERM: "xterm-256color",
|
|
86
|
+
COLORTERM: "truecolor",
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function updateFocusReportingState(session: Pick<TerminalSession, "focusReportingEnabled" | "modeSequenceTail">, chunk: string) {
|
|
91
|
+
const combined = session.modeSequenceTail + chunk
|
|
92
|
+
const regex = /\x1b\[\?1004([hl])/g
|
|
93
|
+
|
|
94
|
+
for (const match of combined.matchAll(regex)) {
|
|
95
|
+
session.focusReportingEnabled = match[1] === "h"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
session.modeSequenceTail = combined.slice(-MODE_SEQUENCE_TAIL_LENGTH)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function filterFocusReportInput(data: string, allowFocusReporting: boolean) {
|
|
102
|
+
if (allowFocusReporting) {
|
|
103
|
+
return data
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return data.replaceAll(FOCUS_IN_SEQUENCE, "").replaceAll(FOCUS_OUT_SEQUENCE, "")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function killTerminalProcessTree(subprocess: Bun.Subprocess | null) {
|
|
110
|
+
if (!subprocess) return
|
|
111
|
+
|
|
112
|
+
const pid = subprocess.pid
|
|
113
|
+
if (typeof pid !== "number") return
|
|
114
|
+
|
|
115
|
+
if (process.platform !== "win32") {
|
|
116
|
+
try {
|
|
117
|
+
process.kill(-pid, "SIGKILL")
|
|
118
|
+
return
|
|
119
|
+
} catch {
|
|
120
|
+
// Fall back to killing only the shell process if group termination fails.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
subprocess.kill("SIGKILL")
|
|
126
|
+
} catch {
|
|
127
|
+
// Ignore subprocess shutdown errors during disposal.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function signalTerminalProcessGroup(subprocess: Bun.Subprocess | null, signal: NodeJS.Signals) {
|
|
132
|
+
if (!subprocess) return false
|
|
133
|
+
|
|
134
|
+
const pid = subprocess.pid
|
|
135
|
+
if (typeof pid !== "number") return false
|
|
136
|
+
|
|
137
|
+
if (process.platform !== "win32") {
|
|
138
|
+
try {
|
|
139
|
+
process.kill(-pid, signal)
|
|
140
|
+
return true
|
|
141
|
+
} catch {
|
|
142
|
+
// Fall back to signaling only the shell if group signaling fails.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
subprocess.kill(signal)
|
|
148
|
+
return true
|
|
149
|
+
} catch {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class TerminalManager {
|
|
155
|
+
private readonly sessions = new Map<string, TerminalSession>()
|
|
156
|
+
private readonly listeners = new Set<(event: TerminalEvent) => void>()
|
|
157
|
+
|
|
158
|
+
onEvent(listener: (event: TerminalEvent) => void) {
|
|
159
|
+
this.listeners.add(listener)
|
|
160
|
+
return () => {
|
|
161
|
+
this.listeners.delete(listener)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
createTerminal(args: CreateTerminalArgs) {
|
|
166
|
+
if (process.platform === "win32") {
|
|
167
|
+
throw new Error("Embedded terminal is currently supported on macOS/Linux only.")
|
|
168
|
+
}
|
|
169
|
+
if (typeof Bun.Terminal !== "function") {
|
|
170
|
+
throw new Error("Embedded terminal requires Bun 1.3.5+ with Bun.Terminal support.")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const existing = this.sessions.get(args.terminalId)
|
|
174
|
+
if (existing) {
|
|
175
|
+
existing.scrollback = clampScrollback(args.scrollback)
|
|
176
|
+
existing.cols = normalizeTerminalDimension(args.cols, existing.cols)
|
|
177
|
+
existing.rows = normalizeTerminalDimension(args.rows, existing.rows)
|
|
178
|
+
existing.headless.options.scrollback = existing.scrollback
|
|
179
|
+
existing.headless.resize(existing.cols, existing.rows)
|
|
180
|
+
existing.terminal.resize(existing.cols, existing.rows)
|
|
181
|
+
signalTerminalProcessGroup(existing.process, "SIGWINCH")
|
|
182
|
+
return this.snapshotOf(existing)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const shell = resolveShell()
|
|
186
|
+
const cols = normalizeTerminalDimension(args.cols, DEFAULT_COLS)
|
|
187
|
+
const rows = normalizeTerminalDimension(args.rows, DEFAULT_ROWS)
|
|
188
|
+
const scrollback = clampScrollback(args.scrollback)
|
|
189
|
+
const title = path.basename(shell) || "shell"
|
|
190
|
+
const headless = new Terminal({ cols, rows, scrollback, allowProposedApi: true })
|
|
191
|
+
const serializeAddon = new SerializeAddon()
|
|
192
|
+
headless.loadAddon(serializeAddon)
|
|
193
|
+
|
|
194
|
+
const session: TerminalSession = {
|
|
195
|
+
terminalId: args.terminalId,
|
|
196
|
+
title,
|
|
197
|
+
cwd: args.workspacePath,
|
|
198
|
+
shell,
|
|
199
|
+
cols,
|
|
200
|
+
rows,
|
|
201
|
+
scrollback,
|
|
202
|
+
status: "running",
|
|
203
|
+
exitCode: null,
|
|
204
|
+
process: null,
|
|
205
|
+
terminal: new Bun.Terminal({
|
|
206
|
+
cols,
|
|
207
|
+
rows,
|
|
208
|
+
name: "xterm-256color",
|
|
209
|
+
data: (_terminal, data) => {
|
|
210
|
+
const chunk = Buffer.from(data).toString("utf8")
|
|
211
|
+
updateFocusReportingState(session, chunk)
|
|
212
|
+
headless.write(chunk)
|
|
213
|
+
this.emit({
|
|
214
|
+
type: "terminal.output",
|
|
215
|
+
terminalId: args.terminalId,
|
|
216
|
+
data: chunk,
|
|
217
|
+
})
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
headless,
|
|
221
|
+
serializeAddon,
|
|
222
|
+
focusReportingEnabled: false,
|
|
223
|
+
modeSequenceTail: "",
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
session.process = Bun.spawn([shell, ...resolveShellArgs(shell)], {
|
|
228
|
+
cwd: args.workspacePath,
|
|
229
|
+
env: createTerminalEnv(),
|
|
230
|
+
terminal: session.terminal,
|
|
231
|
+
})
|
|
232
|
+
} catch (error) {
|
|
233
|
+
session.terminal.close()
|
|
234
|
+
session.serializeAddon.dispose()
|
|
235
|
+
session.headless.dispose()
|
|
236
|
+
throw error
|
|
237
|
+
}
|
|
238
|
+
void session.process.exited.then((exitCode) => {
|
|
239
|
+
const active = this.sessions.get(args.terminalId)
|
|
240
|
+
if (!active) return
|
|
241
|
+
active.status = "exited"
|
|
242
|
+
active.exitCode = exitCode
|
|
243
|
+
this.emit({
|
|
244
|
+
type: "terminal.exit",
|
|
245
|
+
terminalId: args.terminalId,
|
|
246
|
+
exitCode,
|
|
247
|
+
})
|
|
248
|
+
}).catch((error) => {
|
|
249
|
+
const active = this.sessions.get(args.terminalId)
|
|
250
|
+
if (!active) return
|
|
251
|
+
active.status = "exited"
|
|
252
|
+
active.exitCode = 1
|
|
253
|
+
this.emit({
|
|
254
|
+
type: "terminal.output",
|
|
255
|
+
terminalId: args.terminalId,
|
|
256
|
+
data: `\r\n[terminal error] ${error instanceof Error ? error.message : String(error)}\r\n`,
|
|
257
|
+
})
|
|
258
|
+
this.emit({
|
|
259
|
+
type: "terminal.exit",
|
|
260
|
+
terminalId: args.terminalId,
|
|
261
|
+
exitCode: 1,
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
this.sessions.set(args.terminalId, session)
|
|
266
|
+
return this.snapshotOf(session)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
getSnapshot(terminalId: string): TerminalSnapshot | null {
|
|
270
|
+
const session = this.sessions.get(terminalId)
|
|
271
|
+
return session ? this.snapshotOf(session) : null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
write(terminalId: string, data: string) {
|
|
275
|
+
const session = this.sessions.get(terminalId)
|
|
276
|
+
if (!session || session.status === "exited") return
|
|
277
|
+
|
|
278
|
+
const filteredData = filterFocusReportInput(data, session.focusReportingEnabled)
|
|
279
|
+
if (!filteredData) return
|
|
280
|
+
|
|
281
|
+
let cursor = 0
|
|
282
|
+
|
|
283
|
+
while (cursor < filteredData.length) {
|
|
284
|
+
const ctrlCIndex = filteredData.indexOf("\x03", cursor)
|
|
285
|
+
|
|
286
|
+
if (ctrlCIndex === -1) {
|
|
287
|
+
session.terminal.write(filteredData.slice(cursor))
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (ctrlCIndex > cursor) {
|
|
292
|
+
session.terminal.write(filteredData.slice(cursor, ctrlCIndex))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
signalTerminalProcessGroup(session.process, "SIGINT")
|
|
296
|
+
cursor = ctrlCIndex + 1
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
resize(terminalId: string, cols: number, rows: number) {
|
|
301
|
+
const session = this.sessions.get(terminalId)
|
|
302
|
+
if (!session) return
|
|
303
|
+
session.cols = normalizeTerminalDimension(cols, session.cols)
|
|
304
|
+
session.rows = normalizeTerminalDimension(rows, session.rows)
|
|
305
|
+
session.headless.resize(session.cols, session.rows)
|
|
306
|
+
session.terminal.resize(session.cols, session.rows)
|
|
307
|
+
signalTerminalProcessGroup(session.process, "SIGWINCH")
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
close(terminalId: string) {
|
|
311
|
+
const session = this.sessions.get(terminalId)
|
|
312
|
+
if (!session) return
|
|
313
|
+
|
|
314
|
+
this.sessions.delete(terminalId)
|
|
315
|
+
killTerminalProcessTree(session.process)
|
|
316
|
+
session.terminal.close()
|
|
317
|
+
session.serializeAddon.dispose()
|
|
318
|
+
session.headless.dispose()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
closeByCwd(cwd: string) {
|
|
322
|
+
for (const [terminalId, session] of this.sessions.entries()) {
|
|
323
|
+
if (session.cwd !== cwd) continue
|
|
324
|
+
this.close(terminalId)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
closeAll() {
|
|
329
|
+
for (const terminalId of this.sessions.keys()) {
|
|
330
|
+
this.close(terminalId)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private snapshotOf(session: TerminalSession): TerminalSnapshot {
|
|
335
|
+
return {
|
|
336
|
+
terminalId: session.terminalId,
|
|
337
|
+
title: session.title,
|
|
338
|
+
cwd: session.cwd,
|
|
339
|
+
shell: session.shell,
|
|
340
|
+
cols: session.cols,
|
|
341
|
+
rows: session.rows,
|
|
342
|
+
scrollback: session.scrollback,
|
|
343
|
+
serializedState: session.serializeAddon.serialize({ scrollback: session.scrollback }),
|
|
344
|
+
status: session.status,
|
|
345
|
+
exitCode: session.exitCode,
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private emit(event: TerminalEvent) {
|
|
350
|
+
for (const listener of this.listeners) {
|
|
351
|
+
listener(event)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { NatsServer } from "@lagz0ne/nats-embedded"
|
|
3
|
+
import { connect, type NatsConnection } from "@nats-io/transport-node"
|
|
4
|
+
import { jetstream } from "@nats-io/jetstream"
|
|
5
|
+
import { ensureRunnerEventsStream } from "./nats-streams"
|
|
6
|
+
import { runnerEventsSubject } from "../shared/runner-protocol"
|
|
7
|
+
import { TranscriptConsumer, type TranscriptConsumerStore } from "./transcript-consumer"
|
|
8
|
+
import type { TranscriptEntry, SessionStatus, AgentProvider } from "../shared/types"
|
|
9
|
+
|
|
10
|
+
const encoder = new TextEncoder()
|
|
11
|
+
|
|
12
|
+
let server: NatsServer | null = null
|
|
13
|
+
let nc: NatsConnection | null = null
|
|
14
|
+
let consumer: TranscriptConsumer | null = null
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
if (consumer) {
|
|
18
|
+
consumer.stop()
|
|
19
|
+
consumer = null
|
|
20
|
+
}
|
|
21
|
+
if (nc) {
|
|
22
|
+
await nc.drain()
|
|
23
|
+
nc = null
|
|
24
|
+
}
|
|
25
|
+
if (server) {
|
|
26
|
+
await server.stop()
|
|
27
|
+
server = null
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
async function setup() {
|
|
32
|
+
server = await NatsServer.start({ jetstream: true })
|
|
33
|
+
nc = await connect({ servers: server.url })
|
|
34
|
+
await ensureRunnerEventsStream(nc)
|
|
35
|
+
return nc
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeStore(): TranscriptConsumerStore & {
|
|
39
|
+
calls: { method: string; args: unknown[] }[]
|
|
40
|
+
} {
|
|
41
|
+
const calls: { method: string; args: unknown[] }[] = []
|
|
42
|
+
return {
|
|
43
|
+
calls,
|
|
44
|
+
async appendMessage(chatId: string, entry: TranscriptEntry) {
|
|
45
|
+
calls.push({ method: "appendMessage", args: [chatId, entry] })
|
|
46
|
+
},
|
|
47
|
+
async recordTurnFinished(chatId: string) {
|
|
48
|
+
calls.push({ method: "recordTurnFinished", args: [chatId] })
|
|
49
|
+
},
|
|
50
|
+
async recordTurnFailed(chatId: string, error: string) {
|
|
51
|
+
calls.push({ method: "recordTurnFailed", args: [chatId, error] })
|
|
52
|
+
},
|
|
53
|
+
async recordTurnCancelled(chatId: string) {
|
|
54
|
+
calls.push({ method: "recordTurnCancelled", args: [chatId] })
|
|
55
|
+
},
|
|
56
|
+
async setSessionToken(chatId: string, token: string | null) {
|
|
57
|
+
calls.push({ method: "setSessionToken", args: [chatId, token] })
|
|
58
|
+
},
|
|
59
|
+
async renameChat(chatId: string, title: string) {
|
|
60
|
+
calls.push({ method: "renameChat", args: [chatId, title] })
|
|
61
|
+
},
|
|
62
|
+
async setChatProvider(chatId: string, provider: AgentProvider) {
|
|
63
|
+
calls.push({ method: "setChatProvider", args: [chatId, provider] })
|
|
64
|
+
},
|
|
65
|
+
async setPlanMode(chatId: string, planMode: boolean) {
|
|
66
|
+
calls.push({ method: "setPlanMode", args: [chatId, planMode] })
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function publishEvent(conn: NatsConnection, chatId: string, event: Record<string, unknown>) {
|
|
72
|
+
const js = jetstream(conn)
|
|
73
|
+
const subject = runnerEventsSubject(chatId)
|
|
74
|
+
await js.publish(subject, encoder.encode(JSON.stringify({ ...event, chatId })))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Wait until the predicate returns true, polling every intervalMs. Throws after timeoutMs. */
|
|
78
|
+
async function waitFor(predicate: () => boolean, timeoutMs = 3000, intervalMs = 20): Promise<void> {
|
|
79
|
+
const deadline = Date.now() + timeoutMs
|
|
80
|
+
while (!predicate()) {
|
|
81
|
+
if (Date.now() > deadline) throw new Error("waitFor timeout")
|
|
82
|
+
await new Promise((r) => setTimeout(r, intervalMs))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe("TranscriptConsumer", () => {
|
|
87
|
+
test("transcript event calls store.appendMessage and onMessageAppended", async () => {
|
|
88
|
+
const conn = await setup()
|
|
89
|
+
const store = makeStore()
|
|
90
|
+
const appended: { chatId: string; entry: TranscriptEntry }[] = []
|
|
91
|
+
|
|
92
|
+
consumer = new TranscriptConsumer({
|
|
93
|
+
nc: conn,
|
|
94
|
+
store,
|
|
95
|
+
onStateChange: () => {},
|
|
96
|
+
onMessageAppended: (chatId, entry) => appended.push({ chatId, entry }),
|
|
97
|
+
})
|
|
98
|
+
await consumer.start()
|
|
99
|
+
|
|
100
|
+
const entry: TranscriptEntry = { _id: "msg-1", createdAt: Date.now(), kind: "assistant_text", text: "hello" }
|
|
101
|
+
await publishEvent(conn, "chat-1", { type: "transcript", entry })
|
|
102
|
+
|
|
103
|
+
await waitFor(() => store.calls.length >= 1)
|
|
104
|
+
expect(store.calls[0]).toEqual({ method: "appendMessage", args: ["chat-1", entry] })
|
|
105
|
+
expect(appended).toHaveLength(1)
|
|
106
|
+
expect(appended[0]!.chatId).toBe("chat-1")
|
|
107
|
+
expect(appended[0]!.entry).toEqual(entry)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("turn_finished calls store.recordTurnFinished and removes from activeStatuses", async () => {
|
|
111
|
+
const conn = await setup()
|
|
112
|
+
const store = makeStore()
|
|
113
|
+
let stateChanges = 0
|
|
114
|
+
|
|
115
|
+
consumer = new TranscriptConsumer({
|
|
116
|
+
nc: conn,
|
|
117
|
+
store,
|
|
118
|
+
onStateChange: () => { stateChanges++ },
|
|
119
|
+
})
|
|
120
|
+
await consumer.start()
|
|
121
|
+
|
|
122
|
+
// First set a status so there's something to remove
|
|
123
|
+
await publishEvent(conn, "chat-2", { type: "status_change", status: "running" satisfies SessionStatus })
|
|
124
|
+
await waitFor(() => stateChanges >= 1)
|
|
125
|
+
expect(consumer.getActiveStatuses().get("chat-2")).toBe("running")
|
|
126
|
+
|
|
127
|
+
// Now finish
|
|
128
|
+
await publishEvent(conn, "chat-2", { type: "turn_finished" })
|
|
129
|
+
await waitFor(() => store.calls.some((c) => c.method === "recordTurnFinished"))
|
|
130
|
+
expect(store.calls.find((c) => c.method === "recordTurnFinished")).toEqual({
|
|
131
|
+
method: "recordTurnFinished",
|
|
132
|
+
args: ["chat-2"],
|
|
133
|
+
})
|
|
134
|
+
expect(consumer.getActiveStatuses().has("chat-2")).toBe(false)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test("turn_failed calls store.recordTurnFailed and removes from activeStatuses", async () => {
|
|
138
|
+
const conn = await setup()
|
|
139
|
+
const store = makeStore()
|
|
140
|
+
let stateChanges = 0
|
|
141
|
+
|
|
142
|
+
consumer = new TranscriptConsumer({
|
|
143
|
+
nc: conn,
|
|
144
|
+
store,
|
|
145
|
+
onStateChange: () => { stateChanges++ },
|
|
146
|
+
})
|
|
147
|
+
await consumer.start()
|
|
148
|
+
|
|
149
|
+
await publishEvent(conn, "chat-3", { type: "status_change", status: "running" satisfies SessionStatus })
|
|
150
|
+
await waitFor(() => stateChanges >= 1)
|
|
151
|
+
|
|
152
|
+
await publishEvent(conn, "chat-3", { type: "turn_failed", error: "something broke" })
|
|
153
|
+
await waitFor(() => store.calls.some((c) => c.method === "recordTurnFailed"))
|
|
154
|
+
expect(store.calls.find((c) => c.method === "recordTurnFailed")).toEqual({
|
|
155
|
+
method: "recordTurnFailed",
|
|
156
|
+
args: ["chat-3", "something broke"],
|
|
157
|
+
})
|
|
158
|
+
expect(consumer.getActiveStatuses().has("chat-3")).toBe(false)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test("turn_cancelled calls store.recordTurnCancelled and removes from activeStatuses", async () => {
|
|
162
|
+
const conn = await setup()
|
|
163
|
+
const store = makeStore()
|
|
164
|
+
let stateChanges = 0
|
|
165
|
+
|
|
166
|
+
consumer = new TranscriptConsumer({
|
|
167
|
+
nc: conn,
|
|
168
|
+
store,
|
|
169
|
+
onStateChange: () => { stateChanges++ },
|
|
170
|
+
})
|
|
171
|
+
await consumer.start()
|
|
172
|
+
|
|
173
|
+
await publishEvent(conn, "chat-4", { type: "status_change", status: "running" satisfies SessionStatus })
|
|
174
|
+
await waitFor(() => stateChanges >= 1)
|
|
175
|
+
|
|
176
|
+
await publishEvent(conn, "chat-4", { type: "turn_cancelled" })
|
|
177
|
+
await waitFor(() => store.calls.some((c) => c.method === "recordTurnCancelled"))
|
|
178
|
+
expect(store.calls.find((c) => c.method === "recordTurnCancelled")).toEqual({
|
|
179
|
+
method: "recordTurnCancelled",
|
|
180
|
+
args: ["chat-4"],
|
|
181
|
+
})
|
|
182
|
+
expect(consumer.getActiveStatuses().has("chat-4")).toBe(false)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test("session_token calls store.setSessionToken", async () => {
|
|
186
|
+
const conn = await setup()
|
|
187
|
+
const store = makeStore()
|
|
188
|
+
|
|
189
|
+
consumer = new TranscriptConsumer({
|
|
190
|
+
nc: conn,
|
|
191
|
+
store,
|
|
192
|
+
onStateChange: () => {},
|
|
193
|
+
})
|
|
194
|
+
await consumer.start()
|
|
195
|
+
|
|
196
|
+
await publishEvent(conn, "chat-5", { type: "session_token", sessionToken: "tok-abc" })
|
|
197
|
+
await waitFor(() => store.calls.some((c) => c.method === "setSessionToken"))
|
|
198
|
+
expect(store.calls.find((c) => c.method === "setSessionToken")).toEqual({
|
|
199
|
+
method: "setSessionToken",
|
|
200
|
+
args: ["chat-5", "tok-abc"],
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test("title_generated calls store.renameChat", async () => {
|
|
205
|
+
const conn = await setup()
|
|
206
|
+
const store = makeStore()
|
|
207
|
+
|
|
208
|
+
consumer = new TranscriptConsumer({
|
|
209
|
+
nc: conn,
|
|
210
|
+
store,
|
|
211
|
+
onStateChange: () => {},
|
|
212
|
+
})
|
|
213
|
+
await consumer.start()
|
|
214
|
+
|
|
215
|
+
await publishEvent(conn, "chat-6", { type: "title_generated", title: "My Chat Title" })
|
|
216
|
+
await waitFor(() => store.calls.some((c) => c.method === "renameChat"))
|
|
217
|
+
expect(store.calls.find((c) => c.method === "renameChat")).toEqual({
|
|
218
|
+
method: "renameChat",
|
|
219
|
+
args: ["chat-6", "My Chat Title"],
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test("status_change updates activeStatuses", async () => {
|
|
224
|
+
const conn = await setup()
|
|
225
|
+
const store = makeStore()
|
|
226
|
+
let stateChanges = 0
|
|
227
|
+
|
|
228
|
+
consumer = new TranscriptConsumer({
|
|
229
|
+
nc: conn,
|
|
230
|
+
store,
|
|
231
|
+
onStateChange: () => { stateChanges++ },
|
|
232
|
+
})
|
|
233
|
+
await consumer.start()
|
|
234
|
+
|
|
235
|
+
await publishEvent(conn, "chat-7", { type: "status_change", status: "running" satisfies SessionStatus })
|
|
236
|
+
await waitFor(() => stateChanges >= 1)
|
|
237
|
+
expect(consumer.getActiveStatuses().get("chat-7")).toBe("running")
|
|
238
|
+
|
|
239
|
+
await publishEvent(conn, "chat-7", { type: "status_change", status: "waiting_for_user" satisfies SessionStatus })
|
|
240
|
+
await waitFor(() => stateChanges >= 2)
|
|
241
|
+
expect(consumer.getActiveStatuses().get("chat-7")).toBe("waiting_for_user")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test("provider_set calls store.setChatProvider", async () => {
|
|
245
|
+
const conn = await setup()
|
|
246
|
+
const store = makeStore()
|
|
247
|
+
|
|
248
|
+
consumer = new TranscriptConsumer({
|
|
249
|
+
nc: conn,
|
|
250
|
+
store,
|
|
251
|
+
onStateChange: () => {},
|
|
252
|
+
})
|
|
253
|
+
await consumer.start()
|
|
254
|
+
|
|
255
|
+
await publishEvent(conn, "chat-8", { type: "provider_set", provider: "claude" })
|
|
256
|
+
await waitFor(() => store.calls.some((c) => c.method === "setChatProvider"))
|
|
257
|
+
expect(store.calls.find((c) => c.method === "setChatProvider")).toEqual({
|
|
258
|
+
method: "setChatProvider",
|
|
259
|
+
args: ["chat-8", "claude"],
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
test("plan_mode_set calls store.setPlanMode", async () => {
|
|
264
|
+
const conn = await setup()
|
|
265
|
+
const store = makeStore()
|
|
266
|
+
|
|
267
|
+
consumer = new TranscriptConsumer({
|
|
268
|
+
nc: conn,
|
|
269
|
+
store,
|
|
270
|
+
onStateChange: () => {},
|
|
271
|
+
})
|
|
272
|
+
await consumer.start()
|
|
273
|
+
|
|
274
|
+
await publishEvent(conn, "chat-9", { type: "plan_mode_set", planMode: true })
|
|
275
|
+
await waitFor(() => store.calls.some((c) => c.method === "setPlanMode"))
|
|
276
|
+
expect(store.calls.find((c) => c.method === "setPlanMode")).toEqual({
|
|
277
|
+
method: "setPlanMode",
|
|
278
|
+
args: ["chat-9", true],
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test("every event triggers onStateChange", async () => {
|
|
283
|
+
const conn = await setup()
|
|
284
|
+
const store = makeStore()
|
|
285
|
+
let stateChanges = 0
|
|
286
|
+
|
|
287
|
+
consumer = new TranscriptConsumer({
|
|
288
|
+
nc: conn,
|
|
289
|
+
store,
|
|
290
|
+
onStateChange: () => { stateChanges++ },
|
|
291
|
+
})
|
|
292
|
+
await consumer.start()
|
|
293
|
+
|
|
294
|
+
const entry: TranscriptEntry = { _id: "msg-x", createdAt: Date.now(), kind: "assistant_text", text: "hi" }
|
|
295
|
+
await publishEvent(conn, "chat-x", { type: "transcript", entry })
|
|
296
|
+
await publishEvent(conn, "chat-x", { type: "status_change", status: "running" satisfies SessionStatus })
|
|
297
|
+
await publishEvent(conn, "chat-x", { type: "turn_finished" })
|
|
298
|
+
|
|
299
|
+
await waitFor(() => stateChanges >= 3)
|
|
300
|
+
expect(stateChanges).toBeGreaterThanOrEqual(3)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test("pending_tool event triggers onStateChange without store call", async () => {
|
|
304
|
+
const conn = await setup()
|
|
305
|
+
const store = makeStore()
|
|
306
|
+
let stateChanges = 0
|
|
307
|
+
|
|
308
|
+
consumer = new TranscriptConsumer({
|
|
309
|
+
nc: conn,
|
|
310
|
+
store,
|
|
311
|
+
onStateChange: () => { stateChanges++ },
|
|
312
|
+
})
|
|
313
|
+
await consumer.start()
|
|
314
|
+
|
|
315
|
+
await publishEvent(conn, "chat-pt", { type: "pending_tool", tool: { toolUseId: "t1", toolKind: "ask_user_question" } })
|
|
316
|
+
await waitFor(() => stateChanges >= 1)
|
|
317
|
+
// No store method called for pending_tool
|
|
318
|
+
expect(store.calls).toHaveLength(0)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test("context_cleared event does not trigger onStateChange (handled by session_token)", async () => {
|
|
322
|
+
const conn = await setup()
|
|
323
|
+
const store = makeStore()
|
|
324
|
+
let stateChanges = 0
|
|
325
|
+
|
|
326
|
+
consumer = new TranscriptConsumer({
|
|
327
|
+
nc: conn,
|
|
328
|
+
store,
|
|
329
|
+
onStateChange: () => { stateChanges++ },
|
|
330
|
+
})
|
|
331
|
+
await consumer.start()
|
|
332
|
+
|
|
333
|
+
await publishEvent(conn, "chat-cc", { type: "context_cleared" })
|
|
334
|
+
// Give consumer time to process — but state should NOT change
|
|
335
|
+
await new Promise(r => setTimeout(r, 200))
|
|
336
|
+
expect(stateChanges).toBe(0)
|
|
337
|
+
expect(store.calls).toHaveLength(0)
|
|
338
|
+
})
|
|
339
|
+
})
|