@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
|
@@ -4,11 +4,12 @@ import path from 'path';
|
|
|
4
4
|
import express from 'express';
|
|
5
5
|
|
|
6
6
|
import type { ProviderRunFunction } from '@/shared/types.js';
|
|
7
|
-
import { AppError } from '@/shared/utils.js';
|
|
7
|
+
import { AppError, getGitErrorDetails } from '@/shared/utils.js';
|
|
8
8
|
|
|
9
9
|
// cross-spawn: drop-in spawn with Windows .cmd/PATHEXT resolution.
|
|
10
10
|
import { parseGitLogWithStats, parseGitStatusOutput } from './git-parsing.service.js';
|
|
11
11
|
import { deleteLocalBranch } from './git-branch.service.js';
|
|
12
|
+
import { getBranchDiffForFile, listBranchDiffFiles } from './git-branch-diff.service.js';
|
|
12
13
|
|
|
13
14
|
type GitRouterDependencies = {
|
|
14
15
|
fileSystem: typeof import('node:fs/promises');
|
|
@@ -207,10 +208,6 @@ async function validateGitRepository(projectPath) {
|
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
|
|
210
|
-
function getGitErrorDetails(error) {
|
|
211
|
-
return `${error?.message || ''} ${error?.stderr || ''} ${error?.stdout || ''}`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
211
|
function isMissingHeadRevisionError(error) {
|
|
215
212
|
const errorDetails = getGitErrorDetails(error).toLowerCase();
|
|
216
213
|
return errorDetails.includes('unknown revision')
|
|
@@ -787,13 +784,110 @@ router.get('/branches', async (req, res) => {
|
|
|
787
784
|
const branches = [...localBranches, ...remoteBranches]
|
|
788
785
|
.filter((b, i, arr) => arr.indexOf(b) === i);
|
|
789
786
|
|
|
790
|
-
|
|
787
|
+
// Full remote-tracking names (`origin/main`) for callers that need an
|
|
788
|
+
// unambiguous ref, e.g. the Compare tab's base selector.
|
|
789
|
+
const remoteRefs = rawLines
|
|
790
|
+
.filter(b => b.startsWith('remotes/'))
|
|
791
|
+
.map(b => b.replace(/^remotes\//, ''));
|
|
792
|
+
|
|
793
|
+
res.json({ branches, localBranches, remoteBranches, remoteRefs });
|
|
791
794
|
} catch (error) {
|
|
792
795
|
console.error('Git branches error:', error);
|
|
793
796
|
res.json({ error: error.message });
|
|
794
797
|
}
|
|
795
798
|
});
|
|
796
799
|
|
|
800
|
+
// Shared error shaping for the two compare routes: service AppErrors (unknown
|
|
801
|
+
// ref, no merge base) keep their status/code; anything else follows the
|
|
802
|
+
// module's GET convention of a 200 with `error`/`details`.
|
|
803
|
+
function sendBranchDiffError(res, error, context) {
|
|
804
|
+
if (error instanceof AppError) {
|
|
805
|
+
return res.status(error.statusCode).json({ error: error.message, code: error.code, details: error.details });
|
|
806
|
+
}
|
|
807
|
+
console.error(`Git ${context} error:`, error);
|
|
808
|
+
res.json({ error: 'Git operation failed', details: error.message });
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// List files the working copy changed relative to the merge base with `base`
|
|
812
|
+
router.get('/branch-diff', async (req, res) => {
|
|
813
|
+
const { project, base } = req.query;
|
|
814
|
+
|
|
815
|
+
if (!project || !base) {
|
|
816
|
+
return res.status(400).json({ error: 'Project id and base branch are required' });
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
validateBranchName(base);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
return res.status(400).json({ error: error.message });
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
try {
|
|
826
|
+
const projectPath = await getActualProjectPath(project);
|
|
827
|
+
await validateGitRepository(projectPath);
|
|
828
|
+
// Run from the repository root so untracked paths are root-relative like
|
|
829
|
+
// everything else the panel shows, even for projects nested in a repo.
|
|
830
|
+
const repositoryRootPath = await getRepositoryRootPath(projectPath);
|
|
831
|
+
const result = await listBranchDiffFiles({
|
|
832
|
+
projectPath: repositoryRootPath,
|
|
833
|
+
base,
|
|
834
|
+
runCommand: spawnAsync,
|
|
835
|
+
});
|
|
836
|
+
res.json(result);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
sendBranchDiffError(res, error, 'branch diff');
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
// Diff of one file between the merge base with `base` and the working copy.
|
|
843
|
+
// `oldPath` is the pre-rename path of an `R` entry from `/branch-diff`.
|
|
844
|
+
router.get('/branch-diff/file', async (req, res) => {
|
|
845
|
+
const { project, base, file, oldPath } = req.query;
|
|
846
|
+
|
|
847
|
+
if (!project || !base || !file) {
|
|
848
|
+
return res.status(400).json({ error: 'Project id, base branch and file path are required' });
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
try {
|
|
852
|
+
validateBranchName(base);
|
|
853
|
+
validateFilePath(file);
|
|
854
|
+
if (oldPath !== undefined) {
|
|
855
|
+
validateFilePath(oldPath);
|
|
856
|
+
}
|
|
857
|
+
} catch (error) {
|
|
858
|
+
return res.status(400).json({ error: error.message });
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
try {
|
|
862
|
+
const projectPath = await getActualProjectPath(project);
|
|
863
|
+
await validateGitRepository(projectPath);
|
|
864
|
+
|
|
865
|
+
// `/branch-diff` already reports repository-root-relative paths, and
|
|
866
|
+
// compare files need not appear in `git status` (committed-only changes),
|
|
867
|
+
// so the status-based resolver is skipped: its bare-filename fallback
|
|
868
|
+
// could remap e.g. `README.md` onto a changed `docs/README.md`.
|
|
869
|
+
const repositoryRootPath = await getRepositoryRootPath(projectPath);
|
|
870
|
+
const repositoryRelativeFilePath = normalizeRepositoryRelativeFilePath(file);
|
|
871
|
+
validateFilePath(repositoryRelativeFilePath, repositoryRootPath);
|
|
872
|
+
const repositoryRelativeOldPath = oldPath === undefined ? undefined : normalizeRepositoryRelativeFilePath(oldPath);
|
|
873
|
+
if (repositoryRelativeOldPath !== undefined) {
|
|
874
|
+
validateFilePath(repositoryRelativeOldPath, repositoryRootPath);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const diff = await getBranchDiffForFile({
|
|
878
|
+
projectPath: repositoryRootPath,
|
|
879
|
+
base,
|
|
880
|
+
file: repositoryRelativeFilePath,
|
|
881
|
+
oldPath: repositoryRelativeOldPath,
|
|
882
|
+
runCommand: spawnAsync,
|
|
883
|
+
readFile: (absolutePath) => fs.readFile(absolutePath, 'utf-8'),
|
|
884
|
+
});
|
|
885
|
+
res.json({ diff: stripDiffHeaders(diff) });
|
|
886
|
+
} catch (error) {
|
|
887
|
+
sendBranchDiffError(res, error, 'branch diff file');
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
|
|
797
891
|
// Checkout branch
|
|
798
892
|
router.post('/checkout', async (req, res) => {
|
|
799
893
|
const { project, branch } = req.body;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import type { GitCommandResult } from '@/shared/types.js';
|
|
5
|
+
|
|
6
|
+
import { getBranchDiffForFile, listBranchDiffFiles } from '../git-branch-diff.service.js';
|
|
7
|
+
|
|
8
|
+
const MERGE_BASE = 'ad449053aa935934160725092c5ce8d63d336894';
|
|
9
|
+
|
|
10
|
+
// Joins NUL-separated tokens with a trailing NUL, exactly as `-z` output looks.
|
|
11
|
+
const nul = (...tokens: string[]) => tokens.join('\0') + '\0';
|
|
12
|
+
|
|
13
|
+
type FakeRunnerOptions = {
|
|
14
|
+
nameStatus?: string;
|
|
15
|
+
untracked?: string;
|
|
16
|
+
diff?: string;
|
|
17
|
+
failRevParse?: boolean;
|
|
18
|
+
failMergeBase?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fake `spawnAsync`: records every git invocation and answers the handful of
|
|
23
|
+
* commands the service issues from canned output.
|
|
24
|
+
*/
|
|
25
|
+
function createRunner(options: FakeRunnerOptions = {}) {
|
|
26
|
+
const calls: string[][] = [];
|
|
27
|
+
const runCommand = async (_command: string, args: string[]): Promise<GitCommandResult> => {
|
|
28
|
+
calls.push(args);
|
|
29
|
+
const subcommand = args[0];
|
|
30
|
+
|
|
31
|
+
if (subcommand === 'rev-parse') {
|
|
32
|
+
if (options.failRevParse) {
|
|
33
|
+
throw Object.assign(new Error('Command failed'), { stderr: '' });
|
|
34
|
+
}
|
|
35
|
+
return { stdout: `${MERGE_BASE}\n`, stderr: '' };
|
|
36
|
+
}
|
|
37
|
+
if (subcommand === 'merge-base') {
|
|
38
|
+
if (options.failMergeBase) {
|
|
39
|
+
throw Object.assign(new Error('Command failed'), { stderr: 'fatal: no merge base' });
|
|
40
|
+
}
|
|
41
|
+
return { stdout: `${MERGE_BASE}\n`, stderr: '' };
|
|
42
|
+
}
|
|
43
|
+
if (subcommand === 'diff') {
|
|
44
|
+
return { stdout: args.includes('--name-status') ? options.nameStatus ?? '' : options.diff ?? '', stderr: '' };
|
|
45
|
+
}
|
|
46
|
+
if (subcommand === 'ls-files') {
|
|
47
|
+
return { stdout: options.untracked ?? '', stderr: '' };
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Unexpected git command: ${args.join(' ')}`);
|
|
50
|
+
};
|
|
51
|
+
return { calls, runCommand };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test('listBranchDiffFiles verifies the ref before asking git for the merge base', async () => {
|
|
55
|
+
const { calls, runCommand } = createRunner();
|
|
56
|
+
|
|
57
|
+
const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'origin/main', runCommand });
|
|
58
|
+
|
|
59
|
+
assert.equal(result.mergeBase, MERGE_BASE);
|
|
60
|
+
assert.deepEqual(calls.slice(0, 2), [
|
|
61
|
+
['rev-parse', '--verify', '--quiet', '--end-of-options', 'origin/main^{commit}'],
|
|
62
|
+
['merge-base', '--', 'origin/main', 'HEAD'],
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('listBranchDiffFiles rejects a base that would be parsed as a git option without running git', async () => {
|
|
67
|
+
const { calls, runCommand } = createRunner();
|
|
68
|
+
|
|
69
|
+
await assert.rejects(
|
|
70
|
+
listBranchDiffFiles({ projectPath: '/repo', base: '--output=/tmp/x', runCommand }),
|
|
71
|
+
(error: unknown) => {
|
|
72
|
+
assert.equal((error as { code?: string }).code, 'GIT_INVALID_BASE_REF');
|
|
73
|
+
assert.equal((error as { statusCode?: number }).statusCode, 400);
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
assert.deepEqual(calls, []);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('listBranchDiffFiles reports an unknown ref with a stable code', async () => {
|
|
81
|
+
const { runCommand } = createRunner({ failRevParse: true });
|
|
82
|
+
|
|
83
|
+
await assert.rejects(
|
|
84
|
+
listBranchDiffFiles({ projectPath: '/repo', base: 'origin/nope', runCommand }),
|
|
85
|
+
(error: unknown) => {
|
|
86
|
+
assert.equal((error as { code?: string }).code, 'GIT_UNKNOWN_BASE_REF');
|
|
87
|
+
assert.equal((error as { statusCode?: number }).statusCode, 400);
|
|
88
|
+
return true;
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('getBranchDiffForFile reports branches without common history as a 400 AppError', async () => {
|
|
94
|
+
const { runCommand } = createRunner({ failMergeBase: true });
|
|
95
|
+
|
|
96
|
+
await assert.rejects(
|
|
97
|
+
getBranchDiffForFile({ projectPath: '/repo', base: 'orphan', file: 'a.txt', runCommand, readFile: async () => '' }),
|
|
98
|
+
(error: unknown) => {
|
|
99
|
+
assert.equal((error as { name?: string }).name, 'AppError');
|
|
100
|
+
assert.equal((error as { code?: string }).code, 'GIT_NO_MERGE_BASE');
|
|
101
|
+
assert.equal((error as { statusCode?: number }).statusCode, 400);
|
|
102
|
+
return true;
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('listBranchDiffFiles parses NUL-separated name-status output including renames', async () => {
|
|
108
|
+
const { calls, runCommand } = createRunner({
|
|
109
|
+
nameStatus: nul('M', 'src/a.txt', 'R100', 'old name.txt', 'new name.txt', 'A', 'b.txt', 'D', 'gone.txt', 'C75', 'base.txt', 'copy.txt'),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'main', runCommand });
|
|
113
|
+
|
|
114
|
+
assert.equal(result.base, 'main');
|
|
115
|
+
assert.equal(result.mergeBase, MERGE_BASE);
|
|
116
|
+
assert.deepEqual(result.files, [
|
|
117
|
+
{ path: 'b.txt', status: 'A' },
|
|
118
|
+
{ path: 'copy.txt', status: 'A' },
|
|
119
|
+
{ path: 'gone.txt', status: 'D' },
|
|
120
|
+
{ path: 'new name.txt', oldPath: 'old name.txt', status: 'R' },
|
|
121
|
+
{ path: 'src/a.txt', status: 'M' },
|
|
122
|
+
]);
|
|
123
|
+
// The working tree is diffed against the merge base, never the base tip.
|
|
124
|
+
assert.deepEqual(calls[2], ['diff', '--name-status', '-M', '-z', MERGE_BASE, '--']);
|
|
125
|
+
assert.deepEqual(calls[3], ['ls-files', '--others', '--exclude-standard', '-z']);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('listBranchDiffFiles merges untracked files as U and sorts everything by path', async () => {
|
|
129
|
+
const { runCommand } = createRunner({
|
|
130
|
+
nameStatus: nul('M', 'a.txt', 'A', 'b.txt'),
|
|
131
|
+
untracked: nul('c.txt', 'aa.txt'),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'main', runCommand });
|
|
135
|
+
|
|
136
|
+
assert.deepEqual(result.files, [
|
|
137
|
+
{ path: 'a.txt', status: 'M' },
|
|
138
|
+
{ path: 'aa.txt', status: 'U' },
|
|
139
|
+
{ path: 'b.txt', status: 'A' },
|
|
140
|
+
{ path: 'c.txt', status: 'U' },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('listBranchDiffFiles returns an empty list when nothing differs', async () => {
|
|
145
|
+
const { runCommand } = createRunner();
|
|
146
|
+
|
|
147
|
+
const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'main', runCommand });
|
|
148
|
+
|
|
149
|
+
assert.deepEqual(result.files, []);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('getBranchDiffForFile diffs a tracked file against the merge base with the path after --', async () => {
|
|
153
|
+
const diff = 'diff --git a/a.txt b/a.txt\n--- a/a.txt\n+++ b/a.txt\n@@ -1,2 +1,3 @@\n line1\n-line2\n+line2 changed\n+line3\n';
|
|
154
|
+
const { calls, runCommand } = createRunner({ diff });
|
|
155
|
+
let readFileCalls = 0;
|
|
156
|
+
|
|
157
|
+
const result = await getBranchDiffForFile({
|
|
158
|
+
projectPath: '/repo',
|
|
159
|
+
base: 'main',
|
|
160
|
+
file: 'a.txt',
|
|
161
|
+
runCommand,
|
|
162
|
+
readFile: async () => {
|
|
163
|
+
readFileCalls += 1;
|
|
164
|
+
return '';
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
assert.equal(result, diff);
|
|
169
|
+
assert.equal(readFileCalls, 0);
|
|
170
|
+
assert.deepEqual(calls[2], ['ls-files', '--others', '--exclude-standard', '-z', '--', 'a.txt']);
|
|
171
|
+
assert.deepEqual(calls[3], ['diff', '-M', MERGE_BASE, '--', 'a.txt']);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('getBranchDiffForFile hands both paths of a rename to git so it diffs the rename, not a new file', async () => {
|
|
175
|
+
const diff = 'diff --git a/keep.txt b/kept.txt\nsimilarity index 90%\nrename from keep.txt\nrename to kept.txt\n@@ -1 +1 @@\n-x\n+y\n';
|
|
176
|
+
const { calls, runCommand } = createRunner({ diff });
|
|
177
|
+
|
|
178
|
+
const result = await getBranchDiffForFile({
|
|
179
|
+
projectPath: '/repo',
|
|
180
|
+
base: 'main',
|
|
181
|
+
file: 'kept.txt',
|
|
182
|
+
oldPath: 'keep.txt',
|
|
183
|
+
runCommand,
|
|
184
|
+
readFile: async () => '',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
assert.equal(result, diff);
|
|
188
|
+
assert.deepEqual(calls[3], ['diff', '-M', MERGE_BASE, '--', 'keep.txt', 'kept.txt']);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('getBranchDiffForFile synthesizes an all-additions diff for an untracked file', async () => {
|
|
192
|
+
const { calls, runCommand } = createRunner({ untracked: nul('c.txt') });
|
|
193
|
+
const readPaths: string[] = [];
|
|
194
|
+
|
|
195
|
+
const result = await getBranchDiffForFile({
|
|
196
|
+
projectPath: '/repo',
|
|
197
|
+
base: 'main',
|
|
198
|
+
file: 'c.txt',
|
|
199
|
+
runCommand,
|
|
200
|
+
readFile: async (absolutePath) => {
|
|
201
|
+
readPaths.push(absolutePath);
|
|
202
|
+
return 'first\nsecond';
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
assert.deepEqual(readPaths, ['/repo/c.txt']);
|
|
207
|
+
assert.equal(result, '--- /dev/null\n+++ b/c.txt\n@@ -0,0 +1,2 @@\n+first\n+second');
|
|
208
|
+
// No `git diff` is run for untracked files.
|
|
209
|
+
assert.equal(calls.some((args) => args[0] === 'diff'), false);
|
|
210
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import express, { type Request } from 'express';
|
|
2
|
+
|
|
3
|
+
import type { PendingCloneRequests } from '@/modules/projects/services/project-clone-request.service.js';
|
|
4
|
+
import type { startCloneProject } from '@/modules/projects/services/project-clone.service.js';
|
|
5
|
+
import { AppError, asyncHandler } from '@/shared/utils.js';
|
|
6
|
+
|
|
7
|
+
type ProjectCloneRouterDependencies = {
|
|
8
|
+
startCloneProject: typeof startCloneProject;
|
|
9
|
+
pendingCloneRequests: PendingCloneRequests;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type AuthenticatedUser = {
|
|
13
|
+
id?: number | string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function requireAuthenticatedUserId(req: Request): number | string {
|
|
17
|
+
const authenticatedUser = (req as Request & { user?: AuthenticatedUser }).user;
|
|
18
|
+
const userId = authenticatedUser?.id;
|
|
19
|
+
if (userId === undefined || userId === null) {
|
|
20
|
+
throw new AppError('Authenticated user is required', {
|
|
21
|
+
code: 'AUTHENTICATION_REQUIRED',
|
|
22
|
+
statusCode: 401,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return userId;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveRouteErrorMessage(error: unknown): string {
|
|
30
|
+
if (error instanceof AppError) {
|
|
31
|
+
return error.message;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (error instanceof Error && error.message) {
|
|
35
|
+
return error.message;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return 'Failed to clone repository';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates the two halves of a repository clone. The browser's EventSource can
|
|
43
|
+
* only issue a bare GET, and a GET's URL is written to access logs, proxy logs
|
|
44
|
+
* and browser history — so the request that carries the GitHub token is a
|
|
45
|
+
* POST, and the stream's URL names it by an opaque id.
|
|
46
|
+
*/
|
|
47
|
+
export function createProjectCloneRouter(dependencies: ProjectCloneRouterDependencies): express.Router {
|
|
48
|
+
const router = express.Router();
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parks the request under a fresh id for `/clone-progress` to pick up.
|
|
52
|
+
* Nothing is spawned here; the stream that follows runs and reports it.
|
|
53
|
+
*/
|
|
54
|
+
router.post(
|
|
55
|
+
'/clone',
|
|
56
|
+
asyncHandler(async (req, res) => {
|
|
57
|
+
const userId = requireAuthenticatedUserId(req);
|
|
58
|
+
const requestBody = req.body as Record<string, unknown>;
|
|
59
|
+
const cloneId = dependencies.pendingCloneRequests.add(userId, {
|
|
60
|
+
workspacePath: typeof requestBody.path === 'string' ? requestBody.path : '',
|
|
61
|
+
githubUrl: typeof requestBody.githubUrl === 'string' ? requestBody.githubUrl : '',
|
|
62
|
+
githubTokenId: typeof requestBody.githubTokenId === 'number' ? requestBody.githubTokenId : null,
|
|
63
|
+
newGithubToken: typeof requestBody.newGithubToken === 'string' ? requestBody.newGithubToken : null,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
res.json({ cloneId });
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Claims the request the id names before any stream headers go out, so a
|
|
72
|
+
* stale or foreign id gets an ordinary 404 instead of an empty stream.
|
|
73
|
+
*/
|
|
74
|
+
router.get(
|
|
75
|
+
'/clone-progress',
|
|
76
|
+
asyncHandler(async (req, res) => {
|
|
77
|
+
const userId = requireAuthenticatedUserId(req);
|
|
78
|
+
const cloneId = typeof req.query.cloneId === 'string' ? req.query.cloneId : '';
|
|
79
|
+
const cloneRequest = dependencies.pendingCloneRequests.claim(cloneId, userId);
|
|
80
|
+
if (!cloneRequest) {
|
|
81
|
+
throw new AppError('Clone request not found or expired', {
|
|
82
|
+
code: 'CLONE_REQUEST_NOT_FOUND',
|
|
83
|
+
statusCode: 404,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
88
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
89
|
+
res.setHeader('Connection', 'keep-alive');
|
|
90
|
+
res.flushHeaders();
|
|
91
|
+
|
|
92
|
+
const sendEvent = (type: string, data: Record<string, unknown>) => {
|
|
93
|
+
if (res.writableEnded) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// The client can go away while the clone is still validating its
|
|
101
|
+
// input and preparing the directory — before there is an operation to
|
|
102
|
+
// cancel. Remember that it did, so the git process is cancelled the
|
|
103
|
+
// moment it exists rather than left cloning for nobody: the id was
|
|
104
|
+
// claimed above, so no reconnect can ever reach this run.
|
|
105
|
+
let cloneOperation: Awaited<ReturnType<typeof startCloneProject>> | null = null;
|
|
106
|
+
let clientGone = false;
|
|
107
|
+
const closeListener = () => {
|
|
108
|
+
clientGone = true;
|
|
109
|
+
cloneOperation?.cancel();
|
|
110
|
+
};
|
|
111
|
+
req.on('close', closeListener);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
cloneOperation = await dependencies.startCloneProject(
|
|
115
|
+
{ ...cloneRequest, userId },
|
|
116
|
+
{
|
|
117
|
+
onProgress: (message) => {
|
|
118
|
+
sendEvent('progress', { message });
|
|
119
|
+
},
|
|
120
|
+
onComplete: ({ project, message }) => {
|
|
121
|
+
sendEvent('complete', { project, message });
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
if (clientGone) {
|
|
126
|
+
cloneOperation.cancel();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await cloneOperation.waitForCompletion;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
sendEvent('error', { message: resolveRouteErrorMessage(error) });
|
|
132
|
+
} finally {
|
|
133
|
+
req.off('close', closeListener);
|
|
134
|
+
if (!res.writableEnded) {
|
|
135
|
+
res.end();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return router;
|
|
142
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
|
|
3
3
|
import { createProject, updateProjectDisplayName } from '@/modules/projects/services/project-management.service.js';
|
|
4
|
+
import { createProjectCloneRouter } from '@/modules/projects/project-clone.routes.js';
|
|
5
|
+
import { createPendingCloneRequests } from '@/modules/projects/services/project-clone-request.service.js';
|
|
4
6
|
import { startCloneProject } from '@/modules/projects/services/project-clone.service.js';
|
|
5
7
|
import { getProjectTaskMaster } from '@/modules/projects/services/projects-has-taskmaster.service.js';
|
|
6
8
|
import { AppError, asyncHandler, createApiSuccessResponse } from '@/shared/utils.js';
|
|
@@ -8,11 +10,14 @@ import { getArchivedProjectsWithSessions, getProjectSessionsPage, getProjectsWit
|
|
|
8
10
|
import { deleteOrArchiveProject, restoreArchivedProject } from '@/modules/projects/services/project-delete.service.js';
|
|
9
11
|
import { applyLegacyStarredProjectIds, toggleProjectStar } from '@/modules/projects/services/project-star.service.js';
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
/**
|
|
14
|
+
* How long a posted clone request waits for its progress stream to be opened.
|
|
15
|
+
* The client opens the stream as soon as the POST resolves, so this only has
|
|
16
|
+
* to outlast a slow round trip.
|
|
17
|
+
*/
|
|
18
|
+
const PENDING_CLONE_REQUEST_TTL_MS = 60_000;
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
id?: number | string;
|
|
15
|
-
};
|
|
20
|
+
const router = express.Router();
|
|
16
21
|
|
|
17
22
|
function readQueryStringValue(value: unknown): string {
|
|
18
23
|
if (typeof value === 'string') {
|
|
@@ -53,18 +58,6 @@ function parseNonNegativeIntQuery(value: unknown, name: string, fallback: number
|
|
|
53
58
|
return parsedValue;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
function resolveRouteErrorMessage(error: unknown): string {
|
|
57
|
-
if (error instanceof AppError) {
|
|
58
|
-
return error.message;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (error instanceof Error && error.message) {
|
|
62
|
-
return error.message;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return 'Failed to clone repository';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
61
|
router.get(
|
|
69
62
|
'/',
|
|
70
63
|
asyncHandler(async (req, res) => {
|
|
@@ -119,7 +112,7 @@ router.post(
|
|
|
119
112
|
throw new AppError('Repository cloning is not supported on create-project', {
|
|
120
113
|
code: 'CLONE_NOT_SUPPORTED_ON_CREATE_PROJECT',
|
|
121
114
|
statusCode: 400,
|
|
122
|
-
details: 'Use /api/projects/clone
|
|
115
|
+
details: 'Use /api/projects/clone for cloning workflows',
|
|
123
116
|
});
|
|
124
117
|
}
|
|
125
118
|
|
|
@@ -153,70 +146,12 @@ router.post(
|
|
|
153
146
|
}),
|
|
154
147
|
);
|
|
155
148
|
|
|
156
|
-
router.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const sendEvent = (type: string, data: Record<string, unknown>) => {
|
|
163
|
-
if (res.writableEnded) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
let cloneOperation: Awaited<ReturnType<typeof startCloneProject>> | null = null;
|
|
171
|
-
const closeListener = () => {
|
|
172
|
-
cloneOperation?.cancel();
|
|
173
|
-
};
|
|
174
|
-
req.on('close', closeListener);
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const queryParams = req.query as Record<string, unknown>;
|
|
178
|
-
const workspacePath = readQueryStringValue(queryParams.path);
|
|
179
|
-
const githubUrl = readQueryStringValue(queryParams.githubUrl);
|
|
180
|
-
const githubTokenId = readOptionalNumericQueryValue(queryParams.githubTokenId);
|
|
181
|
-
const newGithubToken = readQueryStringValue(queryParams.newGithubToken) || null;
|
|
182
|
-
|
|
183
|
-
const authenticatedUser = (req as typeof req & { user?: AuthenticatedUser }).user;
|
|
184
|
-
const userId = authenticatedUser?.id;
|
|
185
|
-
if (userId === undefined || userId === null) {
|
|
186
|
-
throw new AppError('Authenticated user is required', {
|
|
187
|
-
code: 'AUTHENTICATION_REQUIRED',
|
|
188
|
-
statusCode: 401,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
cloneOperation = await startCloneProject(
|
|
193
|
-
{
|
|
194
|
-
workspacePath,
|
|
195
|
-
githubUrl,
|
|
196
|
-
githubTokenId,
|
|
197
|
-
newGithubToken,
|
|
198
|
-
userId,
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
onProgress: (message) => {
|
|
202
|
-
sendEvent('progress', { message });
|
|
203
|
-
},
|
|
204
|
-
onComplete: ({ project, message }) => {
|
|
205
|
-
sendEvent('complete', { project, message });
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
await cloneOperation.waitForCompletion;
|
|
211
|
-
} catch (error) {
|
|
212
|
-
sendEvent('error', { message: resolveRouteErrorMessage(error) });
|
|
213
|
-
} finally {
|
|
214
|
-
req.off('close', closeListener);
|
|
215
|
-
if (!res.writableEnded) {
|
|
216
|
-
res.end();
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
});
|
|
149
|
+
router.use(
|
|
150
|
+
createProjectCloneRouter({
|
|
151
|
+
startCloneProject,
|
|
152
|
+
pendingCloneRequests: createPendingCloneRequests(PENDING_CLONE_REQUEST_TTL_MS),
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
220
155
|
|
|
221
156
|
router.get(
|
|
222
157
|
'/:projectId/taskmaster',
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export type PendingCloneRequest = {
|
|
4
|
+
workspacePath: string;
|
|
5
|
+
githubUrl: string;
|
|
6
|
+
githubTokenId: number | null;
|
|
7
|
+
newGithubToken: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type PendingCloneRequests = {
|
|
11
|
+
add: (userId: number | string, request: PendingCloneRequest) => string;
|
|
12
|
+
claim: (cloneId: string, userId: number | string) => PendingCloneRequest | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type PendingCloneEntry = {
|
|
16
|
+
userId: number | string;
|
|
17
|
+
request: PendingCloneRequest;
|
|
18
|
+
expiry: NodeJS.Timeout;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Holds clone requests between the POST that submits them and the EventSource
|
|
23
|
+
* GET that runs them. The browser's EventSource can only issue a bare GET, so
|
|
24
|
+
* whatever it needs has to travel in the URL — and a URL is written to access
|
|
25
|
+
* logs, proxy logs and browser history. The token therefore arrives in a POST
|
|
26
|
+
* body and waits here, and the GET carries nothing but the opaque id.
|
|
27
|
+
*
|
|
28
|
+
* Every entry is bound to the user who posted it, is handed out once, and is
|
|
29
|
+
* dropped after `ttlMs` whether or not it was claimed.
|
|
30
|
+
*/
|
|
31
|
+
export function createPendingCloneRequests(ttlMs: number): PendingCloneRequests {
|
|
32
|
+
const pending = new Map<string, PendingCloneEntry>();
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
add(userId, request) {
|
|
36
|
+
const cloneId = randomUUID();
|
|
37
|
+
const expiry = setTimeout(() => {
|
|
38
|
+
pending.delete(cloneId);
|
|
39
|
+
}, ttlMs);
|
|
40
|
+
// An abandoned request must not keep the process alive on shutdown.
|
|
41
|
+
expiry.unref();
|
|
42
|
+
pending.set(cloneId, { userId, request, expiry });
|
|
43
|
+
return cloneId;
|
|
44
|
+
},
|
|
45
|
+
claim(cloneId, userId) {
|
|
46
|
+
const entry = pending.get(cloneId);
|
|
47
|
+
if (!entry || entry.userId !== userId) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
clearTimeout(entry.expiry);
|
|
52
|
+
pending.delete(cloneId);
|
|
53
|
+
return entry.request;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|