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,201 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { scanSkillDirs, SkillCache } from "./skill-discovery"
|
|
6
|
+
|
|
7
|
+
const tempDirs: string[] = []
|
|
8
|
+
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await Promise.all(
|
|
11
|
+
tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))
|
|
12
|
+
)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
async function makeTempDir(prefix = "tinkaria-skill-discovery-") {
|
|
16
|
+
const dir = await mkdtemp(join(tmpdir(), prefix))
|
|
17
|
+
tempDirs.push(dir)
|
|
18
|
+
return dir
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function createSkillDir(parent: string, name: string): Promise<void> {
|
|
22
|
+
const skillDir = join(parent, name)
|
|
23
|
+
await mkdir(skillDir, { recursive: true })
|
|
24
|
+
await writeFile(join(skillDir, `${name}.md`), `---\nname: ${name}\n---\nSkill content`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("scanSkillDirs", () => {
|
|
28
|
+
test("returns skill names from user-level skills dir", async () => {
|
|
29
|
+
const homeDir = await makeTempDir()
|
|
30
|
+
const projectDir = await makeTempDir()
|
|
31
|
+
const skillsDir = join(homeDir, ".claude", "skills")
|
|
32
|
+
await mkdir(skillsDir, { recursive: true })
|
|
33
|
+
await createSkillDir(skillsDir, "commit")
|
|
34
|
+
await createSkillDir(skillsDir, "review-pr")
|
|
35
|
+
|
|
36
|
+
const skills = await scanSkillDirs(projectDir, homeDir)
|
|
37
|
+
expect(skills).toContain("commit")
|
|
38
|
+
expect(skills).toContain("review-pr")
|
|
39
|
+
expect(skills).toHaveLength(2)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("returns skill names from project-level skills dir", async () => {
|
|
43
|
+
const homeDir = await makeTempDir()
|
|
44
|
+
const projectDir = await makeTempDir()
|
|
45
|
+
const skillsDir = join(projectDir, ".claude", "skills")
|
|
46
|
+
await mkdir(skillsDir, { recursive: true })
|
|
47
|
+
await createSkillDir(skillsDir, "c3")
|
|
48
|
+
await createSkillDir(skillsDir, "frontend-design")
|
|
49
|
+
|
|
50
|
+
const skills = await scanSkillDirs(projectDir, homeDir)
|
|
51
|
+
expect(skills).toContain("c3")
|
|
52
|
+
expect(skills).toContain("frontend-design")
|
|
53
|
+
expect(skills).toHaveLength(2)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("merges user and project skills, deduplicates", async () => {
|
|
57
|
+
const homeDir = await makeTempDir()
|
|
58
|
+
const projectDir = await makeTempDir()
|
|
59
|
+
|
|
60
|
+
const userSkills = join(homeDir, ".claude", "skills")
|
|
61
|
+
await mkdir(userSkills, { recursive: true })
|
|
62
|
+
await createSkillDir(userSkills, "commit")
|
|
63
|
+
await createSkillDir(userSkills, "shared-skill")
|
|
64
|
+
|
|
65
|
+
const projSkills = join(projectDir, ".claude", "skills")
|
|
66
|
+
await mkdir(projSkills, { recursive: true })
|
|
67
|
+
await createSkillDir(projSkills, "c3")
|
|
68
|
+
await createSkillDir(projSkills, "shared-skill")
|
|
69
|
+
|
|
70
|
+
const skills = await scanSkillDirs(projectDir, homeDir)
|
|
71
|
+
expect(skills).toContain("commit")
|
|
72
|
+
expect(skills).toContain("c3")
|
|
73
|
+
expect(skills).toContain("shared-skill")
|
|
74
|
+
expect(skills).toHaveLength(3)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("returns empty array when no skills dirs exist", async () => {
|
|
78
|
+
const homeDir = await makeTempDir()
|
|
79
|
+
const projectDir = await makeTempDir()
|
|
80
|
+
|
|
81
|
+
const skills = await scanSkillDirs(projectDir, homeDir)
|
|
82
|
+
expect(skills).toEqual([])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("ignores plain files in skills directory", async () => {
|
|
86
|
+
const homeDir = await makeTempDir()
|
|
87
|
+
const projectDir = await makeTempDir()
|
|
88
|
+
const skillsDir = join(homeDir, ".claude", "skills")
|
|
89
|
+
await mkdir(skillsDir, { recursive: true })
|
|
90
|
+
await createSkillDir(skillsDir, "real-skill")
|
|
91
|
+
await writeFile(join(skillsDir, "not-a-skill.md"), "just a file")
|
|
92
|
+
|
|
93
|
+
const skills = await scanSkillDirs(projectDir, homeDir)
|
|
94
|
+
expect(skills).toEqual(["real-skill"])
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test("returns sorted skill names for deterministic output", async () => {
|
|
98
|
+
const homeDir = await makeTempDir()
|
|
99
|
+
const projectDir = await makeTempDir()
|
|
100
|
+
const skillsDir = join(homeDir, ".claude", "skills")
|
|
101
|
+
await mkdir(skillsDir, { recursive: true })
|
|
102
|
+
await createSkillDir(skillsDir, "zebra")
|
|
103
|
+
await createSkillDir(skillsDir, "alpha")
|
|
104
|
+
await createSkillDir(skillsDir, "middle")
|
|
105
|
+
|
|
106
|
+
const skills = await scanSkillDirs(projectDir, homeDir)
|
|
107
|
+
expect(skills).toEqual(["alpha", "middle", "zebra"])
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe("SkillCache", () => {
|
|
112
|
+
test("returns discovered skills on first call", async () => {
|
|
113
|
+
const homeDir = await makeTempDir()
|
|
114
|
+
const projectDir = await makeTempDir()
|
|
115
|
+
const skillsDir = join(projectDir, ".claude", "skills")
|
|
116
|
+
await mkdir(skillsDir, { recursive: true })
|
|
117
|
+
await createSkillDir(skillsDir, "c3")
|
|
118
|
+
|
|
119
|
+
const cache = new SkillCache({ ttlMs: 5000, homeDir })
|
|
120
|
+
const skills = await cache.get(projectDir)
|
|
121
|
+
expect(skills).toEqual(["c3"])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("returns cached result within TTL", async () => {
|
|
125
|
+
const homeDir = await makeTempDir()
|
|
126
|
+
const projectDir = await makeTempDir()
|
|
127
|
+
const skillsDir = join(projectDir, ".claude", "skills")
|
|
128
|
+
await mkdir(skillsDir, { recursive: true })
|
|
129
|
+
await createSkillDir(skillsDir, "c3")
|
|
130
|
+
|
|
131
|
+
const cache = new SkillCache({ ttlMs: 5000, homeDir })
|
|
132
|
+
const first = await cache.get(projectDir)
|
|
133
|
+
|
|
134
|
+
// Add another skill after first scan
|
|
135
|
+
await createSkillDir(skillsDir, "new-skill")
|
|
136
|
+
const second = await cache.get(projectDir)
|
|
137
|
+
|
|
138
|
+
// Should return cached (stale) result
|
|
139
|
+
expect(second).toEqual(first)
|
|
140
|
+
expect(second).not.toContain("new-skill")
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test("re-scans after TTL expires", async () => {
|
|
144
|
+
const homeDir = await makeTempDir()
|
|
145
|
+
const projectDir = await makeTempDir()
|
|
146
|
+
const skillsDir = join(projectDir, ".claude", "skills")
|
|
147
|
+
await mkdir(skillsDir, { recursive: true })
|
|
148
|
+
await createSkillDir(skillsDir, "c3")
|
|
149
|
+
|
|
150
|
+
const cache = new SkillCache({ ttlMs: 50, homeDir })
|
|
151
|
+
const first = await cache.get(projectDir)
|
|
152
|
+
expect(first).toEqual(["c3"])
|
|
153
|
+
|
|
154
|
+
await createSkillDir(skillsDir, "new-skill")
|
|
155
|
+
await new Promise((r) => setTimeout(r, 60))
|
|
156
|
+
|
|
157
|
+
const second = await cache.get(projectDir)
|
|
158
|
+
expect(second).toContain("c3")
|
|
159
|
+
expect(second).toContain("new-skill")
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test("invalidate clears specific project cache", async () => {
|
|
163
|
+
const homeDir = await makeTempDir()
|
|
164
|
+
const projectDir = await makeTempDir()
|
|
165
|
+
const skillsDir = join(projectDir, ".claude", "skills")
|
|
166
|
+
await mkdir(skillsDir, { recursive: true })
|
|
167
|
+
await createSkillDir(skillsDir, "c3")
|
|
168
|
+
|
|
169
|
+
const cache = new SkillCache({ ttlMs: 60_000, homeDir })
|
|
170
|
+
await cache.get(projectDir)
|
|
171
|
+
|
|
172
|
+
await createSkillDir(skillsDir, "new-skill")
|
|
173
|
+
cache.invalidate(projectDir)
|
|
174
|
+
|
|
175
|
+
const refreshed = await cache.get(projectDir)
|
|
176
|
+
expect(refreshed).toContain("new-skill")
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("invalidate without args clears all caches", async () => {
|
|
180
|
+
const homeDir = await makeTempDir()
|
|
181
|
+
const proj1 = await makeTempDir()
|
|
182
|
+
const proj2 = await makeTempDir()
|
|
183
|
+
|
|
184
|
+
for (const dir of [proj1, proj2]) {
|
|
185
|
+
const skillsDir = join(dir, ".claude", "skills")
|
|
186
|
+
await mkdir(skillsDir, { recursive: true })
|
|
187
|
+
await createSkillDir(skillsDir, "skill-a")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const cache = new SkillCache({ ttlMs: 60_000, homeDir })
|
|
191
|
+
await cache.get(proj1)
|
|
192
|
+
await cache.get(proj2)
|
|
193
|
+
|
|
194
|
+
cache.invalidate()
|
|
195
|
+
|
|
196
|
+
// Add new skills after invalidation
|
|
197
|
+
await createSkillDir(join(proj1, ".claude", "skills"), "added")
|
|
198
|
+
const refreshed = await cache.get(proj1)
|
|
199
|
+
expect(refreshed).toContain("added")
|
|
200
|
+
})
|
|
201
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { homedir } from "node:os"
|
|
4
|
+
import { LOG_PREFIX } from "../shared/branding"
|
|
5
|
+
|
|
6
|
+
async function listSkillNames(dir: string): Promise<string[]> {
|
|
7
|
+
try {
|
|
8
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
9
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name)
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return []
|
|
12
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
13
|
+
console.warn(`${LOG_PREFIX} Failed to scan skills dir ${dir}: ${message}`)
|
|
14
|
+
return []
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function scanSkillDirs(workspacePath: string, homeDir?: string): Promise<string[]> {
|
|
19
|
+
const home = homeDir ?? homedir()
|
|
20
|
+
const userSkillsDir = join(home, ".claude", "skills")
|
|
21
|
+
const projectSkillsDir = join(workspacePath, ".claude", "skills")
|
|
22
|
+
|
|
23
|
+
const [userSkills, projectSkills] = await Promise.all([
|
|
24
|
+
listSkillNames(userSkillsDir),
|
|
25
|
+
listSkillNames(projectSkillsDir),
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const seen = new Set<string>()
|
|
29
|
+
const merged: string[] = []
|
|
30
|
+
for (const name of [...projectSkills, ...userSkills]) {
|
|
31
|
+
if (!seen.has(name)) {
|
|
32
|
+
seen.add(name)
|
|
33
|
+
merged.push(name)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return merged.sort()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CacheEntry {
|
|
40
|
+
skills: string[]
|
|
41
|
+
expiresAt: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface SkillCacheOptions {
|
|
45
|
+
ttlMs?: number
|
|
46
|
+
homeDir?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class SkillCache {
|
|
50
|
+
private readonly cache = new Map<string, CacheEntry>()
|
|
51
|
+
private readonly ttlMs: number
|
|
52
|
+
private readonly homeDir: string | undefined
|
|
53
|
+
|
|
54
|
+
constructor(options?: SkillCacheOptions) {
|
|
55
|
+
this.ttlMs = options?.ttlMs ?? 30_000
|
|
56
|
+
this.homeDir = options?.homeDir
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async get(workspacePath: string): Promise<string[]> {
|
|
60
|
+
const entry = this.cache.get(workspacePath)
|
|
61
|
+
if (entry && Date.now() < entry.expiresAt) {
|
|
62
|
+
return entry.skills
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const skills = await scanSkillDirs(workspacePath, this.homeDir)
|
|
66
|
+
this.cache.set(workspacePath, { skills, expiresAt: Date.now() + this.ttlMs })
|
|
67
|
+
return skills
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
invalidate(workspacePath?: string): void {
|
|
71
|
+
if (workspacePath) {
|
|
72
|
+
this.cache.delete(workspacePath)
|
|
73
|
+
} else {
|
|
74
|
+
this.cache.clear()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// In-memory EventStore stub for unit tests.
|
|
2
|
+
// Implements only the subset of EventStore methods required by tool-callback
|
|
3
|
+
// and related MCP tool tests.
|
|
4
|
+
|
|
5
|
+
import type { ToolRequest, ToolRequestStatus, ToolRequestDecision } from "../../shared/permission-policy"
|
|
6
|
+
import { POLICY_TERMINAL_STATUSES } from "../../shared/permission-policy"
|
|
7
|
+
import type { EventStore } from "../event-store"
|
|
8
|
+
|
|
9
|
+
export function createTestStorage(): { dir: string; cleanup: () => Promise<void> } {
|
|
10
|
+
throw new Error("test-helpers stub — implement when activating PTY mcp-tool tests")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface InMemoryEventStore {
|
|
14
|
+
initialize(): Promise<void>
|
|
15
|
+
putToolRequest(req: ToolRequest): Promise<void>
|
|
16
|
+
getToolRequest(id: string): ToolRequest | null
|
|
17
|
+
listPendingToolRequests(chatId: string): ToolRequest[]
|
|
18
|
+
resolveToolRequest(
|
|
19
|
+
id: string,
|
|
20
|
+
update: { status: ToolRequestStatus; decision: ToolRequestDecision; resolvedAt: number; mismatchReason?: string },
|
|
21
|
+
): Promise<void>
|
|
22
|
+
scanAllToolRequests(): ToolRequest[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createTestEventStore(_dataDir?: string): EventStore {
|
|
26
|
+
const requests = new Map<string, ToolRequest>()
|
|
27
|
+
|
|
28
|
+
const store: InMemoryEventStore = {
|
|
29
|
+
async initialize() {
|
|
30
|
+
// No-op for in-memory store
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async putToolRequest(req: ToolRequest): Promise<void> {
|
|
34
|
+
requests.set(req.id, { ...req })
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
getToolRequest(id: string): ToolRequest | null {
|
|
38
|
+
return requests.get(id) ?? null
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
listPendingToolRequests(chatId: string): ToolRequest[] {
|
|
42
|
+
const result: ToolRequest[] = []
|
|
43
|
+
for (const req of requests.values()) {
|
|
44
|
+
if (req.chatId === chatId && !POLICY_TERMINAL_STATUSES.has(req.status)) {
|
|
45
|
+
result.push({ ...req })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async resolveToolRequest(
|
|
52
|
+
id: string,
|
|
53
|
+
update: { status: ToolRequestStatus; decision: ToolRequestDecision; resolvedAt: number; mismatchReason?: string },
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const existing = requests.get(id)
|
|
56
|
+
if (existing) {
|
|
57
|
+
requests.set(id, { ...existing, ...update })
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
scanAllToolRequests(): ToolRequest[] {
|
|
62
|
+
return Array.from(requests.values()).map((r) => ({ ...r }))
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return store as unknown as EventStore
|
|
67
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
3
|
+
import os from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { TerminalManager } from "./terminal-manager"
|
|
6
|
+
|
|
7
|
+
const SHELL_START_TIMEOUT_MS = 5_000
|
|
8
|
+
const COMMAND_TIMEOUT_MS = 5_000
|
|
9
|
+
const FOCUS_IN_SEQUENCE = "\x1b[I"
|
|
10
|
+
const RAW_READ_HEX_COMMAND = `python3 -c "exec('import os,sys,tty,termios,select\\nfd=sys.stdin.fileno()\\nold=termios.tcgetattr(fd)\\ntty.setraw(fd)\\ntry:\\n sys.stdout.write(\"__RAW_READY__\\\\n\")\\n sys.stdout.flush()\\n r,_,_=select.select([fd],[],[],1)\\n data=os.read(fd,8) if r else b\"\"\\n print(data.hex() or \"__EMPTY__\")\\nfinally:\\n termios.tcsetattr(fd, termios.TCSADRAIN, old)')"\r`
|
|
11
|
+
|
|
12
|
+
const isSupportedPlatform = process.platform !== "win32" && typeof Bun.Terminal === "function"
|
|
13
|
+
const describeIfSupported = isSupportedPlatform ? describe : describe.skip
|
|
14
|
+
|
|
15
|
+
let tempProjectPath = ""
|
|
16
|
+
const originalTestShell = process.env.TINKARIA_SHELL
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
if (!isSupportedPlatform) return
|
|
20
|
+
process.env.TINKARIA_SHELL = "/bin/sh"
|
|
21
|
+
tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "tinkaria-terminal-manager-"))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterAll(() => {
|
|
25
|
+
if (originalTestShell === undefined) {
|
|
26
|
+
delete process.env.TINKARIA_SHELL
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process.env.TINKARIA_SHELL = originalTestShell
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
if (!tempProjectPath) return
|
|
35
|
+
await rm(tempProjectPath, { recursive: true, force: true })
|
|
36
|
+
tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "tinkaria-terminal-manager-"))
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
async function waitFor(check: () => boolean, timeoutMs: number, intervalMs = 25) {
|
|
40
|
+
const startedAt = Date.now()
|
|
41
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
42
|
+
if (check()) return
|
|
43
|
+
await Bun.sleep(intervalMs)
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`Timed out after ${timeoutMs}ms`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function createSession(terminalId: string) {
|
|
49
|
+
const manager = new TerminalManager()
|
|
50
|
+
let output = ""
|
|
51
|
+
manager.onEvent((event) => {
|
|
52
|
+
if (event.type === "terminal.output" && event.terminalId === terminalId) {
|
|
53
|
+
output += event.data
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
manager.createTerminal({
|
|
58
|
+
workspacePath: tempProjectPath,
|
|
59
|
+
terminalId,
|
|
60
|
+
cols: 80,
|
|
61
|
+
rows: 24,
|
|
62
|
+
scrollback: 1_000,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
manager.write(terminalId, "printf '__KANNA_READY__\\n'\r")
|
|
66
|
+
await waitFor(() => output.includes("__KANNA_READY__"), SHELL_START_TIMEOUT_MS)
|
|
67
|
+
await waitForTerminalToSettle(() => output)
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
manager,
|
|
71
|
+
getOutput: () => output,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function waitForOutputToContain(getOutput: () => string, value: string, timeoutMs = COMMAND_TIMEOUT_MS) {
|
|
76
|
+
await waitFor(() => getOutput().includes(value), timeoutMs)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function waitForTerminalToSettle(getOutput: () => string, idleMs = 75, timeoutMs = COMMAND_TIMEOUT_MS) {
|
|
80
|
+
let lastOutput = getOutput()
|
|
81
|
+
let unchangedSince = Date.now()
|
|
82
|
+
|
|
83
|
+
await waitFor(() => {
|
|
84
|
+
const currentOutput = getOutput()
|
|
85
|
+
if (currentOutput !== lastOutput) {
|
|
86
|
+
lastOutput = currentOutput
|
|
87
|
+
unchangedSince = Date.now()
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Date.now() - unchangedSince >= idleMs
|
|
92
|
+
}, timeoutMs)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describeIfSupported("TerminalManager", () => {
|
|
96
|
+
test("ctrl+c interrupts the foreground job and keeps the shell alive", async () => {
|
|
97
|
+
const terminalId = "terminal-ctrl-c-foreground"
|
|
98
|
+
const { manager, getOutput } = await createSession(terminalId)
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
manager.write(terminalId, 'python3 -c "import time; time.sleep(30)"\r')
|
|
102
|
+
await waitFor(() => getOutput().includes("time.sleep(30)"), COMMAND_TIMEOUT_MS)
|
|
103
|
+
|
|
104
|
+
manager.write(terminalId, "\x03")
|
|
105
|
+
manager.write(terminalId, "printf '__KANNA_AFTER_INT__\\n'\r")
|
|
106
|
+
|
|
107
|
+
await waitFor(() => getOutput().includes("__KANNA_AFTER_INT__"), COMMAND_TIMEOUT_MS)
|
|
108
|
+
|
|
109
|
+
const snapshot = manager.getSnapshot(terminalId)
|
|
110
|
+
expect(snapshot?.status).toBe("running")
|
|
111
|
+
expect(getOutput()).toContain("__KANNA_AFTER_INT__")
|
|
112
|
+
} finally {
|
|
113
|
+
manager.close(terminalId)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test("ctrl+c at an idle prompt does not exit the shell", async () => {
|
|
118
|
+
const terminalId = "terminal-ctrl-c-prompt"
|
|
119
|
+
const { manager, getOutput } = await createSession(terminalId)
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const before = getOutput()
|
|
123
|
+
manager.write(terminalId, "\x03")
|
|
124
|
+
|
|
125
|
+
await waitFor(() => getOutput().length > before.length, COMMAND_TIMEOUT_MS)
|
|
126
|
+
|
|
127
|
+
const snapshot = manager.getSnapshot(terminalId)
|
|
128
|
+
expect(snapshot?.status).toBe("running")
|
|
129
|
+
expect(getOutput().length).toBeGreaterThan(before.length)
|
|
130
|
+
} finally {
|
|
131
|
+
manager.close(terminalId)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test("ctrl+d preserves eof behavior", async () => {
|
|
136
|
+
const terminalId = "terminal-ctrl-d"
|
|
137
|
+
const { manager, getOutput } = await createSession(terminalId)
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
manager.write(terminalId, "printf '__KANNA_CTRL_D_READY__\\n'\r")
|
|
141
|
+
await waitFor(() => getOutput().includes("__KANNA_CTRL_D_READY__"), COMMAND_TIMEOUT_MS)
|
|
142
|
+
await waitForTerminalToSettle(getOutput, 200)
|
|
143
|
+
|
|
144
|
+
manager.write(terminalId, "\x04")
|
|
145
|
+
|
|
146
|
+
await waitFor(() => manager.getSnapshot(terminalId)?.status === "exited", COMMAND_TIMEOUT_MS)
|
|
147
|
+
|
|
148
|
+
expect(manager.getSnapshot(terminalId)?.exitCode).toBe(0)
|
|
149
|
+
} finally {
|
|
150
|
+
manager.close(terminalId)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test("filters leaked focus reports while focus mode is disabled", async () => {
|
|
155
|
+
const terminalId = "terminal-focus-filtered"
|
|
156
|
+
const { manager, getOutput } = await createSession(terminalId)
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const beforeLength = getOutput().length
|
|
160
|
+
manager.write(terminalId, RAW_READ_HEX_COMMAND)
|
|
161
|
+
await waitForOutputToContain(getOutput, "__RAW_READY__")
|
|
162
|
+
|
|
163
|
+
manager.write(terminalId, FOCUS_IN_SEQUENCE)
|
|
164
|
+
await waitForOutputToContain(getOutput, "__EMPTY__")
|
|
165
|
+
|
|
166
|
+
const interactionOutput = getOutput().slice(beforeLength)
|
|
167
|
+
expect(interactionOutput).toContain("__EMPTY__")
|
|
168
|
+
expect(interactionOutput).not.toContain("1b5b49")
|
|
169
|
+
} finally {
|
|
170
|
+
manager.close(terminalId)
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test("forwards focus reports when the session mode is enabled", () => {
|
|
175
|
+
const manager = new TerminalManager() as unknown as {
|
|
176
|
+
sessions: Map<
|
|
177
|
+
string,
|
|
178
|
+
{
|
|
179
|
+
status: "running" | "exited"
|
|
180
|
+
focusReportingEnabled: boolean
|
|
181
|
+
terminal: { write: (data: string) => void }
|
|
182
|
+
process: Bun.Subprocess | null
|
|
183
|
+
}
|
|
184
|
+
>
|
|
185
|
+
write: (terminalId: string, data: string) => void
|
|
186
|
+
}
|
|
187
|
+
const writes: string[] = []
|
|
188
|
+
|
|
189
|
+
manager.sessions.set("terminal-focus-forwarded", {
|
|
190
|
+
status: "running",
|
|
191
|
+
focusReportingEnabled: true,
|
|
192
|
+
terminal: {
|
|
193
|
+
write(data: string) {
|
|
194
|
+
writes.push(data)
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
process: null,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
manager.write("terminal-focus-forwarded", FOCUS_IN_SEQUENCE)
|
|
201
|
+
|
|
202
|
+
expect(writes).toEqual([FOCUS_IN_SEQUENCE])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test("resize signals the shell process group with SIGWINCH", () => {
|
|
206
|
+
const manager = new TerminalManager() as unknown as {
|
|
207
|
+
sessions: Map<
|
|
208
|
+
string,
|
|
209
|
+
{
|
|
210
|
+
cols: number
|
|
211
|
+
rows: number
|
|
212
|
+
headless: { resize: (cols: number, rows: number) => void }
|
|
213
|
+
terminal: { resize: (cols: number, rows: number) => void }
|
|
214
|
+
process: { pid: number } | null
|
|
215
|
+
}
|
|
216
|
+
>
|
|
217
|
+
resize: (terminalId: string, cols: number, rows: number) => void
|
|
218
|
+
}
|
|
219
|
+
const resizeCalls: Array<{ cols: number; rows: number }> = []
|
|
220
|
+
const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []
|
|
221
|
+
const originalKill = process.kill
|
|
222
|
+
|
|
223
|
+
;(process as typeof process & {
|
|
224
|
+
kill: (pid: number, signal?: NodeJS.Signals | number) => boolean
|
|
225
|
+
}).kill = ((pid: number, signal?: NodeJS.Signals | number) => {
|
|
226
|
+
if (typeof signal === "string") {
|
|
227
|
+
killCalls.push({ pid, signal })
|
|
228
|
+
}
|
|
229
|
+
return true
|
|
230
|
+
}) as typeof process.kill
|
|
231
|
+
|
|
232
|
+
manager.sessions.set("terminal-resize-sigwinch", {
|
|
233
|
+
cols: 80,
|
|
234
|
+
rows: 24,
|
|
235
|
+
headless: {
|
|
236
|
+
resize(cols, rows) {
|
|
237
|
+
resizeCalls.push({ cols, rows })
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
terminal: {
|
|
241
|
+
resize(cols, rows) {
|
|
242
|
+
resizeCalls.push({ cols, rows })
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
process: { pid: 4321 },
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
manager.resize("terminal-resize-sigwinch", 120, 40)
|
|
250
|
+
} finally {
|
|
251
|
+
process.kill = originalKill
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
expect(resizeCalls).toEqual([
|
|
255
|
+
{ cols: 120, rows: 40 },
|
|
256
|
+
{ cols: 120, rows: 40 },
|
|
257
|
+
])
|
|
258
|
+
expect(killCalls).toContainEqual({ pid: -4321, signal: "SIGWINCH" })
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test("new sessions reset focus mode back to filtered", async () => {
|
|
262
|
+
const manager = new TerminalManager()
|
|
263
|
+
const firstTerminalId = "terminal-focus-first"
|
|
264
|
+
const secondTerminalId = "terminal-focus-second"
|
|
265
|
+
let outputByTerminalId = new Map<string, string>()
|
|
266
|
+
|
|
267
|
+
manager.onEvent((event) => {
|
|
268
|
+
if (event.type !== "terminal.output") return
|
|
269
|
+
outputByTerminalId.set(event.terminalId, `${outputByTerminalId.get(event.terminalId) ?? ""}${event.data}`)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const getOutput = (terminalId: string) => outputByTerminalId.get(terminalId) ?? ""
|
|
273
|
+
|
|
274
|
+
const createManagedSession = async (terminalId: string) => {
|
|
275
|
+
manager.createTerminal({
|
|
276
|
+
workspacePath: tempProjectPath,
|
|
277
|
+
terminalId,
|
|
278
|
+
cols: 80,
|
|
279
|
+
rows: 24,
|
|
280
|
+
scrollback: 1_000,
|
|
281
|
+
})
|
|
282
|
+
manager.write(terminalId, "printf '__KANNA_READY__\\n'\r")
|
|
283
|
+
await waitForOutputToContain(() => getOutput(terminalId), "__KANNA_READY__", SHELL_START_TIMEOUT_MS)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
await createManagedSession(firstTerminalId)
|
|
288
|
+
const firstBeforeLength = getOutput(firstTerminalId).length
|
|
289
|
+
manager.write(firstTerminalId, "printf '\\033[?1004h'\r")
|
|
290
|
+
await waitFor(() => getOutput(firstTerminalId).length > firstBeforeLength, COMMAND_TIMEOUT_MS)
|
|
291
|
+
manager.close(firstTerminalId)
|
|
292
|
+
|
|
293
|
+
await createManagedSession(secondTerminalId)
|
|
294
|
+
const before = getOutput(secondTerminalId).length
|
|
295
|
+
manager.write(secondTerminalId, "cat -v\r")
|
|
296
|
+
await waitFor(() => getOutput(secondTerminalId).length > before, COMMAND_TIMEOUT_MS)
|
|
297
|
+
manager.write(secondTerminalId, FOCUS_IN_SEQUENCE)
|
|
298
|
+
manager.write(secondTerminalId, "\x03")
|
|
299
|
+
manager.write(secondTerminalId, "printf '__KANNA_FRESH_SESSION__\\n'\r")
|
|
300
|
+
await waitForOutputToContain(() => getOutput(secondTerminalId), "__KANNA_FRESH_SESSION__")
|
|
301
|
+
|
|
302
|
+
const interactionOutput = getOutput(secondTerminalId).slice(before)
|
|
303
|
+
expect(interactionOutput).not.toContain("^[[I")
|
|
304
|
+
} finally {
|
|
305
|
+
manager.close(firstTerminalId)
|
|
306
|
+
manager.close(secondTerminalId)
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
})
|