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,57 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
const MAX_SANITIZED_LENGTH = 200
|
|
5
|
+
|
|
6
|
+
function djb2Hash(str: string): number {
|
|
7
|
+
let hash = 0
|
|
8
|
+
for (let i = 0; i < str.length; i++) {
|
|
9
|
+
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0
|
|
10
|
+
}
|
|
11
|
+
return hash
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function hashSuffix(name: string): string {
|
|
15
|
+
// Mirror claude-code/src/utils/sessionStoragePortable.ts: prefer Bun.hash
|
|
16
|
+
// (wyhash) when running under Bun, fall back to djb2 elsewhere. Both encode
|
|
17
|
+
// base36. Cross-runtime stability matters only for paths >200 chars.
|
|
18
|
+
const maybeBun = (globalThis as { Bun?: { hash: (s: string) => bigint } }).Bun
|
|
19
|
+
if (maybeBun && typeof maybeBun.hash === "function") {
|
|
20
|
+
return maybeBun.hash(name).toString(36)
|
|
21
|
+
}
|
|
22
|
+
return Math.abs(djb2Hash(name)).toString(36)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sanitizePath(name: string): string {
|
|
26
|
+
const sanitized = name.replace(/[^a-zA-Z0-9]/g, "-")
|
|
27
|
+
if (sanitized.length <= MAX_SANITIZED_LENGTH) return sanitized
|
|
28
|
+
return `${sanitized.slice(0, MAX_SANITIZED_LENGTH)}-${hashSuffix(name)}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function encodeCwd(cwd: string): string {
|
|
32
|
+
// Ported verbatim from claude-code v2.1.146:
|
|
33
|
+
// bootstrap/state.ts realpath + NFC normalize, then sessionStoragePortable.ts
|
|
34
|
+
// sanitizePath. Throws ENOENT if cwd is missing — callers guarantee an
|
|
35
|
+
// existing directory.
|
|
36
|
+
const real = realpathSync(cwd)
|
|
37
|
+
const normalized = real.normalize("NFC")
|
|
38
|
+
return sanitizePath(normalized)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function computeProjectDir(args: {
|
|
42
|
+
homeDir: string
|
|
43
|
+
cwd: string
|
|
44
|
+
}): string {
|
|
45
|
+
return path.join(args.homeDir, ".claude", "projects", encodeCwd(args.cwd))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function computeJsonlPath(args: {
|
|
49
|
+
homeDir: string
|
|
50
|
+
cwd: string
|
|
51
|
+
sessionId: string
|
|
52
|
+
}): string {
|
|
53
|
+
return path.join(
|
|
54
|
+
computeProjectDir({ homeDir: args.homeDir, cwd: args.cwd }),
|
|
55
|
+
`${args.sessionId}.jsonl`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs"
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
3
|
+
import { homedir, tmpdir } from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { describe, expect, test } from "bun:test"
|
|
6
|
+
import { computeJsonlPath, computeProjectDir, encodeCwd } from "./jsonl-path.adapter"
|
|
7
|
+
|
|
8
|
+
describe("encodeCwd", () => {
|
|
9
|
+
test("absolute path: replaces / with -", () => {
|
|
10
|
+
const expected = homedir().replace(/\//g, "-").replace(/\./g, "-")
|
|
11
|
+
expect(encodeCwd(homedir())).toBe(expected)
|
|
12
|
+
})
|
|
13
|
+
test("absolute path with trailing slash: trims it", () => {
|
|
14
|
+
const expected = homedir().replace(/\//g, "-").replace(/\./g, "-")
|
|
15
|
+
expect(encodeCwd(homedir() + "/")).toBe(expected)
|
|
16
|
+
})
|
|
17
|
+
test("nested path", () => {
|
|
18
|
+
const expected = process.cwd().replace(/\//g, "-").replace(/\./g, "-")
|
|
19
|
+
expect(encodeCwd(process.cwd())).toBe(expected)
|
|
20
|
+
})
|
|
21
|
+
test("root path", () => {
|
|
22
|
+
expect(encodeCwd("/")).toBe("-")
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe("computeJsonlPath", () => {
|
|
27
|
+
test("combines homeDir + encoded cwd + session uuid", async () => {
|
|
28
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "kanna-jsonlpath-"))
|
|
29
|
+
try {
|
|
30
|
+
const realPath = realpathSync(tmp)
|
|
31
|
+
const encodedCwd = realPath.replace(/\//g, "-").replace(/\./g, "-")
|
|
32
|
+
const result = computeJsonlPath({
|
|
33
|
+
homeDir: "/home/u",
|
|
34
|
+
cwd: tmp,
|
|
35
|
+
sessionId: "abc-123",
|
|
36
|
+
})
|
|
37
|
+
expect(result).toBe(`/home/u/.claude/projects/${encodedCwd}/abc-123.jsonl`)
|
|
38
|
+
} finally {
|
|
39
|
+
await rm(tmp, { recursive: true, force: true })
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe("encodeCwd realpath + dot replacement", () => {
|
|
45
|
+
test("resolves macOS /var -> /private/var symlink", async () => {
|
|
46
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "kanna-encodecwd-"))
|
|
47
|
+
try {
|
|
48
|
+
const encoded = encodeCwd(tmp)
|
|
49
|
+
const realPath = realpathSync(tmp)
|
|
50
|
+
const expectedEncoded = realPath.replace(/\//g, "-").replace(/\./g, "-")
|
|
51
|
+
expect(encoded).toBe(expectedEncoded)
|
|
52
|
+
} finally {
|
|
53
|
+
await rm(tmp, { recursive: true, force: true })
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test("replaces dots with dashes in segment names", async () => {
|
|
58
|
+
// mkdtemp ensures the directory exists so realpathSync succeeds
|
|
59
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "kanna.dot-test-"))
|
|
60
|
+
try {
|
|
61
|
+
const encoded = encodeCwd(tmp)
|
|
62
|
+
expect(encoded).not.toContain(".")
|
|
63
|
+
// The "kanna.dot-test-XXXX" segment dot must be replaced
|
|
64
|
+
expect(encoded).toContain("kanna-dot-test-")
|
|
65
|
+
} finally {
|
|
66
|
+
await rm(tmp, { recursive: true, force: true })
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("trailing slash trimmed before encoding", () => {
|
|
71
|
+
const a = encodeCwd("/etc/")
|
|
72
|
+
const b = encodeCwd("/etc")
|
|
73
|
+
expect(a).toBe(b)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("root / encodes to single dash", () => {
|
|
77
|
+
const result = encodeCwd("/")
|
|
78
|
+
expect(result).toBe("-")
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("replaces underscore with dash (claude sanitizePath parity)", async () => {
|
|
82
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "kanna_under_"))
|
|
83
|
+
try {
|
|
84
|
+
const encoded = encodeCwd(tmp)
|
|
85
|
+
expect(encoded).not.toContain("_")
|
|
86
|
+
} finally {
|
|
87
|
+
await rm(tmp, { recursive: true, force: true })
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test("encoded segment matches /[^a-zA-Z0-9-]/ never present", async () => {
|
|
92
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "kanna-charset-"))
|
|
93
|
+
try {
|
|
94
|
+
const encoded = encodeCwd(tmp)
|
|
95
|
+
expect(encoded).toMatch(/^[a-zA-Z0-9-]+$/)
|
|
96
|
+
} finally {
|
|
97
|
+
await rm(tmp, { recursive: true, force: true })
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe("computeProjectDir", () => {
|
|
103
|
+
test("returns .claude/projects/<encodedCwd> path", async () => {
|
|
104
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "kanna-projdir-"))
|
|
105
|
+
try {
|
|
106
|
+
const realPath = realpathSync(tmp)
|
|
107
|
+
const encodedCwd = realPath.replace(/\//g, "-").replace(/\./g, "-")
|
|
108
|
+
const result = computeProjectDir({ homeDir: "/home/user", cwd: tmp })
|
|
109
|
+
expect(result).toBe(`/home/user/.claude/projects/${encodedCwd}`)
|
|
110
|
+
} finally {
|
|
111
|
+
await rm(tmp, { recursive: true, force: true })
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { parseJsonlLine, createJsonlEventParser } from "./jsonl-to-event"
|
|
3
|
+
import type { HarnessEvent } from "../harness-types"
|
|
4
|
+
|
|
5
|
+
describe("parseJsonlLine", () => {
|
|
6
|
+
test("ignores empty lines", () => {
|
|
7
|
+
expect(parseJsonlLine("")).toEqual([])
|
|
8
|
+
expect(parseJsonlLine(" ")).toEqual([])
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("ignores malformed JSON (logs but does not throw)", () => {
|
|
12
|
+
expect(parseJsonlLine("{not json")).toEqual([])
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test("system.init → session_token event", () => {
|
|
16
|
+
const line = JSON.stringify({
|
|
17
|
+
type: "system",
|
|
18
|
+
subtype: "init",
|
|
19
|
+
session_id: "sess-1",
|
|
20
|
+
model: "claude-sonnet-4-6",
|
|
21
|
+
})
|
|
22
|
+
const events = parseJsonlLine(line)
|
|
23
|
+
const sessionTokenEvent = events.find((e) => e.type === "session_token")
|
|
24
|
+
expect(sessionTokenEvent).toBeDefined()
|
|
25
|
+
expect(sessionTokenEvent?.sessionToken).toBe("sess-1")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("assistant message → transcript event with assistant role", () => {
|
|
29
|
+
const line = JSON.stringify({
|
|
30
|
+
type: "assistant",
|
|
31
|
+
message: {
|
|
32
|
+
role: "assistant",
|
|
33
|
+
content: [{ type: "text", text: "hello" }],
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
const events = parseJsonlLine(line)
|
|
37
|
+
const transcriptEvents = events.filter((e) => e.type === "transcript")
|
|
38
|
+
expect(transcriptEvents.length).toBeGreaterThan(0)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("system.rate_limit subtype → rate_limit event", () => {
|
|
42
|
+
const line = JSON.stringify({
|
|
43
|
+
type: "system",
|
|
44
|
+
subtype: "rate_limit",
|
|
45
|
+
resetAt: 1748800000000,
|
|
46
|
+
tz: "PT",
|
|
47
|
+
})
|
|
48
|
+
const events = parseJsonlLine(line)
|
|
49
|
+
const rl = events.find((e) => e.type === "rate_limit")
|
|
50
|
+
expect(rl).toBeDefined()
|
|
51
|
+
expect(rl?.rateLimit?.tz).toBe("PT")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("system.informational without rate-limit content → no rate_limit event", () => {
|
|
55
|
+
const line = JSON.stringify({
|
|
56
|
+
type: "system",
|
|
57
|
+
subtype: "informational",
|
|
58
|
+
content: "Remote Control failed to connect",
|
|
59
|
+
})
|
|
60
|
+
const events = parseJsonlLine(line)
|
|
61
|
+
const rl = events.find((e) => e.type === "rate_limit")
|
|
62
|
+
expect(rl).toBeUndefined()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe("createJsonlEventParser", () => {
|
|
67
|
+
function emitTypes(events: HarnessEvent[]): string[] {
|
|
68
|
+
return events.map((e) => e.type)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
test("D3: emits session_token for every line carrying a session_id (not only system/init)", () => {
|
|
72
|
+
const parser = createJsonlEventParser()
|
|
73
|
+
const initLine = JSON.stringify({
|
|
74
|
+
type: "system",
|
|
75
|
+
subtype: "init",
|
|
76
|
+
session_id: "sess-A",
|
|
77
|
+
})
|
|
78
|
+
const assistantLine = JSON.stringify({
|
|
79
|
+
type: "assistant",
|
|
80
|
+
session_id: "sess-A",
|
|
81
|
+
message: { id: "msg-1", role: "assistant", content: [{ type: "text", text: "hi" }] },
|
|
82
|
+
})
|
|
83
|
+
const initEvents = parser.parse(initLine)
|
|
84
|
+
const assistantEvents = parser.parse(assistantLine)
|
|
85
|
+
expect(initEvents.find((e) => e.type === "session_token")?.sessionToken).toBe("sess-A")
|
|
86
|
+
expect(assistantEvents.find((e) => e.type === "session_token")?.sessionToken).toBe("sess-A")
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("D3: lines without session_id do not emit session_token", () => {
|
|
90
|
+
const parser = createJsonlEventParser()
|
|
91
|
+
const noSession = JSON.stringify({ type: "assistant", message: { role: "assistant", content: [] } })
|
|
92
|
+
const events = parser.parse(noSession)
|
|
93
|
+
expect(events.find((e) => e.type === "session_token")).toBeUndefined()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test("D2: SDK-native rate_limit_event message → rate_limit event via detector", () => {
|
|
97
|
+
const parser = createJsonlEventParser()
|
|
98
|
+
const line = JSON.stringify({
|
|
99
|
+
type: "rate_limit_event",
|
|
100
|
+
rate_limit_info: {
|
|
101
|
+
status: "rejected",
|
|
102
|
+
// Epoch seconds (detector coerces to ms).
|
|
103
|
+
resetsAt: 1_748_800_000,
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
const events = parser.parse(line)
|
|
107
|
+
const rl = events.find((e) => e.type === "rate_limit")
|
|
108
|
+
expect(rl).toBeDefined()
|
|
109
|
+
expect(rl?.rateLimit?.resetAt).toBe(1_748_800_000_000)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("D2: rate_limit_event with status != rejected → no event", () => {
|
|
113
|
+
const parser = createJsonlEventParser()
|
|
114
|
+
const line = JSON.stringify({
|
|
115
|
+
type: "rate_limit_event",
|
|
116
|
+
rate_limit_info: { status: "allowed", resetsAt: 1_748_800_000 },
|
|
117
|
+
})
|
|
118
|
+
const events = parser.parse(line)
|
|
119
|
+
expect(events.find((e) => e.type === "rate_limit")).toBeUndefined()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test("D2: legacy system/rate_limit shape still recognised", () => {
|
|
123
|
+
const parser = createJsonlEventParser()
|
|
124
|
+
const line = JSON.stringify({
|
|
125
|
+
type: "system",
|
|
126
|
+
subtype: "rate_limit",
|
|
127
|
+
resetAt: 1748800000000,
|
|
128
|
+
tz: "PT",
|
|
129
|
+
})
|
|
130
|
+
const events = parser.parse(line)
|
|
131
|
+
const rl = events.find((e) => e.type === "rate_limit")
|
|
132
|
+
expect(rl).toBeDefined()
|
|
133
|
+
expect(rl?.rateLimit?.tz).toBe("PT")
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test("D1: assistant message with usage → context_window_updated transcript", () => {
|
|
137
|
+
const parser = createJsonlEventParser()
|
|
138
|
+
const line = JSON.stringify({
|
|
139
|
+
type: "assistant",
|
|
140
|
+
message: {
|
|
141
|
+
id: "msg-usage-1",
|
|
142
|
+
role: "assistant",
|
|
143
|
+
content: [{ type: "text", text: "hi" }],
|
|
144
|
+
},
|
|
145
|
+
usage: {
|
|
146
|
+
input_tokens: 100,
|
|
147
|
+
cache_creation_input_tokens: 0,
|
|
148
|
+
cache_read_input_tokens: 0,
|
|
149
|
+
output_tokens: 25,
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
const events = parser.parse(line)
|
|
153
|
+
const ctxEvents = events.filter(
|
|
154
|
+
(e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "context_window_updated",
|
|
155
|
+
)
|
|
156
|
+
expect(ctxEvents).toHaveLength(1)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test("D1: duplicate assistant usage id is deduped", () => {
|
|
160
|
+
const parser = createJsonlEventParser()
|
|
161
|
+
const line = JSON.stringify({
|
|
162
|
+
type: "assistant",
|
|
163
|
+
message: {
|
|
164
|
+
id: "msg-dedup",
|
|
165
|
+
role: "assistant",
|
|
166
|
+
content: [{ type: "text", text: "hi" }],
|
|
167
|
+
},
|
|
168
|
+
usage: { input_tokens: 50, output_tokens: 10 },
|
|
169
|
+
})
|
|
170
|
+
const first = parser.parse(line)
|
|
171
|
+
const second = parser.parse(line)
|
|
172
|
+
const firstCtx = first.filter(
|
|
173
|
+
(e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "context_window_updated",
|
|
174
|
+
)
|
|
175
|
+
const secondCtx = second.filter(
|
|
176
|
+
(e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "context_window_updated",
|
|
177
|
+
)
|
|
178
|
+
expect(firstCtx).toHaveLength(1)
|
|
179
|
+
expect(secondCtx).toHaveLength(0)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test("D1: result message after assistant emits final context_window_updated", () => {
|
|
183
|
+
const parser = createJsonlEventParser()
|
|
184
|
+
parser.parse(JSON.stringify({
|
|
185
|
+
type: "assistant",
|
|
186
|
+
message: { id: "msg-1", role: "assistant", content: [{ type: "text", text: "hi" }] },
|
|
187
|
+
usage: { input_tokens: 80, output_tokens: 20 },
|
|
188
|
+
}))
|
|
189
|
+
const resultLine = JSON.stringify({
|
|
190
|
+
type: "result",
|
|
191
|
+
subtype: "success",
|
|
192
|
+
result: "done",
|
|
193
|
+
isError: false,
|
|
194
|
+
durationMs: 1000,
|
|
195
|
+
usage: { input_tokens: 80, output_tokens: 20 },
|
|
196
|
+
modelUsage: {
|
|
197
|
+
"claude-sonnet-4-6": { contextWindow: 200000, inputTokens: 80, outputTokens: 20 },
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
const events = parser.parse(resultLine)
|
|
201
|
+
const ctxEvents = events.filter(
|
|
202
|
+
(e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "context_window_updated",
|
|
203
|
+
)
|
|
204
|
+
expect(ctxEvents).toHaveLength(1)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test("D1: 1M context window floor preserved when modelUsage reports 200k", () => {
|
|
208
|
+
const parser = createJsonlEventParser({ configuredContextWindow: 1_000_000 })
|
|
209
|
+
parser.parse(JSON.stringify({
|
|
210
|
+
type: "assistant",
|
|
211
|
+
message: { id: "msg-1m", role: "assistant", content: [{ type: "text", text: "hi" }] },
|
|
212
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
213
|
+
}))
|
|
214
|
+
const resultLine = JSON.stringify({
|
|
215
|
+
type: "result",
|
|
216
|
+
subtype: "success",
|
|
217
|
+
result: "done",
|
|
218
|
+
isError: false,
|
|
219
|
+
durationMs: 500,
|
|
220
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
221
|
+
modelUsage: { "claude-sonnet-4-6": { contextWindow: 200000 } },
|
|
222
|
+
})
|
|
223
|
+
const events = parser.parse(resultLine)
|
|
224
|
+
const ctx = events.find(
|
|
225
|
+
(e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "context_window_updated",
|
|
226
|
+
)
|
|
227
|
+
const usage = (ctx?.entry as { usage?: { maxTokens?: number } } | undefined)?.usage
|
|
228
|
+
expect(usage?.maxTokens).toBe(1_000_000)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
test("emitTypes helper produces deterministic order across calls", () => {
|
|
232
|
+
const parser = createJsonlEventParser()
|
|
233
|
+
const types = emitTypes(parser.parse(JSON.stringify({
|
|
234
|
+
type: "system",
|
|
235
|
+
subtype: "init",
|
|
236
|
+
session_id: "sess-X",
|
|
237
|
+
})))
|
|
238
|
+
expect(types[0]).toBe("session_token")
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { HarnessEvent } from "../harness-types"
|
|
2
|
+
import type { ContextWindowUsageSnapshot } from "../../shared/types"
|
|
3
|
+
import {
|
|
4
|
+
normalizeClaudeStreamMessage,
|
|
5
|
+
normalizeClaudeUsageSnapshot,
|
|
6
|
+
resolveFinalTurnUsage,
|
|
7
|
+
maxClaudeContextWindowFromModelUsage,
|
|
8
|
+
getClaudeAssistantMessageUsageId,
|
|
9
|
+
timestamped,
|
|
10
|
+
} from "./agent-normalizers"
|
|
11
|
+
import { ClaudeLimitDetector } from "./../auto-continue/limit-detector"
|
|
12
|
+
|
|
13
|
+
export interface JsonlEventParser {
|
|
14
|
+
/** Parse one JSONL line; returns zero or more harness events. Stateful — updates internal usage / context-window tracking across calls. */
|
|
15
|
+
parse(rawLine: string): HarnessEvent[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CreateJsonlEventParserOptions {
|
|
19
|
+
/** Per-model context-window floor (e.g. 1_000_000 for `[1m]` models). */
|
|
20
|
+
configuredContextWindow?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Stateful JSONL → HarnessEvent parser. One instance per PTY session so
|
|
25
|
+
* usage snapshots can be diffed across `assistant` → `result` messages,
|
|
26
|
+
* matching the SDK driver's `createClaudeHarnessStream` shape.
|
|
27
|
+
*/
|
|
28
|
+
export function createJsonlEventParser(opts: CreateJsonlEventParserOptions = {}): JsonlEventParser {
|
|
29
|
+
let seenAssistantUsageIds = new Set<string>()
|
|
30
|
+
let latestUsageSnapshot: ContextWindowUsageSnapshot | null = null
|
|
31
|
+
let lastKnownContextWindow: number | undefined = opts.configuredContextWindow
|
|
32
|
+
const detector = new ClaudeLimitDetector()
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
parse(rawLine: string): HarnessEvent[] {
|
|
36
|
+
const trimmed = rawLine.trim()
|
|
37
|
+
if (!trimmed) return []
|
|
38
|
+
let parsed: unknown
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(trimmed)
|
|
41
|
+
} catch {
|
|
42
|
+
console.warn("[claude-pty/jsonl] failed to parse line", trimmed.slice(0, 120))
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
if (!parsed || typeof parsed !== "object") return []
|
|
46
|
+
const message = parsed as Record<string, unknown>
|
|
47
|
+
const events: HarnessEvent[] = []
|
|
48
|
+
|
|
49
|
+
// D3 — emit session_token for any message carrying a session_id, not
|
|
50
|
+
// just `system/init`. Matches the SDK driver loop in
|
|
51
|
+
// createClaudeHarnessStream (agent.ts).
|
|
52
|
+
if (typeof message.session_id === "string" && message.session_id.length > 0) {
|
|
53
|
+
events.push({ type: "session_token", sessionToken: message.session_id })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// D2 — recognise both shapes:
|
|
57
|
+
// (a) the SDK-native `rate_limit_event` message Claude Code mirrors
|
|
58
|
+
// into JSONL when running under the agent SDK
|
|
59
|
+
// (b) legacy `system/rate_limit` shape kept for any older CLI build
|
|
60
|
+
// that emits it (existing kanna call sites).
|
|
61
|
+
if (message.type === "rate_limit_event") {
|
|
62
|
+
const detection = detector.detectFromSdkRateLimitInfo(
|
|
63
|
+
"",
|
|
64
|
+
(message as { rate_limit_info?: unknown }).rate_limit_info,
|
|
65
|
+
)
|
|
66
|
+
if (detection) {
|
|
67
|
+
events.push({ type: "rate_limit", rateLimit: { resetAt: detection.resetAt, tz: detection.tz } })
|
|
68
|
+
}
|
|
69
|
+
} else if (message.type === "system" && message.subtype === "rate_limit") {
|
|
70
|
+
const resetAt = typeof message.resetAt === "number" ? message.resetAt : Date.now()
|
|
71
|
+
const tz = typeof message.tz === "string" ? message.tz : "UTC"
|
|
72
|
+
events.push({ type: "rate_limit", rateLimit: { resetAt, tz } })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// D1 — assistant message usage delta → context_window_updated.
|
|
76
|
+
if (message.type === "assistant") {
|
|
77
|
+
const usageId = getClaudeAssistantMessageUsageId(message)
|
|
78
|
+
const usageSnapshot = normalizeClaudeUsageSnapshot(
|
|
79
|
+
(message as { usage?: unknown }).usage,
|
|
80
|
+
lastKnownContextWindow,
|
|
81
|
+
)
|
|
82
|
+
if (usageId && usageSnapshot && !seenAssistantUsageIds.has(usageId)) {
|
|
83
|
+
seenAssistantUsageIds.add(usageId)
|
|
84
|
+
latestUsageSnapshot = usageSnapshot
|
|
85
|
+
events.push({
|
|
86
|
+
type: "transcript",
|
|
87
|
+
entry: timestamped({ kind: "context_window_updated", usage: usageSnapshot }),
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// D1 — turn-end context window emit. Preserves the configured-window
|
|
93
|
+
// floor so the SDK-internal `modelUsage.contextWindow` of 200_000
|
|
94
|
+
// can't silently override a 1M-beta opt-in.
|
|
95
|
+
if (message.type === "result") {
|
|
96
|
+
const resultContextWindow = maxClaudeContextWindowFromModelUsage(
|
|
97
|
+
(message as { modelUsage?: unknown }).modelUsage,
|
|
98
|
+
)
|
|
99
|
+
if (resultContextWindow !== undefined) {
|
|
100
|
+
lastKnownContextWindow = Math.max(lastKnownContextWindow ?? 0, resultContextWindow)
|
|
101
|
+
}
|
|
102
|
+
const accumulatedUsage = normalizeClaudeUsageSnapshot(
|
|
103
|
+
(message as { usage?: unknown }).usage,
|
|
104
|
+
lastKnownContextWindow,
|
|
105
|
+
)
|
|
106
|
+
const finalUsage = resolveFinalTurnUsage(
|
|
107
|
+
latestUsageSnapshot,
|
|
108
|
+
accumulatedUsage,
|
|
109
|
+
lastKnownContextWindow,
|
|
110
|
+
)
|
|
111
|
+
if (finalUsage) {
|
|
112
|
+
events.push({
|
|
113
|
+
type: "transcript",
|
|
114
|
+
entry: timestamped({ kind: "context_window_updated", usage: finalUsage }),
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
seenAssistantUsageIds = new Set<string>()
|
|
118
|
+
latestUsageSnapshot = null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const entries = normalizeClaudeStreamMessage(parsed)
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
events.push({ type: "transcript", entry })
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.warn("[claude-pty/jsonl] normalizeClaudeStreamMessage threw", err)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return events
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Stateless wrapper kept for callers that don't need usage tracking.
|
|
137
|
+
* Behaves the same as before D1/D2/D3 landed: no usage diff, no per-message
|
|
138
|
+
* session_token. New callers should use `createJsonlEventParser` instead.
|
|
139
|
+
*/
|
|
140
|
+
export function parseJsonlLine(rawLine: string): HarnessEvent[] {
|
|
141
|
+
const trimmed = rawLine.trim()
|
|
142
|
+
if (!trimmed) return []
|
|
143
|
+
let parsed: unknown
|
|
144
|
+
try {
|
|
145
|
+
parsed = JSON.parse(trimmed)
|
|
146
|
+
} catch {
|
|
147
|
+
console.warn("[claude-pty/jsonl] failed to parse line", trimmed.slice(0, 120))
|
|
148
|
+
return []
|
|
149
|
+
}
|
|
150
|
+
if (!parsed || typeof parsed !== "object") return []
|
|
151
|
+
const message = parsed as Record<string, unknown>
|
|
152
|
+
const events: HarnessEvent[] = []
|
|
153
|
+
|
|
154
|
+
if (message.type === "system" && message.subtype === "init" && typeof message.session_id === "string") {
|
|
155
|
+
events.push({ type: "session_token", sessionToken: message.session_id })
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (message.type === "system" && message.subtype === "rate_limit") {
|
|
159
|
+
const resetAt = typeof message.resetAt === "number" ? message.resetAt : Date.now()
|
|
160
|
+
const tz = typeof message.tz === "string" ? message.tz : "UTC"
|
|
161
|
+
events.push({ type: "rate_limit", rateLimit: { resetAt, tz } })
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const entries = normalizeClaudeStreamMessage(parsed)
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
events.push({ type: "transcript", entry })
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.warn("[claude-pty/jsonl] normalizeClaudeStreamMessage threw", err)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return events
|
|
174
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { OutputRing, OUTPUT_RING_DEFAULT_BYTES } from "./output-ring"
|
|
3
|
+
|
|
4
|
+
describe("OutputRing", () => {
|
|
5
|
+
test("appends and returns full content under capacity", () => {
|
|
6
|
+
const r = new OutputRing(100)
|
|
7
|
+
r.append("hello ")
|
|
8
|
+
r.append("world")
|
|
9
|
+
expect(r.tail()).toBe("hello world")
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test("drops oldest bytes once capacity exceeded", () => {
|
|
13
|
+
const r = new OutputRing(5)
|
|
14
|
+
r.append("abcdefgh")
|
|
15
|
+
expect(r.tail()).toBe("defgh")
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("default capacity is 256 KB", () => {
|
|
19
|
+
expect(OUTPUT_RING_DEFAULT_BYTES).toBe(256 * 1024)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test("contains(needle) returns true when present in tail", () => {
|
|
23
|
+
const r = new OutputRing(100)
|
|
24
|
+
r.append("Please run /login")
|
|
25
|
+
expect(r.contains("/login")).toBe(true)
|
|
26
|
+
expect(r.contains("foobar")).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("contains works after rotation", () => {
|
|
30
|
+
const r = new OutputRing(20)
|
|
31
|
+
r.append("xxxxxxxxxxxxx")
|
|
32
|
+
r.append("Please run /login")
|
|
33
|
+
expect(r.contains("/login")).toBe(true)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const OUTPUT_RING_DEFAULT_BYTES = 256 * 1024
|
|
2
|
+
|
|
3
|
+
export class OutputRing {
|
|
4
|
+
private buf = ""
|
|
5
|
+
private readonly capacity: number
|
|
6
|
+
|
|
7
|
+
constructor(capacityBytes: number = OUTPUT_RING_DEFAULT_BYTES) {
|
|
8
|
+
this.capacity = capacityBytes
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
append(chunk: string): void {
|
|
12
|
+
this.buf += chunk
|
|
13
|
+
if (this.buf.length > this.capacity) {
|
|
14
|
+
this.buf = this.buf.slice(this.buf.length - this.capacity)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
tail(): string {
|
|
19
|
+
return this.buf
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
contains(needle: string): boolean {
|
|
23
|
+
return this.buf.includes(needle)
|
|
24
|
+
}
|
|
25
|
+
}
|