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,274 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto"
|
|
2
|
+
import type {
|
|
3
|
+
ChatPermissionPolicy,
|
|
4
|
+
ToolRequest,
|
|
5
|
+
ToolRequestDecision,
|
|
6
|
+
ToolRequestStatus,
|
|
7
|
+
} from "../../shared/permission-policy"
|
|
8
|
+
import { POLICY_TERMINAL_STATUSES } from "../../shared/permission-policy"
|
|
9
|
+
import { policy } from "../claude-pty-mcp/permission-gate"
|
|
10
|
+
import { canonicalArgsHash } from "../claude-pty-mcp/canonical-args"
|
|
11
|
+
import type { EventStore } from "../event-store"
|
|
12
|
+
|
|
13
|
+
export interface ToolCallbackServiceArgs {
|
|
14
|
+
store: EventStore
|
|
15
|
+
serverSecret: string
|
|
16
|
+
now: () => number
|
|
17
|
+
timeoutMs: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ToolCallbackSubmitArgs {
|
|
21
|
+
chatId: string
|
|
22
|
+
sessionId: string
|
|
23
|
+
toolUseId: string
|
|
24
|
+
toolName: string
|
|
25
|
+
args: Record<string, unknown>
|
|
26
|
+
chatPolicy: ChatPermissionPolicy
|
|
27
|
+
cwd: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ToolCallbackResult {
|
|
31
|
+
status: ToolRequestStatus
|
|
32
|
+
decision: ToolRequestDecision
|
|
33
|
+
mismatchReason?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ToolCallbackService {
|
|
37
|
+
submit(args: ToolCallbackSubmitArgs): Promise<ToolCallbackResult>
|
|
38
|
+
answer(id: string, decision: ToolRequestDecision): Promise<void>
|
|
39
|
+
cancel(id: string, reason: string): Promise<void>
|
|
40
|
+
cancelAllForChat(chatId: string, reason: string): Promise<void>
|
|
41
|
+
cancelAllForSession(sessionId: string, reason: string): Promise<void>
|
|
42
|
+
recoverOnStartup(): Promise<void>
|
|
43
|
+
tickTimeouts(): Promise<void>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createToolCallbackService(opts: ToolCallbackServiceArgs): ToolCallbackService {
|
|
47
|
+
interface PendingWaiter {
|
|
48
|
+
resolve: (r: ToolCallbackResult) => void
|
|
49
|
+
expiresAt: number
|
|
50
|
+
}
|
|
51
|
+
const waiters = new Map<string, PendingWaiter[]>()
|
|
52
|
+
// Tracks the canonical (toolName, canonicalArgsHash) per (chatId, sessionId,
|
|
53
|
+
// toolUseId) so we detect mismatched retries. toolUseId is generated by the
|
|
54
|
+
// claude CLI per-session and restarts at "1" for each new spawn, so keying
|
|
55
|
+
// by toolUseId alone collides across chats / restarts.
|
|
56
|
+
const seenToolUseIds = new Map<string, { id: string; toolName: string; canonicalArgsHash: string }>()
|
|
57
|
+
function seenKey(s: { chatId: string; sessionId: string; toolUseId: string }): string {
|
|
58
|
+
return `${s.chatId}|${s.sessionId}|${s.toolUseId}`
|
|
59
|
+
}
|
|
60
|
+
// In-memory mirror of persisted records keyed by id — lets submit() check
|
|
61
|
+
// existing state synchronously (before the first await) so that concurrent
|
|
62
|
+
// calls within the same event-loop turn see the correct state.
|
|
63
|
+
const inMemory = new Map<string, ToolRequest>()
|
|
64
|
+
|
|
65
|
+
function hmacId(s: ToolCallbackSubmitArgs, hash: string): string {
|
|
66
|
+
const h = createHmac("sha256", opts.serverSecret)
|
|
67
|
+
h.update(`${s.chatId}|${s.sessionId}|${s.toolUseId}|${s.toolName}|${hash}`)
|
|
68
|
+
return h.digest("hex")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveWaiters(id: string, result: ToolCallbackResult) {
|
|
72
|
+
const ws = waiters.get(id) ?? []
|
|
73
|
+
waiters.delete(id)
|
|
74
|
+
for (const w of ws) w.resolve(result)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function persistPut(req: ToolRequest): Promise<void> {
|
|
78
|
+
inMemory.set(req.id, { ...req })
|
|
79
|
+
await opts.store.putToolRequest(req)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function persistResolve(
|
|
83
|
+
id: string,
|
|
84
|
+
update: { status: ToolRequestStatus; decision: ToolRequestDecision; resolvedAt: number; mismatchReason?: string },
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
const existing = inMemory.get(id)
|
|
87
|
+
if (existing) {
|
|
88
|
+
inMemory.set(id, { ...existing, ...update })
|
|
89
|
+
}
|
|
90
|
+
await opts.store.resolveToolRequest(id, update)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const svc: ToolCallbackService = {
|
|
94
|
+
submit(args) {
|
|
95
|
+
const hash = canonicalArgsHash(args.args)
|
|
96
|
+
const id = hmacId(args, hash)
|
|
97
|
+
|
|
98
|
+
// ── Arg-mismatch check (synchronous, no I/O) ──────────────────────────
|
|
99
|
+
const seen = seenToolUseIds.get(seenKey(args))
|
|
100
|
+
if (seen && (seen.toolName !== args.toolName || seen.canonicalArgsHash !== hash)) {
|
|
101
|
+
const reason = `argument_mismatch: canonicalArgsHash differs from prior submission for toolUseId=${args.toolUseId}`
|
|
102
|
+
const decision: ToolRequestDecision = { kind: "deny", reason }
|
|
103
|
+
const now = opts.now()
|
|
104
|
+
const mismatchReq: ToolRequest = {
|
|
105
|
+
id,
|
|
106
|
+
chatId: args.chatId,
|
|
107
|
+
sessionId: args.sessionId,
|
|
108
|
+
toolUseId: args.toolUseId,
|
|
109
|
+
toolName: args.toolName,
|
|
110
|
+
arguments: args.args,
|
|
111
|
+
canonicalArgsHash: hash,
|
|
112
|
+
policyVerdict: "auto-deny",
|
|
113
|
+
status: "arg_mismatch",
|
|
114
|
+
decision,
|
|
115
|
+
mismatchReason: reason,
|
|
116
|
+
createdAt: now,
|
|
117
|
+
resolvedAt: now,
|
|
118
|
+
expiresAt: now,
|
|
119
|
+
}
|
|
120
|
+
// Await persistence so a caller scanning the store after submit() sees the record.
|
|
121
|
+
return persistPut(mismatchReq).then(() => ({ status: "arg_mismatch" as const, decision, mismatchReason: reason }))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Idempotency: check in-memory mirror (synchronous) ─────────────────
|
|
125
|
+
const existing = inMemory.get(id)
|
|
126
|
+
if (existing && POLICY_TERMINAL_STATUSES.has(existing.status)) {
|
|
127
|
+
return Promise.resolve({
|
|
128
|
+
status: existing.status,
|
|
129
|
+
decision: existing.decision ?? { kind: "deny", reason: "unknown" },
|
|
130
|
+
mismatchReason: existing.mismatchReason,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
if (existing) {
|
|
134
|
+
// Already pending — attach a new waiter.
|
|
135
|
+
return new Promise<ToolCallbackResult>((resolve) => {
|
|
136
|
+
const list = waiters.get(id) ?? []
|
|
137
|
+
list.push({ resolve, expiresAt: existing.expiresAt })
|
|
138
|
+
waiters.set(id, list)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── New request ───────────────────────────────────────────────────────
|
|
143
|
+
const verdict = policy.evaluate({
|
|
144
|
+
toolName: args.toolName,
|
|
145
|
+
args: args.args,
|
|
146
|
+
chatPolicy: args.chatPolicy,
|
|
147
|
+
cwd: args.cwd,
|
|
148
|
+
})
|
|
149
|
+
const now = opts.now()
|
|
150
|
+
const expiresAt = now + opts.timeoutMs
|
|
151
|
+
const req: ToolRequest = {
|
|
152
|
+
id,
|
|
153
|
+
chatId: args.chatId,
|
|
154
|
+
sessionId: args.sessionId,
|
|
155
|
+
toolUseId: args.toolUseId,
|
|
156
|
+
toolName: args.toolName,
|
|
157
|
+
arguments: args.args,
|
|
158
|
+
canonicalArgsHash: hash,
|
|
159
|
+
policyVerdict: verdict.verdict,
|
|
160
|
+
status: "pending",
|
|
161
|
+
createdAt: now,
|
|
162
|
+
expiresAt,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Register synchronously so subsequent calls within the same tick see it.
|
|
166
|
+
inMemory.set(id, { ...req })
|
|
167
|
+
seenToolUseIds.set(seenKey(args), { id, toolName: args.toolName, canonicalArgsHash: hash })
|
|
168
|
+
|
|
169
|
+
if (verdict.verdict === "auto-allow" || verdict.verdict === "auto-deny") {
|
|
170
|
+
const decision: ToolRequestDecision = verdict.verdict === "auto-allow"
|
|
171
|
+
? { kind: "allow", reason: verdict.reason }
|
|
172
|
+
: { kind: "deny", reason: verdict.reason }
|
|
173
|
+
const resolvedReq: ToolRequest = { ...req, status: "answered", decision, resolvedAt: now }
|
|
174
|
+
inMemory.set(id, resolvedReq)
|
|
175
|
+
// Persist in background; caller gets immediate result.
|
|
176
|
+
void (async () => {
|
|
177
|
+
await opts.store.putToolRequest(req)
|
|
178
|
+
await opts.store.resolveToolRequest(id, { status: "answered", decision, resolvedAt: now })
|
|
179
|
+
})()
|
|
180
|
+
return Promise.resolve({ status: "answered", decision })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// "ask" verdict → persist then wait for external answer.
|
|
184
|
+
const pendingPromise = new Promise<ToolCallbackResult>((resolve) => {
|
|
185
|
+
const list = waiters.get(id) ?? []
|
|
186
|
+
list.push({ resolve, expiresAt })
|
|
187
|
+
waiters.set(id, list)
|
|
188
|
+
})
|
|
189
|
+
void opts.store.putToolRequest(req)
|
|
190
|
+
return pendingPromise
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
async answer(id, decision) {
|
|
194
|
+
const existing = inMemory.get(id) ?? opts.store.getToolRequest(id)
|
|
195
|
+
if (!existing || POLICY_TERMINAL_STATUSES.has(existing.status)) return
|
|
196
|
+
await persistResolve(id, { status: "answered", decision, resolvedAt: opts.now() })
|
|
197
|
+
resolveWaiters(id, { status: "answered", decision })
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
async cancel(id, reason) {
|
|
201
|
+
const existing = inMemory.get(id) ?? opts.store.getToolRequest(id)
|
|
202
|
+
if (!existing || POLICY_TERMINAL_STATUSES.has(existing.status)) return
|
|
203
|
+
const decision: ToolRequestDecision = { kind: "deny", reason: `canceled: ${reason}` }
|
|
204
|
+
await persistResolve(id, { status: "canceled", decision, resolvedAt: opts.now() })
|
|
205
|
+
resolveWaiters(id, { status: "canceled", decision })
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
async cancelAllForChat(chatId, reason) {
|
|
209
|
+
// Collect pending ids from in-memory mirror first (synchronous), then
|
|
210
|
+
// also check store for any records loaded before this service started.
|
|
211
|
+
const pendingIds = new Set<string>()
|
|
212
|
+
for (const [id, req] of inMemory.entries()) {
|
|
213
|
+
if (req.chatId === chatId && req.status === "pending") pendingIds.add(id)
|
|
214
|
+
}
|
|
215
|
+
const storeList = opts.store.listPendingToolRequests(chatId)
|
|
216
|
+
for (const req of storeList) pendingIds.add(req.id)
|
|
217
|
+
for (const id of pendingIds) await svc.cancel(id, reason)
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
async cancelAllForSession(sessionId, reason) {
|
|
221
|
+
const ids = Array.from(waiters.keys())
|
|
222
|
+
for (const id of ids) {
|
|
223
|
+
const req = inMemory.get(id) ?? opts.store.getToolRequest(id)
|
|
224
|
+
if (req && req.sessionId === sessionId) await svc.cancel(id, reason)
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
async recoverOnStartup() {
|
|
229
|
+
const all = opts.store.scanAllToolRequests()
|
|
230
|
+
for (const req of all) {
|
|
231
|
+
if (req.status !== "pending") continue
|
|
232
|
+
const decision: ToolRequestDecision = { kind: "deny", reason: "server_restarted" }
|
|
233
|
+
await persistResolve(req.id, { status: "session_closed", decision, resolvedAt: opts.now() })
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
async tickTimeouts() {
|
|
238
|
+
const now = opts.now()
|
|
239
|
+
for (const [id, list] of waiters.entries()) {
|
|
240
|
+
if (list.length === 0) continue
|
|
241
|
+
if (list[0].expiresAt > now) continue
|
|
242
|
+
const decision: ToolRequestDecision = { kind: "deny", reason: "timeout" }
|
|
243
|
+
await persistResolve(id, { status: "timeout", decision, resolvedAt: now })
|
|
244
|
+
resolveWaiters(id, { status: "timeout", decision })
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return svc
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Creates a ToolCallbackService and immediately calls recoverOnStartup()
|
|
254
|
+
* to fail-close any pending tool requests left over from a previous server
|
|
255
|
+
* run. KANNA_SERVER_SECRET should be set in the environment for stable
|
|
256
|
+
* HMAC ids within a process lifetime; if unset, a fresh random UUID is used
|
|
257
|
+
* (cross-restart idempotency is not required because recoverOnStartup()
|
|
258
|
+
* already closes all pending records).
|
|
259
|
+
*/
|
|
260
|
+
export async function initToolCallbackOnBoot(args: {
|
|
261
|
+
store: EventStore
|
|
262
|
+
serverSecret: string
|
|
263
|
+
now?: () => number
|
|
264
|
+
timeoutMs?: number
|
|
265
|
+
}): Promise<ToolCallbackService> {
|
|
266
|
+
const svc = createToolCallbackService({
|
|
267
|
+
store: args.store,
|
|
268
|
+
serverSecret: args.serverSecret,
|
|
269
|
+
now: args.now ?? (() => Date.now()),
|
|
270
|
+
timeoutMs: args.timeoutMs ?? 600_000,
|
|
271
|
+
})
|
|
272
|
+
await svc.recoverOnStartup()
|
|
273
|
+
return svc
|
|
274
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import {
|
|
3
|
+
sendUserPrompt,
|
|
4
|
+
sendExitCommand,
|
|
5
|
+
dismissTrustDialogIfPresent,
|
|
6
|
+
waitForTuiReady,
|
|
7
|
+
waitForTuiReadyWithTrustDismiss,
|
|
8
|
+
TRUST_DIALOG_MARKER,
|
|
9
|
+
TUI_READY_MARKER,
|
|
10
|
+
} from "./tui-control"
|
|
11
|
+
import { OutputRing } from "./output-ring"
|
|
12
|
+
import type { PtyProcess } from "./pty-process.adapter"
|
|
13
|
+
|
|
14
|
+
function fakePty(): PtyProcess & { sent: string[] } {
|
|
15
|
+
const sent: string[] = []
|
|
16
|
+
return {
|
|
17
|
+
sent,
|
|
18
|
+
pid: 99997,
|
|
19
|
+
async sendInput(data: string) { sent.push(data) },
|
|
20
|
+
resize() { /* noop */ },
|
|
21
|
+
exited: new Promise<number>(() => { /* never */ }),
|
|
22
|
+
close() { /* noop */ },
|
|
23
|
+
kill() { /* noop */ },
|
|
24
|
+
} as PtyProcess & { sent: string[] }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("sendUserPrompt", () => {
|
|
28
|
+
test("writes bracketed-paste wrapped text then separate carriage return once ring grows", async () => {
|
|
29
|
+
const pty = fakePty()
|
|
30
|
+
const ring = new OutputRing()
|
|
31
|
+
// Simulate the TUI rendering the paste preview shortly after the paste write.
|
|
32
|
+
setTimeout(() => ring.append("[Pasted text #1 +56 lines]"), 5)
|
|
33
|
+
await sendUserPrompt(pty, ring, "say hi", { commitTimeoutMs: 500, pollMs: 1 })
|
|
34
|
+
expect(pty.sent).toEqual(["\x1b[200~say hi\x1b[201~", "\r"])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("empty string still emits paste markers + carriage return after commit", async () => {
|
|
38
|
+
const pty = fakePty()
|
|
39
|
+
const ring = new OutputRing()
|
|
40
|
+
setTimeout(() => ring.append("x"), 5)
|
|
41
|
+
await sendUserPrompt(pty, ring, "", { commitTimeoutMs: 500, pollMs: 1 })
|
|
42
|
+
expect(pty.sent).toEqual(["\x1b[200~\x1b[201~", "\r"])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("sends Enter after commitTimeoutMs even if the ring never grows (degraded fallback)", async () => {
|
|
46
|
+
const pty = fakePty()
|
|
47
|
+
const ring = new OutputRing() // never grows
|
|
48
|
+
const start = Date.now()
|
|
49
|
+
await sendUserPrompt(pty, ring, "no echo", { commitTimeoutMs: 50, pollMs: 5 })
|
|
50
|
+
const elapsed = Date.now() - start
|
|
51
|
+
expect(pty.sent).toEqual(["\x1b[200~no echo\x1b[201~", "\r"])
|
|
52
|
+
expect(elapsed).toBeGreaterThanOrEqual(45)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe("sendExitCommand", () => {
|
|
57
|
+
test("writes /exit + carriage return", async () => {
|
|
58
|
+
const pty = fakePty()
|
|
59
|
+
await sendExitCommand(pty)
|
|
60
|
+
expect(pty.sent).toEqual(["/exit\r"])
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe("dismissTrustDialogIfPresent", () => {
|
|
65
|
+
test("sends carriage return when ringbuf contains trust marker", async () => {
|
|
66
|
+
const pty = fakePty()
|
|
67
|
+
const ring = new OutputRing()
|
|
68
|
+
ring.append("Quick safety check: Do you trust this folder? trust this folder")
|
|
69
|
+
const dismissed = await dismissTrustDialogIfPresent(pty, ring)
|
|
70
|
+
expect(dismissed).toBe(true)
|
|
71
|
+
expect(pty.sent).toEqual(["\r"])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("does nothing when ringbuf lacks trust marker", async () => {
|
|
75
|
+
const pty = fakePty()
|
|
76
|
+
const ring = new OutputRing()
|
|
77
|
+
ring.append("Welcome back c!")
|
|
78
|
+
const dismissed = await dismissTrustDialogIfPresent(pty, ring)
|
|
79
|
+
expect(dismissed).toBe(false)
|
|
80
|
+
expect(pty.sent).toEqual([])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("exported TRUST_DIALOG_MARKER is the substring matched", () => {
|
|
84
|
+
expect(TRUST_DIALOG_MARKER).toBe("trust this folder")
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe("waitForTuiReady", () => {
|
|
89
|
+
test("returns 'marker' when ringbuf already contains the input-box marker", async () => {
|
|
90
|
+
const ring = new OutputRing()
|
|
91
|
+
ring.append("❯ ")
|
|
92
|
+
const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10, quietPeriodMs: 0 })
|
|
93
|
+
expect(result).toBe("marker")
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test("returns 'timeout' when no marker appears within hardCapMs", async () => {
|
|
97
|
+
const ring = new OutputRing()
|
|
98
|
+
const result = await waitForTuiReady(ring, { hardCapMs: 200, pollMs: 10, quietPeriodMs: 0 })
|
|
99
|
+
expect(result).toBe("timeout")
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("polls until marker appears", async () => {
|
|
103
|
+
const ring = new OutputRing()
|
|
104
|
+
setTimeout(() => ring.append("❯ "), 50)
|
|
105
|
+
const start = Date.now()
|
|
106
|
+
const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10, quietPeriodMs: 0 })
|
|
107
|
+
const elapsed = Date.now() - start
|
|
108
|
+
expect(result).toBe("marker")
|
|
109
|
+
expect(elapsed).toBeLessThan(300)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Regression: claude TUI v2.1.146 leaks `❯ ` during splash/trust/MCP render
|
|
113
|
+
// before Ink mounts the keyboard handler. Returning "marker" on first hit
|
|
114
|
+
// makes the driver send the first prompt into a TUI that discards stdin.
|
|
115
|
+
// The quiet-period gate waits for ring growth to settle as a proxy for
|
|
116
|
+
// "input handler attached" — drops the race that causes PTY chats to hang
|
|
117
|
+
// with no transcript file ever created.
|
|
118
|
+
test("waits for ring to stay quiet for quietPeriodMs after marker hit", async () => {
|
|
119
|
+
const ring = new OutputRing()
|
|
120
|
+
ring.append("splash banner ❯ ")
|
|
121
|
+
// Keep appending bytes for 200 ms after the marker first appears — the
|
|
122
|
+
// gate must NOT resolve while the TUI is still rendering.
|
|
123
|
+
const interval = setInterval(() => ring.append("."), 30)
|
|
124
|
+
setTimeout(() => clearInterval(interval), 200)
|
|
125
|
+
const start = Date.now()
|
|
126
|
+
const result = await waitForTuiReady(ring, { hardCapMs: 2000, pollMs: 10, quietPeriodMs: 150 })
|
|
127
|
+
const elapsed = Date.now() - start
|
|
128
|
+
expect(result).toBe("marker")
|
|
129
|
+
// Render bursts (200 ms) + quiet period (150 ms) = at least 350 ms.
|
|
130
|
+
expect(elapsed).toBeGreaterThanOrEqual(300)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("resolves immediately when ring stays quiet from the start", async () => {
|
|
134
|
+
const ring = new OutputRing()
|
|
135
|
+
ring.append("❯ ")
|
|
136
|
+
const start = Date.now()
|
|
137
|
+
const result = await waitForTuiReady(ring, { hardCapMs: 2000, pollMs: 10, quietPeriodMs: 80 })
|
|
138
|
+
const elapsed = Date.now() - start
|
|
139
|
+
expect(result).toBe("marker")
|
|
140
|
+
expect(elapsed).toBeGreaterThanOrEqual(70)
|
|
141
|
+
expect(elapsed).toBeLessThan(250)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test("exported TUI_READY_MARKER is the input-box prompt", () => {
|
|
145
|
+
expect(TUI_READY_MARKER).toBe("❯ ")
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test("returns 'marker' when TUI renders ❯ with \\x1b[1C instead of space", async () => {
|
|
149
|
+
const ring = new OutputRing()
|
|
150
|
+
// Real TUI output: space after ❯ is cursor-forward-1, not a literal space
|
|
151
|
+
ring.append("❯\x1b[1C")
|
|
152
|
+
const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10 })
|
|
153
|
+
expect(result).toBe("marker")
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test("returns 'marker' when TUI renders ❯ followed by U+00A0 (non-breaking space)", async () => {
|
|
157
|
+
const ring = new OutputRing()
|
|
158
|
+
// Real TUI output: ❯ is followed by NBSP (U+00A0), not a regular space
|
|
159
|
+
ring.append("❯ ")
|
|
160
|
+
const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10 })
|
|
161
|
+
expect(result).toBe("marker")
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe("dismissTrustDialogIfPresent (ANSI-encoded ring)", () => {
|
|
166
|
+
test("detects trust dialog when words separated by \\x1b[1C (TUI rendering)", async () => {
|
|
167
|
+
const pty = fakePty()
|
|
168
|
+
const ring = new OutputRing()
|
|
169
|
+
// Real TUI output: spaces rendered as cursor-forward-1 escape sequences
|
|
170
|
+
ring.append("\x1b[1C❯\x1b[1C1.\x1b[1CYes,\x1b[1CI\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder\r\n")
|
|
171
|
+
const dismissed = await dismissTrustDialogIfPresent(pty, ring)
|
|
172
|
+
expect(dismissed).toBe(true)
|
|
173
|
+
expect(pty.sent).toEqual(["\r"])
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test("does not false-trigger on plain text without trust marker", async () => {
|
|
177
|
+
const pty = fakePty()
|
|
178
|
+
const ring = new OutputRing()
|
|
179
|
+
ring.append("\x1b[1CWelcome\x1b[1Cback!\x1b[0m❯ ")
|
|
180
|
+
const dismissed = await dismissTrustDialogIfPresent(pty, ring)
|
|
181
|
+
expect(dismissed).toBe(false)
|
|
182
|
+
expect(pty.sent).toEqual([])
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe("waitForTuiReadyWithTrustDismiss", () => {
|
|
187
|
+
test("returns 'ready' immediately when input box already present", async () => {
|
|
188
|
+
const pty = fakePty()
|
|
189
|
+
const ring = new OutputRing()
|
|
190
|
+
ring.append("❯ ")
|
|
191
|
+
const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 500, pollMs: 10 })
|
|
192
|
+
expect(result).toBe("ready")
|
|
193
|
+
expect(pty.sent).toEqual([])
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test("dismisses ANSI trust dialog then resolves when input box appears", async () => {
|
|
197
|
+
const pty = fakePty()
|
|
198
|
+
const ring = new OutputRing()
|
|
199
|
+
// Trust dialog with ANSI-encoded text appears first
|
|
200
|
+
ring.append("\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder")
|
|
201
|
+
// After 50ms simulate TUI loading: input box appears after dismiss sends \r
|
|
202
|
+
setTimeout(() => ring.append("❯ "), 80)
|
|
203
|
+
const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 1000, pollMs: 10 })
|
|
204
|
+
expect(result).toBe("ready")
|
|
205
|
+
expect(pty.sent).toContain("\r")
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test("returns 'timeout' when neither marker appears", async () => {
|
|
209
|
+
const pty = fakePty()
|
|
210
|
+
const ring = new OutputRing()
|
|
211
|
+
const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 150, pollMs: 10 })
|
|
212
|
+
expect(result).toBe("timeout")
|
|
213
|
+
expect(pty.sent).toEqual([])
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test("returns 'ready' when TUI renders ❯ with \\x1b[1C (ANSI cursor-forward)", async () => {
|
|
217
|
+
const pty = fakePty()
|
|
218
|
+
const ring = new OutputRing()
|
|
219
|
+
ring.append("❯\x1b[1C")
|
|
220
|
+
const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 500, pollMs: 10 })
|
|
221
|
+
expect(result).toBe("ready")
|
|
222
|
+
expect(pty.sent).toEqual([])
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test("does not false-trigger on trust dialog's own ❯ selection cursor", async () => {
|
|
226
|
+
// Trust dialog renders "❯ 1. Yes, I trust this folder" as ANSI:
|
|
227
|
+
// "❯\x1b[1C1.\x1b[1CYes,...trust\x1b[1Cthis\x1b[1Cfolder"
|
|
228
|
+
// stripAnsi gives "❯ 1. Yes, I trust this folder" which contains "❯ "
|
|
229
|
+
// → must NOT trigger "ready" while trust dialog is present
|
|
230
|
+
const pty = fakePty()
|
|
231
|
+
const ring = new OutputRing()
|
|
232
|
+
ring.append("\x1b[1C❯\x1b[1C1.\x1b[1CYes,\x1b[1CI\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder\r\n")
|
|
233
|
+
setTimeout(() => ring.append("❯\x1b[1C"), 80)
|
|
234
|
+
const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 1000, pollMs: 10 })
|
|
235
|
+
expect(result).toBe("ready")
|
|
236
|
+
expect(pty.sent).toContain("\r")
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test("dismisses trust dialog only once even if marker persists in ring", async () => {
|
|
240
|
+
const pty = fakePty()
|
|
241
|
+
const ring = new OutputRing()
|
|
242
|
+
ring.append("\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder")
|
|
243
|
+
setTimeout(() => ring.append("❯ "), 80)
|
|
244
|
+
await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 1000, pollMs: 10 })
|
|
245
|
+
// \r should appear exactly once (dismiss sent once, not repeated each poll)
|
|
246
|
+
expect(pty.sent.filter((s) => s === "\r")).toHaveLength(1)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// Regression: same race as waitForTuiReady — after trust dismiss the input
|
|
250
|
+
// box marker can render before Ink's keyboard handler mounts. Quiet-period
|
|
251
|
+
// gate must apply on this path too so prompts don't land into a discarding
|
|
252
|
+
// TUI under load.
|
|
253
|
+
test("waits for ring to stay quiet for quietPeriodMs after post-dismiss marker hit", async () => {
|
|
254
|
+
const pty = fakePty()
|
|
255
|
+
const ring = new OutputRing()
|
|
256
|
+
ring.append("\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder")
|
|
257
|
+
// After dismiss, marker arrives at t=50, then more bytes for 200 ms.
|
|
258
|
+
setTimeout(() => ring.append("❯ "), 50)
|
|
259
|
+
const interval = setInterval(() => ring.append("."), 30)
|
|
260
|
+
setTimeout(() => clearInterval(interval), 250)
|
|
261
|
+
const start = Date.now()
|
|
262
|
+
const result = await waitForTuiReadyWithTrustDismiss(pty, ring, {
|
|
263
|
+
hardCapMs: 2000,
|
|
264
|
+
pollMs: 10,
|
|
265
|
+
quietPeriodMs: 150,
|
|
266
|
+
})
|
|
267
|
+
const elapsed = Date.now() - start
|
|
268
|
+
expect(result).toBe("ready")
|
|
269
|
+
// Bursts end ~250 ms + quiet 150 ms.
|
|
270
|
+
expect(elapsed).toBeGreaterThanOrEqual(350)
|
|
271
|
+
})
|
|
272
|
+
})
|