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,234 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
|
2
|
+
import {
|
|
3
|
+
PushSubscriptionStore,
|
|
4
|
+
createPushRouter,
|
|
5
|
+
initVapid,
|
|
6
|
+
getVapidPublicKey,
|
|
7
|
+
type StoredPushSubscription,
|
|
8
|
+
} from "./push-notifications"
|
|
9
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
10
|
+
import { tmpdir } from "node:os"
|
|
11
|
+
import path from "node:path"
|
|
12
|
+
|
|
13
|
+
describe("PushSubscriptionStore", () => {
|
|
14
|
+
let dir: string
|
|
15
|
+
let store: PushSubscriptionStore
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
dir = await mkdtemp(path.join(tmpdir(), "push-test-"))
|
|
19
|
+
store = new PushSubscriptionStore(path.join(dir, "push-subscriptions.json"))
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
await rm(dir, { recursive: true, force: true })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test("add and list subscriptions", () => {
|
|
27
|
+
const sub: StoredPushSubscription = {
|
|
28
|
+
endpoint: "https://push.example.com/abc",
|
|
29
|
+
keys: { p256dh: "key1", auth: "auth1" },
|
|
30
|
+
}
|
|
31
|
+
store.add(sub)
|
|
32
|
+
expect(store.getAll()).toHaveLength(1)
|
|
33
|
+
expect(store.getAll()[0].endpoint).toBe("https://push.example.com/abc")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("deduplicates by endpoint", () => {
|
|
37
|
+
const sub: StoredPushSubscription = {
|
|
38
|
+
endpoint: "https://push.example.com/abc",
|
|
39
|
+
keys: { p256dh: "key1", auth: "auth1" },
|
|
40
|
+
}
|
|
41
|
+
store.add(sub)
|
|
42
|
+
store.add(sub)
|
|
43
|
+
expect(store.getAll()).toHaveLength(1)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("remove by endpoint", () => {
|
|
47
|
+
const sub: StoredPushSubscription = {
|
|
48
|
+
endpoint: "https://push.example.com/abc",
|
|
49
|
+
keys: { p256dh: "key1", auth: "auth1" },
|
|
50
|
+
}
|
|
51
|
+
store.add(sub)
|
|
52
|
+
store.remove("https://push.example.com/abc")
|
|
53
|
+
expect(store.getAll()).toHaveLength(0)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("persists to disk and loads back", async () => {
|
|
57
|
+
const sub: StoredPushSubscription = {
|
|
58
|
+
endpoint: "https://push.example.com/abc",
|
|
59
|
+
keys: { p256dh: "key1", auth: "auth1" },
|
|
60
|
+
}
|
|
61
|
+
store.add(sub)
|
|
62
|
+
await store.save()
|
|
63
|
+
|
|
64
|
+
const store2 = new PushSubscriptionStore(path.join(dir, "push-subscriptions.json"))
|
|
65
|
+
await store2.load()
|
|
66
|
+
expect(store2.getAll()).toHaveLength(1)
|
|
67
|
+
expect(store2.getAll()[0].endpoint).toBe("https://push.example.com/abc")
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("load handles missing file gracefully", async () => {
|
|
71
|
+
const store2 = new PushSubscriptionStore(path.join(dir, "nonexistent.json"))
|
|
72
|
+
await store2.load()
|
|
73
|
+
expect(store2.getAll()).toHaveLength(0)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe("createPushRouter", () => {
|
|
78
|
+
let dir: string
|
|
79
|
+
let store: PushSubscriptionStore
|
|
80
|
+
|
|
81
|
+
beforeEach(async () => {
|
|
82
|
+
dir = await mkdtemp(path.join(tmpdir(), "push-test-"))
|
|
83
|
+
store = new PushSubscriptionStore(path.join(dir, "push-subscriptions.json"))
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
afterEach(async () => {
|
|
87
|
+
await rm(dir, { recursive: true, force: true })
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("GET /api/push/vapid-key returns public key", async () => {
|
|
91
|
+
const savedPublic = process.env.VAPID_PUBLIC_KEY
|
|
92
|
+
const savedPrivate = process.env.VAPID_PRIVATE_KEY
|
|
93
|
+
// Use real VAPID keys (web-push validates key length)
|
|
94
|
+
const keys = (await import("web-push")).generateVAPIDKeys()
|
|
95
|
+
process.env.VAPID_PUBLIC_KEY = keys.publicKey
|
|
96
|
+
process.env.VAPID_PRIVATE_KEY = keys.privateKey
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
initVapid()
|
|
100
|
+
const router = createPushRouter(store)
|
|
101
|
+
const req = new Request("http://localhost/api/push/vapid-key")
|
|
102
|
+
const res = await router(req)
|
|
103
|
+
expect(res.status).toBe(200)
|
|
104
|
+
const body = await res.json()
|
|
105
|
+
expect(body.publicKey).toBe(keys.publicKey)
|
|
106
|
+
} finally {
|
|
107
|
+
if (savedPublic) process.env.VAPID_PUBLIC_KEY = savedPublic
|
|
108
|
+
else delete process.env.VAPID_PUBLIC_KEY
|
|
109
|
+
if (savedPrivate) process.env.VAPID_PRIVATE_KEY = savedPrivate
|
|
110
|
+
else delete process.env.VAPID_PRIVATE_KEY
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test("POST /api/push/subscribe adds subscription", async () => {
|
|
115
|
+
const router = createPushRouter(store)
|
|
116
|
+
const sub = {
|
|
117
|
+
endpoint: "https://push.example.com/xyz",
|
|
118
|
+
keys: { p256dh: "k", auth: "a" },
|
|
119
|
+
}
|
|
120
|
+
const req = new Request("http://localhost/api/push/subscribe", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: { "Content-Type": "application/json" },
|
|
123
|
+
body: JSON.stringify(sub),
|
|
124
|
+
})
|
|
125
|
+
const res = await router(req)
|
|
126
|
+
expect(res.status).toBe(201)
|
|
127
|
+
expect(store.getAll()).toHaveLength(1)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test("DELETE /api/push/subscribe removes subscription", async () => {
|
|
131
|
+
store.add({
|
|
132
|
+
endpoint: "https://push.example.com/xyz",
|
|
133
|
+
keys: { p256dh: "k", auth: "a" },
|
|
134
|
+
})
|
|
135
|
+
const router = createPushRouter(store)
|
|
136
|
+
const req = new Request("http://localhost/api/push/subscribe", {
|
|
137
|
+
method: "DELETE",
|
|
138
|
+
headers: { "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({ endpoint: "https://push.example.com/xyz" }),
|
|
140
|
+
})
|
|
141
|
+
const res = await router(req)
|
|
142
|
+
expect(res.status).toBe(200)
|
|
143
|
+
expect(store.getAll()).toHaveLength(0)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("GET /api/push/vapid-key returns 503 when not configured", async () => {
|
|
147
|
+
const savedPublic = process.env.VAPID_PUBLIC_KEY
|
|
148
|
+
const savedPrivate = process.env.VAPID_PRIVATE_KEY
|
|
149
|
+
delete process.env.VAPID_PUBLIC_KEY
|
|
150
|
+
delete process.env.VAPID_PRIVATE_KEY
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
initVapid()
|
|
154
|
+
const router = createPushRouter(store)
|
|
155
|
+
const req = new Request("http://localhost/api/push/vapid-key")
|
|
156
|
+
const res = await router(req)
|
|
157
|
+
expect(res.status).toBe(503)
|
|
158
|
+
const body = await res.json()
|
|
159
|
+
expect(body.error).toBe("Push notifications not configured")
|
|
160
|
+
} finally {
|
|
161
|
+
if (savedPublic) process.env.VAPID_PUBLIC_KEY = savedPublic
|
|
162
|
+
if (savedPrivate) process.env.VAPID_PRIVATE_KEY = savedPrivate
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test("POST /api/push/subscribe validates body - missing fields returns 400", async () => {
|
|
167
|
+
const router = createPushRouter(store)
|
|
168
|
+
|
|
169
|
+
// Missing endpoint entirely
|
|
170
|
+
const res1 = await router(new Request("http://localhost/api/push/subscribe", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { "Content-Type": "application/json" },
|
|
173
|
+
body: JSON.stringify({ keys: { p256dh: "k", auth: "a" } }),
|
|
174
|
+
}))
|
|
175
|
+
expect(res1.status).toBe(400)
|
|
176
|
+
|
|
177
|
+
// Missing keys
|
|
178
|
+
const res2 = await router(new Request("http://localhost/api/push/subscribe", {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json" },
|
|
181
|
+
body: JSON.stringify({ endpoint: "https://push.example.com/abc" }),
|
|
182
|
+
}))
|
|
183
|
+
expect(res2.status).toBe(400)
|
|
184
|
+
|
|
185
|
+
// Invalid JSON
|
|
186
|
+
const res3 = await router(new Request("http://localhost/api/push/subscribe", {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: { "Content-Type": "application/json" },
|
|
189
|
+
body: "not json",
|
|
190
|
+
}))
|
|
191
|
+
expect(res3.status).toBe(400)
|
|
192
|
+
|
|
193
|
+
// Invalid endpoint URL
|
|
194
|
+
const res4 = await router(new Request("http://localhost/api/push/subscribe", {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: { "Content-Type": "application/json" },
|
|
197
|
+
body: JSON.stringify({ endpoint: "not-a-url", keys: { p256dh: "k", auth: "a" } }),
|
|
198
|
+
}))
|
|
199
|
+
expect(res4.status).toBe(400)
|
|
200
|
+
|
|
201
|
+
// Missing key fields (p256dh/auth)
|
|
202
|
+
const res5 = await router(new Request("http://localhost/api/push/subscribe", {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: { "Content-Type": "application/json" },
|
|
205
|
+
body: JSON.stringify({ endpoint: "https://push.example.com/abc", keys: { p256dh: 123, auth: "a" } }),
|
|
206
|
+
}))
|
|
207
|
+
expect(res5.status).toBe(400)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test("unknown route returns 404", async () => {
|
|
211
|
+
const router = createPushRouter(store)
|
|
212
|
+
const req = new Request("http://localhost/api/push/unknown")
|
|
213
|
+
const res = await router(req)
|
|
214
|
+
expect(res.status).toBe(404)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe("initVapid", () => {
|
|
219
|
+
test("returns false when env vars not set", () => {
|
|
220
|
+
const savedPublic = process.env.VAPID_PUBLIC_KEY
|
|
221
|
+
const savedPrivate = process.env.VAPID_PRIVATE_KEY
|
|
222
|
+
delete process.env.VAPID_PUBLIC_KEY
|
|
223
|
+
delete process.env.VAPID_PRIVATE_KEY
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const result = initVapid()
|
|
227
|
+
expect(result).toBe(false)
|
|
228
|
+
expect(getVapidPublicKey()).toBeNull()
|
|
229
|
+
} finally {
|
|
230
|
+
if (savedPublic) process.env.VAPID_PUBLIC_KEY = savedPublic
|
|
231
|
+
if (savedPrivate) process.env.VAPID_PRIVATE_KEY = savedPrivate
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
})
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import webpush from "web-push"
|
|
2
|
+
import { LOG_PREFIX } from "../shared/branding"
|
|
3
|
+
|
|
4
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export interface StoredPushSubscription {
|
|
7
|
+
endpoint: string
|
|
8
|
+
keys: { p256dh: string; auth: string }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PushPayload {
|
|
12
|
+
title: string
|
|
13
|
+
body: string
|
|
14
|
+
url?: string
|
|
15
|
+
tag?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ── VAPID ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
let vapidConfigured = false
|
|
21
|
+
let vapidPublicKey: string | null = null
|
|
22
|
+
|
|
23
|
+
export function initVapid(): boolean {
|
|
24
|
+
const publicKey = process.env.VAPID_PUBLIC_KEY
|
|
25
|
+
const privateKey = process.env.VAPID_PRIVATE_KEY
|
|
26
|
+
const subject = process.env.VAPID_SUBJECT ?? "mailto:tinkaria@localhost"
|
|
27
|
+
|
|
28
|
+
if (!publicKey || !privateKey) {
|
|
29
|
+
console.warn(LOG_PREFIX, "VAPID keys not configured — push notifications disabled")
|
|
30
|
+
vapidConfigured = false
|
|
31
|
+
vapidPublicKey = null
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
webpush.setVapidDetails(subject, publicKey, privateKey)
|
|
36
|
+
vapidConfigured = true
|
|
37
|
+
vapidPublicKey = publicKey
|
|
38
|
+
console.warn(LOG_PREFIX, "Push notifications enabled (VAPID configured)")
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getVapidPublicKey(): string | null {
|
|
43
|
+
return vapidPublicKey
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Subscription Store ─────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export class PushSubscriptionStore {
|
|
49
|
+
private subscriptions = new Map<string, StoredPushSubscription>()
|
|
50
|
+
private readonly filePath: string
|
|
51
|
+
|
|
52
|
+
constructor(filePath: string) {
|
|
53
|
+
this.filePath = filePath
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
add(sub: StoredPushSubscription): void {
|
|
57
|
+
this.subscriptions.set(sub.endpoint, sub)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
remove(endpoint: string): void {
|
|
61
|
+
this.subscriptions.delete(endpoint)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getAll(): StoredPushSubscription[] {
|
|
65
|
+
return [...this.subscriptions.values()]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async save(): Promise<void> {
|
|
69
|
+
const data = JSON.stringify(this.getAll(), null, 2)
|
|
70
|
+
await Bun.write(this.filePath, data)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async load(): Promise<void> {
|
|
74
|
+
try {
|
|
75
|
+
const file = Bun.file(this.filePath)
|
|
76
|
+
if (!(await file.exists())) return
|
|
77
|
+
const data = await file.json()
|
|
78
|
+
if (!Array.isArray(data)) return
|
|
79
|
+
for (const sub of data) {
|
|
80
|
+
if (sub.endpoint && sub.keys?.p256dh && sub.keys?.auth) {
|
|
81
|
+
this.subscriptions.set(sub.endpoint, sub)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (err: unknown) {
|
|
85
|
+
console.warn(LOG_PREFIX, "Failed to load push subscriptions:", err instanceof Error ? err.message : String(err))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Push Sender ────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export async function sendPushToAll(
|
|
93
|
+
store: PushSubscriptionStore,
|
|
94
|
+
payload: PushPayload,
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
if (!vapidConfigured) return
|
|
97
|
+
|
|
98
|
+
const subscriptions = store.getAll()
|
|
99
|
+
if (subscriptions.length === 0) return
|
|
100
|
+
|
|
101
|
+
const body = JSON.stringify(payload)
|
|
102
|
+
const expiredEndpoints: string[] = []
|
|
103
|
+
|
|
104
|
+
await Promise.allSettled(
|
|
105
|
+
subscriptions.map(async (sub) => {
|
|
106
|
+
try {
|
|
107
|
+
await webpush.sendNotification(
|
|
108
|
+
{ endpoint: sub.endpoint, keys: sub.keys },
|
|
109
|
+
body,
|
|
110
|
+
)
|
|
111
|
+
} catch (err: unknown) {
|
|
112
|
+
const statusCode = err instanceof Error && "statusCode" in err
|
|
113
|
+
? (err as { statusCode: number }).statusCode
|
|
114
|
+
: 0
|
|
115
|
+
if (statusCode === 404 || statusCode === 410) {
|
|
116
|
+
expiredEndpoints.push(sub.endpoint)
|
|
117
|
+
} else {
|
|
118
|
+
console.warn(
|
|
119
|
+
LOG_PREFIX,
|
|
120
|
+
`Push failed for ${sub.endpoint}:`,
|
|
121
|
+
err instanceof Error ? err.message : String(err),
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
// Clean up expired subscriptions
|
|
129
|
+
for (const endpoint of expiredEndpoints) {
|
|
130
|
+
store.remove(endpoint)
|
|
131
|
+
}
|
|
132
|
+
if (expiredEndpoints.length > 0) {
|
|
133
|
+
await store.save()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── HTTP Router ────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
export function createPushRouter(
|
|
140
|
+
store: PushSubscriptionStore,
|
|
141
|
+
): (req: Request) => Promise<Response> {
|
|
142
|
+
return async (req: Request): Promise<Response> => {
|
|
143
|
+
const url = new URL(req.url)
|
|
144
|
+
|
|
145
|
+
if (url.pathname === "/api/push/vapid-key" && req.method === "GET") {
|
|
146
|
+
const key = getVapidPublicKey()
|
|
147
|
+
if (!key) {
|
|
148
|
+
return Response.json({ error: "Push notifications not configured" }, { status: 503 })
|
|
149
|
+
}
|
|
150
|
+
return Response.json({ publicKey: key })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (url.pathname === "/api/push/subscribe" && req.method === "POST") {
|
|
154
|
+
let body: Record<string, unknown>
|
|
155
|
+
try { body = await req.json() } catch {
|
|
156
|
+
return Response.json({ error: "Invalid JSON" }, { status: 400 })
|
|
157
|
+
}
|
|
158
|
+
if (!body.endpoint || typeof body.endpoint !== "string" || !body.keys) {
|
|
159
|
+
return Response.json({ error: "Invalid subscription" }, { status: 400 })
|
|
160
|
+
}
|
|
161
|
+
try { new URL(body.endpoint) } catch {
|
|
162
|
+
return Response.json({ error: "Invalid endpoint URL" }, { status: 400 })
|
|
163
|
+
}
|
|
164
|
+
const keys = body.keys as Record<string, unknown>
|
|
165
|
+
if (typeof keys.p256dh !== "string" || typeof keys.auth !== "string") {
|
|
166
|
+
return Response.json({ error: "Invalid subscription keys" }, { status: 400 })
|
|
167
|
+
}
|
|
168
|
+
store.add({ endpoint: body.endpoint, keys: { p256dh: keys.p256dh, auth: keys.auth } })
|
|
169
|
+
await store.save()
|
|
170
|
+
return Response.json({ ok: true }, { status: 201 })
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (url.pathname === "/api/push/subscribe" && req.method === "DELETE") {
|
|
174
|
+
let body: Record<string, unknown>
|
|
175
|
+
try { body = await req.json() } catch {
|
|
176
|
+
return Response.json({ error: "Invalid JSON" }, { status: 400 })
|
|
177
|
+
}
|
|
178
|
+
if (!body.endpoint || typeof body.endpoint !== "string") {
|
|
179
|
+
return Response.json({ error: "Missing endpoint" }, { status: 400 })
|
|
180
|
+
}
|
|
181
|
+
store.remove(body.endpoint)
|
|
182
|
+
await store.save()
|
|
183
|
+
return Response.json({ ok: true })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return Response.json({ error: "Not found" }, { status: 404 })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { generateTitleForChat } from "./generate-title"
|
|
3
|
+
import { generateForkPromptForChat } from "./generate-fork-context"
|
|
4
|
+
import { QuickResponseAdapter } from "./quick-response"
|
|
5
|
+
|
|
6
|
+
describe("QuickResponseAdapter", () => {
|
|
7
|
+
test("returns the Claude structured result when it validates", async () => {
|
|
8
|
+
const adapter = new QuickResponseAdapter({
|
|
9
|
+
runClaudeStructured: async () => ({ title: "Claude title" }),
|
|
10
|
+
runCodexStructured: async () => ({ title: "Codex title" }),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const result = await adapter.generateStructured({
|
|
14
|
+
cwd: "/tmp/project",
|
|
15
|
+
task: "title generation",
|
|
16
|
+
prompt: "Generate a title",
|
|
17
|
+
schema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
title: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
required: ["title"],
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
},
|
|
25
|
+
parse: (value) => {
|
|
26
|
+
const output = value && typeof value === "object" ? value as { title?: unknown } : {}
|
|
27
|
+
return typeof output.title === "string" ? output.title : null
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(result).toBe("Claude title")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test("falls back to Codex when Claude fails validation", async () => {
|
|
35
|
+
const adapter = new QuickResponseAdapter({
|
|
36
|
+
runClaudeStructured: async () => ({ bad: true }),
|
|
37
|
+
runCodexStructured: async () => ({ title: "Codex title" }),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const result = await adapter.generateStructured({
|
|
41
|
+
cwd: "/tmp/project",
|
|
42
|
+
task: "title generation",
|
|
43
|
+
prompt: "Generate a title",
|
|
44
|
+
schema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
title: { type: "string" },
|
|
48
|
+
},
|
|
49
|
+
required: ["title"],
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
},
|
|
52
|
+
parse: (value) => {
|
|
53
|
+
const output = value && typeof value === "object" ? value as { title?: unknown } : {}
|
|
54
|
+
return typeof output.title === "string" ? output.title : null
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
expect(result).toBe("Codex title")
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test("falls back to Codex when Claude throws", async () => {
|
|
62
|
+
const adapter = new QuickResponseAdapter({
|
|
63
|
+
runClaudeStructured: async () => {
|
|
64
|
+
throw new Error("Not authenticated")
|
|
65
|
+
},
|
|
66
|
+
runCodexStructured: async () => ({ title: "Codex title" }),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const result = await adapter.generateStructured({
|
|
70
|
+
cwd: "/tmp/project",
|
|
71
|
+
task: "title generation",
|
|
72
|
+
prompt: "Generate a title",
|
|
73
|
+
schema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
title: { type: "string" },
|
|
77
|
+
},
|
|
78
|
+
required: ["title"],
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
},
|
|
81
|
+
parse: (value) => {
|
|
82
|
+
const output = value && typeof value === "object" ? value as { title?: unknown } : {}
|
|
83
|
+
return typeof output.title === "string" ? output.title : null
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
expect(result).toBe("Codex title")
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe("generateTitleForChat", () => {
|
|
92
|
+
test("sanitizes generated titles", async () => {
|
|
93
|
+
const title = await generateTitleForChat(
|
|
94
|
+
"hello",
|
|
95
|
+
"/tmp/project",
|
|
96
|
+
new QuickResponseAdapter({
|
|
97
|
+
runClaudeStructured: async () => ({ title: " Example\nTitle " }),
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
expect(title).toBe("Example Title")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("rejects invalid generated titles", async () => {
|
|
105
|
+
const title = await generateTitleForChat(
|
|
106
|
+
"hello",
|
|
107
|
+
"/tmp/project",
|
|
108
|
+
new QuickResponseAdapter({
|
|
109
|
+
runClaudeStructured: async () => ({ title: " " }),
|
|
110
|
+
runCodexStructured: async () => ({ title: "New Chat" }),
|
|
111
|
+
})
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(title).toBeNull()
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe("generateForkPromptForChat", () => {
|
|
119
|
+
test("builds a dedicated-session brief from analyzed intent and compacted context", async () => {
|
|
120
|
+
const prompt = await generateForkPromptForChat(
|
|
121
|
+
"Focus on the auth race fix",
|
|
122
|
+
[],
|
|
123
|
+
"/tmp/project",
|
|
124
|
+
undefined,
|
|
125
|
+
new QuickResponseAdapter({
|
|
126
|
+
runClaudeStructured: async (args) => {
|
|
127
|
+
if (args.task.includes("analysis")) {
|
|
128
|
+
return {
|
|
129
|
+
compactInstruction: "Keep the auth-race evidence only.",
|
|
130
|
+
nextInstruction: "Fix the auth race.",
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
summary: "## Relevant Context\nKeep the existing API.",
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
expect(prompt).toBe([
|
|
141
|
+
"## Objective",
|
|
142
|
+
"Fix the auth race.",
|
|
143
|
+
"",
|
|
144
|
+
"## Relevant Context",
|
|
145
|
+
"## Relevant Context",
|
|
146
|
+
"Keep the existing API.",
|
|
147
|
+
"",
|
|
148
|
+
"## Constraints",
|
|
149
|
+
"Preserve proven constraints from the context above. Call out contradictions or missing evidence before making risky changes.",
|
|
150
|
+
"",
|
|
151
|
+
"## Next Step",
|
|
152
|
+
"Start directly on the objective using the compacted context above.",
|
|
153
|
+
].join("\n"))
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test("falls back to a composed brief when structured output is invalid", async () => {
|
|
157
|
+
const prompt = await generateForkPromptForChat(
|
|
158
|
+
" Continue the mobile keyboard fix ",
|
|
159
|
+
[],
|
|
160
|
+
"/tmp/project",
|
|
161
|
+
undefined,
|
|
162
|
+
new QuickResponseAdapter({
|
|
163
|
+
runClaudeStructured: async () => ({ nope: true }),
|
|
164
|
+
runCodexStructured: async () => ({ prompt: " " }),
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
expect(prompt).toContain("## Objective\nContinue the mobile keyboard fix")
|
|
169
|
+
expect(prompt).toContain("## Relevant Context\nNo prior transcript context was available.")
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test("includes preset guidance in the analysis prompt", async () => {
|
|
173
|
+
let capturedPrompt = ""
|
|
174
|
+
await generateForkPromptForChat(
|
|
175
|
+
"Focus on an alternative design",
|
|
176
|
+
[],
|
|
177
|
+
"/tmp/project",
|
|
178
|
+
"alternative_approach",
|
|
179
|
+
new QuickResponseAdapter({
|
|
180
|
+
runClaudeStructured: async (args) => {
|
|
181
|
+
if (args.task.includes("analysis")) {
|
|
182
|
+
capturedPrompt = args.prompt
|
|
183
|
+
return {
|
|
184
|
+
compactInstruction: "Preserve only the key constraints.",
|
|
185
|
+
nextInstruction: "Explore the alternative.",
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { summary: "## Relevant Context\nAlternative-ready constraints." }
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
expect(capturedPrompt).toContain("Selected preset: Alternative approach.")
|
|
194
|
+
expect(capturedPrompt).toContain("exploring a different solution path")
|
|
195
|
+
})
|
|
196
|
+
})
|