@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
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
2
3
|
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
5
|
+
import http from 'node:http';
|
|
6
|
+
import type { AddressInfo } from 'node:net';
|
|
7
|
+
import os from 'node:os';
|
|
3
8
|
import path from 'node:path';
|
|
4
9
|
import { PassThrough } from 'node:stream';
|
|
5
10
|
import test from 'node:test';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { promisify } from 'node:util';
|
|
6
13
|
|
|
7
14
|
import { startCloneProject } from '@/modules/projects/services/project-clone.service.js';
|
|
8
15
|
import { AppError } from '@/shared/utils.js';
|
|
@@ -110,6 +117,66 @@ test('startCloneProject rejects github URL values that begin with option prefixe
|
|
|
110
117
|
);
|
|
111
118
|
});
|
|
112
119
|
|
|
120
|
+
for (const [label, githubUrl] of [
|
|
121
|
+
['a token as the user', 'https://ghp_supersecrettoken1234567890abcd@github.com/example/repo.git'],
|
|
122
|
+
['a user and password', 'https://user:ghp_supersecrettoken1234567890abcd@github.com/example/repo.git'],
|
|
123
|
+
['a password on an ssh URL', 'ssh://git:secret@github.com/example/repo.git'],
|
|
124
|
+
] as const) {
|
|
125
|
+
test(`startCloneProject rejects a github URL carrying ${label} before git sees it`, async () => {
|
|
126
|
+
// A credential in the URL would ride git's argv, its stderr and the
|
|
127
|
+
// clone's `.git/config` — every channel the token field keeps it out of.
|
|
128
|
+
let spawned = 0;
|
|
129
|
+
await assert.rejects(
|
|
130
|
+
async () =>
|
|
131
|
+
startCloneProject(
|
|
132
|
+
{ workspacePath: '/workspace/root', githubUrl, userId: 1 },
|
|
133
|
+
{ onProgress: () => undefined, onComplete: () => undefined },
|
|
134
|
+
buildDependencies({ spawnGitClone: () => { spawned += 1; throw new Error('must not spawn'); } }),
|
|
135
|
+
),
|
|
136
|
+
(error: unknown) => {
|
|
137
|
+
assert.ok(error instanceof AppError);
|
|
138
|
+
assert.equal(error.code, 'GITHUB_URL_CARRIES_CREDENTIALS');
|
|
139
|
+
assert.ok(!error.message.includes('ghp_supersecrettoken'), 'the error must not echo the credential');
|
|
140
|
+
return true;
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
assert.equal(spawned, 0);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
test('startCloneProject rejects an http(s) URL the parser cannot read before git carries it', async () => {
|
|
148
|
+
// 'https://token@github.com:notaport/x' is no URL to the parser, so the
|
|
149
|
+
// credential check could not see the token — and git would have had it on
|
|
150
|
+
// its command line until curl refused the port.
|
|
151
|
+
let spawned = 0;
|
|
152
|
+
await assert.rejects(
|
|
153
|
+
async () =>
|
|
154
|
+
startCloneProject(
|
|
155
|
+
{ workspacePath: '/workspace/root', githubUrl: 'https://ghp_supersecrettoken1234567890abcd@github.com:notaport/example/repo.git', userId: 1 },
|
|
156
|
+
{ onProgress: () => undefined, onComplete: () => undefined },
|
|
157
|
+
buildDependencies({ spawnGitClone: () => { spawned += 1; throw new Error('must not spawn'); } }),
|
|
158
|
+
),
|
|
159
|
+
(error: unknown) => {
|
|
160
|
+
assert.ok(error instanceof AppError);
|
|
161
|
+
assert.equal(error.code, 'INVALID_GITHUB_URL');
|
|
162
|
+
return true;
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
assert.equal(spawned, 0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('startCloneProject accepts the ssh login name that is not a credential', async () => {
|
|
169
|
+
// `git@` on an SSH URL is the login every SSH clone uses, not a secret.
|
|
170
|
+
const gitProcess = createMockGitProcess();
|
|
171
|
+
const operation = await startCloneProject(
|
|
172
|
+
{ workspacePath: '/workspace/root', githubUrl: 'ssh://git@github.com/example/repo.git', userId: 1 },
|
|
173
|
+
{ onProgress: () => undefined, onComplete: () => undefined },
|
|
174
|
+
buildDependencies({ spawnGitClone: () => gitProcess as never }),
|
|
175
|
+
);
|
|
176
|
+
gitProcess.emit('close', 0);
|
|
177
|
+
await operation.waitForCompletion;
|
|
178
|
+
});
|
|
179
|
+
|
|
113
180
|
test('startCloneProject rejects when selected github token does not exist', async () => {
|
|
114
181
|
await assert.rejects(
|
|
115
182
|
async () =>
|
|
@@ -181,3 +248,312 @@ test('startCloneProject completes and emits complete payload when git exits succ
|
|
|
181
248
|
assert.equal(resolvedCompletePayload.message, 'Repository cloned successfully');
|
|
182
249
|
assert.equal((resolvedCompletePayload.project.projectId as string) || '', 'project-1');
|
|
183
250
|
});
|
|
251
|
+
|
|
252
|
+
type SpawnCall = { cloneUrl: string; clonePath: string; environment: NodeJS.ProcessEnv };
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Runs a clone against a mock git process and captures exactly what would be
|
|
256
|
+
* handed to `spawn`: the URL and path (the only variable argv elements) and
|
|
257
|
+
* the environment. The token has to reach git through the environment alone.
|
|
258
|
+
*/
|
|
259
|
+
async function captureSpawnCall(input: {
|
|
260
|
+
githubTokenId?: number;
|
|
261
|
+
newGithubToken?: string;
|
|
262
|
+
storedToken?: string;
|
|
263
|
+
}): Promise<SpawnCall> {
|
|
264
|
+
const gitProcess = createMockGitProcess();
|
|
265
|
+
let spawnCall: SpawnCall | null = null;
|
|
266
|
+
|
|
267
|
+
const operation = await startCloneProject(
|
|
268
|
+
{
|
|
269
|
+
workspacePath: '/workspace/root',
|
|
270
|
+
githubUrl: 'https://github.com/example/repo.git',
|
|
271
|
+
githubTokenId: input.githubTokenId,
|
|
272
|
+
newGithubToken: input.newGithubToken,
|
|
273
|
+
userId: 1,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
onProgress: () => undefined,
|
|
277
|
+
onComplete: () => undefined,
|
|
278
|
+
},
|
|
279
|
+
buildDependencies({
|
|
280
|
+
getGithubTokenById: async () => ({ github_token: input.storedToken ?? 'unused' }),
|
|
281
|
+
spawnGitClone: (cloneUrl, clonePath, environment) => {
|
|
282
|
+
spawnCall = { cloneUrl, clonePath, environment };
|
|
283
|
+
return gitProcess as any;
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
gitProcess.emit('close', 0);
|
|
289
|
+
await operation.waitForCompletion;
|
|
290
|
+
|
|
291
|
+
assert.notEqual(spawnCall, null, 'git was never spawned');
|
|
292
|
+
return spawnCall as unknown as SpawnCall;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (const [label, input, token] of [
|
|
296
|
+
['a stored token', { githubTokenId: 7, storedToken: 'ghp_storedsecret1234567890' }, 'ghp_storedsecret1234567890'],
|
|
297
|
+
// Characters `url.username=` would have percent-encoded, so a check for the
|
|
298
|
+
// raw token alone could not have caught the URL channel.
|
|
299
|
+
['a new token', { newGithubToken: 'ghp_new:secret@with/odd-chars' }, 'ghp_new:secret@with/odd-chars'],
|
|
300
|
+
] as const) {
|
|
301
|
+
test(`startCloneProject hands git ${label} through the credential helper, never the clone URL`, async () => {
|
|
302
|
+
const { cloneUrl, clonePath, environment } = await captureSpawnCall(input);
|
|
303
|
+
|
|
304
|
+
// The URL git receives is the one the user typed: no username, no password,
|
|
305
|
+
// nothing encoded — so neither argv, stderr nor `.git/config` can carry it.
|
|
306
|
+
assert.equal(cloneUrl, 'https://github.com/example/repo.git');
|
|
307
|
+
assert.equal(clonePath, path.join('/workspace/root', 'repo'));
|
|
308
|
+
|
|
309
|
+
assert.equal(environment.CLOUDCLI_GITHUB_TOKEN, token);
|
|
310
|
+
assert.equal(environment.GIT_CONFIG_COUNT, '2');
|
|
311
|
+
assert.equal(environment.GIT_CONFIG_KEY_0, 'credential.helper');
|
|
312
|
+
assert.equal(environment.GIT_CONFIG_VALUE_0, '');
|
|
313
|
+
assert.equal(environment.GIT_CONFIG_KEY_1, 'credential.https://github.com.helper');
|
|
314
|
+
assert.match(environment.GIT_CONFIG_VALUE_1 ?? '', /\$CLOUDCLI_GITHUB_TOKEN/);
|
|
315
|
+
assert.equal(environment.GIT_TERMINAL_PROMPT, '0');
|
|
316
|
+
|
|
317
|
+
// The helper's command line is itself a process argv, so the token must be
|
|
318
|
+
// read from the variable rather than pasted into the helper text.
|
|
319
|
+
for (const [name, value] of Object.entries(environment)) {
|
|
320
|
+
if (name === 'CLOUDCLI_GITHUB_TOKEN') {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
assert.ok(!(value ?? '').includes(token), `token leaked into ${name}`);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
test('startCloneProject leaves the credential helper unset when no token was given', async () => {
|
|
329
|
+
const { cloneUrl, environment } = await captureSpawnCall({});
|
|
330
|
+
|
|
331
|
+
assert.equal(cloneUrl, 'https://github.com/example/repo.git');
|
|
332
|
+
assert.equal(environment.GIT_TERMINAL_PROMPT, '0');
|
|
333
|
+
assert.equal(environment.GIT_CONFIG_COUNT, undefined);
|
|
334
|
+
assert.equal(environment.CLOUDCLI_GITHUB_TOKEN, undefined);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* `git clone --progress` writes every byte of its progress to stderr — stdout
|
|
339
|
+
* stays empty — so these tests drive stderr, the pipe that carries the progress
|
|
340
|
+
* the user watches and the reason a clone failed.
|
|
341
|
+
*/
|
|
342
|
+
async function runCloneWithStderr(
|
|
343
|
+
chunks: string[],
|
|
344
|
+
exitCode: number | null,
|
|
345
|
+
): Promise<{ progressMessages: string[]; failure: AppError | null }> {
|
|
346
|
+
const gitProcess = createMockGitProcess();
|
|
347
|
+
const progressMessages: string[] = [];
|
|
348
|
+
|
|
349
|
+
const operation = await startCloneProject(
|
|
350
|
+
{
|
|
351
|
+
workspacePath: '/workspace/root',
|
|
352
|
+
githubUrl: 'https://github.com/example/repo.git',
|
|
353
|
+
newGithubToken: 'ghp_supersecrettoken1234567890abcd',
|
|
354
|
+
userId: 1,
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
onProgress: (message) => {
|
|
358
|
+
progressMessages.push(message);
|
|
359
|
+
},
|
|
360
|
+
onComplete: () => undefined,
|
|
361
|
+
},
|
|
362
|
+
buildDependencies({ spawnGitClone: () => gitProcess as any }),
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
for (const chunk of chunks) {
|
|
366
|
+
gitProcess.stderr.write(chunk);
|
|
367
|
+
}
|
|
368
|
+
gitProcess.stderr.end();
|
|
369
|
+
// Let the PassThrough deliver every `data` event and its `end` before the
|
|
370
|
+
// process is reported closed, exactly as a real pipe does.
|
|
371
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
372
|
+
gitProcess.emit('close', exitCode);
|
|
373
|
+
|
|
374
|
+
let failure: AppError | null = null;
|
|
375
|
+
try {
|
|
376
|
+
await operation.waitForCompletion;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
failure = error as AppError;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return { progressMessages, failure };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
test('startCloneProject forwards each stderr chunk to onProgress as it arrives', async () => {
|
|
385
|
+
const gitProcess = createMockGitProcess();
|
|
386
|
+
const progressMessages: string[] = [];
|
|
387
|
+
|
|
388
|
+
const operation = await startCloneProject(
|
|
389
|
+
{
|
|
390
|
+
workspacePath: '/workspace/root',
|
|
391
|
+
githubUrl: 'https://github.com/example/repo.git',
|
|
392
|
+
newGithubToken: 'ghp_supersecrettoken1234567890abcd',
|
|
393
|
+
userId: 1,
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
onProgress: (message) => {
|
|
397
|
+
progressMessages.push(message);
|
|
398
|
+
},
|
|
399
|
+
onComplete: () => undefined,
|
|
400
|
+
},
|
|
401
|
+
buildDependencies({ spawnGitClone: () => gitProcess as any }),
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
assert.deepEqual(progressMessages, ["Cloning into 'repo'..."]);
|
|
405
|
+
|
|
406
|
+
// Each chunk has to surface on its own event before the next one is written
|
|
407
|
+
// and long before the process exits: the progress UI is fed by these events,
|
|
408
|
+
// so anything buffered until close would leave the user staring at nothing.
|
|
409
|
+
const chunks = [
|
|
410
|
+
'remote: Enumerating objects: 20, done.\n',
|
|
411
|
+
'Receiving objects: 45% (9/20)\r',
|
|
412
|
+
'Receiving objects: 100% (20/20), done.\n',
|
|
413
|
+
];
|
|
414
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
415
|
+
gitProcess.stderr.write(chunk);
|
|
416
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
417
|
+
assert.deepEqual(
|
|
418
|
+
progressMessages,
|
|
419
|
+
["Cloning into 'repo'...", ...chunks.slice(0, index + 1).map((line) => line.trim())],
|
|
420
|
+
`chunk ${index + 1} did not arrive on its own event`,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
gitProcess.stderr.end();
|
|
425
|
+
gitProcess.emit('close', 0);
|
|
426
|
+
await operation.waitForCompletion;
|
|
427
|
+
|
|
428
|
+
assert.deepEqual(progressMessages, [
|
|
429
|
+
"Cloning into 'repo'...",
|
|
430
|
+
'remote: Enumerating objects: 20, done.',
|
|
431
|
+
'Receiving objects: 45% (9/20)',
|
|
432
|
+
'Receiving objects: 100% (20/20), done.',
|
|
433
|
+
]);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('startCloneProject reports the git failure reason from stderr', async () => {
|
|
437
|
+
const { failure } = await runCloneWithStderr(
|
|
438
|
+
["remote: Repository not found.\nfatal: repository 'https://github.com/example/repo.git/' not found\n"],
|
|
439
|
+
128,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
assert.equal(failure?.code, 'GIT_CLONE_FAILED');
|
|
443
|
+
assert.equal(failure?.message, 'Repository not found. Please check the URL and ensure you have access.');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('startCloneProject falls back to the last stderr text for an unrecognised failure', async () => {
|
|
447
|
+
const { failure } = await runCloneWithStderr(
|
|
448
|
+
["fatal: unable to access 'https://github.com/example/repo.git/': SSL error\n"],
|
|
449
|
+
128,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
assert.equal(failure?.code, 'GIT_CLONE_FAILED');
|
|
453
|
+
assert.equal(failure?.message, "fatal: unable to access 'https://github.com/example/repo.git/': SSL error");
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const execFileAsync = promisify(execFile);
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Real git, real clone: proves the credential-helper environment is something
|
|
460
|
+
* git accepts, and that the resulting repository records a credential-free
|
|
461
|
+
* remote. The `file://` transport never asks the helper for anything, so a
|
|
462
|
+
* placeholder token stands in for the real one.
|
|
463
|
+
*/
|
|
464
|
+
test('startCloneProject clones a real repository with the credential helper in git environment', async () => {
|
|
465
|
+
const root = await mkdtemp(path.join(os.tmpdir(), 'project-clone-service-'));
|
|
466
|
+
try {
|
|
467
|
+
const seedPath = path.join(root, 'seed');
|
|
468
|
+
const originPath = path.join(root, 'origin.git');
|
|
469
|
+
const workspacePath = path.join(root, 'workspace');
|
|
470
|
+
await execFileAsync('git', ['init', '-q', seedPath]);
|
|
471
|
+
await execFileAsync('git', [
|
|
472
|
+
'-C', seedPath,
|
|
473
|
+
'-c', 'user.email=clone@example.test',
|
|
474
|
+
'-c', 'user.name=Clone Test',
|
|
475
|
+
'commit', '-q', '--allow-empty', '-m', 'initial',
|
|
476
|
+
]);
|
|
477
|
+
await execFileAsync('git', ['clone', '-q', '--bare', seedPath, originPath]);
|
|
478
|
+
|
|
479
|
+
const originUrl = pathToFileURL(originPath).href;
|
|
480
|
+
const token = 'ghp_placeholder-file-transport-never-asks';
|
|
481
|
+
const progressMessages: string[] = [];
|
|
482
|
+
let completeMessage = '';
|
|
483
|
+
|
|
484
|
+
const operation = await startCloneProject(
|
|
485
|
+
{
|
|
486
|
+
workspacePath,
|
|
487
|
+
githubUrl: originUrl,
|
|
488
|
+
newGithubToken: token,
|
|
489
|
+
userId: 1,
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
onProgress: (message) => {
|
|
493
|
+
progressMessages.push(message);
|
|
494
|
+
},
|
|
495
|
+
onComplete: ({ message }) => {
|
|
496
|
+
completeMessage = message;
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
validatePath: async () => ({ valid: true, resolvedPath: workspacePath }),
|
|
501
|
+
registerProject: async (projectPath, customName) => ({
|
|
502
|
+
project: { path: projectPath, name: customName },
|
|
503
|
+
}),
|
|
504
|
+
},
|
|
505
|
+
);
|
|
506
|
+
await operation.waitForCompletion;
|
|
507
|
+
|
|
508
|
+
assert.equal(completeMessage, 'Repository cloned successfully');
|
|
509
|
+
// The first message is synthesised by the service; everything after it
|
|
510
|
+
// came off git's own stderr, which is what the progress UI relies on.
|
|
511
|
+
assert.ok(
|
|
512
|
+
progressMessages.length >= 2,
|
|
513
|
+
`no progress arrived from git: ${JSON.stringify(progressMessages)}`,
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
const cloneConfig = await readFile(path.join(workspacePath, 'origin', '.git', 'config'), 'utf8');
|
|
517
|
+
assert.match(cloneConfig, new RegExp(`url = ${originUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
|
|
518
|
+
assert.ok(!cloneConfig.includes(token), `token written to .git/config: ${cloneConfig}`);
|
|
519
|
+
} finally {
|
|
520
|
+
await rm(root, { recursive: true, force: true });
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test('startCloneProject offers the GitHub token to no host but github.com', async () => {
|
|
525
|
+
// The helper used to answer every credential challenge: a clone from any
|
|
526
|
+
// host the user typed — over plain http too — received the GitHub token.
|
|
527
|
+
// Real git against a local server that challenges for Basic auth.
|
|
528
|
+
const root = await mkdtemp(path.join(os.tmpdir(), 'project-clone-scope-'));
|
|
529
|
+
const authorizations: Array<string | null> = [];
|
|
530
|
+
const server = http.createServer((request, response) => {
|
|
531
|
+
authorizations.push(request.headers.authorization ?? null);
|
|
532
|
+
response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="probe"' });
|
|
533
|
+
response.end('auth required');
|
|
534
|
+
});
|
|
535
|
+
await new Promise<void>((resolve) => { server.listen(0, '127.0.0.1', resolve); });
|
|
536
|
+
const { port } = server.address() as AddressInfo;
|
|
537
|
+
const token = 'ghp_supersecrettoken1234567890abcd';
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
let failure = '';
|
|
541
|
+
const operation = await startCloneProject(
|
|
542
|
+
{ workspacePath: root, githubUrl: `http://127.0.0.1:${port}/example/repo.git`, newGithubToken: token, userId: 1 },
|
|
543
|
+
{ onProgress: () => undefined, onComplete: () => undefined },
|
|
544
|
+
{ validatePath: async () => ({ valid: true, resolvedPath: root }) },
|
|
545
|
+
);
|
|
546
|
+
await operation.waitForCompletion.catch((error: unknown) => { failure = error instanceof Error ? error.message : String(error); });
|
|
547
|
+
|
|
548
|
+
assert.ok(authorizations.length >= 1, 'git never reached the server');
|
|
549
|
+
assert.ok(
|
|
550
|
+
authorizations.every((authorization) => authorization === null),
|
|
551
|
+
`the token was offered to a foreign host: ${JSON.stringify(authorizations)}`,
|
|
552
|
+
);
|
|
553
|
+
assert.match(failure, /Authentication failed/);
|
|
554
|
+
} finally {
|
|
555
|
+
server.close();
|
|
556
|
+
await rm(root, { recursive: true, force: true });
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
@@ -6,5 +6,9 @@ export { providerRuntimeService } from './services/provider-runtime.service.js';
|
|
|
6
6
|
// providerModelsService: used by Commands to list models and resolve the active session model.
|
|
7
7
|
export { providerModelsService } from './services/provider-models.service.js';
|
|
8
8
|
|
|
9
|
+
// sessionsService: used by the websocket module's chat gateway to resolve an
|
|
10
|
+
// edited message's resume point, which only the providers module can read.
|
|
11
|
+
export { sessionsService } from './services/sessions.service.js';
|
|
12
|
+
|
|
9
13
|
export { initializeSessionsWatcher } from './services/sessions-watcher.service.js';
|
|
10
14
|
export { closeSessionsWatcher } from './services/sessions-watcher.service.js';
|
|
@@ -6,7 +6,7 @@ import spawn from 'cross-spawn';
|
|
|
6
6
|
|
|
7
7
|
import { resolveClaudeCodeExecutablePath } from '@/shared/claude-cli-path.js';
|
|
8
8
|
import type { IProviderAuth } from '@/shared/interfaces.js';
|
|
9
|
-
import type { ProviderAuthStatus } from '@/shared/types.js';
|
|
9
|
+
import type { ProviderAuthStatus, ProviderAuthSubscriptionOverride } from '@/shared/types.js';
|
|
10
10
|
import { readObjectRecord, readOptionalString } from '@/shared/utils.js';
|
|
11
11
|
|
|
12
12
|
type ClaudeCredentialsStatus = {
|
|
@@ -14,6 +14,16 @@ type ClaudeCredentialsStatus = {
|
|
|
14
14
|
email: string | null;
|
|
15
15
|
method: string | null;
|
|
16
16
|
error?: string;
|
|
17
|
+
subscriptionOverride?: ProviderAuthSubscriptionOverride;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* An API-key style credential found in the env or settings.json, plus the
|
|
22
|
+
* user-facing label the settings UI has always shown for it. The labels are
|
|
23
|
+
* kept verbatim so existing consumers of `email` see no change.
|
|
24
|
+
*/
|
|
25
|
+
type ClaudeApiKeyCredential = Pick<ProviderAuthSubscriptionOverride, 'variable' | 'source'> & {
|
|
26
|
+
label: string;
|
|
17
27
|
};
|
|
18
28
|
|
|
19
29
|
const hasErrorCode = (error: unknown, code: string): boolean => (
|
|
@@ -25,7 +35,9 @@ export class ClaudeProviderAuth implements IProviderAuth {
|
|
|
25
35
|
* Checks whether the Claude Code CLI is available on this host.
|
|
26
36
|
*/
|
|
27
37
|
private checkInstalled(): boolean {
|
|
28
|
-
|
|
38
|
+
// cross-spawn resolves shims and PATHEXT itself, so the bare command is a
|
|
39
|
+
// usable fallback here even where the SDK's raw spawn could not use it.
|
|
40
|
+
const cliPath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH) ?? 'claude';
|
|
29
41
|
try {
|
|
30
42
|
spawn.sync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 });
|
|
31
43
|
return true;
|
|
@@ -60,6 +72,7 @@ export class ClaudeProviderAuth implements IProviderAuth {
|
|
|
60
72
|
email: credentials.authenticated ? credentials.email || 'Authenticated' : credentials.email,
|
|
61
73
|
method: credentials.method,
|
|
62
74
|
error: credentials.authenticated ? undefined : credentials.error || 'Not authenticated',
|
|
75
|
+
...(credentials.subscriptionOverride ? { subscriptionOverride: credentials.subscriptionOverride } : {}),
|
|
63
76
|
};
|
|
64
77
|
}
|
|
65
78
|
|
|
@@ -78,26 +91,54 @@ export class ClaudeProviderAuth implements IProviderAuth {
|
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
/**
|
|
81
|
-
*
|
|
94
|
+
* Finds an API-key style credential in the same order Claude Code checks them:
|
|
95
|
+
* process env first (auth token, then API key), then the settings.json env block.
|
|
82
96
|
*/
|
|
83
|
-
private
|
|
84
|
-
const missingCredentialsError = 'Claude CLI is not authenticated. Run claude /login or configure ANTHROPIC_API_KEY.';
|
|
85
|
-
|
|
97
|
+
private findApiKeyCredential(settingsEnv: Record<string, unknown>): ClaudeApiKeyCredential | null {
|
|
86
98
|
if (process.env.ANTHROPIC_AUTH_TOKEN?.trim()) {
|
|
87
|
-
return {
|
|
99
|
+
return { variable: 'ANTHROPIC_AUTH_TOKEN', source: 'process_env', label: 'Auth Token' };
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
if (process.env.ANTHROPIC_API_KEY?.trim()) {
|
|
91
|
-
return {
|
|
103
|
+
return { variable: 'ANTHROPIC_API_KEY', source: 'process_env', label: 'API Key Auth' };
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
const settingsEnv = await this.loadSettingsEnv();
|
|
95
106
|
if (readOptionalString(settingsEnv.ANTHROPIC_API_KEY)) {
|
|
96
|
-
return {
|
|
107
|
+
return { variable: 'ANTHROPIC_API_KEY', source: 'settings_file', label: 'API Key Auth' };
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
if (readOptionalString(settingsEnv.ANTHROPIC_AUTH_TOKEN)) {
|
|
100
|
-
return {
|
|
111
|
+
return { variable: 'ANTHROPIC_AUTH_TOKEN', source: 'settings_file', label: 'Configured via settings.json' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks Claude credentials in the same priority order used by Claude Code.
|
|
119
|
+
*/
|
|
120
|
+
private async checkCredentials(): Promise<ClaudeCredentialsStatus> {
|
|
121
|
+
const settingsEnv = await this.loadSettingsEnv();
|
|
122
|
+
|
|
123
|
+
const apiKey = this.findApiKeyCredential(settingsEnv);
|
|
124
|
+
if (apiKey) {
|
|
125
|
+
const status: ClaudeCredentialsStatus = { authenticated: true, email: apiKey.label, method: 'api_key' };
|
|
126
|
+
|
|
127
|
+
// The key wins, but Claude Code says nothing when it does so while a
|
|
128
|
+
// `claude /login` subscription is also signed in — every request is then
|
|
129
|
+
// billed pay-as-you-go to the key (issue #568). Surface the bypassed
|
|
130
|
+
// login so the settings page can warn about it; an expired or missing
|
|
131
|
+
// login is not being bypassed, so it stays silent.
|
|
132
|
+
const subscription = await this.readCredentialsFile();
|
|
133
|
+
if (subscription.authenticated) {
|
|
134
|
+
status.subscriptionOverride = {
|
|
135
|
+
variable: apiKey.variable,
|
|
136
|
+
source: apiKey.source,
|
|
137
|
+
subscriptionEmail: subscription.email,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return status;
|
|
101
142
|
}
|
|
102
143
|
|
|
103
144
|
if (process.env.CLAUDE_CODE_OAUTH_TOKEN?.trim()) {
|
|
@@ -108,6 +149,17 @@ export class ClaudeProviderAuth implements IProviderAuth {
|
|
|
108
149
|
return { authenticated: true, email: 'OAuth Token (long-lived)', method: 'environment' };
|
|
109
150
|
}
|
|
110
151
|
|
|
152
|
+
return this.readCredentialsFile();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Reads the `claude /login` OAuth session from ~/.claude/.credentials.json and
|
|
157
|
+
* reports whether it is still usable. Shared by the fallback path (when no
|
|
158
|
+
* env credential exists) and by the API-key path (to detect a bypassed login).
|
|
159
|
+
*/
|
|
160
|
+
private async readCredentialsFile(): Promise<ClaudeCredentialsStatus> {
|
|
161
|
+
const missingCredentialsError = 'Claude CLI is not authenticated. Run claude /login or configure ANTHROPIC_API_KEY.';
|
|
162
|
+
|
|
111
163
|
try {
|
|
112
164
|
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
113
165
|
const content = await readFile(credPath, 'utf8');
|
|
@@ -126,6 +178,24 @@ export class ClaudeProviderAuth implements IProviderAuth {
|
|
|
126
178
|
};
|
|
127
179
|
}
|
|
128
180
|
|
|
181
|
+
// `accessToken` is short-lived (hours). Claude Code renews it silently
|
|
182
|
+
// from `refreshToken` on the next CLI invocation, so an expired access
|
|
183
|
+
// token alongside a live refresh token is still a working login. Before
|
|
184
|
+
// this check, a still-signed-in account read as "login has expired"
|
|
185
|
+
// until something else happened to run the CLI — which is why opening
|
|
186
|
+
// the Shell tab and coming back made Settings flip to Connected.
|
|
187
|
+
const refreshToken = readOptionalString(oauth?.refreshToken);
|
|
188
|
+
const refreshTokenExpiresAt = typeof oauth?.refreshTokenExpiresAt === 'number'
|
|
189
|
+
? oauth.refreshTokenExpiresAt
|
|
190
|
+
: undefined;
|
|
191
|
+
if (refreshToken && (!refreshTokenExpiresAt || Date.now() < refreshTokenExpiresAt)) {
|
|
192
|
+
return {
|
|
193
|
+
authenticated: true,
|
|
194
|
+
email,
|
|
195
|
+
method: 'credentials_file',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
129
199
|
return {
|
|
130
200
|
authenticated: false,
|
|
131
201
|
email: null,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { forkSession as forkClaudeSession } from '@anthropic-ai/claude-agent-sdk';
|
|
5
|
+
|
|
6
|
+
import type { IProviderFork } from '@/shared/interfaces.js';
|
|
7
|
+
import { AppError } from '@/shared/utils.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Branches a Claude conversation by copying its transcript into a new session
|
|
11
|
+
* file.
|
|
12
|
+
*
|
|
13
|
+
* The SDK owns this: it remaps every message uuid and rewrites the parentUuid
|
|
14
|
+
* chain, which is what makes the copy resumable rather than just a duplicate
|
|
15
|
+
* file. `upToMessageId` is inclusive of the row it names.
|
|
16
|
+
*/
|
|
17
|
+
export class ClaudeForkProvider implements IProviderFork {
|
|
18
|
+
async forkSession(input: {
|
|
19
|
+
providerSessionId: string;
|
|
20
|
+
jsonlPath: string;
|
|
21
|
+
projectPath: string;
|
|
22
|
+
upToAnchorId?: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
}): Promise<{ providerSessionId: string; jsonlPath: string }> {
|
|
25
|
+
// `dir` is the session's working directory, which the SDK encodes into the
|
|
26
|
+
// `~/.claude/projects/<encoded>` folder name itself — passing that folder
|
|
27
|
+
// makes it encode an already-encoded path and find nothing.
|
|
28
|
+
const { sessionId } = await forkClaudeSession(input.providerSessionId, {
|
|
29
|
+
dir: input.projectPath,
|
|
30
|
+
upToMessageId: input.upToAnchorId,
|
|
31
|
+
title: input.title,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!sessionId) {
|
|
35
|
+
throw new AppError('Claude did not return a session id for the fork.', {
|
|
36
|
+
code: 'FORK_FAILED',
|
|
37
|
+
statusCode: 502,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Confirmed rather than assumed: the caller is about to write a database
|
|
42
|
+
// row claiming this file exists, and a half-created row would show up in
|
|
43
|
+
// the sidebar as a session that can never be opened.
|
|
44
|
+
// The fork lands beside the transcript it was copied from.
|
|
45
|
+
const forkedPath = path.join(path.dirname(input.jsonlPath), `${sessionId}.jsonl`);
|
|
46
|
+
try {
|
|
47
|
+
await stat(forkedPath);
|
|
48
|
+
} catch {
|
|
49
|
+
throw new AppError('Claude reported a fork but wrote no transcript for it.', {
|
|
50
|
+
code: 'FORK_FAILED',
|
|
51
|
+
statusCode: 502,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { providerSessionId: sessionId, jsonlPath: forkedPath };
|
|
56
|
+
}
|
|
57
|
+
}
|