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,227 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtemp, rm, writeFile, appendFile, mkdir } from "node:fs/promises"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import type { Query } from "@anthropic-ai/claude-agent-sdk"
|
|
6
|
+
import { createClaudeHarnessStream } from "./agent-normalizers"
|
|
7
|
+
import { createJsonlEventParser } from "./jsonl-to-event"
|
|
8
|
+
import { startTranscriptStream } from "./tui-source.adapter"
|
|
9
|
+
import type { HarnessEvent } from "../harness-types"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Phase 6 — SDK ↔ PTY HarnessEvent equivalence matrix.
|
|
13
|
+
*
|
|
14
|
+
* Drives both paths with the same Claude-SDK message fixtures and
|
|
15
|
+
* asserts they emit the same `HarnessEvent` sequence (after stripping
|
|
16
|
+
* volatile fields like `_id` and `createdAt`). The claude CLI mirrors
|
|
17
|
+
* SDKMessage shapes into the JSONL transcript verbatim, so a single
|
|
18
|
+
* fixture stands in for both:
|
|
19
|
+
* - SDK path: yielded from a fake `Query` iterable into
|
|
20
|
+
* `createClaudeHarnessStream`.
|
|
21
|
+
* - PTY path: serialized to JSON and fed to `createJsonlEventParser`
|
|
22
|
+
* one line per message.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
function fakeQuery(messages: unknown[]): Query {
|
|
26
|
+
const q = (async function* () {
|
|
27
|
+
for (const m of messages) yield m as never
|
|
28
|
+
})()
|
|
29
|
+
return q as unknown as Query
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalize(events: HarnessEvent[]): unknown[] {
|
|
33
|
+
return events.map((ev) => {
|
|
34
|
+
if (ev.type === "transcript") {
|
|
35
|
+
const { _id: _i, createdAt: _c, ...rest } = ev.entry as unknown as Record<string, unknown> & {
|
|
36
|
+
_id?: string
|
|
37
|
+
createdAt?: number
|
|
38
|
+
}
|
|
39
|
+
return { type: ev.type, entry: rest }
|
|
40
|
+
}
|
|
41
|
+
return ev
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function collectSdk(messages: unknown[], configuredContextWindow?: number): Promise<HarnessEvent[]> {
|
|
46
|
+
const events: HarnessEvent[] = []
|
|
47
|
+
for await (const ev of createClaudeHarnessStream(fakeQuery(messages), configuredContextWindow)) {
|
|
48
|
+
events.push(ev)
|
|
49
|
+
}
|
|
50
|
+
return events
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function ptyEventsViaTranscriptStream(messages: unknown[], configuredContextWindow?: number): Promise<HarnessEvent[]> {
|
|
54
|
+
const tmpDir = await mkdtemp(path.join(tmpdir(), "kanna-parity-"))
|
|
55
|
+
const projectDir = path.join(tmpDir, "projects", "fake")
|
|
56
|
+
await mkdir(projectDir, { recursive: true })
|
|
57
|
+
const filePath = path.join(projectDir, "fixture.jsonl")
|
|
58
|
+
await writeFile(filePath, "")
|
|
59
|
+
const stream = await startTranscriptStream({ projectDir, knownFilePath: filePath, firstFileTimeoutMs: 2000 })
|
|
60
|
+
const parser = createJsonlEventParser({ configuredContextWindow })
|
|
61
|
+
const events: HarnessEvent[] = []
|
|
62
|
+
const writeAll = (async () => {
|
|
63
|
+
for (const m of messages) {
|
|
64
|
+
await appendFile(filePath, JSON.stringify(m) + "\n")
|
|
65
|
+
await new Promise<void>((r) => setTimeout(r, 30))
|
|
66
|
+
}
|
|
67
|
+
await appendFile(filePath, '{"type":"__parity_sentinel__"}\n')
|
|
68
|
+
})()
|
|
69
|
+
const collectDone = (async () => {
|
|
70
|
+
for await (const line of stream.lines) {
|
|
71
|
+
let parsed: { type?: string }
|
|
72
|
+
try { parsed = JSON.parse(line) as { type?: string } } catch { continue }
|
|
73
|
+
if (parsed.type === "__parity_sentinel__") break
|
|
74
|
+
for (const ev of parser.parse(line)) events.push(ev)
|
|
75
|
+
}
|
|
76
|
+
})()
|
|
77
|
+
await writeAll
|
|
78
|
+
await collectDone
|
|
79
|
+
stream.close()
|
|
80
|
+
await rm(tmpDir, { recursive: true, force: true })
|
|
81
|
+
return events
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function assertSameEvents(messages: unknown[], configuredContextWindow?: number): Promise<void> {
|
|
85
|
+
const sdk = await collectSdk(messages, configuredContextWindow)
|
|
86
|
+
const pty = await ptyEventsViaTranscriptStream(messages, configuredContextWindow)
|
|
87
|
+
expect(normalize(pty)).toEqual(normalize(sdk))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe("SDK ↔ PTY HarnessEvent equivalence matrix", () => {
|
|
91
|
+
test("simple turn: system/init → assistant → result", async () => {
|
|
92
|
+
await assertSameEvents([
|
|
93
|
+
{
|
|
94
|
+
type: "system",
|
|
95
|
+
subtype: "init",
|
|
96
|
+
session_id: "sess-1",
|
|
97
|
+
model: "claude-sonnet-4-6",
|
|
98
|
+
tools: [],
|
|
99
|
+
mcp_servers: [],
|
|
100
|
+
slash_commands: [],
|
|
101
|
+
cwd: "/tmp",
|
|
102
|
+
permissionMode: "acceptEdits",
|
|
103
|
+
apiKeySource: "none",
|
|
104
|
+
claude_code_version: "0.0.0",
|
|
105
|
+
output_style: "default",
|
|
106
|
+
skills: [],
|
|
107
|
+
plugins: [],
|
|
108
|
+
uuid: "u1",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: "assistant",
|
|
112
|
+
session_id: "sess-1",
|
|
113
|
+
message: {
|
|
114
|
+
id: "msg-1",
|
|
115
|
+
role: "assistant",
|
|
116
|
+
content: [{ type: "text", text: "hi" }],
|
|
117
|
+
},
|
|
118
|
+
usage: { input_tokens: 100, output_tokens: 25 },
|
|
119
|
+
uuid: "u2",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "result",
|
|
123
|
+
subtype: "success",
|
|
124
|
+
session_id: "sess-1",
|
|
125
|
+
result: "done",
|
|
126
|
+
is_error: false,
|
|
127
|
+
duration_ms: 500,
|
|
128
|
+
usage: { input_tokens: 100, output_tokens: 25 },
|
|
129
|
+
modelUsage: { "claude-sonnet-4-6": { contextWindow: 200000 } },
|
|
130
|
+
uuid: "u3",
|
|
131
|
+
},
|
|
132
|
+
])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test("rate_limit_event (SDK-native shape)", async () => {
|
|
136
|
+
await assertSameEvents([
|
|
137
|
+
{
|
|
138
|
+
type: "rate_limit_event",
|
|
139
|
+
session_id: "sess-rl",
|
|
140
|
+
rate_limit_info: { status: "rejected", resetsAt: 1_748_800_000 },
|
|
141
|
+
},
|
|
142
|
+
])
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("prompt-too-long error result", async () => {
|
|
146
|
+
await assertSameEvents([
|
|
147
|
+
{
|
|
148
|
+
type: "result",
|
|
149
|
+
subtype: "error",
|
|
150
|
+
session_id: "sess-err",
|
|
151
|
+
is_error: true,
|
|
152
|
+
result: "prompt is too long",
|
|
153
|
+
duration_ms: 0,
|
|
154
|
+
uuid: "u-err",
|
|
155
|
+
},
|
|
156
|
+
])
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test("multiple assistant messages dedupe on usage id (same id seen twice)", async () => {
|
|
160
|
+
await assertSameEvents([
|
|
161
|
+
{
|
|
162
|
+
type: "assistant",
|
|
163
|
+
session_id: "sess-d",
|
|
164
|
+
message: { id: "dup", role: "assistant", content: [{ type: "text", text: "a" }] },
|
|
165
|
+
usage: { input_tokens: 10, output_tokens: 3 },
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
type: "assistant",
|
|
169
|
+
session_id: "sess-d",
|
|
170
|
+
message: { id: "dup", role: "assistant", content: [{ type: "text", text: "a" }] },
|
|
171
|
+
usage: { input_tokens: 10, output_tokens: 3 },
|
|
172
|
+
},
|
|
173
|
+
])
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test("1M context window floor preserved against modelUsage.contextWindow=200k", async () => {
|
|
177
|
+
await assertSameEvents(
|
|
178
|
+
[
|
|
179
|
+
{
|
|
180
|
+
type: "assistant",
|
|
181
|
+
session_id: "sess-1m",
|
|
182
|
+
message: { id: "msg-1m", role: "assistant", content: [{ type: "text", text: "x" }] },
|
|
183
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: "result",
|
|
187
|
+
subtype: "success",
|
|
188
|
+
session_id: "sess-1m",
|
|
189
|
+
is_error: false,
|
|
190
|
+
duration_ms: 500,
|
|
191
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
192
|
+
modelUsage: { "claude-sonnet-4-6": { contextWindow: 200000 } },
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
1_000_000,
|
|
196
|
+
)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test("session_token emitted from every message carrying session_id", async () => {
|
|
200
|
+
await assertSameEvents([
|
|
201
|
+
{ type: "system", subtype: "init", session_id: "a", model: "m", tools: [], mcp_servers: [], slash_commands: [], cwd: "/", permissionMode: "acceptEdits", apiKeySource: "none", claude_code_version: "0", output_style: "d", skills: [], plugins: [], uuid: "1" },
|
|
202
|
+
{ type: "assistant", session_id: "a", message: { id: "m1", role: "assistant", content: [] } },
|
|
203
|
+
{ type: "result", subtype: "success", session_id: "a", is_error: false, duration_ms: 0, uuid: "r" },
|
|
204
|
+
])
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test("compact_boundary turn does not produce phantom context_window_updated", async () => {
|
|
208
|
+
await assertSameEvents([
|
|
209
|
+
{
|
|
210
|
+
type: "system",
|
|
211
|
+
subtype: "compact_boundary",
|
|
212
|
+
session_id: "sess-c",
|
|
213
|
+
compact_metadata: { trigger: "auto", pre_tokens: 50000 },
|
|
214
|
+
uuid: "cb",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
type: "result",
|
|
218
|
+
subtype: "success",
|
|
219
|
+
session_id: "sess-c",
|
|
220
|
+
is_error: false,
|
|
221
|
+
duration_ms: 100,
|
|
222
|
+
usage: { input_tokens: 1000, cache_read_input_tokens: 49000, output_tokens: 0 },
|
|
223
|
+
uuid: "r-c",
|
|
224
|
+
},
|
|
225
|
+
])
|
|
226
|
+
})
|
|
227
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import process from "node:process"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* On-disk registry of claude PTY children so a non-graceful server crash
|
|
7
|
+
* does not leak orphan claude processes (Bun.Terminal allocates a PTY via
|
|
8
|
+
* `setsid`, so the child lives in its own session and survives parent
|
|
9
|
+
* death). On the next server boot `reapStale()` SIGKILLs each recorded
|
|
10
|
+
* process group and removes its runtimeDir (mcp-config.json + settings).
|
|
11
|
+
*
|
|
12
|
+
* Mirrors {@link import("../terminal-pid-registry").TerminalPidRegistry}
|
|
13
|
+
* but adds `runtimeDir` so we can clean up the tmp dir kanna allocated
|
|
14
|
+
* for the spawn (otherwise it leaks every restart).
|
|
15
|
+
*/
|
|
16
|
+
export interface ClaudePtyEntry {
|
|
17
|
+
chatId: string
|
|
18
|
+
sessionId: string
|
|
19
|
+
pid: number
|
|
20
|
+
cwd: string
|
|
21
|
+
runtimeDir: string
|
|
22
|
+
createdAt: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface RegistryFile {
|
|
26
|
+
entries: ClaudePtyEntry[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class ClaudePtyRegistry {
|
|
30
|
+
private readonly filePath: string
|
|
31
|
+
private entries: ClaudePtyEntry[] = []
|
|
32
|
+
private loaded = false
|
|
33
|
+
private writeQueue: Promise<void> = Promise.resolve()
|
|
34
|
+
|
|
35
|
+
constructor(filePath: string) {
|
|
36
|
+
this.filePath = filePath
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async register(entry: Omit<ClaudePtyEntry, "createdAt">): Promise<void> {
|
|
40
|
+
await this.loadIfNeeded()
|
|
41
|
+
const next = this.entries.filter((existing) => existing.sessionId !== entry.sessionId)
|
|
42
|
+
next.push({ ...entry, createdAt: Date.now() })
|
|
43
|
+
this.entries = next
|
|
44
|
+
await this.persist()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async unregister(sessionId: string): Promise<void> {
|
|
48
|
+
await this.loadIfNeeded()
|
|
49
|
+
this.entries = this.entries.filter((entry) => entry.sessionId !== sessionId)
|
|
50
|
+
await this.persist()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async reapStale(): Promise<ClaudePtyEntry[]> {
|
|
54
|
+
const stored = await this.readFromDisk()
|
|
55
|
+
if (stored.length === 0) {
|
|
56
|
+
this.entries = []
|
|
57
|
+
this.loaded = true
|
|
58
|
+
return []
|
|
59
|
+
}
|
|
60
|
+
for (const entry of stored) {
|
|
61
|
+
killPgroup(entry.pid)
|
|
62
|
+
// Best-effort: remove the spawn's runtimeDir (mcp-config.json +
|
|
63
|
+
// settings.local.json + any other kanna-side scratch). Children
|
|
64
|
+
// wrote nothing user-facing here, but the dir leaks per restart
|
|
65
|
+
// without cleanup.
|
|
66
|
+
if (entry.runtimeDir && entry.runtimeDir.length > 0) {
|
|
67
|
+
try { await rm(entry.runtimeDir, { recursive: true, force: true }) } catch {
|
|
68
|
+
/* swallow — best-effort */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
this.entries = []
|
|
73
|
+
this.loaded = true
|
|
74
|
+
await this.persist()
|
|
75
|
+
return stored
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async loadIfNeeded() {
|
|
79
|
+
if (this.loaded) return
|
|
80
|
+
this.entries = await this.readFromDisk()
|
|
81
|
+
this.loaded = true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async readFromDisk(): Promise<ClaudePtyEntry[]> {
|
|
85
|
+
let raw: string
|
|
86
|
+
try {
|
|
87
|
+
raw = await readFile(this.filePath, "utf8")
|
|
88
|
+
} catch {
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(raw) as Partial<RegistryFile>
|
|
93
|
+
if (!parsed || !Array.isArray(parsed.entries)) return []
|
|
94
|
+
return parsed.entries.filter(isValidEntry)
|
|
95
|
+
} catch {
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async persist() {
|
|
101
|
+
const snapshot: RegistryFile = { entries: [...this.entries] }
|
|
102
|
+
const serialized = JSON.stringify(snapshot)
|
|
103
|
+
this.writeQueue = this.writeQueue
|
|
104
|
+
.catch(() => undefined)
|
|
105
|
+
.then(async () => {
|
|
106
|
+
await mkdir(path.dirname(this.filePath), { recursive: true })
|
|
107
|
+
await writeFile(this.filePath, serialized, "utf8")
|
|
108
|
+
})
|
|
109
|
+
await this.writeQueue
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isValidEntry(value: unknown): value is ClaudePtyEntry {
|
|
114
|
+
if (!value || typeof value !== "object") return false
|
|
115
|
+
const candidate = value as Partial<ClaudePtyEntry>
|
|
116
|
+
return (
|
|
117
|
+
typeof candidate.chatId === "string"
|
|
118
|
+
&& typeof candidate.sessionId === "string"
|
|
119
|
+
&& typeof candidate.pid === "number"
|
|
120
|
+
&& Number.isFinite(candidate.pid)
|
|
121
|
+
&& typeof candidate.cwd === "string"
|
|
122
|
+
&& typeof candidate.runtimeDir === "string"
|
|
123
|
+
&& typeof candidate.createdAt === "number"
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function killPgroup(pid: number) {
|
|
128
|
+
if (process.platform === "win32") return
|
|
129
|
+
if (!Number.isFinite(pid) || pid <= 0) return
|
|
130
|
+
try {
|
|
131
|
+
process.kill(-pid, "SIGKILL")
|
|
132
|
+
} catch {
|
|
133
|
+
// ESRCH (already gone) and EPERM (race with kernel reap) are fine.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtemp, readFile, rm, writeFile, mkdir, stat } from "node:fs/promises"
|
|
3
|
+
import os from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { ClaudePtyRegistry } from "./pid-registry.adapter"
|
|
6
|
+
|
|
7
|
+
let tempDir = ""
|
|
8
|
+
let registryPath = ""
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
tempDir = await mkdtemp(path.join(os.tmpdir(), "kanna-claude-pty-registry-"))
|
|
12
|
+
registryPath = path.join(tempDir, "claude-pty.json")
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
if (tempDir) {
|
|
17
|
+
await rm(tempDir, { recursive: true, force: true })
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe("ClaudePtyRegistry", () => {
|
|
22
|
+
test("register persists entries with sessionId, pid, cwd, runtimeDir", async () => {
|
|
23
|
+
const registry = new ClaudePtyRegistry(registryPath)
|
|
24
|
+
await registry.register({ chatId: "c1", sessionId: "s1", pid: 12345, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
|
|
25
|
+
await registry.register({ chatId: "c2", sessionId: "s2", pid: 23456, cwd: "/tmp/b", runtimeDir: "/tmp/r2" })
|
|
26
|
+
|
|
27
|
+
const raw = JSON.parse(await readFile(registryPath, "utf8")) as {
|
|
28
|
+
entries: Array<{ chatId: string; sessionId: string; pid: number; runtimeDir: string }>
|
|
29
|
+
}
|
|
30
|
+
expect(raw.entries).toHaveLength(2)
|
|
31
|
+
expect(raw.entries[0]).toMatchObject({ chatId: "c1", sessionId: "s1", pid: 12345, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
|
|
32
|
+
expect(raw.entries[1]).toMatchObject({ chatId: "c2", sessionId: "s2", pid: 23456, cwd: "/tmp/b", runtimeDir: "/tmp/r2" })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("re-registering the same sessionId replaces the prior entry", async () => {
|
|
36
|
+
const registry = new ClaudePtyRegistry(registryPath)
|
|
37
|
+
await registry.register({ chatId: "c1", sessionId: "s1", pid: 100, cwd: "/tmp/old", runtimeDir: "/tmp/r-old" })
|
|
38
|
+
await registry.register({ chatId: "c1", sessionId: "s1", pid: 200, cwd: "/tmp/new", runtimeDir: "/tmp/r-new" })
|
|
39
|
+
|
|
40
|
+
const raw = JSON.parse(await readFile(registryPath, "utf8")) as { entries: Array<{ pid: number }> }
|
|
41
|
+
expect(raw.entries).toHaveLength(1)
|
|
42
|
+
expect(raw.entries[0]?.pid).toBe(200)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("unregister removes only the matching sessionId", async () => {
|
|
46
|
+
const registry = new ClaudePtyRegistry(registryPath)
|
|
47
|
+
await registry.register({ chatId: "c1", sessionId: "s1", pid: 1, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
|
|
48
|
+
await registry.register({ chatId: "c2", sessionId: "s2", pid: 2, cwd: "/tmp/b", runtimeDir: "/tmp/r2" })
|
|
49
|
+
await registry.unregister("s1")
|
|
50
|
+
|
|
51
|
+
const raw = JSON.parse(await readFile(registryPath, "utf8")) as { entries: Array<{ sessionId: string }> }
|
|
52
|
+
expect(raw.entries).toHaveLength(1)
|
|
53
|
+
expect(raw.entries[0]?.sessionId).toBe("s2")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("reapStale kills live process groups, removes runtimeDirs, and clears the file", async () => {
|
|
57
|
+
const child = Bun.spawn(
|
|
58
|
+
["python3", "-c", "import os, sys, time; os.setsid(); sys.stdout.write('ready\\n'); sys.stdout.flush(); time.sleep(60)"],
|
|
59
|
+
{ stdout: "pipe", stderr: "ignore", stdin: "ignore" },
|
|
60
|
+
)
|
|
61
|
+
const reader = child.stdout.getReader()
|
|
62
|
+
const decoded = new TextDecoder().decode((await reader.read()).value ?? new Uint8Array())
|
|
63
|
+
expect(decoded).toContain("ready")
|
|
64
|
+
reader.releaseLock()
|
|
65
|
+
const childPid = child.pid
|
|
66
|
+
|
|
67
|
+
const runtimeDir = path.join(tempDir, "spawn-runtime")
|
|
68
|
+
await mkdir(runtimeDir, { recursive: true })
|
|
69
|
+
await writeFile(path.join(runtimeDir, "mcp-config.json"), "{}", "utf8")
|
|
70
|
+
|
|
71
|
+
await writeFile(
|
|
72
|
+
registryPath,
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
entries: [
|
|
75
|
+
{ chatId: "c1", sessionId: "s1", pid: childPid, cwd: "/tmp/a", runtimeDir, createdAt: Date.now() },
|
|
76
|
+
{ chatId: "c2", sessionId: "s2", pid: 999_999_999, cwd: "/tmp/b", runtimeDir: "/tmp/nonexistent", createdAt: Date.now() },
|
|
77
|
+
],
|
|
78
|
+
}),
|
|
79
|
+
"utf8",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const registry = new ClaudePtyRegistry(registryPath)
|
|
83
|
+
const reaped = await registry.reapStale()
|
|
84
|
+
|
|
85
|
+
expect(reaped.map((entry) => entry.sessionId).sort()).toEqual(["s1", "s2"])
|
|
86
|
+
|
|
87
|
+
const exited = await Promise.race([
|
|
88
|
+
child.exited,
|
|
89
|
+
new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 3_000)),
|
|
90
|
+
])
|
|
91
|
+
expect(exited).not.toBe("timeout")
|
|
92
|
+
expect(child.signalCode).toBe("SIGKILL")
|
|
93
|
+
void childPid
|
|
94
|
+
|
|
95
|
+
// runtimeDir cleaned up
|
|
96
|
+
await expect(stat(runtimeDir)).rejects.toThrow()
|
|
97
|
+
|
|
98
|
+
const raw = JSON.parse(await readFile(registryPath, "utf8")) as { entries: unknown[] }
|
|
99
|
+
expect(raw.entries).toEqual([])
|
|
100
|
+
}, 30_000)
|
|
101
|
+
|
|
102
|
+
test("reapStale tolerates a missing registry file", async () => {
|
|
103
|
+
const registry = new ClaudePtyRegistry(registryPath)
|
|
104
|
+
const reaped = await registry.reapStale()
|
|
105
|
+
expect(reaped).toEqual([])
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("reapStale tolerates a malformed registry file", async () => {
|
|
109
|
+
await writeFile(registryPath, "not json", "utf8")
|
|
110
|
+
const registry = new ClaudePtyRegistry(registryPath)
|
|
111
|
+
const reaped = await registry.reapStale()
|
|
112
|
+
expect(reaped).toEqual([])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test("register creates the parent directory if missing", async () => {
|
|
116
|
+
const nestedPath = path.join(tempDir, "nested", "deep", "claude-pty.json")
|
|
117
|
+
const registry = new ClaudePtyRegistry(nestedPath)
|
|
118
|
+
await registry.register({ chatId: "c1", sessionId: "s1", pid: 1, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
|
|
119
|
+
const raw = JSON.parse(await readFile(nestedPath, "utf8")) as { entries: unknown[] }
|
|
120
|
+
expect(raw.entries).toHaveLength(1)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { open } from "node:fs/promises"
|
|
3
|
+
|
|
4
|
+
export async function computeBinarySha256(filePath: string): Promise<string> {
|
|
5
|
+
const fd = await open(filePath, "r")
|
|
6
|
+
try {
|
|
7
|
+
const hash = createHash("sha256")
|
|
8
|
+
const buf = Buffer.alloc(64 * 1024)
|
|
9
|
+
let pos = 0
|
|
10
|
+
while (true) {
|
|
11
|
+
const { bytesRead } = await fd.read(buf, 0, buf.length, pos)
|
|
12
|
+
if (bytesRead === 0) break
|
|
13
|
+
hash.update(buf.subarray(0, bytesRead))
|
|
14
|
+
pos += bytesRead
|
|
15
|
+
}
|
|
16
|
+
return hash.digest("hex")
|
|
17
|
+
} finally {
|
|
18
|
+
await fd.close()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { computeBinarySha256 } from "./binary-fingerprint.adapter"
|
|
3
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
|
|
7
|
+
describe("computeBinarySha256", () => {
|
|
8
|
+
test("returns 64-char hex sha256 of file contents", async () => {
|
|
9
|
+
const dir = await mkdtemp(path.join(tmpdir(), "kanna-binsha-"))
|
|
10
|
+
try {
|
|
11
|
+
const f = path.join(dir, "fake-claude")
|
|
12
|
+
await writeFile(f, "hello", "utf8")
|
|
13
|
+
const sha = await computeBinarySha256(f)
|
|
14
|
+
expect(sha).toMatch(/^[0-9a-f]{64}$/)
|
|
15
|
+
} finally { await rm(dir, { recursive: true, force: true }) }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("identical content → identical sha", async () => {
|
|
19
|
+
const dir = await mkdtemp(path.join(tmpdir(), "kanna-binsha-"))
|
|
20
|
+
try {
|
|
21
|
+
const a = path.join(dir, "a")
|
|
22
|
+
const b = path.join(dir, "b")
|
|
23
|
+
await writeFile(a, "x", "utf8")
|
|
24
|
+
await writeFile(b, "x", "utf8")
|
|
25
|
+
expect(await computeBinarySha256(a)).toBe(await computeBinarySha256(b))
|
|
26
|
+
} finally { await rm(dir, { recursive: true, force: true }) }
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("throws when file does not exist", async () => {
|
|
30
|
+
await expect(computeBinarySha256("/nonexistent/path")).rejects.toThrow()
|
|
31
|
+
})
|
|
32
|
+
})
|