@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
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import pty, { type IPty } from 'node-pty';
|
|
6
6
|
import { WebSocket, type RawData } from 'ws';
|
|
7
7
|
|
|
8
|
-
import { parseIncomingJsonObject } from '@/shared/utils.js';
|
|
8
|
+
import { parseIncomingJsonObject, stripAnsiSequences } from '@/shared/utils.js';
|
|
9
9
|
|
|
10
10
|
type ShellIncomingMessage = {
|
|
11
11
|
type?: string;
|
|
@@ -19,8 +19,12 @@ type ShellIncomingMessage = {
|
|
|
19
19
|
initialCommand?: string;
|
|
20
20
|
isPlainShell?: boolean;
|
|
21
21
|
forceRestart?: boolean;
|
|
22
|
+
bypassPermissions?: boolean;
|
|
23
|
+
colorScheme?: string;
|
|
22
24
|
};
|
|
23
25
|
|
|
26
|
+
type ShellColorScheme = 'light' | 'dark';
|
|
27
|
+
|
|
24
28
|
type PtySessionEntry = {
|
|
25
29
|
pty: IPty;
|
|
26
30
|
ws: WebSocket | null;
|
|
@@ -28,18 +32,17 @@ type PtySessionEntry = {
|
|
|
28
32
|
timeoutId: NodeJS.Timeout | null;
|
|
29
33
|
projectPath: string;
|
|
30
34
|
sessionId: string | null;
|
|
35
|
+
// The app theme a Claude CLI in this pty was launched for; null for any other
|
|
36
|
+
// program. A running CLI keeps its launch-time colours, so a reattaching
|
|
37
|
+
// client needs this, not its own current theme, to know if they differ.
|
|
38
|
+
claudeColorScheme: ShellColorScheme | null;
|
|
31
39
|
};
|
|
32
40
|
|
|
33
41
|
const ptySessionsMap = new Map<string, PtySessionEntry>();
|
|
34
42
|
const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
|
|
35
43
|
const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
|
|
36
|
-
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
|
|
37
44
|
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
|
|
38
45
|
|
|
39
|
-
function stripAnsiSequences(value: string): string {
|
|
40
|
-
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
46
|
function normalizeDetectedUrl(url: string): string | null {
|
|
44
47
|
const cleanedUrl = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
|
|
45
48
|
if (!cleanedUrl) {
|
|
@@ -89,6 +92,17 @@ function extractUrlsFromText(value: string): string[] {
|
|
|
89
92
|
return Array.from(new Set([...directMatches, ...wrappedMatches]));
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Tells the client which app theme the Claude CLI in its pty was launched for,
|
|
97
|
+
* so the Shell can suggest a restart once the app theme no longer matches it.
|
|
98
|
+
* Clients that predate this frame ignore unknown frame types.
|
|
99
|
+
*/
|
|
100
|
+
function sendClaudeColorScheme(ws: WebSocket, colorScheme: ShellColorScheme | null): void {
|
|
101
|
+
if (colorScheme && ws.readyState === WebSocket.OPEN) {
|
|
102
|
+
ws.send(JSON.stringify({ type: 'claude_theme', colorScheme }));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
92
106
|
function shouldAutoOpenUrlFromOutput(value: string): boolean {
|
|
93
107
|
const normalizedOutput = value.toLowerCase();
|
|
94
108
|
return (
|
|
@@ -129,6 +143,14 @@ function readNumber(value: unknown, fallback: number): number {
|
|
|
129
143
|
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
130
144
|
}
|
|
131
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Reads the app theme the Shell tab is painted in. Older clients send none and
|
|
148
|
+
* anything unexpected is ignored, which keeps the launch exactly as before.
|
|
149
|
+
*/
|
|
150
|
+
function readColorScheme(value: unknown): ShellColorScheme | null {
|
|
151
|
+
return value === 'light' || value === 'dark' ? value : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
132
154
|
/**
|
|
133
155
|
* Parses incoming websocket shell messages and keeps processing safe when
|
|
134
156
|
* malformed payloads are received.
|
|
@@ -172,13 +194,23 @@ function resolveResumeSessionId(
|
|
|
172
194
|
return resolvedSessionId;
|
|
173
195
|
}
|
|
174
196
|
|
|
197
|
+
type ShellLaunch = {
|
|
198
|
+
command: string;
|
|
199
|
+
/**
|
|
200
|
+
* Whether the command starts a Claude CLI that paints in the app's colour
|
|
201
|
+
* scheme, so a later theme toggle can be applied by restarting it.
|
|
202
|
+
*/
|
|
203
|
+
claudeFollowsAppTheme: boolean;
|
|
204
|
+
};
|
|
205
|
+
|
|
175
206
|
/**
|
|
176
207
|
* Resolves provider command line for plain shell and agent-backed shell modes.
|
|
177
208
|
*/
|
|
178
209
|
function buildShellCommand(
|
|
179
210
|
message: ShellIncomingMessage,
|
|
180
|
-
dependencies: ShellWebSocketDependencies
|
|
181
|
-
|
|
211
|
+
dependencies: ShellWebSocketDependencies,
|
|
212
|
+
projectPath: string
|
|
213
|
+
): ShellLaunch {
|
|
182
214
|
const hasSession = readBoolean(message.hasSession);
|
|
183
215
|
const initialCommand = readString(message.initialCommand);
|
|
184
216
|
const provider = readString(message.provider, 'claude');
|
|
@@ -188,42 +220,251 @@ function buildShellCommand(
|
|
|
188
220
|
(!!initialCommand && !hasSession) ||
|
|
189
221
|
provider === 'plain-shell';
|
|
190
222
|
|
|
223
|
+
const otherProgram = (command: string): ShellLaunch => ({ command, claudeFollowsAppTheme: false });
|
|
224
|
+
|
|
191
225
|
if (isPlainShell) {
|
|
192
|
-
return initialCommand;
|
|
226
|
+
return otherProgram(initialCommand);
|
|
193
227
|
}
|
|
194
228
|
|
|
195
229
|
if (provider === 'cursor') {
|
|
196
230
|
if (resumeSessionId) {
|
|
197
|
-
return `cursor-agent --resume="${resumeSessionId}"
|
|
231
|
+
return otherProgram(`cursor-agent --resume="${resumeSessionId}"`);
|
|
198
232
|
}
|
|
199
|
-
return 'cursor-agent';
|
|
233
|
+
return otherProgram('cursor-agent');
|
|
200
234
|
}
|
|
201
235
|
|
|
202
236
|
if (provider === 'codex') {
|
|
203
237
|
if (resumeSessionId) {
|
|
204
238
|
if (os.platform() === 'win32') {
|
|
205
|
-
return `codex resume "${resumeSessionId}"; if ($LASTEXITCODE -ne 0) { codex }
|
|
239
|
+
return otherProgram(`codex resume "${resumeSessionId}"; if ($LASTEXITCODE -ne 0) { codex }`);
|
|
206
240
|
}
|
|
207
|
-
return `codex resume "${resumeSessionId}" || codex
|
|
241
|
+
return otherProgram(`codex resume "${resumeSessionId}" || codex`);
|
|
208
242
|
}
|
|
209
|
-
return 'codex';
|
|
243
|
+
return otherProgram('codex');
|
|
210
244
|
}
|
|
211
245
|
|
|
212
246
|
if (provider === 'opencode') {
|
|
213
247
|
if (resumeSessionId) {
|
|
214
|
-
return `opencode --session "${resumeSessionId}"
|
|
248
|
+
return otherProgram(`opencode --session "${resumeSessionId}"`);
|
|
215
249
|
}
|
|
216
|
-
return initialCommand || 'opencode';
|
|
250
|
+
return otherProgram(initialCommand || 'opencode');
|
|
217
251
|
}
|
|
218
252
|
|
|
219
|
-
|
|
253
|
+
// Launching with the flag is what unlocks "bypass permissions" in the CLI's
|
|
254
|
+
// shift+tab permission-mode cycle; it cannot be enabled from inside a
|
|
255
|
+
// session started without it.
|
|
256
|
+
const bypassFlag = readBoolean(message.bypassPermissions)
|
|
257
|
+
? ' --dangerously-skip-permissions'
|
|
258
|
+
: '';
|
|
259
|
+
const theme = buildClaudeThemeLaunch(readColorScheme(message.colorScheme), projectPath);
|
|
260
|
+
const launchFlags = `${bypassFlag}${theme.flag}`;
|
|
220
261
|
if (resumeSessionId) {
|
|
221
|
-
|
|
222
|
-
|
|
262
|
+
const command = os.platform() === 'win32'
|
|
263
|
+
? `claude --resume "${resumeSessionId}"${launchFlags}; if ($LASTEXITCODE -ne 0) { claude${launchFlags} }`
|
|
264
|
+
: `claude --resume "${resumeSessionId}"${launchFlags} || claude${launchFlags}`;
|
|
265
|
+
return { command, claudeFollowsAppTheme: theme.followsAppTheme };
|
|
266
|
+
}
|
|
267
|
+
if (initialCommand) {
|
|
268
|
+
// A caller's own command (e.g. `claude /login`) is launched as given.
|
|
269
|
+
return otherProgram(initialCommand);
|
|
270
|
+
}
|
|
271
|
+
return { command: `claude${launchFlags}`, claudeFollowsAppTheme: theme.followsAppTheme };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Claude Code's built-in theme ids (the `/theme` picker of CLI 2.1.280). A
|
|
275
|
+
// `custom:<slug>` theme is valid too; any other value is ignored by the CLI.
|
|
276
|
+
const CLAUDE_THEME_IDS = new Set([
|
|
277
|
+
'auto',
|
|
278
|
+
'dark',
|
|
279
|
+
'light',
|
|
280
|
+
'dark-daltonized',
|
|
281
|
+
'light-daltonized',
|
|
282
|
+
'dark-ansi',
|
|
283
|
+
'light-ansi',
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
// Dark themes and their light counterparts. A light theme, `auto` (it asks the
|
|
287
|
+
// terminal for its background, which xterm answers from the live theme) or a
|
|
288
|
+
// custom theme is the user's own choice and is left alone.
|
|
289
|
+
const LIGHT_CLAUDE_THEME_BY_DARK_THEME = new Map([
|
|
290
|
+
['dark', 'light'],
|
|
291
|
+
['dark-daltonized', 'light-daltonized'],
|
|
292
|
+
['dark-ansi', 'light-ansi'],
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
// The CLI refuses settings files over 2 MiB, so a bigger one holds no theme it
|
|
296
|
+
// would use. The legacy global config (`~/.claude.json`) is not a settings file
|
|
297
|
+
// and can grow to several MB; its cap is only there so a huge file is never
|
|
298
|
+
// pulled into the server's memory.
|
|
299
|
+
const MAX_CLAUDE_SETTINGS_FILE_BYTES = 2 * 1024 * 1024;
|
|
300
|
+
const MAX_CLAUDE_GLOBAL_CONFIG_FILE_BYTES = 64 * 1024 * 1024;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Reads a config file only when it is a regular, non-empty file of sane size,
|
|
304
|
+
* else returns null. The project can be a cloned repository whose
|
|
305
|
+
* `.claude/settings.local.json` is a symlink to a FIFO or to a device such as
|
|
306
|
+
* /dev/zero: reading one would stall the server's event loop or exhaust its
|
|
307
|
+
* memory. `statSync` follows the symlink without opening the target. Empty
|
|
308
|
+
* files are skipped too: they hold no theme, and kernel pseudo-files (procfs,
|
|
309
|
+
* tracefs) report a size of 0 even when a read would block.
|
|
310
|
+
*/
|
|
311
|
+
function readRegularConfigFile(filePath: string, maxBytes = MAX_CLAUDE_SETTINGS_FILE_BYTES): string | null {
|
|
312
|
+
try {
|
|
313
|
+
const stats = fs.statSync(filePath);
|
|
314
|
+
if (!stats.isFile() || stats.size === 0 || stats.size > maxBytes) {
|
|
315
|
+
return null;
|
|
223
316
|
}
|
|
224
|
-
return
|
|
317
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
225
320
|
}
|
|
226
|
-
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Reads the `theme` key of one Claude config file. A missing, unreadable,
|
|
325
|
+
* special (FIFO, device) or unparsable file, or a value the CLI would reject,
|
|
326
|
+
* counts as unset.
|
|
327
|
+
*/
|
|
328
|
+
function readClaudeThemeFile(filePath: string, maxBytes: number): string | null {
|
|
329
|
+
const text = readRegularConfigFile(filePath, maxBytes);
|
|
330
|
+
if (text === null) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const config: unknown = JSON.parse(text);
|
|
335
|
+
const theme = config && typeof config === 'object' ? (config as Record<string, unknown>).theme : undefined;
|
|
336
|
+
if (typeof theme !== 'string') {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
return CLAUDE_THEME_IDS.has(theme) || theme.startsWith('custom:') ? theme : null;
|
|
340
|
+
} catch {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Finds the theme Claude Code would start with in `projectPath`, read-only and
|
|
347
|
+
* in the CLI's own order: local, project and user settings, then the legacy
|
|
348
|
+
* global config key, then its "dark" default. Managed settings are not read:
|
|
349
|
+
* they outrank `--settings`, so no flag could change their theme anyway.
|
|
350
|
+
*/
|
|
351
|
+
function resolveClaudeTheme(projectPath: string): string {
|
|
352
|
+
const configDirOverride = process.env.CLAUDE_CONFIG_DIR;
|
|
353
|
+
const configHome = configDirOverride || path.join(os.homedir(), '.claude');
|
|
354
|
+
const legacyGlobalConfigPath = path.join(configHome, '.config.json');
|
|
355
|
+
const configFiles: Array<[string, number]> = [
|
|
356
|
+
[path.join(projectPath, '.claude', 'settings.local.json'), MAX_CLAUDE_SETTINGS_FILE_BYTES],
|
|
357
|
+
[path.join(projectPath, '.claude', 'settings.json'), MAX_CLAUDE_SETTINGS_FILE_BYTES],
|
|
358
|
+
[path.join(configHome, 'settings.json'), MAX_CLAUDE_SETTINGS_FILE_BYTES],
|
|
359
|
+
[
|
|
360
|
+
fs.existsSync(legacyGlobalConfigPath)
|
|
361
|
+
? legacyGlobalConfigPath
|
|
362
|
+
: path.join(configDirOverride || os.homedir(), '.claude.json'),
|
|
363
|
+
MAX_CLAUDE_GLOBAL_CONFIG_FILE_BYTES,
|
|
364
|
+
],
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
for (const [filePath, maxBytes] of configFiles) {
|
|
368
|
+
const theme = readClaudeThemeFile(filePath, maxBytes);
|
|
369
|
+
if (theme) {
|
|
370
|
+
return theme;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return 'dark';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let hasLoggedThemeSettingsFileError = false;
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Returns a settings file that holds only `{"theme": theme}`, writing it when
|
|
380
|
+
* it is missing or differs. It lives in the app's own data folder
|
|
381
|
+
* (`~/.cloudcli`, like the chat assets); the user's Claude config is never
|
|
382
|
+
* written. The write goes through a temp file and a rename, so a CLI starting
|
|
383
|
+
* at the same time never reads half a file. Returns null, and logs once, when
|
|
384
|
+
* the file cannot be written (a read-only home, say): the launch then simply
|
|
385
|
+
* goes ahead without a theme.
|
|
386
|
+
*/
|
|
387
|
+
function ensureClaudeThemeSettingsFile(theme: string): string | null {
|
|
388
|
+
const contents = `${JSON.stringify({ theme })}\n`;
|
|
389
|
+
let tempPath: string | null = null;
|
|
390
|
+
try {
|
|
391
|
+
const filePath = path.join(os.homedir(), '.cloudcli', `claude-shell-theme-${theme}.json`);
|
|
392
|
+
// Anything but the expected regular file is replaced; the rename never
|
|
393
|
+
// opens what it replaces.
|
|
394
|
+
if (readRegularConfigFile(filePath) !== contents) {
|
|
395
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
396
|
+
tempPath = `${filePath}.${process.pid}.tmp`;
|
|
397
|
+
fs.writeFileSync(tempPath, contents);
|
|
398
|
+
fs.renameSync(tempPath, filePath);
|
|
399
|
+
}
|
|
400
|
+
return filePath;
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (tempPath) {
|
|
403
|
+
// Asynchronous and unchecked: cleaning up must not throw into the launch.
|
|
404
|
+
fs.rm(tempPath, { force: true }, () => undefined);
|
|
405
|
+
}
|
|
406
|
+
if (!hasLoggedThemeSettingsFileError) {
|
|
407
|
+
hasLoggedThemeSettingsFileError = true;
|
|
408
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
409
|
+
console.warn(`[WARN] Shell: cannot write the Claude theme settings file, launching without it: ${message}`);
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Quotes one argument for the shell that runs the launch command. bash takes
|
|
417
|
+
* everything between single quotes literally, so a `'` closes the quote, adds
|
|
418
|
+
* an escaped one and reopens it. PowerShell (win32) single quotes are literal
|
|
419
|
+
* too, backslashes included; a quote is doubled, and PowerShell also treats
|
|
420
|
+
* the typographic single quotes as quote characters.
|
|
421
|
+
*/
|
|
422
|
+
function quoteShellArgument(value: string): string {
|
|
423
|
+
if (os.platform() === 'win32') {
|
|
424
|
+
return `'${value.replace(/['\u2018\u2019\u201A\u201B]/g, '$&$&')}'`;
|
|
425
|
+
}
|
|
426
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
type ClaudeThemeLaunch = {
|
|
430
|
+
flag: string;
|
|
431
|
+
/** Whether the CLI will paint in the app's colour scheme. */
|
|
432
|
+
followsAppTheme: boolean;
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Claude Code paints its own colours in its own theme, dark by default, so in
|
|
437
|
+
* a light Shell tab its prompt rows became dark bands. Only a light tab gets a
|
|
438
|
+
* flag, and only when the user's theme is a dark one: it is swapped for the
|
|
439
|
+
* matching light variant. `--settings` applies it to this launch only. It is
|
|
440
|
+
* given a file path rather than inline JSON because PowerShell strips the
|
|
441
|
+
* JSON's double quotes when it passes an argument to a native program.
|
|
442
|
+
*
|
|
443
|
+
* The CLI follows the app theme when it gets that flag, when a dark tab runs a
|
|
444
|
+
* dark theme, and with `auto` (it asks the terminal, which xterm answers from
|
|
445
|
+
* the live theme). A light or custom theme the user chose, or a flag that
|
|
446
|
+
* could not be written, paints the same in either app theme, so restarting
|
|
447
|
+
* after a toggle would change nothing.
|
|
448
|
+
*/
|
|
449
|
+
function buildClaudeThemeLaunch(colorScheme: ShellColorScheme | null, projectPath: string): ClaudeThemeLaunch {
|
|
450
|
+
if (!colorScheme) {
|
|
451
|
+
return { flag: '', followsAppTheme: false };
|
|
452
|
+
}
|
|
453
|
+
const userTheme = resolveClaudeTheme(projectPath);
|
|
454
|
+
if (userTheme === 'auto') {
|
|
455
|
+
return { flag: '', followsAppTheme: true };
|
|
456
|
+
}
|
|
457
|
+
const lightTheme = LIGHT_CLAUDE_THEME_BY_DARK_THEME.get(userTheme);
|
|
458
|
+
if (!lightTheme) {
|
|
459
|
+
return { flag: '', followsAppTheme: false };
|
|
460
|
+
}
|
|
461
|
+
if (colorScheme === 'dark') {
|
|
462
|
+
return { flag: '', followsAppTheme: true };
|
|
463
|
+
}
|
|
464
|
+
const settingsPath = ensureClaudeThemeSettingsFile(lightTheme);
|
|
465
|
+
return settingsPath
|
|
466
|
+
? { flag: ` --settings ${quoteShellArgument(settingsPath)}`, followsAppTheme: true }
|
|
467
|
+
: { flag: '', followsAppTheme: false };
|
|
227
468
|
}
|
|
228
469
|
|
|
229
470
|
function readEnvValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
|
|
@@ -311,6 +552,7 @@ export function handleShellConnection(
|
|
|
311
552
|
const provider = readString(data.provider, 'claude');
|
|
312
553
|
const initialCommand = readString(data.initialCommand);
|
|
313
554
|
const forceRestart = readBoolean(data.forceRestart);
|
|
555
|
+
const colorScheme = readColorScheme(data.colorScheme);
|
|
314
556
|
const isPlainShell =
|
|
315
557
|
readBoolean(data.isPlainShell) ||
|
|
316
558
|
(!!initialCommand && !hasSession) ||
|
|
@@ -370,6 +612,7 @@ export function handleShellConnection(
|
|
|
370
612
|
}
|
|
371
613
|
|
|
372
614
|
existingSession.ws = ws;
|
|
615
|
+
sendClaudeColorScheme(ws, existingSession.claudeColorScheme);
|
|
373
616
|
return;
|
|
374
617
|
}
|
|
375
618
|
|
|
@@ -390,8 +633,13 @@ export function handleShellConnection(
|
|
|
390
633
|
return;
|
|
391
634
|
}
|
|
392
635
|
|
|
393
|
-
const shellCommand = buildShellCommand(
|
|
636
|
+
const { command: shellCommand, claudeFollowsAppTheme } = buildShellCommand(
|
|
637
|
+
data,
|
|
638
|
+
dependencies,
|
|
639
|
+
resolvedProjectPath,
|
|
640
|
+
);
|
|
394
641
|
const resumeSessionId = resolveResumeSessionId(data, dependencies);
|
|
642
|
+
const claudeColorScheme = claudeFollowsAppTheme ? colorScheme : null;
|
|
395
643
|
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
|
|
396
644
|
const shellArgs =
|
|
397
645
|
os.platform() === 'win32' ? ['-Command', shellCommand] : ['-c', shellCommand];
|
|
@@ -410,6 +658,10 @@ export function handleShellConnection(
|
|
|
410
658
|
TERM: 'xterm-256color',
|
|
411
659
|
COLORTERM: 'truecolor',
|
|
412
660
|
FORCE_COLOR: '3',
|
|
661
|
+
// rxvt's "foreground;background" palette indices (15 = white, 0 = black).
|
|
662
|
+
// Programs that cannot query the terminal (vim, Claude Code's "auto"
|
|
663
|
+
// theme before its OSC 11 reply arrives) read it to pick light or dark.
|
|
664
|
+
...(colorScheme ? { COLORFGBG: colorScheme === 'light' ? '0;15' : '15;0' } : {}),
|
|
413
665
|
},
|
|
414
666
|
});
|
|
415
667
|
|
|
@@ -420,6 +672,7 @@ export function handleShellConnection(
|
|
|
420
672
|
timeoutId: null,
|
|
421
673
|
projectPath,
|
|
422
674
|
sessionId,
|
|
675
|
+
claudeColorScheme,
|
|
423
676
|
});
|
|
424
677
|
|
|
425
678
|
shellProcess.onData((chunk) => {
|
|
@@ -548,6 +801,7 @@ export function handleShellConnection(
|
|
|
548
801
|
data: welcomeMsg,
|
|
549
802
|
})
|
|
550
803
|
);
|
|
804
|
+
sendClaudeColorScheme(ws, claudeColorScheme);
|
|
551
805
|
return;
|
|
552
806
|
}
|
|
553
807
|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import test, { mock } from 'node:test';
|
|
7
|
+
|
|
8
|
+
import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
|
|
9
|
+
import { handleChatConnection } from '@/modules/websocket/services/chat-websocket.service.js';
|
|
10
|
+
import { chatRunRegistry } from '@/modules/websocket/services/chat-run-registry.service.js';
|
|
11
|
+
import { connectedClients } from '@/modules/websocket/services/websocket-state.service.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A session's turn ends while its agents, workflow or backgrounded command are
|
|
15
|
+
* still going. The run registry marks the run completed at the turn's
|
|
16
|
+
* `complete`, but the runtime keeps producing events through that run's
|
|
17
|
+
* writer — task progress, and the turn the CLI pushes when the task reports.
|
|
18
|
+
* A tab opened during that work has to attach to the run to see them, and the
|
|
19
|
+
* run has to still be there to attach to.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const SESSION_ID = 'held-session';
|
|
23
|
+
const FIVE_MINUTES = 5 * 60 * 1000;
|
|
24
|
+
|
|
25
|
+
function createFakeSocket() {
|
|
26
|
+
const socket = new EventEmitter() as EventEmitter & {
|
|
27
|
+
readyState: number;
|
|
28
|
+
frames: Array<Record<string, unknown>>;
|
|
29
|
+
send: (data: string) => void;
|
|
30
|
+
};
|
|
31
|
+
socket.readyState = 1;
|
|
32
|
+
socket.frames = [];
|
|
33
|
+
socket.send = (data: string) => socket.frames.push(JSON.parse(data) as Record<string, unknown>);
|
|
34
|
+
return socket;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function withIsolatedDatabase(runTest: () => Promise<void>): Promise<void> {
|
|
38
|
+
const previousDatabasePath = process.env.DATABASE_PATH;
|
|
39
|
+
const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'chat-background-subscribe-'));
|
|
40
|
+
closeConnection();
|
|
41
|
+
process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
|
|
42
|
+
await initializeDatabase();
|
|
43
|
+
try {
|
|
44
|
+
sessionsDb.createAppSession(SESSION_ID, 'claude', tempDirectory);
|
|
45
|
+
await runTest();
|
|
46
|
+
} finally {
|
|
47
|
+
connectedClients.clear();
|
|
48
|
+
chatRunRegistry.clearAll();
|
|
49
|
+
chatRunRegistry.setRetentionGuard(() => false);
|
|
50
|
+
closeConnection();
|
|
51
|
+
if (previousDatabasePath === undefined) {
|
|
52
|
+
delete process.env.DATABASE_PATH;
|
|
53
|
+
} else {
|
|
54
|
+
process.env.DATABASE_PATH = previousDatabasePath;
|
|
55
|
+
}
|
|
56
|
+
await rm(tempDirectory, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Starts a run for the session and ends its turn, leaving the run completed but registered. */
|
|
61
|
+
function startAndCompleteRun(connection: ReturnType<typeof createFakeSocket>) {
|
|
62
|
+
const run = chatRunRegistry.startRun({
|
|
63
|
+
appSessionId: SESSION_ID,
|
|
64
|
+
provider: 'claude',
|
|
65
|
+
providerSessionId: 'native-1',
|
|
66
|
+
connection: connection as never,
|
|
67
|
+
userId: 1,
|
|
68
|
+
});
|
|
69
|
+
assert.ok(run);
|
|
70
|
+
run.writer.send({ kind: 'complete', provider: 'claude', sessionId: 'native-1', exitCode: 0 });
|
|
71
|
+
assert.equal(chatRunRegistry.isProcessing(SESSION_ID), false);
|
|
72
|
+
return run;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const settle = () => new Promise((resolve) => { setTimeout(resolve, 30); });
|
|
76
|
+
|
|
77
|
+
test('a tab that subscribes during background work receives the run\'s later events', async () => {
|
|
78
|
+
await withIsolatedDatabase(async () => {
|
|
79
|
+
const run = startAndCompleteRun(createFakeSocket());
|
|
80
|
+
|
|
81
|
+
const newTab = createFakeSocket();
|
|
82
|
+
handleChatConnection(
|
|
83
|
+
newTab as never,
|
|
84
|
+
{ user: { id: 1 } } as never,
|
|
85
|
+
{ runtime: { hasBackgroundWork: (sessionId: string) => sessionId === SESSION_ID, getPendingApprovalsForSession: () => [] } as never },
|
|
86
|
+
);
|
|
87
|
+
newTab.emit('message', JSON.stringify({ type: 'chat.subscribe', sessions: [{ sessionId: SESSION_ID, lastSeq: 0 }] }));
|
|
88
|
+
await settle();
|
|
89
|
+
|
|
90
|
+
// Not processing — the composer stays usable — but attached.
|
|
91
|
+
assert.equal(newTab.frames[0]?.kind, 'chat_subscribed');
|
|
92
|
+
assert.equal(newTab.frames[0]?.isProcessing, false);
|
|
93
|
+
|
|
94
|
+
run.writer.send({ kind: 'task_status', provider: 'claude', sessionId: 'native-1', event: 'notification', taskId: 't1', status: 'completed' } as never);
|
|
95
|
+
assert.ok(
|
|
96
|
+
newTab.frames.some((frame) => frame.kind === 'task_status'),
|
|
97
|
+
'the task event must reach the tab that subscribed after the turn ended',
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('a tab that subscribes to an idle session is not attached to its old run', async () => {
|
|
103
|
+
await withIsolatedDatabase(async () => {
|
|
104
|
+
const run = startAndCompleteRun(createFakeSocket());
|
|
105
|
+
|
|
106
|
+
const newTab = createFakeSocket();
|
|
107
|
+
handleChatConnection(
|
|
108
|
+
newTab as never,
|
|
109
|
+
{ user: { id: 1 } } as never,
|
|
110
|
+
{ runtime: { hasBackgroundWork: () => false, getPendingApprovalsForSession: () => [] } as never },
|
|
111
|
+
);
|
|
112
|
+
newTab.emit('message', JSON.stringify({ type: 'chat.subscribe', sessions: [{ sessionId: SESSION_ID, lastSeq: 0 }] }));
|
|
113
|
+
await settle();
|
|
114
|
+
|
|
115
|
+
run.writer.send({ kind: 'task_status', provider: 'claude', sessionId: 'native-1', event: 'notification', taskId: 't1', status: 'completed' } as never);
|
|
116
|
+
assert.ok(!newTab.frames.some((frame) => frame.kind === 'task_status'));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('a completed run stays registered while its session still has background work', async () => {
|
|
121
|
+
mock.timers.enable({ apis: ['setTimeout'] });
|
|
122
|
+
try {
|
|
123
|
+
await withIsolatedDatabase(async () => {
|
|
124
|
+
let working = true;
|
|
125
|
+
chatRunRegistry.setRetentionGuard((sessionId) => sessionId === SESSION_ID && working);
|
|
126
|
+
startAndCompleteRun(createFakeSocket());
|
|
127
|
+
|
|
128
|
+
mock.timers.tick(FIVE_MINUTES);
|
|
129
|
+
assert.ok(chatRunRegistry.getRun(SESSION_ID), 'the run must outlive the retention window while work is outstanding');
|
|
130
|
+
|
|
131
|
+
working = false;
|
|
132
|
+
mock.timers.tick(FIVE_MINUTES);
|
|
133
|
+
assert.equal(chatRunRegistry.getRun(SESSION_ID), undefined, 'once the work is gone the run is evicted as before');
|
|
134
|
+
});
|
|
135
|
+
} finally {
|
|
136
|
+
mock.timers.reset();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('a retained run\'s eviction timer cannot evict the session\'s next run early', async () => {
|
|
141
|
+
// Run one completes with work outstanding and is re-armed at five minutes;
|
|
142
|
+
// run two takes the slot and completes with two minutes of retention to
|
|
143
|
+
// go. Run one's timer firing on the slot alone would evict run two.
|
|
144
|
+
mock.timers.enable({ apis: ['setTimeout'] });
|
|
145
|
+
try {
|
|
146
|
+
await withIsolatedDatabase(async () => {
|
|
147
|
+
let working = true;
|
|
148
|
+
chatRunRegistry.setRetentionGuard((sessionId) => sessionId === SESSION_ID && working);
|
|
149
|
+
const first = startAndCompleteRun(createFakeSocket());
|
|
150
|
+
mock.timers.tick(FIVE_MINUTES);
|
|
151
|
+
assert.equal(chatRunRegistry.getRun(SESSION_ID), first);
|
|
152
|
+
|
|
153
|
+
working = false;
|
|
154
|
+
mock.timers.tick(FIVE_MINUTES - 2 * 60 * 1000);
|
|
155
|
+
const second = startAndCompleteRun(createFakeSocket());
|
|
156
|
+
assert.equal(chatRunRegistry.getRun(SESSION_ID), second);
|
|
157
|
+
|
|
158
|
+
// Run one's re-armed timer fires now; run two's has two minutes left.
|
|
159
|
+
mock.timers.tick(2 * 60 * 1000);
|
|
160
|
+
assert.equal(chatRunRegistry.getRun(SESSION_ID), second, 'run two keeps its own retention window');
|
|
161
|
+
mock.timers.tick(FIVE_MINUTES);
|
|
162
|
+
assert.equal(chatRunRegistry.getRun(SESSION_ID), undefined);
|
|
163
|
+
});
|
|
164
|
+
} finally {
|
|
165
|
+
mock.timers.reset();
|
|
166
|
+
}
|
|
167
|
+
});
|