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,182 @@
|
|
|
1
|
+
import type { PtyProcess } from "./pty-process.adapter"
|
|
2
|
+
import type { OutputRing } from "./output-ring"
|
|
3
|
+
|
|
4
|
+
export const TRUST_DIALOG_MARKER = "trust this folder"
|
|
5
|
+
export const TUI_READY_MARKER = "❯ "
|
|
6
|
+
export const TUI_READY_HARD_CAP_DEFAULT_MS = 3000
|
|
7
|
+
// Quiet-period gate: after first ❯ marker hit, the TUI may still be in the
|
|
8
|
+
// middle of rendering (splash → cwd line → MCP spinner → input frame). The
|
|
9
|
+
// marker can leak from a transient render before Ink mounts the keyboard
|
|
10
|
+
// handler. Waiting for the output ring to stay quiet for this long after the
|
|
11
|
+
// marker is the cheapest proxy for "input handler attached and ready". Drops
|
|
12
|
+
// the race where the first prompt lands during splash and gets discarded.
|
|
13
|
+
export const TUI_READY_QUIET_DEFAULT_MS = 300
|
|
14
|
+
|
|
15
|
+
// Strip VT100/ANSI escape sequences and normalize non-breaking spaces so
|
|
16
|
+
// plain-text markers can be matched against raw PTY output. The TUI renders:
|
|
17
|
+
// - spaces as \x1b[1C (cursor-right-1) — replaced with regular space
|
|
18
|
+
// - the ❯ input prompt followed by U+00A0 (NBSP) — normalized to regular space
|
|
19
|
+
function stripAnsi(s: string): string {
|
|
20
|
+
return s
|
|
21
|
+
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, " ")
|
|
22
|
+
.replace(/\x1b./g, "")
|
|
23
|
+
.replace(/\u00a0/g, " ")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WaitForTuiReadyOpts {
|
|
27
|
+
hardCapMs?: number
|
|
28
|
+
pollMs?: number
|
|
29
|
+
quietPeriodMs?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function waitForTuiReady(
|
|
33
|
+
ring: OutputRing,
|
|
34
|
+
opts: WaitForTuiReadyOpts = {},
|
|
35
|
+
): Promise<"marker" | "timeout"> {
|
|
36
|
+
const hardCapMs = opts.hardCapMs ?? TUI_READY_HARD_CAP_DEFAULT_MS
|
|
37
|
+
const pollMs = opts.pollMs ?? 50
|
|
38
|
+
const quietPeriodMs = opts.quietPeriodMs ?? TUI_READY_QUIET_DEFAULT_MS
|
|
39
|
+
const start = Date.now()
|
|
40
|
+
while (true) {
|
|
41
|
+
if (stripAnsi(ring.tail()).includes(TUI_READY_MARKER)) {
|
|
42
|
+
await waitForRingQuiet(ring, { quietMs: quietPeriodMs, pollMs, deadline: start + hardCapMs })
|
|
43
|
+
return "marker"
|
|
44
|
+
}
|
|
45
|
+
if (Date.now() - start >= hardCapMs) return "timeout"
|
|
46
|
+
await new Promise((r) => setTimeout(r, pollMs))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* After the ❯ marker first appears, wait until the ring stays the same size
|
|
52
|
+
* for `quietMs` straight — that is the cheapest proxy for "TUI render queue
|
|
53
|
+
* drained, Ink keyboard handler mounted". If the deadline is reached first,
|
|
54
|
+
* return early (best-effort: we did see the marker). Polls every `pollMs`.
|
|
55
|
+
*/
|
|
56
|
+
async function waitForRingQuiet(
|
|
57
|
+
ring: OutputRing,
|
|
58
|
+
opts: { quietMs: number; pollMs: number; deadline: number },
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
if (opts.quietMs <= 0) return
|
|
61
|
+
let lastLength = ring.tail().length
|
|
62
|
+
let quietStart = Date.now()
|
|
63
|
+
while (Date.now() - quietStart < opts.quietMs) {
|
|
64
|
+
if (Date.now() >= opts.deadline) return
|
|
65
|
+
await new Promise((r) => setTimeout(r, opts.pollMs))
|
|
66
|
+
const currentLength = ring.tail().length
|
|
67
|
+
if (currentLength !== lastLength) {
|
|
68
|
+
lastLength = currentLength
|
|
69
|
+
quietStart = Date.now()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function dismissTrustDialogIfPresent(
|
|
75
|
+
pty: PtyProcess,
|
|
76
|
+
ring: OutputRing,
|
|
77
|
+
): Promise<boolean> {
|
|
78
|
+
// Strip ANSI before matching: the TUI renders spaces as \x1b[1C so the
|
|
79
|
+
// literal phrase "trust this folder" never appears in the raw ring bytes.
|
|
80
|
+
if (!stripAnsi(ring.tail()).includes(TRUST_DIALOG_MARKER)) return false
|
|
81
|
+
await pty.sendInput("\r")
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface WaitForTuiReadyWithTrustDismissOpts {
|
|
86
|
+
hardCapMs?: number
|
|
87
|
+
pollMs?: number
|
|
88
|
+
quietPeriodMs?: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Combined helper: polls for the TUI input-box marker ("❯ ") while
|
|
93
|
+
* concurrently watching for the trust dialog. Dismisses the dialog once
|
|
94
|
+
* (via \r) and keeps polling until the real input box appears.
|
|
95
|
+
*
|
|
96
|
+
* Use this instead of separate waitForTuiReady + dismissTrustDialogIfPresent
|
|
97
|
+
* calls — the two-step approach races: the trust dialog blocks the input box,
|
|
98
|
+
* so waitForTuiReady times out before the dialog is dismissed.
|
|
99
|
+
*/
|
|
100
|
+
export async function waitForTuiReadyWithTrustDismiss(
|
|
101
|
+
pty: PtyProcess,
|
|
102
|
+
ring: OutputRing,
|
|
103
|
+
opts: WaitForTuiReadyWithTrustDismissOpts = {},
|
|
104
|
+
): Promise<"ready" | "timeout"> {
|
|
105
|
+
const hardCapMs = opts.hardCapMs ?? 15_000
|
|
106
|
+
const pollMs = opts.pollMs ?? 50
|
|
107
|
+
const quietPeriodMs = opts.quietPeriodMs ?? TUI_READY_QUIET_DEFAULT_MS
|
|
108
|
+
const start = Date.now()
|
|
109
|
+
let trustDismissed = false
|
|
110
|
+
// After dismissing the trust dialog, only match the ready marker against
|
|
111
|
+
// content added after the dismiss point — the trust dialog rendering itself
|
|
112
|
+
// contains "❯\x1b[1C1. Yes,..." which strips to "❯ 1. Yes,..." and would
|
|
113
|
+
// false-trigger the TUI_READY_MARKER check if the full ring were searched.
|
|
114
|
+
let postDismissOffset = 0
|
|
115
|
+
|
|
116
|
+
while (Date.now() - start < hardCapMs) {
|
|
117
|
+
const raw = ring.tail()
|
|
118
|
+
if (!trustDismissed && stripAnsi(raw).includes(TRUST_DIALOG_MARKER)) {
|
|
119
|
+
postDismissOffset = raw.length
|
|
120
|
+
await pty.sendInput("\r")
|
|
121
|
+
trustDismissed = true
|
|
122
|
+
} else {
|
|
123
|
+
const checkWindow = trustDismissed ? raw.slice(postDismissOffset) : raw
|
|
124
|
+
if (stripAnsi(checkWindow).includes(TUI_READY_MARKER)) {
|
|
125
|
+
await waitForRingQuiet(ring, { quietMs: quietPeriodMs, pollMs, deadline: start + hardCapMs })
|
|
126
|
+
return "ready"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await new Promise((r) => setTimeout(r, pollMs))
|
|
130
|
+
}
|
|
131
|
+
return "timeout"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface SendUserPromptOpts {
|
|
135
|
+
/**
|
|
136
|
+
* Hard cap on how long to wait for the TUI to commit the bracketed
|
|
137
|
+
* paste to its input box before sending Enter. Defaults to 2 s.
|
|
138
|
+
*/
|
|
139
|
+
commitTimeoutMs?: number
|
|
140
|
+
/** Poll interval while waiting for ring growth. Defaults to 10 ms. */
|
|
141
|
+
pollMs?: number
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function sendUserPrompt(
|
|
145
|
+
pty: PtyProcess,
|
|
146
|
+
ring: OutputRing,
|
|
147
|
+
text: string,
|
|
148
|
+
opts: SendUserPromptOpts = {},
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
// Bracketed paste (\x1b[200~...\x1b[201~) tells the TUI "this is pasted
|
|
151
|
+
// text, do not interpret control chars" so newlines in `text` don't
|
|
152
|
+
// submit prematurely. The follow-up \r is the actual "submit" key.
|
|
153
|
+
//
|
|
154
|
+
// The catch: claude's TUI processes bracketed paste asynchronously —
|
|
155
|
+
// multi-line pastes get collapsed into a "[Pasted text #N +X lines]"
|
|
156
|
+
// reference, and the input box rendering happens AFTER the paste-end
|
|
157
|
+
// marker is consumed. If we send \r before that rendering completes,
|
|
158
|
+
// the keystroke is absorbed into the still-open paste buffer instead
|
|
159
|
+
// of being treated as submit. A fixed-time sleep here is a brittle
|
|
160
|
+
// timing hack — system load, model effort settings, and PTY scheduling
|
|
161
|
+
// all shift the window.
|
|
162
|
+
//
|
|
163
|
+
// Adaptive fix: snapshot the output ring length, write the paste,
|
|
164
|
+
// then wait until the ring GROWS (i.e. the TUI rendered something in
|
|
165
|
+
// response to the paste) before sending Enter. The grow signal is
|
|
166
|
+
// deterministic — if it never arrives within commitTimeoutMs we fall
|
|
167
|
+
// through and send Enter anyway, matching prior timeout behaviour.
|
|
168
|
+
const commitTimeoutMs = opts.commitTimeoutMs ?? 2_000
|
|
169
|
+
const pollMs = opts.pollMs ?? 10
|
|
170
|
+
const baseline = ring.tail().length
|
|
171
|
+
await pty.sendInput(`\x1b[200~${text}\x1b[201~`)
|
|
172
|
+
const deadline = Date.now() + commitTimeoutMs
|
|
173
|
+
while (Date.now() < deadline) {
|
|
174
|
+
if (ring.tail().length > baseline) break
|
|
175
|
+
await new Promise((r) => setTimeout(r, pollMs))
|
|
176
|
+
}
|
|
177
|
+
await pty.sendInput("\r")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function sendExitCommand(pty: PtyProcess): Promise<void> {
|
|
181
|
+
await pty.sendInput("/exit\r")
|
|
182
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
|
2
|
+
import { mkdtemp, rm, writeFile, mkdir, appendFile } from "node:fs/promises"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import {
|
|
6
|
+
findLatestTranscript,
|
|
7
|
+
startTranscriptStream,
|
|
8
|
+
waitForResultEntry,
|
|
9
|
+
} from "./tui-source.adapter"
|
|
10
|
+
import { encodeCwd } from "./jsonl-path.adapter"
|
|
11
|
+
|
|
12
|
+
let workHome: string
|
|
13
|
+
let projectDir: string
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
workHome = await mkdtemp(path.join(tmpdir(), "kanna-tui-source-"))
|
|
17
|
+
projectDir = path.join(workHome, ".claude", "projects", "fake-cwd")
|
|
18
|
+
await mkdir(projectDir, { recursive: true })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await rm(workHome, { recursive: true, force: true })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe("findLatestTranscript", () => {
|
|
26
|
+
test("returns null when project dir empty", async () => {
|
|
27
|
+
const result = await findLatestTranscript(projectDir)
|
|
28
|
+
expect(result).toBeNull()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test("returns path of newest .jsonl file", async () => {
|
|
32
|
+
const fileA = path.join(projectDir, "aaa.jsonl")
|
|
33
|
+
const fileB = path.join(projectDir, "bbb.jsonl")
|
|
34
|
+
await writeFile(fileA, "{}\n")
|
|
35
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
36
|
+
await writeFile(fileB, "{}\n")
|
|
37
|
+
const result = await findLatestTranscript(projectDir)
|
|
38
|
+
expect(result).toBe(fileB)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("ignores non-.jsonl files", async () => {
|
|
42
|
+
await writeFile(path.join(projectDir, "notes.txt"), "hello")
|
|
43
|
+
const result = await findLatestTranscript(projectDir)
|
|
44
|
+
expect(result).toBeNull()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("returns null when project dir does not exist", async () => {
|
|
48
|
+
const result = await findLatestTranscript(path.join(workHome, "no-such-dir"))
|
|
49
|
+
expect(result).toBeNull()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test("minMtimeMs filter skips JSONLs older than the floor", async () => {
|
|
53
|
+
const stale = path.join(projectDir, "stale.jsonl")
|
|
54
|
+
const fresh = path.join(projectDir, "fresh.jsonl")
|
|
55
|
+
await writeFile(stale, "{}\n")
|
|
56
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
57
|
+
const floor = Date.now()
|
|
58
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
59
|
+
await writeFile(fresh, "{}\n")
|
|
60
|
+
const result = await findLatestTranscript(projectDir, { minMtimeMs: floor })
|
|
61
|
+
expect(result).toBe(fresh)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("minMtimeMs returns null when every JSONL is older than the floor", async () => {
|
|
65
|
+
const a = path.join(projectDir, "a.jsonl")
|
|
66
|
+
await writeFile(a, "{}\n")
|
|
67
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
68
|
+
const result = await findLatestTranscript(projectDir, { minMtimeMs: Date.now() })
|
|
69
|
+
expect(result).toBeNull()
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe("startTranscriptStream (dir-watch)", () => {
|
|
74
|
+
test("picks up file written after stream start", async () => {
|
|
75
|
+
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
76
|
+
const filePath = path.join(projectDir, "new.jsonl")
|
|
77
|
+
setTimeout(() => writeFile(filePath, '{"type":"hello"}\n'), 100)
|
|
78
|
+
const resolved = await stream.filePath
|
|
79
|
+
expect(resolved).toBe(filePath)
|
|
80
|
+
stream.close()
|
|
81
|
+
}, 5000)
|
|
82
|
+
|
|
83
|
+
test("opens existing file when present at start", async () => {
|
|
84
|
+
const filePath = path.join(projectDir, "existing.jsonl")
|
|
85
|
+
await writeFile(filePath, '{"type":"hello"}\n')
|
|
86
|
+
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
87
|
+
const resolved = await stream.filePath
|
|
88
|
+
expect(resolved).toBe(filePath)
|
|
89
|
+
stream.close()
|
|
90
|
+
}, 5000)
|
|
91
|
+
|
|
92
|
+
test("emits complete lines as they are appended", async () => {
|
|
93
|
+
const filePath = path.join(projectDir, "stream.jsonl")
|
|
94
|
+
await writeFile(filePath, '{"type":"one"}\n')
|
|
95
|
+
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
96
|
+
const iter = stream.lines[Symbol.asyncIterator]()
|
|
97
|
+
const first = await iter.next()
|
|
98
|
+
expect(first.value).toBe('{"type":"one"}')
|
|
99
|
+
setTimeout(() => writeFile(filePath, '{"type":"one"}\n{"type":"two"}\n'), 100)
|
|
100
|
+
const second = await iter.next()
|
|
101
|
+
expect(second.value).toBe('{"type":"two"}')
|
|
102
|
+
stream.close()
|
|
103
|
+
}, 5000)
|
|
104
|
+
|
|
105
|
+
test("holds partial line across writes", async () => {
|
|
106
|
+
const filePath = path.join(projectDir, "partial.jsonl")
|
|
107
|
+
await writeFile(filePath, '{"type":')
|
|
108
|
+
const stream = await startTranscriptStream({
|
|
109
|
+
projectDir,
|
|
110
|
+
firstFileTimeoutMs: 2000,
|
|
111
|
+
pollMode: true,
|
|
112
|
+
pollIntervalMs: 30,
|
|
113
|
+
})
|
|
114
|
+
const iter = stream.lines[Symbol.asyncIterator]()
|
|
115
|
+
let resolved = false
|
|
116
|
+
// Capture the first pending promise — it should not resolve yet (partial line)
|
|
117
|
+
const firstPromise = iter.next().then((r: IteratorResult<string>) => { resolved = true; return r })
|
|
118
|
+
await new Promise((r) => setTimeout(r, 200))
|
|
119
|
+
expect(resolved).toBe(false)
|
|
120
|
+
// Overwrite the file with a complete line — poller should pick it up
|
|
121
|
+
await writeFile(filePath, '{"type":"one"}\n')
|
|
122
|
+
const first = await firstPromise
|
|
123
|
+
expect(first.value).toBe('{"type":"one"}')
|
|
124
|
+
stream.close()
|
|
125
|
+
}, 5000)
|
|
126
|
+
|
|
127
|
+
test("times out when no file appears within firstFileTimeoutMs", async () => {
|
|
128
|
+
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 200 })
|
|
129
|
+
await expect(stream.filePath).rejects.toThrow(/transcript file did not appear/)
|
|
130
|
+
stream.close()
|
|
131
|
+
}, 5000)
|
|
132
|
+
|
|
133
|
+
test("knownFilePath skips dir-watch", async () => {
|
|
134
|
+
const filePath = path.join(projectDir, "known.jsonl")
|
|
135
|
+
await writeFile(filePath, '{"type":"hello"}\n')
|
|
136
|
+
const stream = await startTranscriptStream({
|
|
137
|
+
projectDir,
|
|
138
|
+
knownFilePath: filePath,
|
|
139
|
+
firstFileTimeoutMs: 500,
|
|
140
|
+
})
|
|
141
|
+
const resolved = await stream.filePath
|
|
142
|
+
expect(resolved).toBe(filePath)
|
|
143
|
+
stream.close()
|
|
144
|
+
}, 5000)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
describe("startTranscriptStream (poll-mode)", () => {
|
|
148
|
+
test("emits lines via polling when pollMode=true", async () => {
|
|
149
|
+
const stream = await startTranscriptStream({
|
|
150
|
+
projectDir,
|
|
151
|
+
pollMode: true,
|
|
152
|
+
pollIntervalMs: 30,
|
|
153
|
+
firstFileTimeoutMs: 2000,
|
|
154
|
+
})
|
|
155
|
+
const filePath = path.join(projectDir, "poll.jsonl")
|
|
156
|
+
setTimeout(() => writeFile(filePath, '{"type":"polled"}\n'), 100)
|
|
157
|
+
const iter = stream.lines[Symbol.asyncIterator]()
|
|
158
|
+
const first = await iter.next()
|
|
159
|
+
expect(first.value).toBe('{"type":"polled"}')
|
|
160
|
+
stream.close()
|
|
161
|
+
}, 5000)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe("startTranscriptStream (registry resolution)", () => {
|
|
165
|
+
// Regression: claude-code's per-pid session registry pins the JSONL path
|
|
166
|
+
// to the live child. When the registry-resolved JSONL never appears (e.g.
|
|
167
|
+
// claude was spawned but no prompt sent), older builds fell back to the
|
|
168
|
+
// newest mtime in the project dir — which is another concurrent chat's
|
|
169
|
+
// JSONL. That caused cross-session transcript bleed. The fix removed the
|
|
170
|
+
// fallback: registry path is authoritative, poll until close.
|
|
171
|
+
test("registry resolves but JSONL missing — never falls back to other JSONL in same dir", async () => {
|
|
172
|
+
const pid = 99001
|
|
173
|
+
// Real cwd dir so `encodeCwd` (which realpaths) doesn't ENOENT.
|
|
174
|
+
const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
|
|
175
|
+
const encoded = encodeCwd(realCwd)
|
|
176
|
+
const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
|
|
177
|
+
await mkdir(ownProjectDir, { recursive: true })
|
|
178
|
+
const sessionsDir = path.join(workHome, ".claude", "sessions")
|
|
179
|
+
await mkdir(sessionsDir, { recursive: true })
|
|
180
|
+
const ownSessionId = "our-session-aaaaaaaaaa"
|
|
181
|
+
await writeFile(
|
|
182
|
+
path.join(sessionsDir, `${pid}.json`),
|
|
183
|
+
JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
|
|
184
|
+
)
|
|
185
|
+
// Tempt the bug: drop a NEWER unrelated JSONL into the same project dir.
|
|
186
|
+
// Under the old mtime fallback this would have been picked up after
|
|
187
|
+
// `firstFileTimeoutMs` elapsed with no own-JSONL present.
|
|
188
|
+
const strangerFile = path.join(ownProjectDir, "stranger-session.jsonl")
|
|
189
|
+
await writeFile(strangerFile, '{"type":"assistant","message":{"content":[{"type":"text","text":"NOT OURS"}]}}\n')
|
|
190
|
+
|
|
191
|
+
const stream = await startTranscriptStream({
|
|
192
|
+
projectDir: ownProjectDir,
|
|
193
|
+
homeDir: workHome,
|
|
194
|
+
claudeChildPid: pid,
|
|
195
|
+
sessionRegistryTimeoutMs: 300,
|
|
196
|
+
// High timeout so the registry-poll timeout cannot fire during the
|
|
197
|
+
// 600 ms pending check below — this test guards bleed isolation, not
|
|
198
|
+
// timeout behaviour (covered separately).
|
|
199
|
+
firstFileTimeoutMs: 5_000,
|
|
200
|
+
pollIntervalMs: 20,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// filePath must NOT resolve to the stranger file even after timeouts.
|
|
204
|
+
const beforeWrite = await Promise.race([
|
|
205
|
+
stream.filePath
|
|
206
|
+
.then((fp) => ({ kind: "resolved" as const, fp }))
|
|
207
|
+
.catch((err: Error) => ({ kind: "rejected" as const, err })),
|
|
208
|
+
new Promise<{ kind: "pending" }>((r) => setTimeout(() => r({ kind: "pending" }), 600)),
|
|
209
|
+
])
|
|
210
|
+
expect(beforeWrite.kind).toBe("pending")
|
|
211
|
+
|
|
212
|
+
// Now the registry-pointed JSONL appears — filePath should resolve to it.
|
|
213
|
+
const ownFile = path.join(ownProjectDir, `${ownSessionId}.jsonl`)
|
|
214
|
+
await writeFile(ownFile, '{"type":"assistant","message":{"content":[{"type":"text","text":"ours"}]}}\n')
|
|
215
|
+
const resolved = await stream.filePath
|
|
216
|
+
expect(resolved).toBe(ownFile)
|
|
217
|
+
expect(resolved).not.toBe(strangerFile)
|
|
218
|
+
stream.close()
|
|
219
|
+
}, 5000)
|
|
220
|
+
|
|
221
|
+
// Regression: when claude TUI rendered the input box but the first prompt
|
|
222
|
+
// never reached it (input-handler mount race, splash banner swallow, etc.),
|
|
223
|
+
// the registry-resolved JSONL was never created and the driver waited
|
|
224
|
+
// forever inside locateFirstFile. firstFileTimeoutMs now bounds that wait;
|
|
225
|
+
// the rejection surfaces as a failure event and the user can retry instead
|
|
226
|
+
// of seeing a wedged session.
|
|
227
|
+
test("registry resolved but JSONL never appears — filePath rejects after firstFileTimeoutMs", async () => {
|
|
228
|
+
const pid = 99003
|
|
229
|
+
const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
|
|
230
|
+
const encoded = encodeCwd(realCwd)
|
|
231
|
+
const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
|
|
232
|
+
await mkdir(ownProjectDir, { recursive: true })
|
|
233
|
+
const sessionsDir = path.join(workHome, ".claude", "sessions")
|
|
234
|
+
await mkdir(sessionsDir, { recursive: true })
|
|
235
|
+
const ownSessionId = "our-session-cccccccccc"
|
|
236
|
+
await writeFile(
|
|
237
|
+
path.join(sessionsDir, `${pid}.json`),
|
|
238
|
+
JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const stream = await startTranscriptStream({
|
|
242
|
+
projectDir: ownProjectDir,
|
|
243
|
+
homeDir: workHome,
|
|
244
|
+
claudeChildPid: pid,
|
|
245
|
+
sessionRegistryTimeoutMs: 300,
|
|
246
|
+
firstFileTimeoutMs: 150,
|
|
247
|
+
pollIntervalMs: 20,
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const start = Date.now()
|
|
251
|
+
await expect(stream.filePath).rejects.toThrow(/did not appear in 150ms/)
|
|
252
|
+
const elapsed = Date.now() - start
|
|
253
|
+
// Allow scheduler slack but ensure we did not wait orders of magnitude longer.
|
|
254
|
+
expect(elapsed).toBeLessThan(1_500)
|
|
255
|
+
stream.close()
|
|
256
|
+
}, 5000)
|
|
257
|
+
|
|
258
|
+
test("registry resolves with existing JSONL — returns registry path immediately", async () => {
|
|
259
|
+
const pid = 99002
|
|
260
|
+
const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
|
|
261
|
+
const encoded = encodeCwd(realCwd)
|
|
262
|
+
const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
|
|
263
|
+
await mkdir(ownProjectDir, { recursive: true })
|
|
264
|
+
const sessionsDir = path.join(workHome, ".claude", "sessions")
|
|
265
|
+
await mkdir(sessionsDir, { recursive: true })
|
|
266
|
+
const ownSessionId = "our-session-bbbbbbbbbb"
|
|
267
|
+
const ownFile = path.join(ownProjectDir, `${ownSessionId}.jsonl`)
|
|
268
|
+
await writeFile(ownFile, "{}\n")
|
|
269
|
+
await writeFile(
|
|
270
|
+
path.join(sessionsDir, `${pid}.json`),
|
|
271
|
+
JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
|
|
272
|
+
)
|
|
273
|
+
// Newer stranger JSONL must not win — registry path is authoritative.
|
|
274
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
275
|
+
await writeFile(path.join(ownProjectDir, "stranger.jsonl"), "{}\n")
|
|
276
|
+
|
|
277
|
+
const stream = await startTranscriptStream({
|
|
278
|
+
projectDir: ownProjectDir,
|
|
279
|
+
homeDir: workHome,
|
|
280
|
+
claudeChildPid: pid,
|
|
281
|
+
})
|
|
282
|
+
const resolved = await stream.filePath
|
|
283
|
+
expect(resolved).toBe(ownFile)
|
|
284
|
+
stream.close()
|
|
285
|
+
}, 5000)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
describe("startTranscriptStream (safety-net poll vs fs.watch drops)", () => {
|
|
289
|
+
// Regression: fs.watch on macOS/FSEvents was observed to coalesce or drop
|
|
290
|
+
// events when claude appended `assistant` + `system/turn_duration` rows in
|
|
291
|
+
// rapid succession at the end of a turn — Kanna's stream would silently
|
|
292
|
+
// stop reading at ~52k bytes while the JSONL grew to ~55k. The safety-net
|
|
293
|
+
// poll runs alongside fs.watch and guarantees eventual delivery.
|
|
294
|
+
test("appends made after stream setup are delivered even with no further watcher fires", async () => {
|
|
295
|
+
const filePath = path.join(projectDir, "watched.jsonl")
|
|
296
|
+
await writeFile(filePath, '{"type":"system","subtype":"init"}\n')
|
|
297
|
+
const stream = await startTranscriptStream({
|
|
298
|
+
projectDir,
|
|
299
|
+
knownFilePath: filePath,
|
|
300
|
+
firstFileTimeoutMs: 500,
|
|
301
|
+
})
|
|
302
|
+
const iter = stream.lines[Symbol.asyncIterator]()
|
|
303
|
+
const first = await iter.next()
|
|
304
|
+
expect(first.value).toContain('"system"')
|
|
305
|
+
|
|
306
|
+
// Append multiple rows AFTER the watcher is set up. On a buggy build
|
|
307
|
+
// where fs.watch drops the second/third append, the safety-net poll
|
|
308
|
+
// (fires every 500 ms) must still pick them up within the test
|
|
309
|
+
// timeout.
|
|
310
|
+
await appendFile(filePath, '{"type":"assistant","message":{"content":[{"type":"text","text":"a"}]}}\n')
|
|
311
|
+
await appendFile(filePath, '{"type":"assistant","message":{"content":[{"type":"text","text":"b"}]}}\n')
|
|
312
|
+
await appendFile(filePath, '{"type":"system","subtype":"turn_duration","durationMs":42}\n')
|
|
313
|
+
|
|
314
|
+
const collected: string[] = []
|
|
315
|
+
const deadline = Date.now() + 2_000
|
|
316
|
+
while (collected.length < 3 && Date.now() < deadline) {
|
|
317
|
+
const nxt = await Promise.race([
|
|
318
|
+
iter.next(),
|
|
319
|
+
new Promise<{ value: undefined; done: true }>((r) => setTimeout(() => r({ value: undefined, done: true }), 1_500)),
|
|
320
|
+
])
|
|
321
|
+
if (nxt.done) break
|
|
322
|
+
collected.push(nxt.value)
|
|
323
|
+
}
|
|
324
|
+
expect(collected.length).toBe(3)
|
|
325
|
+
expect(collected[0]).toContain('"a"')
|
|
326
|
+
expect(collected[1]).toContain('"b"')
|
|
327
|
+
expect(collected[2]).toContain("turn_duration")
|
|
328
|
+
stream.close()
|
|
329
|
+
}, 8000)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
describe("waitForResultEntry", () => {
|
|
333
|
+
test("resolves on first result line", async () => {
|
|
334
|
+
const filePath = path.join(projectDir, "result.jsonl")
|
|
335
|
+
await writeFile(filePath, '{"type":"system"}\n{"type":"assistant"}\n')
|
|
336
|
+
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
337
|
+
setTimeout(() => writeFile(filePath, '{"type":"system"}\n{"type":"assistant"}\n{"type":"result","subtype":"success"}\n'), 100)
|
|
338
|
+
const entry = await waitForResultEntry(stream, { timeoutMs: 2000 })
|
|
339
|
+
expect(entry.parsed.type).toBe("result")
|
|
340
|
+
stream.close()
|
|
341
|
+
}, 5000)
|
|
342
|
+
|
|
343
|
+
test("rejects on abort signal", async () => {
|
|
344
|
+
const filePath = path.join(projectDir, "abort.jsonl")
|
|
345
|
+
await writeFile(filePath, '{"type":"system"}\n')
|
|
346
|
+
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
347
|
+
const ctrl = new AbortController()
|
|
348
|
+
setTimeout(() => ctrl.abort(), 50)
|
|
349
|
+
await expect(waitForResultEntry(stream, { signal: ctrl.signal })).rejects.toThrow(/aborted/i)
|
|
350
|
+
stream.close()
|
|
351
|
+
}, 5000)
|
|
352
|
+
|
|
353
|
+
test("rejects on timeout", async () => {
|
|
354
|
+
const filePath = path.join(projectDir, "timeout.jsonl")
|
|
355
|
+
await writeFile(filePath, '{"type":"system"}\n')
|
|
356
|
+
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
357
|
+
await expect(waitForResultEntry(stream, { timeoutMs: 100 })).rejects.toThrow(/timed out/i)
|
|
358
|
+
stream.close()
|
|
359
|
+
}, 5000)
|
|
360
|
+
})
|