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,194 @@
|
|
|
1
|
+
export interface LimitDetection {
|
|
2
|
+
chatId: string
|
|
3
|
+
resetAt: number
|
|
4
|
+
tz: string
|
|
5
|
+
raw: unknown
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface LimitDetector {
|
|
9
|
+
detect(chatId: string, error: unknown): LimitDetection | null
|
|
10
|
+
detectFromResultText?(chatId: string, text: string, nowMs?: number): LimitDetection | null
|
|
11
|
+
detectFromSdkRateLimitInfo?(chatId: string, info: unknown): LimitDetection | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ErrorLike {
|
|
15
|
+
message?: string
|
|
16
|
+
status?: number
|
|
17
|
+
headers?: Record<string, string>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function extractHeaders(error: unknown): Record<string, string> {
|
|
21
|
+
if (error && typeof error === "object" && "headers" in error) {
|
|
22
|
+
const headers = (error as ErrorLike).headers
|
|
23
|
+
if (headers && typeof headers === "object") return headers
|
|
24
|
+
}
|
|
25
|
+
return {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseBody(error: unknown): Record<string, unknown> | null {
|
|
29
|
+
if (!error || typeof error !== "object") return null
|
|
30
|
+
const message = (error as ErrorLike).message
|
|
31
|
+
if (!message) return null
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(message)
|
|
34
|
+
return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : null
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseIsoMillis(value: unknown): number | null {
|
|
41
|
+
if (typeof value !== "string" || !value) return null
|
|
42
|
+
const millis = new Date(value).getTime()
|
|
43
|
+
return Number.isFinite(millis) ? millis : null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function zonedWallClockToUtcMs(
|
|
47
|
+
year: number, month: number, day: number, hour: number, minute: number, tz: string,
|
|
48
|
+
): number {
|
|
49
|
+
const utcGuess = Date.UTC(year, month - 1, day, hour, minute)
|
|
50
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
51
|
+
timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit",
|
|
52
|
+
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false,
|
|
53
|
+
})
|
|
54
|
+
const parts = Object.fromEntries(
|
|
55
|
+
dtf.formatToParts(new Date(utcGuess))
|
|
56
|
+
.filter((part) => part.type !== "literal")
|
|
57
|
+
.map((part) => [part.type, part.value]),
|
|
58
|
+
)
|
|
59
|
+
const asLocal = Date.UTC(
|
|
60
|
+
Number(parts.year), Number(parts.month) - 1, Number(parts.day),
|
|
61
|
+
parts.hour === "24" ? 0 : Number(parts.hour), Number(parts.minute),
|
|
62
|
+
)
|
|
63
|
+
return utcGuess - (asLocal - utcGuess)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function parseResetFromText(text: string, nowMs: number = Date.now()): { resetAt: number; tz: string } | null {
|
|
67
|
+
if (typeof text !== "string") return null
|
|
68
|
+
const match = text.match(/resets\s+(\d{1,2})(?::(\d{2}))?(am|pm)\s*\(([^)]+)\)/i)
|
|
69
|
+
if (!match) return null
|
|
70
|
+
const hour12 = Number(match[1])
|
|
71
|
+
const minute = match[2] ? Number(match[2]) : 0
|
|
72
|
+
const meridiem = match[3].toLowerCase()
|
|
73
|
+
const tz = match[4].trim()
|
|
74
|
+
if (!Number.isFinite(hour12) || hour12 < 1 || hour12 > 12) return null
|
|
75
|
+
if (!Number.isFinite(minute) || minute < 0 || minute > 59) return null
|
|
76
|
+
const hour24 = meridiem === "pm"
|
|
77
|
+
? (hour12 === 12 ? 12 : hour12 + 12)
|
|
78
|
+
: (hour12 === 12 ? 0 : hour12)
|
|
79
|
+
let tzYear: number, tzMonth: number, tzDay: number
|
|
80
|
+
try {
|
|
81
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
82
|
+
timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit",
|
|
83
|
+
})
|
|
84
|
+
const parts = Object.fromEntries(
|
|
85
|
+
dtf.formatToParts(new Date(nowMs))
|
|
86
|
+
.filter((part) => part.type !== "literal")
|
|
87
|
+
.map((part) => [part.type, part.value]),
|
|
88
|
+
)
|
|
89
|
+
tzYear = Number(parts.year)
|
|
90
|
+
tzMonth = Number(parts.month)
|
|
91
|
+
tzDay = Number(parts.day)
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
let resetAt = zonedWallClockToUtcMs(tzYear, tzMonth, tzDay, hour24, minute, tz)
|
|
96
|
+
if (resetAt <= nowMs) {
|
|
97
|
+
const next = new Date(Date.UTC(tzYear, tzMonth - 1, tzDay) + 24 * 3600_000)
|
|
98
|
+
resetAt = zonedWallClockToUtcMs(
|
|
99
|
+
next.getUTCFullYear(), next.getUTCMonth() + 1, next.getUTCDate(), hour24, minute, tz,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
return { resetAt, tz }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class ClaudeLimitDetector implements LimitDetector {
|
|
106
|
+
detect(chatId: string, error: unknown): LimitDetection | null {
|
|
107
|
+
const body = parseBody(error)
|
|
108
|
+
const inner = body && typeof body.error === "object" && body.error !== null
|
|
109
|
+
? (body.error as Record<string, unknown>)
|
|
110
|
+
: null
|
|
111
|
+
const isRateLimit = inner?.type === "rate_limit_error"
|
|
112
|
+
|| (error as ErrorLike | null)?.status === 429 && inner?.type === "rate_limit_error"
|
|
113
|
+
|
|
114
|
+
if (isRateLimit) {
|
|
115
|
+
const headers = extractHeaders(error)
|
|
116
|
+
const resetAt = parseIsoMillis(headers["anthropic-ratelimit-unified-reset"])
|
|
117
|
+
?? parseIsoMillis(inner?.resets_at)
|
|
118
|
+
?? parseIsoMillis(inner?.reset_at)
|
|
119
|
+
if (resetAt !== null) {
|
|
120
|
+
const tz = headers["x-anthropic-timezone"]
|
|
121
|
+
?? (typeof inner?.timezone === "string" ? (inner.timezone as string) : null)
|
|
122
|
+
?? "system"
|
|
123
|
+
return { chatId, resetAt, tz, raw: error }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback: the Claude Code SDK rethrows CLI result errors as
|
|
128
|
+
// `Error("Claude Code returned an error result: <text>")`. Parse the
|
|
129
|
+
// text directly for "You've hit your limit · resets ..." / "usage limit
|
|
130
|
+
// reached|<unix>" forms.
|
|
131
|
+
const message = (error as ErrorLike | null)?.message
|
|
132
|
+
if (typeof message === "string") {
|
|
133
|
+
return this.detectFromResultText(chatId, message)
|
|
134
|
+
}
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
detectFromResultText(chatId: string, text: string, nowMs: number = Date.now()): LimitDetection | null {
|
|
139
|
+
const parsed = parseResetFromText(text, nowMs)
|
|
140
|
+
if (parsed) return { chatId, resetAt: parsed.resetAt, tz: parsed.tz, raw: text }
|
|
141
|
+
const pipe = parseClaudeUsageLimitPipe(text)
|
|
142
|
+
if (pipe !== null) return { chatId, resetAt: pipe, tz: "system", raw: text }
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
detectFromSdkRateLimitInfo(chatId: string, info: unknown): LimitDetection | null {
|
|
147
|
+
if (!info || typeof info !== "object") return null
|
|
148
|
+
const rec = info as Record<string, unknown>
|
|
149
|
+
if (rec.status !== "rejected") return null
|
|
150
|
+
const raw = rec.resetsAt
|
|
151
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null
|
|
152
|
+
// SDK emits `resetsAt` as epoch seconds for claude.ai subscription limits;
|
|
153
|
+
// coerce to ms defensively (anything below year 5138 in ms is below 1e14).
|
|
154
|
+
const resetAt = raw < 1e12 ? Math.round(raw * 1000) : raw
|
|
155
|
+
return { chatId, resetAt, tz: "system", raw: info }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function parseClaudeUsageLimitPipe(text: string): number | null {
|
|
160
|
+
// Claude CLI sometimes returns "Claude AI usage limit reached|<unix-seconds>".
|
|
161
|
+
if (typeof text !== "string") return null
|
|
162
|
+
const match = text.match(/usage limit reached\|(\d{9,13})/i)
|
|
163
|
+
if (!match) return null
|
|
164
|
+
const value = Number(match[1])
|
|
165
|
+
if (!Number.isFinite(value) || value <= 0) return null
|
|
166
|
+
return value < 1e12 ? value * 1000 : value
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface JsonRpcErrorLike {
|
|
170
|
+
code?: number
|
|
171
|
+
message?: string
|
|
172
|
+
data?: Record<string, unknown>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export class CodexLimitDetector implements LimitDetector {
|
|
176
|
+
detect(chatId: string, error: unknown): LimitDetection | null {
|
|
177
|
+
if (!error || typeof error !== "object") return null
|
|
178
|
+
const rpc = error as JsonRpcErrorLike
|
|
179
|
+
const data = rpc.data && typeof rpc.data === "object" ? rpc.data : null
|
|
180
|
+
const isRateLimit = data?.code === "rate_limit" || rpc.code === -32001
|
|
181
|
+
if (!isRateLimit) return null
|
|
182
|
+
|
|
183
|
+
let resetAt: number | null
|
|
184
|
+
if (typeof data?.resets_at_ms === "number" && Number.isFinite(data.resets_at_ms)) {
|
|
185
|
+
resetAt = data.resets_at_ms
|
|
186
|
+
} else {
|
|
187
|
+
resetAt = parseIsoMillis(data?.resets_at)
|
|
188
|
+
}
|
|
189
|
+
if (resetAt === null) return null
|
|
190
|
+
|
|
191
|
+
const tz = typeof data?.timezone === "string" ? (data.timezone as string) : "system"
|
|
192
|
+
return { chatId, resetAt, tz, raw: error }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/server/bm25.test.ts
|
|
2
|
+
import { describe, expect, test } from "bun:test"
|
|
3
|
+
import { BM25Index } from "./bm25"
|
|
4
|
+
|
|
5
|
+
describe("BM25Index", () => {
|
|
6
|
+
describe("tokenize", () => {
|
|
7
|
+
test("lowercases and splits on whitespace/punctuation", () => {
|
|
8
|
+
const index = new BM25Index<string>()
|
|
9
|
+
index.add("d1", "Hello, World! This is a test.")
|
|
10
|
+
const results = index.search("hello")
|
|
11
|
+
expect(results.length).toBe(1)
|
|
12
|
+
expect(results[0].id).toBe("d1")
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test("filters stopwords", () => {
|
|
16
|
+
const index = new BM25Index<string>()
|
|
17
|
+
index.add("d1", "the quick brown fox")
|
|
18
|
+
index.add("d2", "a lazy dog")
|
|
19
|
+
// "the" and "a" are stopwords, should not dominate results
|
|
20
|
+
const results = index.search("quick")
|
|
21
|
+
expect(results.length).toBe(1)
|
|
22
|
+
expect(results[0].id).toBe("d1")
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe("search", () => {
|
|
27
|
+
test("ranks relevant documents higher", () => {
|
|
28
|
+
const index = new BM25Index<string>()
|
|
29
|
+
index.add("d1", "postgres database migration schema users table")
|
|
30
|
+
index.add("d2", "react component button styling CSS")
|
|
31
|
+
index.add("d3", "database connection pool postgres config")
|
|
32
|
+
|
|
33
|
+
const results = index.search("postgres database")
|
|
34
|
+
expect(results.length).toBeGreaterThanOrEqual(2)
|
|
35
|
+
// d1 and d3 both mention postgres+database terms
|
|
36
|
+
const ids = results.map((r) => r.id)
|
|
37
|
+
expect(ids).toContain("d1")
|
|
38
|
+
expect(ids).toContain("d3")
|
|
39
|
+
// d2 should not appear or rank very low
|
|
40
|
+
expect(ids.indexOf("d1")).toBeLessThan(ids.indexOf("d2") === -1 ? Infinity : ids.indexOf("d2"))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test("returns empty for no matches", () => {
|
|
44
|
+
const index = new BM25Index<string>()
|
|
45
|
+
index.add("d1", "hello world")
|
|
46
|
+
const results = index.search("nonexistent")
|
|
47
|
+
expect(results).toEqual([])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("respects limit parameter", () => {
|
|
51
|
+
const index = new BM25Index<string>()
|
|
52
|
+
for (let i = 0; i < 20; i++) {
|
|
53
|
+
index.add(`d${i}`, `document ${i} about testing`)
|
|
54
|
+
}
|
|
55
|
+
const results = index.search("testing", 5)
|
|
56
|
+
expect(results.length).toBe(5)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("handles multi-field documents via concatenation", () => {
|
|
60
|
+
const index = new BM25Index<string>()
|
|
61
|
+
index.add("d1", "auth middleware implementation error handling retry logic")
|
|
62
|
+
index.add("d2", "auth login form validation")
|
|
63
|
+
const results = index.search("auth error handling")
|
|
64
|
+
expect(results[0].id).toBe("d1")
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe("remove", () => {
|
|
69
|
+
test("removes document from index", () => {
|
|
70
|
+
const index = new BM25Index<string>()
|
|
71
|
+
index.add("d1", "hello world")
|
|
72
|
+
index.add("d2", "hello there")
|
|
73
|
+
index.remove("d1")
|
|
74
|
+
const results = index.search("hello")
|
|
75
|
+
expect(results.length).toBe(1)
|
|
76
|
+
expect(results[0].id).toBe("d2")
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe("size", () => {
|
|
81
|
+
test("tracks document count", () => {
|
|
82
|
+
const index = new BM25Index<string>()
|
|
83
|
+
expect(index.size).toBe(0)
|
|
84
|
+
index.add("d1", "hello")
|
|
85
|
+
expect(index.size).toBe(1)
|
|
86
|
+
index.add("d2", "world")
|
|
87
|
+
expect(index.size).toBe(2)
|
|
88
|
+
index.remove("d1")
|
|
89
|
+
expect(index.size).toBe(1)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// src/server/bm25.ts
|
|
2
|
+
|
|
3
|
+
const STOPWORDS = new Set([
|
|
4
|
+
"a", "an", "the", "is", "it", "of", "in", "to", "and", "or", "for",
|
|
5
|
+
"on", "at", "by", "with", "from", "as", "be", "was", "were", "been",
|
|
6
|
+
"are", "has", "had", "have", "do", "does", "did", "but", "not", "this",
|
|
7
|
+
"that", "these", "those", "i", "we", "you", "he", "she", "they",
|
|
8
|
+
])
|
|
9
|
+
|
|
10
|
+
function tokenize(text: string): string[] {
|
|
11
|
+
return text
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.split(/[^a-z0-9_]+/)
|
|
14
|
+
.filter((t) => t.length > 1 && !STOPWORDS.has(t))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface DocEntry {
|
|
18
|
+
tokens: string[]
|
|
19
|
+
length: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BM25Result<ID> {
|
|
23
|
+
id: ID
|
|
24
|
+
score: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class BM25Index<ID extends string = string> {
|
|
28
|
+
private readonly k1 = 1.2
|
|
29
|
+
private readonly b = 0.75
|
|
30
|
+
private readonly docs = new Map<ID, DocEntry>()
|
|
31
|
+
private readonly invertedIndex = new Map<string, Set<ID>>()
|
|
32
|
+
private totalLength = 0
|
|
33
|
+
|
|
34
|
+
get size(): number {
|
|
35
|
+
return this.docs.size
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private get avgLength(): number {
|
|
39
|
+
return this.docs.size === 0 ? 0 : this.totalLength / this.docs.size
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
add(id: ID, text: string): void {
|
|
43
|
+
this.remove(id)
|
|
44
|
+
const tokens = tokenize(text)
|
|
45
|
+
this.docs.set(id, { tokens, length: tokens.length })
|
|
46
|
+
this.totalLength += tokens.length
|
|
47
|
+
|
|
48
|
+
for (const token of tokens) {
|
|
49
|
+
let set = this.invertedIndex.get(token)
|
|
50
|
+
if (!set) {
|
|
51
|
+
set = new Set()
|
|
52
|
+
this.invertedIndex.set(token, set)
|
|
53
|
+
}
|
|
54
|
+
set.add(id)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
remove(id: ID): void {
|
|
59
|
+
const doc = this.docs.get(id)
|
|
60
|
+
if (!doc) return
|
|
61
|
+
this.totalLength -= doc.length
|
|
62
|
+
this.docs.delete(id)
|
|
63
|
+
for (const token of doc.tokens) {
|
|
64
|
+
const set = this.invertedIndex.get(token)
|
|
65
|
+
if (set) {
|
|
66
|
+
set.delete(id)
|
|
67
|
+
if (set.size === 0) this.invertedIndex.delete(token)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
search(query: string, limit = 10): BM25Result<ID>[] {
|
|
73
|
+
const queryTokens = tokenize(query)
|
|
74
|
+
if (queryTokens.length === 0) return []
|
|
75
|
+
|
|
76
|
+
const scores = new Map<ID, number>()
|
|
77
|
+
const N = this.docs.size
|
|
78
|
+
const avgDl = this.avgLength
|
|
79
|
+
|
|
80
|
+
for (const qt of queryTokens) {
|
|
81
|
+
const postings = this.invertedIndex.get(qt)
|
|
82
|
+
if (!postings) continue
|
|
83
|
+
|
|
84
|
+
const df = postings.size
|
|
85
|
+
const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5))
|
|
86
|
+
|
|
87
|
+
for (const docId of postings) {
|
|
88
|
+
const doc = this.docs.get(docId)!
|
|
89
|
+
const tf = doc.tokens.filter((t) => t === qt).length
|
|
90
|
+
const tfNorm = (tf * (this.k1 + 1)) / (tf + this.k1 * (1 - this.b + this.b * (doc.length / avgDl)))
|
|
91
|
+
const score = idf * tfNorm
|
|
92
|
+
scores.set(docId, (scores.get(docId) ?? 0) + score)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [...scores.entries()]
|
|
97
|
+
.sort((a, b) => b[1] - a[1])
|
|
98
|
+
.slice(0, limit)
|
|
99
|
+
.map(([id, score]) => ({ id, score }))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, test, expect, afterEach } from "bun:test"
|
|
2
|
+
import { connect, type NatsConnection } from "@nats-io/transport-node"
|
|
3
|
+
import { jetstream, DeliverPolicy } from "@nats-io/jetstream"
|
|
4
|
+
import { NatsBridge } from "./nats-bridge"
|
|
5
|
+
import { ensureChatMessageStream, CHAT_MESSAGE_EVENTS_STREAM } from "./nats-streams"
|
|
6
|
+
import { chatMessageSubject } from "../shared/nats-subjects"
|
|
7
|
+
import { compressPayload, decompressPayload } from "../shared/compression"
|
|
8
|
+
|
|
9
|
+
const encoder = new TextEncoder()
|
|
10
|
+
const decoder = new TextDecoder()
|
|
11
|
+
|
|
12
|
+
let bridge: NatsBridge | null = null
|
|
13
|
+
let nc: NatsConnection | null = null
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await nc?.drain()
|
|
17
|
+
nc = null
|
|
18
|
+
if (bridge) {
|
|
19
|
+
await bridge.dispose()
|
|
20
|
+
bridge = null
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
async function setup() {
|
|
25
|
+
bridge = await NatsBridge.create()
|
|
26
|
+
nc = await connect({ servers: bridge.natsUrl })
|
|
27
|
+
await ensureChatMessageStream(nc)
|
|
28
|
+
return nc
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("chat message events via JetStream ordered consumer", () => {
|
|
32
|
+
test("consumer receives events published to JetStream", async () => {
|
|
33
|
+
const conn = await setup()
|
|
34
|
+
const js = jetstream(conn)
|
|
35
|
+
const chatId = "test-chat-js-1"
|
|
36
|
+
const subject = chatMessageSubject(chatId)
|
|
37
|
+
|
|
38
|
+
// Create ordered consumer with DeliverPolicy.New
|
|
39
|
+
const consumer = await js.consumers.get(CHAT_MESSAGE_EVENTS_STREAM, {
|
|
40
|
+
filter_subjects: subject,
|
|
41
|
+
deliver_policy: DeliverPolicy.New,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Publish events (mimicking server-side publishChatMessage)
|
|
45
|
+
const events = [
|
|
46
|
+
{ chatId, entry: { id: "1", kind: "message", content: "hello" } },
|
|
47
|
+
{ chatId, entry: { id: "2", kind: "message", content: "world" } },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for (const event of events) {
|
|
51
|
+
const payload = compressPayload(encoder.encode(JSON.stringify(event)))
|
|
52
|
+
await js.publish(subject, payload)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Consume and verify
|
|
56
|
+
const received: unknown[] = []
|
|
57
|
+
const messages = await consumer.consume()
|
|
58
|
+
for await (const msg of messages) {
|
|
59
|
+
const decoded = await decompressPayload(msg.data)
|
|
60
|
+
const data = JSON.parse(decoder.decode(decoded))
|
|
61
|
+
received.push(data)
|
|
62
|
+
if (received.length >= 2) break
|
|
63
|
+
}
|
|
64
|
+
await messages.close()
|
|
65
|
+
|
|
66
|
+
expect(received).toHaveLength(2)
|
|
67
|
+
expect(received[0]).toEqual(events[0])
|
|
68
|
+
expect(received[1]).toEqual(events[1])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test("consumer filters by chat subject", async () => {
|
|
72
|
+
const conn = await setup()
|
|
73
|
+
const js = jetstream(conn)
|
|
74
|
+
|
|
75
|
+
const chatId1 = "chat-js-filter-1"
|
|
76
|
+
const chatId2 = "chat-js-filter-2"
|
|
77
|
+
|
|
78
|
+
const consumer = await js.consumers.get(CHAT_MESSAGE_EVENTS_STREAM, {
|
|
79
|
+
filter_subjects: chatMessageSubject(chatId1),
|
|
80
|
+
deliver_policy: DeliverPolicy.New,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Publish to chat 2 (should be filtered out)
|
|
84
|
+
await js.publish(
|
|
85
|
+
chatMessageSubject(chatId2),
|
|
86
|
+
encoder.encode(JSON.stringify({ chatId: chatId2, entry: { id: "x" } }))
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Publish to chat 1 (should be received)
|
|
90
|
+
await js.publish(
|
|
91
|
+
chatMessageSubject(chatId1),
|
|
92
|
+
encoder.encode(JSON.stringify({ chatId: chatId1, entry: { id: "1" } }))
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const messages = await consumer.consume()
|
|
96
|
+
const received: unknown[] = []
|
|
97
|
+
for await (const msg of messages) {
|
|
98
|
+
const data = JSON.parse(decoder.decode(msg.data))
|
|
99
|
+
received.push(data)
|
|
100
|
+
if (received.length >= 1) break
|
|
101
|
+
}
|
|
102
|
+
await messages.close()
|
|
103
|
+
|
|
104
|
+
expect(received).toHaveLength(1)
|
|
105
|
+
expect(received[0]).toEqual({ chatId: chatId1, entry: { id: "1" } })
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("compressed events are decompressed correctly by consumer", async () => {
|
|
109
|
+
const conn = await setup()
|
|
110
|
+
const js = jetstream(conn)
|
|
111
|
+
const chatId = "test-chat-compress"
|
|
112
|
+
const subject = chatMessageSubject(chatId)
|
|
113
|
+
|
|
114
|
+
const consumer = await js.consumers.get(CHAT_MESSAGE_EVENTS_STREAM, {
|
|
115
|
+
filter_subjects: subject,
|
|
116
|
+
deliver_policy: DeliverPolicy.New,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Create a large payload that will trigger compression (> 64KB threshold)
|
|
120
|
+
const largeContent = "x".repeat(100_000)
|
|
121
|
+
const event = { chatId, entry: { id: "big", content: largeContent } }
|
|
122
|
+
const payload = compressPayload(encoder.encode(JSON.stringify(event)))
|
|
123
|
+
|
|
124
|
+
await js.publish(subject, payload)
|
|
125
|
+
|
|
126
|
+
const messages = await consumer.consume()
|
|
127
|
+
for await (const msg of messages) {
|
|
128
|
+
const decoded = await decompressPayload(msg.data)
|
|
129
|
+
const data = JSON.parse(decoder.decode(decoded)) as typeof event
|
|
130
|
+
expect(data.entry.content).toBe(largeContent)
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
await messages.close()
|
|
134
|
+
})
|
|
135
|
+
})
|