@cloudcli-ai/cloudcli 1.37.2 → 1.37.4
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/README.md +269 -0
- package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-Dmkwx38H.js} +1 -1
- package/dist/assets/{arc-B8jF-ja9.js → arc-DLe9AYuO.js} +1 -1
- package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-CBbbbNUW.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-CaMEaWu4.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-DhjMgNnR.js} +1 -1
- package/dist/assets/channel-CWrmwsir.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-yjl3-F-i.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-DsAYaWEP.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-Bt5qmqOX.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-yWrWVvUt.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-Dt66K5xb.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-DA0vzaj4.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CUkbx96V.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-DWKxx1WH.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-BqluplN0.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-DelKg3Xf.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-COAGCC6w.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-COAGCC6w.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-s1hygY8e.js → cose-bilkent-JH36ORCC-DHV93-Em.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-B9HZgoGX.js} +1 -1
- package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-hFjjEQg8.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-CpeVOC7V.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-D0H1UoRA.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-6Y7BQUup.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-C0nNOwht.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-En6QlOVX.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-CmPiNeGD.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-Bygzp1Qt.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CwScRfzl.js} +1 -1
- package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-bKX-0xP6.js} +1 -1
- package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-DtZPKXGd.js} +1 -1
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-CUOtdL1o.js} +1 -1
- package/dist/assets/index-CdjIFxM2.js +5004 -0
- package/dist/assets/index-DgafhGTF.css +32 -0
- package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-ciRQTS0h.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-CgjhQZ84.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-BsbQSeIW.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-CrwzY9Jw.js} +1 -1
- package/dist/assets/{linear-iPR-J9f7.js → linear-BUaCDr6l.js} +1 -1
- package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-ClgdUU0j.js} +5 -5
- package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-BictljY1.js} +1 -1
- package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-BXdByy_U.js} +1 -1
- package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-uZsMfkRX.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-7lCv4QIj.js} +1 -1
- package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-07dxIWbi.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Cb31Yv1_.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-X0YPSyF9.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-BdBxSIlP.js} +1 -1
- package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
- package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-DQM5xAyD.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-DJ3xkLUF.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-vAaE4zNo.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-rjfYdW0w.js} +2 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-TV2IF4Nq.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-DZMra5do.js} +1 -1
- package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
- package/dist/assets/{vennDiagram-T6HMQDX7-DuF1uZMY.js → vennDiagram-T6HMQDX7-BevD8uu0.js} +1 -1
- package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-DsIyXB7z.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-G3PlF3ko.js} +1 -1
- package/dist/index.html +3 -3
- package/dist-server/server/index.js +11 -4
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +9 -2
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/agent/agent.routes.js +177 -130
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
- package/dist-server/server/modules/agent/tests/agent-run-registration.test.js +306 -0
- package/dist-server/server/modules/agent/tests/agent-run-registration.test.js.map +1 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +48 -2
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
- package/dist-server/server/modules/database/connection.js +1 -1
- package/dist-server/server/modules/database/connection.js.map +1 -1
- package/dist-server/server/modules/database/index.js +5 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +45 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/provider-models.js +60 -6
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -1
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +144 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
- package/dist-server/server/modules/database/schema.js +99 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +99 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.module.js +2 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.service.js +44 -7
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +126 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch-diff.service.js +142 -0
- package/dist-server/server/modules/git/git-branch-diff.service.js.map +1 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -1
- package/dist-server/server/modules/git/git.routes.js +92 -5
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js +160 -0
- package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/project-clone.routes.js +108 -0
- package/dist-server/server/modules/projects/project-clone.routes.js.map +1 -0
- package/dist-server/server/modules/projects/projects.routes.js +13 -66
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-clone-request.service.js +36 -0
- package/dist-server/server/modules/projects/services/project-clone-request.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-clone.service.js +86 -32
- package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
- package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
- package/dist-server/server/modules/projects/tests/project-clone.routes.test.js +191 -0
- package/dist-server/server/modules/projects/tests/project-clone.routes.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js +275 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +3 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +62 -9
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +31 -12
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +472 -53
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +38 -11
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +1007 -60
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +47 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +71 -53
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1621 -431
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +9 -2
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +16 -8
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +274 -9
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +19 -2
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +134 -2
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js +100 -0
- package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js +97 -0
- package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +23 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +59 -5
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +8 -0
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +2 -4
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-history-cache.service.js +91 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +252 -12
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +183 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-background-work.test.js +172 -0
- package/dist-server/server/modules/providers/tests/claude-background-work.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-compaction.test.js +84 -0
- package/dist-server/server/modules/providers/tests/claude-compaction.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-message-model.test.js +123 -0
- package/dist-server/server/modules/providers/tests/claude-message-model.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
- package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js +265 -0
- package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js +1815 -33
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js +228 -0
- package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-models.test.js +25 -0
- package/dist-server/server/modules/providers/tests/codex-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js +83 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +354 -15
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/cursor-auth.test.js +167 -0
- package/dist-server/server/modules/providers/tests/cursor-auth.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +196 -4
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +19 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js +30 -0
- package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +79 -1
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +190 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +139 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +219 -1
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/scheduled-messages/index.js +5 -0
- package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +175 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +290 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
- package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
- package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
- package/dist-server/server/modules/user/user.module.js +10 -1
- package/dist-server/server/modules/user/user.module.js.map +1 -1
- package/dist-server/server/modules/user/user.routes.js +44 -0
- package/dist-server/server/modules/user/user.routes.js.map +1 -1
- package/dist-server/server/modules/user/user.service.js +75 -0
- package/dist-server/server/modules/user/user.service.js.map +1 -1
- package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
- package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +7 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +37 -49
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +204 -10
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +236 -20
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js +137 -0
- package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js +96 -0
- package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +500 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
- package/dist-server/server/shared/claude-cli-path.js +22 -5
- package/dist-server/server/shared/claude-cli-path.js.map +1 -1
- package/dist-server/server/shared/index.js +3 -0
- package/dist-server/server/shared/index.js.map +1 -0
- package/dist-server/server/shared/message-unification.js +376 -0
- package/dist-server/server/shared/message-unification.js.map +1 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
- package/dist-server/server/shared/tests/message-unification.test.js +174 -0
- package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
- package/dist-server/server/shared/tests/read-only-roots.test.js +124 -0
- package/dist-server/server/shared/tests/read-only-roots.test.js.map +1 -0
- package/dist-server/server/shared/tests/strip-ansi-sequences.test.js +31 -0
- package/dist-server/server/shared/tests/strip-ansi-sequences.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +160 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/electron/desktopWindow.js +6 -0
- package/electron/viewHost.js +25 -2
- package/package.json +20 -17
- package/server/index.ts +16 -5
- package/server/modules/agent/agent.module.ts +11 -1
- package/server/modules/agent/agent.routes.ts +200 -142
- package/server/modules/agent/tests/agent-run-registration.test.ts +348 -0
- package/server/modules/agent/tests/agent.routes.test.ts +51 -2
- package/server/modules/browser-use/browser-use.routes.ts +96 -33
- package/server/modules/database/connection.ts +2 -1
- package/server/modules/database/index.ts +10 -0
- package/server/modules/database/migrations.ts +51 -0
- package/server/modules/database/repositories/provider-models.ts +79 -6
- package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
- package/server/modules/database/repositories/session-drafts.db.ts +171 -0
- package/server/modules/database/repositories/sessions.db.ts +204 -3
- package/server/modules/database/repositories/user-preferences.db.ts +80 -0
- package/server/modules/database/schema.ts +103 -0
- package/server/modules/database/tests/provider-models.db.integration.test.ts +113 -0
- package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
- package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
- package/server/modules/file-tree/file-tree.module.ts +2 -1
- package/server/modules/file-tree/file-tree.service.ts +49 -7
- package/server/modules/file-tree/tests/file-tree.service.test.ts +163 -1
- package/server/modules/git/git-branch-diff.service.ts +204 -0
- package/server/modules/git/git-branch.service.ts +2 -12
- package/server/modules/git/git.routes.ts +100 -6
- package/server/modules/git/tests/git-branch-diff.service.test.ts +210 -0
- package/server/modules/projects/project-clone.routes.ts +142 -0
- package/server/modules/projects/projects.routes.ts +16 -81
- package/server/modules/projects/services/project-clone-request.service.ts +56 -0
- package/server/modules/projects/services/project-clone.service.ts +102 -34
- package/server/modules/projects/services/project-delete.service.ts +13 -1
- package/server/modules/projects/services/project-star.service.ts +2 -2
- package/server/modules/projects/tests/project-clone.routes.test.ts +246 -0
- package/server/modules/projects/tests/project-clone.service.test.ts +376 -0
- package/server/modules/providers/index.ts +4 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +81 -11
- package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +37 -17
- package/server/modules/providers/list/claude/claude-runtime.provider.js +510 -54
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +40 -14
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +1195 -63
- package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
- package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +47 -0
- package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +105 -70
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +1847 -455
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +10 -2
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +17 -8
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +297 -9
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +30 -2
- package/server/modules/providers/provider.routes.ts +162 -2
- package/server/modules/providers/services/claude-cli-liveness.service.ts +121 -0
- package/server/modules/providers/services/claude-settings-permissions.service.ts +127 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +40 -1
- package/server/modules/providers/services/provider-models.service.ts +71 -4
- package/server/modules/providers/services/provider-runtime.service.ts +11 -0
- package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
- package/server/modules/providers/services/session-conversations-search.service.ts +2 -5
- package/server/modules/providers/services/session-history-cache.service.ts +150 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
- package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
- package/server/modules/providers/services/sessions.service.ts +309 -17
- package/server/modules/providers/tests/claude-auth.test.ts +208 -0
- package/server/modules/providers/tests/claude-background-work.test.ts +213 -0
- package/server/modules/providers/tests/claude-compaction.test.ts +101 -0
- package/server/modules/providers/tests/claude-message-model.test.ts +160 -0
- package/server/modules/providers/tests/claude-models.test.ts +87 -0
- package/server/modules/providers/tests/claude-runtime-hold.test.ts +309 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +2167 -37
- package/server/modules/providers/tests/claude-settings-permissions.test.ts +271 -0
- package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
- package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
- package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
- package/server/modules/providers/tests/codex-models.test.ts +37 -0
- package/server/modules/providers/tests/codex-runtime.test.ts +94 -0
- package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +396 -15
- package/server/modules/providers/tests/cursor-auth.test.ts +187 -0
- package/server/modules/providers/tests/opencode-models.test.ts +266 -8
- package/server/modules/providers/tests/opencode-sessions.test.ts +40 -1
- package/server/modules/providers/tests/provider-capabilities.service.test.ts +37 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +99 -1
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
- package/server/modules/providers/tests/provider.routes.test.ts +230 -1
- package/server/modules/providers/tests/session-fork.test.ts +173 -0
- package/server/modules/providers/tests/session-history-cache.service.test.ts +165 -0
- package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
- package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
- package/server/modules/providers/tests/sessions.service.test.ts +303 -1
- package/server/modules/providers/tests/skills.test.ts +21 -5
- package/server/modules/scheduled-messages/index.ts +8 -0
- package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
- package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +216 -0
- package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
- package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +354 -0
- package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
- package/server/modules/user/tests/user.service.test.ts +90 -0
- package/server/modules/user/user.module.ts +10 -1
- package/server/modules/user/user.routes.ts +44 -0
- package/server/modules/user/user.service.ts +105 -0
- package/server/modules/voice/tests/voice.service.test.ts +3 -2
- package/server/modules/websocket/README.md +4 -2
- package/server/modules/websocket/index.ts +8 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +45 -54
- package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
- package/server/modules/websocket/services/chat-websocket.service.ts +302 -16
- package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +276 -22
- package/server/modules/websocket/tests/chat-background-subscribe.test.ts +167 -0
- package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
- package/server/modules/websocket/tests/chat-stop-task.test.ts +127 -0
- package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +629 -0
- package/server/shared/claude-cli-path.ts +24 -6
- package/server/shared/index.ts +3 -0
- package/server/shared/interfaces.ts +91 -0
- package/server/shared/message-unification.ts +449 -0
- package/server/shared/tests/claude-cli-path.test.ts +58 -1
- package/server/shared/tests/message-unification.test.ts +218 -0
- package/server/shared/tests/read-only-roots.test.ts +139 -0
- package/server/shared/tests/strip-ansi-sequences.test.ts +42 -0
- package/server/shared/types.ts +382 -7
- package/server/shared/utils.ts +176 -0
- package/dist/assets/channel-_falYbU1.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
- package/dist/assets/index-CzPLBvXT.css +0 -32
- package/dist/assets/index-DFkBBuSA.js +0 -1549
- package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
|
@@ -13,6 +13,15 @@ function createDependencies(overrides: Partial<UserDependencies> = {}): UserDepe
|
|
|
13
13
|
completeOnboarding: () => undefined,
|
|
14
14
|
hasCompletedOnboarding: () => false,
|
|
15
15
|
},
|
|
16
|
+
preferences: {
|
|
17
|
+
getPreferences: () => ({}),
|
|
18
|
+
savePreferences: () => undefined,
|
|
19
|
+
},
|
|
20
|
+
drafts: {
|
|
21
|
+
getDrafts: () => [],
|
|
22
|
+
saveDraft: () => undefined,
|
|
23
|
+
deleteDraft: () => undefined,
|
|
24
|
+
},
|
|
16
25
|
readSystemGitConfig: async () => ({ git_name: null, git_email: null }),
|
|
17
26
|
applyGlobalGitConfig: async () => undefined,
|
|
18
27
|
logInfo: () => undefined,
|
|
@@ -59,3 +68,84 @@ test('updateGitConfig persists valid input and invokes the Git adapter', async (
|
|
|
59
68
|
'git:Alice:alice@example.com',
|
|
60
69
|
]);
|
|
61
70
|
});
|
|
71
|
+
|
|
72
|
+
test('savePreferences forwards only the keys the client sent', () => {
|
|
73
|
+
const saved: Array<Record<string, unknown>> = [];
|
|
74
|
+
const service = createUserService(createDependencies({
|
|
75
|
+
preferences: {
|
|
76
|
+
getPreferences: () => ({ theme: 'dark' }),
|
|
77
|
+
savePreferences: (_id, updates) => saved.push(updates),
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const result = service.savePreferences(3, { theme: 'light' });
|
|
82
|
+
|
|
83
|
+
assert.deepEqual(saved, [{ theme: 'light' }]);
|
|
84
|
+
assert.deepEqual(result.preferences, { theme: 'dark' });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('savePreferences rejects a non-object body', () => {
|
|
88
|
+
const service = createUserService(createDependencies());
|
|
89
|
+
|
|
90
|
+
assert.throws(() => service.savePreferences(3, ['theme']), /object/i);
|
|
91
|
+
assert.throws(() => service.savePreferences(3, 'theme'), /object/i);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('savePreferences rejects an over-long preference key', () => {
|
|
95
|
+
const service = createUserService(createDependencies());
|
|
96
|
+
|
|
97
|
+
assert.throws(() => service.savePreferences(3, { ['k'.repeat(201)]: 1 }), /1-200/);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('saveDraft stores the text and queued message under the given scope', () => {
|
|
101
|
+
const saved: unknown[][] = [];
|
|
102
|
+
const service = createUserService(createDependencies({
|
|
103
|
+
drafts: {
|
|
104
|
+
getDrafts: () => [],
|
|
105
|
+
saveDraft: (...args) => saved.push(args),
|
|
106
|
+
deleteDraft: () => undefined,
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
service.saveDraft(5, 'session-1', {
|
|
111
|
+
scope: 'session-1',
|
|
112
|
+
text: 'half a thought',
|
|
113
|
+
queuedMessage: { content: 'later' },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
assert.deepEqual(saved, [[5, 'session-1', {
|
|
117
|
+
text: 'half a thought',
|
|
118
|
+
queuedMessage: { content: 'later' },
|
|
119
|
+
}]]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('saveDraft defaults a missing queued message to null rather than dropping the row', () => {
|
|
123
|
+
const saved: unknown[][] = [];
|
|
124
|
+
const service = createUserService(createDependencies({
|
|
125
|
+
drafts: {
|
|
126
|
+
getDrafts: () => [],
|
|
127
|
+
saveDraft: (...args) => saved.push(args),
|
|
128
|
+
deleteDraft: () => undefined,
|
|
129
|
+
},
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
service.saveDraft(5, 'session-1', { scope: 'session-1', text: 'typing' });
|
|
133
|
+
|
|
134
|
+
assert.deepEqual(saved, [[5, 'session-1', { text: 'typing', queuedMessage: null }]]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('saveDraft rejects a blank or over-long scope', () => {
|
|
138
|
+
const service = createUserService(createDependencies());
|
|
139
|
+
|
|
140
|
+
assert.throws(() => service.saveDraft(5, ' ', { text: 'x' }), /scope/i);
|
|
141
|
+
assert.throws(() => service.saveDraft(5, 'x'.repeat(201), { text: 'x' }), /scope/i);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('saveDraft rejects text past the storage limit', () => {
|
|
145
|
+
const service = createUserService(createDependencies());
|
|
146
|
+
|
|
147
|
+
assert.throws(
|
|
148
|
+
() => service.saveDraft(5, 'session-1', { text: 'x'.repeat(100_001) }),
|
|
149
|
+
/too long/i,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import spawn from 'cross-spawn';
|
|
2
2
|
|
|
3
|
-
import { userDb } from '@/modules/database/index.js';
|
|
3
|
+
import { sessionDraftsDb, userDb, userPreferencesDb } from '@/modules/database/index.js';
|
|
4
4
|
|
|
5
5
|
import { createUserRouter } from './user.routes.js';
|
|
6
6
|
import { createUserService } from './user.service.js';
|
|
@@ -45,6 +45,15 @@ const userService = createUserService({
|
|
|
45
45
|
completeOnboarding: (userId) => userDb.completeOnboarding(userId),
|
|
46
46
|
hasCompletedOnboarding: (userId) => userDb.hasCompletedOnboarding(userId),
|
|
47
47
|
},
|
|
48
|
+
preferences: {
|
|
49
|
+
getPreferences: (userId) => userPreferencesDb.getPreferences(userId),
|
|
50
|
+
savePreferences: (userId, updates) => userPreferencesDb.savePreferences(userId, updates),
|
|
51
|
+
},
|
|
52
|
+
drafts: {
|
|
53
|
+
getDrafts: (userId) => sessionDraftsDb.getDrafts(userId),
|
|
54
|
+
saveDraft: (userId, scope, draft) => sessionDraftsDb.saveDraft(userId, scope, draft),
|
|
55
|
+
deleteDraft: (userId, scope) => sessionDraftsDb.deleteDraft(userId, scope),
|
|
56
|
+
},
|
|
48
57
|
readSystemGitConfig,
|
|
49
58
|
applyGlobalGitConfig: async (gitName, gitEmail) => {
|
|
50
59
|
await runGit(['config', '--global', 'user.name', gitName]);
|
|
@@ -46,5 +46,49 @@ export function createUserRouter(service: ReturnType<typeof createUserService>):
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
router.get('/preferences', (req, res, next) => {
|
|
50
|
+
try {
|
|
51
|
+
res.json(service.getPreferences(readUserId(req)));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
next(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
router.patch('/preferences', (req, res, next) => {
|
|
58
|
+
try {
|
|
59
|
+
res.json(service.savePreferences(readUserId(req), req.body));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
next(error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
router.get('/drafts', (req, res, next) => {
|
|
66
|
+
try {
|
|
67
|
+
res.json(service.getDrafts(readUserId(req)));
|
|
68
|
+
} catch (error) {
|
|
69
|
+
next(error);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// The scope is a session id or `project:<id>`, so it is read from the body
|
|
74
|
+
// rather than the path: neither form is guaranteed to be URL-path-safe.
|
|
75
|
+
router.put('/drafts', (req, res, next) => {
|
|
76
|
+
try {
|
|
77
|
+
const body = req.body as { scope?: unknown };
|
|
78
|
+
res.json(service.saveDraft(readUserId(req), body?.scope, req.body));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
next(error);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
router.delete('/drafts', (req, res, next) => {
|
|
85
|
+
try {
|
|
86
|
+
const body = req.body as { scope?: unknown };
|
|
87
|
+
res.json(service.deleteDraft(readUserId(req), body?.scope));
|
|
88
|
+
} catch (error) {
|
|
89
|
+
next(error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
49
93
|
return router;
|
|
50
94
|
}
|
|
@@ -5,6 +5,14 @@ type GitConfig = {
|
|
|
5
5
|
git_email: string | null;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
/** One chat scope's unsent composer text and queued message. */
|
|
9
|
+
type DraftRecord = {
|
|
10
|
+
scope: string;
|
|
11
|
+
text: string;
|
|
12
|
+
queuedMessage: unknown | null;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
8
16
|
type UserDependencies = {
|
|
9
17
|
users: {
|
|
10
18
|
getGitConfig(userId: number): GitConfig | undefined;
|
|
@@ -12,12 +20,65 @@ type UserDependencies = {
|
|
|
12
20
|
completeOnboarding(userId: number): void;
|
|
13
21
|
hasCompletedOnboarding(userId: number): boolean;
|
|
14
22
|
};
|
|
23
|
+
preferences: {
|
|
24
|
+
getPreferences(userId: number): Record<string, unknown>;
|
|
25
|
+
savePreferences(userId: number, updates: Record<string, unknown>): void;
|
|
26
|
+
};
|
|
27
|
+
drafts: {
|
|
28
|
+
getDrafts(userId: number): DraftRecord[];
|
|
29
|
+
saveDraft(userId: number, scope: string, draft: { text: string; queuedMessage: unknown | null }): void;
|
|
30
|
+
deleteDraft(userId: number, scope: string): void;
|
|
31
|
+
};
|
|
15
32
|
readSystemGitConfig(): Promise<GitConfig>;
|
|
16
33
|
applyGlobalGitConfig(gitName: string, gitEmail: string): Promise<void>;
|
|
17
34
|
logInfo(message: string): void;
|
|
18
35
|
logError(message: string, error: unknown): void;
|
|
19
36
|
};
|
|
20
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The longest a draft scope or preference key may be.
|
|
40
|
+
*
|
|
41
|
+
* Scopes are session UUIDs or `project:<id>`, and preference keys are fixed
|
|
42
|
+
* identifiers, so anything longer is a client bug or an attempt to use the
|
|
43
|
+
* table as general storage.
|
|
44
|
+
*/
|
|
45
|
+
const MAX_KEY_LENGTH = 200;
|
|
46
|
+
|
|
47
|
+
/** Guards against a runaway composer filling the database with one row. */
|
|
48
|
+
const MAX_DRAFT_TEXT_LENGTH = 100_000;
|
|
49
|
+
|
|
50
|
+
const readDraftScope = (value: unknown): string => {
|
|
51
|
+
const scope = typeof value === 'string' ? value.trim() : '';
|
|
52
|
+
if (!scope || scope.length > MAX_KEY_LENGTH) {
|
|
53
|
+
throw new AppError('A draft scope of 1-200 characters is required', {
|
|
54
|
+
code: 'INVALID_DRAFT_SCOPE',
|
|
55
|
+
statusCode: 400,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return scope;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const readPreferenceUpdates = (body: unknown): Record<string, unknown> => {
|
|
62
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
63
|
+
throw new AppError('Preferences must be sent as an object', {
|
|
64
|
+
code: 'INVALID_PREFERENCES',
|
|
65
|
+
statusCode: 400,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const updates = body as Record<string, unknown>;
|
|
70
|
+
for (const key of Object.keys(updates)) {
|
|
71
|
+
if (!key || key.length > MAX_KEY_LENGTH) {
|
|
72
|
+
throw new AppError('Preference keys must be 1-200 characters', {
|
|
73
|
+
code: 'INVALID_PREFERENCE_KEY',
|
|
74
|
+
statusCode: 400,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return updates;
|
|
80
|
+
};
|
|
81
|
+
|
|
21
82
|
/** Creates user-profile workflows with explicit repository and Git adapters. */
|
|
22
83
|
export function createUserService(dependencies: UserDependencies) {
|
|
23
84
|
return {
|
|
@@ -81,5 +142,49 @@ export function createUserService(dependencies: UserDependencies) {
|
|
|
81
142
|
hasCompletedOnboarding: dependencies.users.hasCompletedOnboarding(userId),
|
|
82
143
|
};
|
|
83
144
|
},
|
|
145
|
+
|
|
146
|
+
/** Every stored preference at once; the client fills gaps with its defaults. */
|
|
147
|
+
getPreferences(userId: number) {
|
|
148
|
+
return { success: true, preferences: dependencies.preferences.getPreferences(userId) };
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Merge-patch: only the keys in the request body are touched, so two
|
|
153
|
+
* independent settings screens can save concurrently without one erasing
|
|
154
|
+
* the other's work.
|
|
155
|
+
*/
|
|
156
|
+
savePreferences(userId: number, body: unknown) {
|
|
157
|
+
const updates = readPreferenceUpdates(body);
|
|
158
|
+
dependencies.preferences.savePreferences(userId, updates);
|
|
159
|
+
return { success: true, preferences: dependencies.preferences.getPreferences(userId) };
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
getDrafts(userId: number) {
|
|
163
|
+
return { success: true, drafts: dependencies.drafts.getDrafts(userId) };
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
saveDraft(userId: number, scopeInput: unknown, body: unknown) {
|
|
167
|
+
const scope = readDraftScope(scopeInput);
|
|
168
|
+
const payload = (body ?? {}) as { text?: unknown; queuedMessage?: unknown };
|
|
169
|
+
const text = typeof payload.text === 'string' ? payload.text : '';
|
|
170
|
+
|
|
171
|
+
if (text.length > MAX_DRAFT_TEXT_LENGTH) {
|
|
172
|
+
throw new AppError('Draft text is too long to store', {
|
|
173
|
+
code: 'DRAFT_TEXT_TOO_LONG',
|
|
174
|
+
statusCode: 413,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
dependencies.drafts.saveDraft(userId, scope, {
|
|
179
|
+
text,
|
|
180
|
+
queuedMessage: payload.queuedMessage ?? null,
|
|
181
|
+
});
|
|
182
|
+
return { success: true };
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
deleteDraft(userId: number, scopeInput: unknown) {
|
|
186
|
+
dependencies.drafts.deleteDraft(userId, readDraftScope(scopeInput));
|
|
187
|
+
return { success: true };
|
|
188
|
+
},
|
|
84
189
|
};
|
|
85
190
|
}
|
|
@@ -47,8 +47,9 @@ test('transcribes with injected fetch and request-level credential/model overrid
|
|
|
47
47
|
|
|
48
48
|
assert.deepEqual(result, { ok: true, value: { text: 'hello' } });
|
|
49
49
|
assert.equal(requestedUrl, 'https://voice.example/v1/audio/transcriptions');
|
|
50
|
-
assert.
|
|
51
|
-
assert.equal((requestedOptions
|
|
50
|
+
assert.ok(requestedOptions, 'fetchBackend should receive request options');
|
|
51
|
+
assert.equal((requestedOptions.headers as Record<string, string>).Authorization, 'Bearer request-key');
|
|
52
|
+
assert.equal((requestedOptions.body as FormData).get('model'), 'custom-whisper');
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
test('forwards the explicit TTS format and maps backend authentication failures', async () => {
|
|
@@ -33,7 +33,7 @@ Benefits:
|
|
|
33
33
|
|---|---|
|
|
34
34
|
| `services/websocket-server.service.ts` | Creates `WebSocketServer`, binds `verifyClient`, routes connection by pathname |
|
|
35
35
|
| `services/websocket-auth.service.ts` | Authenticates upgrade requests and attaches `request.user` |
|
|
36
|
-
| `services/chat-websocket.service.ts` | Handles the `/ws` chat protocol (`chat.send` / `chat.abort` / `chat.subscribe` / `chat.permission-response`) |
|
|
36
|
+
| `services/chat-websocket.service.ts` | Handles the `/ws` chat protocol (`chat.send` / `chat.abort` / `chat.stop-task` / `chat.subscribe` / `chat.permission-response`) |
|
|
37
37
|
| `services/chat-run-registry.service.ts` | Tracks live provider runs per app session id: seq numbering, event replay buffer, provider-id mapping, completion state |
|
|
38
38
|
| `services/chat-session-writer.service.ts` | Gateway writer handed to provider runtimes: remaps provider session ids to app ids, swallows `session_created`, assigns `seq` |
|
|
39
39
|
| `services/shell-websocket.service.ts` | Handles `/shell` PTY lifecycle, reconnect buffering, auth URL detection |
|
|
@@ -108,7 +108,7 @@ When a chat socket connects:
|
|
|
108
108
|
|
|
109
109
|
1. Add socket to `connectedClients`.
|
|
110
110
|
2. Parse each incoming message with `parseIncomingJsonObject`.
|
|
111
|
-
3. Dispatch by `data.type` (
|
|
111
|
+
3. Dispatch by `data.type` (six message types, none provider-specific).
|
|
112
112
|
4. On close, remove socket from `connectedClients`.
|
|
113
113
|
|
|
114
114
|
### Session identity model
|
|
@@ -130,7 +130,9 @@ flowchart TD
|
|
|
130
130
|
B -->|ok| D{data.type}
|
|
131
131
|
|
|
132
132
|
D -->|chat.send| E[resolve session row -> startRun -> providerRuntimeService.run]
|
|
133
|
+
D -->|chat.edit-send| E2[history_truncated -> rewind or resume at the anchor -> same as chat.send]
|
|
133
134
|
D -->|chat.abort| F[providerRuntimeService.abort + synthetic complete]
|
|
135
|
+
D -->|chat.stop-task| F2[providerRuntimeService.stopBackgroundTask]
|
|
134
136
|
D -->|chat.subscribe| G[chat_subscribed ack + attach socket + replay events seq > lastSeq]
|
|
135
137
|
D -->|chat.permission-response| H[providerRuntimeService.resolveToolApproval]
|
|
136
138
|
D -->|other| I[send kind:protocol_error]
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export { WS_OPEN_STATE, connectedClients } from './services/websocket-state.service.js';
|
|
2
2
|
export { createWebSocketServer } from './services/websocket-server.service.js';
|
|
3
3
|
export { chatRunRegistry } from './services/chat-run-registry.service.js';
|
|
4
|
+
// Consumed by the providers module's sessions watcher, which announces the
|
|
5
|
+
// sessions it (re)indexed from disk through the same builder the chat gateway
|
|
6
|
+
// uses, so both paths put the identical delta on the wire.
|
|
7
|
+
export { broadcastSessionUpserted, broadcastSessionUpsertedBatch } from './services/session-upsert-broadcast.service.js';
|
|
8
|
+
// runDetachedChatTurn: used by the scheduled-messages module to run a turn
|
|
9
|
+
// from a timer, with no socket to stream to or report errors on.
|
|
10
|
+
export { runDetachedChatTurn } from './services/chat-websocket.service.js';
|
|
11
|
+
export type { ProviderRuntimeGateway } from './services/chat-websocket.service.js';
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { projectsDb, sessionsDb } from '@/modules/database/index.js';
|
|
4
|
-
import { generateDisplayName } from '@/modules/projects/index.js';
|
|
1
|
+
import { sessionsDb } from '@/modules/database/index.js';
|
|
5
2
|
import { ChatSessionWriter } from '@/modules/websocket/services/chat-session-writer.service.js';
|
|
6
|
-
import {
|
|
3
|
+
import { broadcastSessionUpserted } from '@/modules/websocket/services/session-upsert-broadcast.service.js';
|
|
7
4
|
import type {
|
|
8
5
|
LLMProvider,
|
|
9
6
|
NormalizedMessage,
|
|
@@ -62,54 +59,31 @@ const MAX_BUFFERED_EVENTS_PER_RUN = 5000;
|
|
|
62
59
|
*/
|
|
63
60
|
const runs = new Map<string, ChatRun>();
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const displayName = project?.custom_project_name?.trim()
|
|
74
|
-
? project.custom_project_name
|
|
75
|
-
: await generateDisplayName(path.basename(projectPath ?? '') || (projectPath ?? ''), projectPath);
|
|
76
|
-
|
|
77
|
-
const payload = JSON.stringify({
|
|
78
|
-
kind: 'session_upserted',
|
|
79
|
-
sessionId: row.session_id,
|
|
80
|
-
providerSessionId: row.provider_session_id,
|
|
81
|
-
provider: row.provider,
|
|
82
|
-
session: {
|
|
83
|
-
id: row.session_id,
|
|
84
|
-
summary: row.custom_name || '',
|
|
85
|
-
messageCount: 0,
|
|
86
|
-
lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
|
|
87
|
-
},
|
|
88
|
-
project: project
|
|
89
|
-
? {
|
|
90
|
-
projectId: project.project_id,
|
|
91
|
-
path: project.project_path,
|
|
92
|
-
fullPath: project.project_path,
|
|
93
|
-
displayName,
|
|
94
|
-
isStarred: Boolean(project.isStarred),
|
|
95
|
-
}
|
|
96
|
-
: null,
|
|
97
|
-
timestamp: new Date().toISOString(),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
connectedClients.forEach((client) => {
|
|
101
|
-
if (client.readyState === WS_OPEN_STATE) {
|
|
102
|
-
client.send(payload);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
}
|
|
62
|
+
/**
|
|
63
|
+
* Answers whether a completed run must stay registered a while longer. Set by
|
|
64
|
+
* the composition root to the provider runtimes' background-work check: a
|
|
65
|
+
* session whose turn ended but whose agents, workflows or commands are still
|
|
66
|
+
* running keeps sending live events through this run's writer, and a tab that
|
|
67
|
+
* subscribes meanwhile needs the run to attach to.
|
|
68
|
+
*/
|
|
69
|
+
let retainCompletedRun: (appSessionId: string) => boolean = () => false;
|
|
106
70
|
|
|
107
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Schedules one run's eviction. The timer is bound to the run it was armed
|
|
73
|
+
* for: a later run can take the session's slot while this one's retention —
|
|
74
|
+
* re-armed for as long as the guard holds — is still pending, and firing on
|
|
75
|
+
* the slot alone would evict that newer run early.
|
|
76
|
+
*/
|
|
77
|
+
function evictRunLater(run: ChatRun): void {
|
|
108
78
|
const timer = setTimeout(() => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
79
|
+
if (runs.get(run.appSessionId) !== run || run.status !== 'completed') {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (retainCompletedRun(run.appSessionId)) {
|
|
83
|
+
evictRunLater(run);
|
|
84
|
+
return;
|
|
112
85
|
}
|
|
86
|
+
runs.delete(run.appSessionId);
|
|
113
87
|
}, COMPLETED_RUN_RETENTION_MS);
|
|
114
88
|
|
|
115
89
|
// Never keep the process alive just to evict a buffered run.
|
|
@@ -149,7 +123,7 @@ function decorateAndRecordEvent(run: ChatRun, message: NormalizedMessage): Norma
|
|
|
149
123
|
outbound.actualSessionId = run.appSessionId;
|
|
150
124
|
run.status = 'completed';
|
|
151
125
|
run.completedAt = Date.now();
|
|
152
|
-
evictRunLater(run
|
|
126
|
+
evictRunLater(run);
|
|
153
127
|
}
|
|
154
128
|
|
|
155
129
|
run.events.push(outbound);
|
|
@@ -178,7 +152,7 @@ function recordProviderSessionId(run: ChatRun, providerSessionId: string): void
|
|
|
178
152
|
|
|
179
153
|
try {
|
|
180
154
|
sessionsDb.assignProviderSessionId(run.appSessionId, providerSessionId);
|
|
181
|
-
void
|
|
155
|
+
void broadcastSessionUpserted(run.appSessionId).catch((error) => {
|
|
182
156
|
const message = error instanceof Error ? error.message : String(error);
|
|
183
157
|
console.error('[ChatRunRegistry] Failed to broadcast canonical session mapping', {
|
|
184
158
|
appSessionId: run.appSessionId,
|
|
@@ -205,6 +179,11 @@ function recordProviderSessionId(run: ChatRun, providerSessionId: string): void
|
|
|
205
179
|
* regardless of which provider runtime produced them.
|
|
206
180
|
*/
|
|
207
181
|
export const chatRunRegistry = {
|
|
182
|
+
/** Installs the check that keeps a completed run registered while its session still has background work. */
|
|
183
|
+
setRetentionGuard(guard: (appSessionId: string) => boolean): void {
|
|
184
|
+
retainCompletedRun = guard;
|
|
185
|
+
},
|
|
186
|
+
|
|
208
187
|
/**
|
|
209
188
|
* Starts tracking a run and returns it, or `null` when a run is already in
|
|
210
189
|
* progress for the session (callers must reject the duplicate send).
|
|
@@ -213,7 +192,13 @@ export const chatRunRegistry = {
|
|
|
213
192
|
appSessionId: string;
|
|
214
193
|
provider: LLMProvider;
|
|
215
194
|
providerSessionId: string | null;
|
|
216
|
-
|
|
195
|
+
/**
|
|
196
|
+
* The socket that asked for this run, or `null` for one nobody is watching
|
|
197
|
+
* — a scheduled message fires with no browser attached. The writer's event
|
|
198
|
+
* buffer still records everything, so a client that subscribes later
|
|
199
|
+
* replays the run from its start.
|
|
200
|
+
*/
|
|
201
|
+
connection: RealtimeClientConnection | null;
|
|
217
202
|
userId: string | number | null;
|
|
218
203
|
}): ChatRun | null {
|
|
219
204
|
const existing = runs.get(input.appSessionId);
|
|
@@ -273,11 +258,17 @@ export const chatRunRegistry = {
|
|
|
273
258
|
},
|
|
274
259
|
|
|
275
260
|
/**
|
|
276
|
-
*
|
|
261
|
+
* Adds a websocket connection to a run's live audience.
|
|
277
262
|
*
|
|
278
263
|
* This is the generic replacement for the Claude-only writer reconnect:
|
|
279
264
|
* after a page refresh the new socket subscribes and immediately starts
|
|
280
265
|
* receiving the still-running stream, for every provider.
|
|
266
|
+
*
|
|
267
|
+
* Subscribing does not take the stream away from sockets that were already
|
|
268
|
+
* watching — a session open in two places stays live in both, and the
|
|
269
|
+
* refreshed tab's abandoned socket is dropped when the next event finds it
|
|
270
|
+
* closed. Replay stays per-connection because each client sends its own
|
|
271
|
+
* `lastSeq` with `chat.subscribe`.
|
|
281
272
|
*/
|
|
282
273
|
attachConnection(appSessionId: string, connection: RealtimeClientConnection): boolean {
|
|
283
274
|
const run = runs.get(appSessionId);
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
import { createCompleteMessage, readObjectRecord } from '@/shared/utils.js';
|
|
8
8
|
|
|
9
9
|
type ChatSessionWriterOptions = {
|
|
10
|
-
connection: RealtimeClientConnection;
|
|
10
|
+
connection: RealtimeClientConnection | null;
|
|
11
11
|
userId: string | number | null;
|
|
12
12
|
provider: LLMProvider;
|
|
13
13
|
/** Provider-native id when resuming an existing session, otherwise null. */
|
|
@@ -44,7 +44,6 @@ type ChatSessionWriterOptions = {
|
|
|
44
44
|
* intercepted and recorded as the provider-id mapping as well.
|
|
45
45
|
*/
|
|
46
46
|
export class ChatSessionWriter {
|
|
47
|
-
ws: RealtimeClientConnection;
|
|
48
47
|
userId: string | number | null;
|
|
49
48
|
/**
|
|
50
49
|
* Some runtimes feature-detect their writer with this flag; keep it so the
|
|
@@ -53,6 +52,17 @@ export class ChatSessionWriter {
|
|
|
53
52
|
isWebSocketWriter = true;
|
|
54
53
|
|
|
55
54
|
private readonly options: ChatSessionWriterOptions;
|
|
55
|
+
/**
|
|
56
|
+
* Every socket currently watching this run, not just the one that started
|
|
57
|
+
* it. A session can legitimately be open in more than one place — a second
|
|
58
|
+
* browser tab, a phone alongside a laptop, the desktop app beside the web
|
|
59
|
+
* app — and each of those subscribes through `attachConnection`.
|
|
60
|
+
*
|
|
61
|
+
* Holding a single connection here meant the newest subscriber took the
|
|
62
|
+
* stream away from everyone before it, so a second tab silently froze
|
|
63
|
+
* mid-run and only caught up on the next REST history refresh.
|
|
64
|
+
*/
|
|
65
|
+
private readonly connections = new Set<RealtimeClientConnection>();
|
|
56
66
|
/**
|
|
57
67
|
* The provider-native session id as the runtime knows it. Kept locally
|
|
58
68
|
* (besides the registry) because runtimes read it back via `getSessionId()`
|
|
@@ -63,7 +73,12 @@ export class ChatSessionWriter {
|
|
|
63
73
|
|
|
64
74
|
constructor(options: ChatSessionWriterOptions) {
|
|
65
75
|
this.options = options;
|
|
66
|
-
|
|
76
|
+
// A run started by a timer has no audience yet. Everything still goes
|
|
77
|
+
// through `decorateOutboundEvent` into the run's replay buffer, so a
|
|
78
|
+
// client that subscribes mid-run catches up from the beginning.
|
|
79
|
+
if (options.connection) {
|
|
80
|
+
this.connections.add(options.connection);
|
|
81
|
+
}
|
|
67
82
|
this.userId = options.userId;
|
|
68
83
|
this.providerSessionId = options.providerSessionId;
|
|
69
84
|
}
|
|
@@ -116,8 +131,13 @@ export class ChatSessionWriter {
|
|
|
116
131
|
}
|
|
117
132
|
}
|
|
118
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Adds a socket to the run's live audience. Named for the `WebSocketWriter`
|
|
136
|
+
* method it stands in for, so runtime adapters keep working unchanged —
|
|
137
|
+
* but it adds rather than replaces.
|
|
138
|
+
*/
|
|
119
139
|
updateWebSocket(newConnection: RealtimeClientConnection): void {
|
|
120
|
-
this.
|
|
140
|
+
this.connections.add(newConnection);
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
setSessionId(sessionId: string): void {
|
|
@@ -138,8 +158,18 @@ export class ChatSessionWriter {
|
|
|
138
158
|
}
|
|
139
159
|
|
|
140
160
|
private forward(message: NormalizedMessage): void {
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
const payload = JSON.stringify(message);
|
|
162
|
+
|
|
163
|
+
// Sending is also when dead sockets get collected: a refreshed tab leaves
|
|
164
|
+
// its old connection behind, and without dropping it here the set would
|
|
165
|
+
// grow for the whole life of the run. Deleting during iteration is safe
|
|
166
|
+
// for a Set — the removed entry is simply not revisited.
|
|
167
|
+
for (const connection of this.connections) {
|
|
168
|
+
if (connection.readyState === WS_OPEN_STATE) {
|
|
169
|
+
connection.send(payload);
|
|
170
|
+
} else {
|
|
171
|
+
this.connections.delete(connection);
|
|
172
|
+
}
|
|
143
173
|
}
|
|
144
174
|
}
|
|
145
175
|
}
|