@cloudcli-ai/cloudcli 1.37.3 → 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 +1 -1
- package/dist/assets/{abnfDiagram-N423BO3Z-CJntskwA.js → abnfDiagram-N423BO3Z-Dmkwx38H.js} +1 -1
- package/dist/assets/{arc-CIfH4uMN.js → arc-DLe9AYuO.js} +1 -1
- package/dist/assets/{architectureDiagram-T3A2C74G-_IpEkukg.js → architectureDiagram-T3A2C74G-CBbbbNUW.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-BttxrpgZ.js → blockDiagram-VBNYF7ZC-CaMEaWu4.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-g51dSRr1.js → c4Diagram-5PPSVZJV-DhjMgNnR.js} +1 -1
- package/dist/assets/channel-CWrmwsir.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-D_-LbQUa.js → chunk-2GRJ4B5K-yjl3-F-i.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-BtlHzwaO.js → chunk-2Q5K7J3B-DsAYaWEP.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-UzkpvpAT.js → chunk-5RXB4S5H-Bt5qmqOX.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BKwx5Gag.js → chunk-5VM5RSS4-yWrWVvUt.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-D_KEBH5f.js → chunk-6Q2QTUOP-Dt66K5xb.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-rK2DGyto.js → chunk-GF5L2VYU-DA0vzaj4.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CeBl_N9i.js → chunk-JWPE2WC7-CUkbx96V.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-BiQLWVIr.js → chunk-KBJHAD2P-DWKxx1WH.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-C1knVGV5.js → chunk-RYQCIY6F-BqluplN0.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CtKbJkvF.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-DjX8y40Z.js → cose-bilkent-JH36ORCC-DHV93-Em.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-DM6YeRba.js → cynefin-VYW2F7L2-B9HZgoGX.js} +1 -1
- package/dist/assets/{cynefinDiagram-MW4NZA55-wZJ-vy5B.js → cynefinDiagram-MW4NZA55-hFjjEQg8.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-BzP0ejkH.js → dagre-VZM6K2ZE-CpeVOC7V.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-BTL_j23J.js → diagram-7IWD3JNH-D0H1UoRA.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-Cf1Norgu.js → diagram-B4RE2ZJO-6Y7BQUup.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-DPiUEKr-.js → diagram-LBJQPF4R-C0nNOwht.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-CTcTpiz5.js → diagram-Q27KOJAE-En6QlOVX.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-DPMSdYm0.js → diagram-UB23O5K3-CmPiNeGD.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-7yvD6uiF.js → ebnfDiagram-BXEA7PRR-Bygzp1Qt.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-CfgeDoV6.js → erDiagram-JOGREHBK-CwScRfzl.js} +1 -1
- package/dist/assets/{flowDiagram-UKHOOZJN-B27ng5zu.js → flowDiagram-UKHOOZJN-bKX-0xP6.js} +1 -1
- package/dist/assets/{ganttDiagram-PKOTCBZU-CcC6b_7t.js → ganttDiagram-PKOTCBZU-DtZPKXGd.js} +1 -1
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-BX5kPGrO.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-CUP-SlN4.js → infoDiagram-6WML65LV-ciRQTS0h.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-BTTDhOpX.js → ishikawaDiagram-WSZJBQD7-CgjhQZ84.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-ARXcc61m.js → journeyDiagram-NVQOT4AX-BsbQSeIW.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-BieMpqMA.js → kanban-definition-27J2QSJJ-CrwzY9Jw.js} +1 -1
- package/dist/assets/{linear-61vxjRnP.js → linear-BUaCDr6l.js} +1 -1
- package/dist/assets/{mermaid.core-nhnwkfQx.js → mermaid.core-ClgdUU0j.js} +5 -5
- package/dist/assets/{mindmap-definition-FAOFIHXS-Cm6x5vT1.js → mindmap-definition-FAOFIHXS-BictljY1.js} +1 -1
- package/dist/assets/{pegDiagram-VL7TDLO6-CcYBq_9H.js → pegDiagram-VL7TDLO6-BXdByy_U.js} +1 -1
- package/dist/assets/{pieDiagram-7S7Q4E2Y-CfvIjfzo.js → pieDiagram-7S7Q4E2Y-uZsMfkRX.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-SgYoWPNb.js → quadrantDiagram-CIZ2JOQS-7lCv4QIj.js} +1 -1
- package/dist/assets/{railroadDiagram-AXF67PYL-D2Mu5E_f.js → railroadDiagram-AXF67PYL-07dxIWbi.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-Azwe_k4Q.js → requirementDiagram-LRYGKXZP-Cb31Yv1_.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-DN1Ju3PA.js → sankeyDiagram-W5VNT64P-X0YPSyF9.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-Bj4qC34S.js → sequenceDiagram-SI44F4Z6-BdBxSIlP.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-Co4PdklG.js → sizeCapture-X5ZJPWSS-DQM5xAyD.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-nOvL9dRC.js → stateDiagram-OKZ733FA-DJ3xkLUF.js} +1 -1
- package/dist/assets/{stateDiagram-v2-UEYNNEHI-DNv8ppw7.js → stateDiagram-v2-UEYNNEHI-vAaE4zNo.js} +1 -1
- package/dist/assets/{swimlanes-SLNWSIFB-CCK3wMid.js → swimlanes-SLNWSIFB-rjfYdW0w.js} +2 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-TV2IF4Nq.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-BC29RNFm.js → timeline-definition-Z64GVDOM-DZMra5do.js} +1 -1
- package/dist/assets/{vennDiagram-T6HMQDX7-pZlZCtEo.js → vennDiagram-T6HMQDX7-BevD8uu0.js} +1 -1
- package/dist/assets/{wardleyDiagram-T6FBY63Y-De8k2-zM.js → wardleyDiagram-T6FBY63Y-DsIyXB7z.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-Bk8ykH0w.js → xychartDiagram-ELKLHX3M-G3PlF3ko.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +4 -1
- 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 +175 -128
- 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 +18 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +13 -0
- 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/sessions.db.js +7 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +7 -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/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/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/list/claude/claude-auth.provider.js +42 -8
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +6 -16
- 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 +305 -16
- 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 +686 -77
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +31 -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 +11 -4
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +57 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.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 +8 -2
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +16 -3
- 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 +110 -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 +9 -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/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 +4 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +73 -3
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +131 -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-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 +1368 -12
- 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/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 +32 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +50 -0
- 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 +79 -0
- 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.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-history-cache.service.test.js +19 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +175 -1
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +13 -1
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -1
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +36 -1
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +27 -5
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +38 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +230 -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-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/shell-websocket.service.test.js +420 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
- package/dist-server/server/shared/message-unification.js +1 -1
- package/dist-server/server/shared/message-unification.js.map +1 -1
- 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 +126 -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 +4 -4
- package/server/index.ts +5 -1
- package/server/modules/agent/agent.module.ts +11 -1
- package/server/modules/agent/agent.routes.ts +198 -140
- package/server/modules/agent/tests/agent-run-registration.test.ts +348 -0
- package/server/modules/agent/tests/agent.routes.test.ts +18 -1
- package/server/modules/database/migrations.ts +14 -0
- package/server/modules/database/repositories/provider-models.ts +79 -6
- package/server/modules/database/repositories/sessions.db.ts +8 -0
- package/server/modules/database/schema.ts +7 -0
- package/server/modules/database/tests/provider-models.db.integration.test.ts +113 -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/tests/project-clone.routes.test.ts +246 -0
- package/server/modules/projects/tests/project-clone.service.test.ts +376 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +60 -10
- package/server/modules/providers/list/claude/claude-models.provider.ts +10 -22
- package/server/modules/providers/list/claude/claude-runtime.provider.js +322 -15
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +40 -14
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +796 -88
- package/server/modules/providers/list/codex/codex-models.provider.ts +31 -0
- package/server/modules/providers/list/codex/codex-runtime.provider.ts +12 -4
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +63 -0
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +10 -2
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +9 -2
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +18 -3
- 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 +131 -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 +17 -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/session-conversations-search.service.ts +2 -5
- package/server/modules/providers/services/session-history-cache.service.ts +13 -2
- package/server/modules/providers/services/sessions.service.ts +108 -8
- package/server/modules/providers/tests/claude-auth.test.ts +150 -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-runtime-hold.test.ts +309 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +1630 -12
- package/server/modules/providers/tests/claude-settings-permissions.test.ts +271 -0
- package/server/modules/providers/tests/codex-models.test.ts +37 -0
- package/server/modules/providers/tests/codex-runtime.test.ts +36 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +59 -0
- package/server/modules/providers/tests/cursor-auth.test.ts +187 -0
- package/server/modules/providers/tests/opencode-models.test.ts +100 -0
- 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.routes.test.ts +230 -1
- package/server/modules/providers/tests/session-history-cache.service.test.ts +22 -0
- package/server/modules/providers/tests/sessions.service.test.ts +237 -1
- package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +14 -1
- package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +45 -1
- package/server/modules/websocket/README.md +4 -2
- package/server/modules/websocket/services/chat-run-registry.service.ts +29 -5
- package/server/modules/websocket/services/chat-websocket.service.ts +58 -1
- package/server/modules/websocket/services/shell-websocket.service.ts +269 -22
- package/server/modules/websocket/tests/chat-background-subscribe.test.ts +167 -0
- package/server/modules/websocket/tests/chat-stop-task.test.ts +127 -0
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +521 -0
- package/server/shared/interfaces.ts +29 -0
- package/server/shared/message-unification.ts +1 -1
- 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 +261 -7
- package/server/shared/utils.ts +136 -0
- package/dist/assets/channel-C5xU9jMw.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +0 -1
- package/dist/assets/index-BTDHQ6Id.css +0 -32
- package/dist/assets/index-sFfz8iBR.js +0 -4931
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +0 -8
|
@@ -34,6 +34,37 @@ export const CODEX_PREDEFINED_MODELS: ProviderModelsDefinition = {
|
|
|
34
34
|
],
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
value: 'gpt-6-sol',
|
|
39
|
+
label: 'GPT-6 Sol',
|
|
40
|
+
description: 'Workhorse model for coding and everyday work.',
|
|
41
|
+
effort: {
|
|
42
|
+
default: 'medium',
|
|
43
|
+
values: [
|
|
44
|
+
{ value: 'low' },
|
|
45
|
+
{ value: 'medium' },
|
|
46
|
+
{ value: 'high' },
|
|
47
|
+
{ value: 'xhigh' },
|
|
48
|
+
{ value: 'max' },
|
|
49
|
+
{ value: 'ultra' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: 'gpt-6-luna',
|
|
55
|
+
label: 'GPT-6 Luna',
|
|
56
|
+
description: 'Fast and affordable model for easier tasks.',
|
|
57
|
+
effort: {
|
|
58
|
+
default: 'medium',
|
|
59
|
+
values: [
|
|
60
|
+
{ value: 'low' },
|
|
61
|
+
{ value: 'medium' },
|
|
62
|
+
{ value: 'high' },
|
|
63
|
+
{ value: 'xhigh' },
|
|
64
|
+
{ value: 'max' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
37
68
|
{
|
|
38
69
|
value: 'gpt-5.6-sol',
|
|
39
70
|
label: 'GPT-5.6 Sol',
|
|
@@ -34,6 +34,10 @@ type ActiveCodexSession = {
|
|
|
34
34
|
|
|
35
35
|
const activeCodexSessions = new Map<string, ActiveCodexSession>();
|
|
36
36
|
|
|
37
|
+
// Codex CLI requires non-whitespace stdin even when --image arguments are
|
|
38
|
+
// present, so attachment-only turns need a small text instruction.
|
|
39
|
+
const CODEX_IMAGE_ONLY_PROMPT = 'Please analyze the attached image(s).';
|
|
40
|
+
|
|
37
41
|
/**
|
|
38
42
|
* Item types whose in-flight updates are worth showing. These are the ones a
|
|
39
43
|
* user waits on — a shell command's output, an MCP call, and the running plan.
|
|
@@ -341,8 +345,12 @@ async function queryCodex(
|
|
|
341
345
|
// Execute with streaming. Turns with image attachments send structured
|
|
342
346
|
// input items so Codex reads the images from their local asset paths.
|
|
343
347
|
const promptWithFiles = appendFilesInputTag(command, files);
|
|
344
|
-
const
|
|
345
|
-
|
|
348
|
+
const normalizedImages = normalizeImageDescriptors(images);
|
|
349
|
+
const promptWithImageFallback = !promptWithFiles.trim() && normalizedImages.length > 0
|
|
350
|
+
? CODEX_IMAGE_ONLY_PROMPT
|
|
351
|
+
: promptWithFiles;
|
|
352
|
+
const turnInput = normalizedImages.length > 0
|
|
353
|
+
? buildCodexInputItems(promptWithImageFallback, normalizedImages, workingDirectory)
|
|
346
354
|
: promptWithFiles;
|
|
347
355
|
const streamedTurn = await thread.runStreamed(turnInput, {
|
|
348
356
|
signal: abortController.signal
|
|
@@ -528,8 +536,8 @@ export const codexRuntime = {
|
|
|
528
536
|
*/
|
|
529
537
|
function sendMessage(ws: ProviderRuntimeWriter, data: unknown) {
|
|
530
538
|
try {
|
|
531
|
-
if (ws.
|
|
532
|
-
//
|
|
539
|
+
if (ws.isWebSocketWriter) {
|
|
540
|
+
// The gateway writer handles stringification
|
|
533
541
|
ws.send(data);
|
|
534
542
|
} else if (typeof ws.send === 'function') {
|
|
535
543
|
// Raw WebSocket - stringify here
|
|
@@ -220,6 +220,39 @@ function extractCodexTextContent(content: unknown): string {
|
|
|
220
220
|
.join('\n');
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
/** Reads image inputs from a typed UserMessage item. */
|
|
224
|
+
function extractCodexTypedUserImages(
|
|
225
|
+
content: unknown,
|
|
226
|
+
): Array<{ path?: string; data?: string }> | undefined {
|
|
227
|
+
if (!Array.isArray(content)) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const attachments: Array<{ path?: string; data?: string }> = [];
|
|
232
|
+
for (const rawEntry of content) {
|
|
233
|
+
const entry = readObjectRecord(rawEntry);
|
|
234
|
+
if (!entry) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const value = entry.type === 'local_image'
|
|
239
|
+
? readNonEmptyString(entry.path)
|
|
240
|
+
: entry.type === 'image'
|
|
241
|
+
? readNonEmptyString(entry.image_url)
|
|
242
|
+
: undefined;
|
|
243
|
+
if (!value) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (value.startsWith('data:')) {
|
|
247
|
+
attachments.push({ data: value });
|
|
248
|
+
} else {
|
|
249
|
+
attachments.push(...toImageAttachments([value]));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return attachments.length > 0 ? attachments : undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
223
256
|
/**
|
|
224
257
|
* Reads the markdown out of Codex's `<proposed_plan>` envelope.
|
|
225
258
|
*
|
|
@@ -1312,6 +1345,36 @@ async function getCodexSessionMessages(sessionId: string): Promise<CodexHistoryR
|
|
|
1312
1345
|
continue;
|
|
1313
1346
|
}
|
|
1314
1347
|
|
|
1348
|
+
// Paginated rollouts carry user prompts as `item_completed` /
|
|
1349
|
+
// `UserMessage`; wire `response_item` user rows are internal, not prompts.
|
|
1350
|
+
if (payload.type === 'item_completed') {
|
|
1351
|
+
const item = readObjectRecord(payload.item);
|
|
1352
|
+
if (item?.type === 'UserMessage') {
|
|
1353
|
+
const content = extractCodexTextContent(item.content);
|
|
1354
|
+
const images = extractCodexTypedUserImages(item.content);
|
|
1355
|
+
if (content.trim() || images?.length) {
|
|
1356
|
+
// Only the first prompt of a turn is anchored, matching the legacy
|
|
1357
|
+
// `user_message` branch below: a turn can hold several prompts and
|
|
1358
|
+
// the edit cut is per turn.
|
|
1359
|
+
const turnId = readNonEmptyString(payload.turn_id)
|
|
1360
|
+
?? turns.getCurrentTurnId();
|
|
1361
|
+
const isFirstPromptOfTurn = Boolean(turnId) && !anchoredTurnIds.has(turnId as string);
|
|
1362
|
+
if (isFirstPromptOfTurn) {
|
|
1363
|
+
anchoredTurnIds.add(turnId as string);
|
|
1364
|
+
}
|
|
1365
|
+
const row: AnyRecord = {
|
|
1366
|
+
type: 'user',
|
|
1367
|
+
timestamp,
|
|
1368
|
+
message: { role: 'user', content },
|
|
1369
|
+
...(images ? { images } : {}),
|
|
1370
|
+
...(isFirstPromptOfTurn ? { turnId } : {}),
|
|
1371
|
+
};
|
|
1372
|
+
messages.push(row);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1315
1378
|
if (isVisibleCodexUserMessage(payload)) {
|
|
1316
1379
|
// Only the first prompt of a turn is anchored. A turn can hold more
|
|
1317
1380
|
// than one — a follow-up queued while the turn was running is written
|
|
@@ -10,6 +10,12 @@ type CursorLoginStatus = {
|
|
|
10
10
|
error?: string;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// `cursor-agent status` decides "logged in" from the locally stored tokens, then fetches the
|
|
14
|
+
// account details from Cursor's API. That request cannot change the verdict (on failure the CLI
|
|
15
|
+
// still reports logged in), it only adds a network round trip to the CLI's startup (3-6 s on a
|
|
16
|
+
// busy host even when logged out). A 5 s cap reported fresh logins as "Command timeout" (#551).
|
|
17
|
+
const STATUS_TIMEOUT_MS = 20_000;
|
|
18
|
+
|
|
13
19
|
export class CursorProviderAuth implements IProviderAuth {
|
|
14
20
|
/**
|
|
15
21
|
* Checks whether the cursor-agent CLI is available on this host.
|
|
@@ -71,7 +77,7 @@ export class CursorProviderAuth implements IProviderAuth {
|
|
|
71
77
|
error: 'Command timeout',
|
|
72
78
|
});
|
|
73
79
|
}
|
|
74
|
-
},
|
|
80
|
+
}, STATUS_TIMEOUT_MS);
|
|
75
81
|
|
|
76
82
|
try {
|
|
77
83
|
childProcess = spawn('cursor-agent', ['status']);
|
|
@@ -112,8 +118,10 @@ export class CursorProviderAuth implements IProviderAuth {
|
|
|
112
118
|
return;
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
// e.g. "Logged in (unable to fetch user details)" when the account request failed.
|
|
122
|
+
// There is no email to show, so the UI uses its own fallback label.
|
|
115
123
|
if (stdout.includes('Logged in')) {
|
|
116
|
-
resolve({ authenticated: true, email:
|
|
124
|
+
resolve({ authenticated: true, email: null, method: 'cli' });
|
|
117
125
|
return;
|
|
118
126
|
}
|
|
119
127
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
normalizeAttachmentDescriptors
|
|
7
7
|
} from '@/shared/image-attachments.js';
|
|
8
8
|
import { notifyRunFailed, notifyRunStopped } from '@/modules/notifications/index.js';
|
|
9
|
-
import { createCompleteMessage, createNormalizedMessage, flattenPromptForWindowsShell } from '@/shared/utils.js';
|
|
9
|
+
import { createCompleteMessage, createNormalizedMessage, flattenPromptForWindowsShell, stripAnsiSequences } from '@/shared/utils.js';
|
|
10
10
|
|
|
11
11
|
// cross-spawn resolves .cmd shims/PATHEXT on Windows and delegates to
|
|
12
12
|
// child_process.spawn everywhere else.
|
|
@@ -277,7 +277,14 @@ async function spawnCursor(command, options = {}, ws, context) {
|
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
// The CLI styles its stderr for a terminal; the chat renders plain
|
|
281
|
+
// text, so the escapes have to go before the text is surfaced.
|
|
282
|
+
const cleanedStderrText = stripAnsiSequences(stderrText);
|
|
283
|
+
if (!cleanedStderrText.trim()) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
ws.send(createNormalizedMessage({ kind: 'error', content: cleanedStderrText, sessionId: capturedSessionId || sessionId || null, provider: 'cursor' }));
|
|
281
288
|
});
|
|
282
289
|
|
|
283
290
|
// Handle process completion
|
|
@@ -406,11 +406,26 @@ const parseOpenCodeSessionModelValue = (rawModel: unknown): string | null => {
|
|
|
406
406
|
return null;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
|
|
409
|
+
const modelId = readOptionalString(record.id)
|
|
410
410
|
?? readOptionalString(record.model)
|
|
411
411
|
?? readOptionalString(record.name)
|
|
412
|
-
?? readOptionalString(record.value)
|
|
413
|
-
|
|
412
|
+
?? readOptionalString(record.value);
|
|
413
|
+
if (!modelId) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// OpenCode persists the provider and model id in separate fields (for
|
|
418
|
+
// example {"id":"z-ai/glm-5.3-flash","providerID":"openrouter"}), and its
|
|
419
|
+
// CLI's `--model` flag only accepts the composed `<providerID>/<modelID>`
|
|
420
|
+
// form. Compose it here so a resumed session resolves the same model
|
|
421
|
+
// instead of failing with an unknown-model error.
|
|
422
|
+
const providerId = readOptionalString(record.providerID)
|
|
423
|
+
?? readOptionalString(record.providerId);
|
|
424
|
+
if (providerId && !modelId.startsWith(`${providerId}/`)) {
|
|
425
|
+
return `${providerId}/${modelId}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return modelId;
|
|
414
429
|
};
|
|
415
430
|
|
|
416
431
|
/** Provider registry model adapter for OpenCode predefined models and session metadata. */
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
normalizeAttachmentDescriptors
|
|
10
10
|
} from '@/shared/image-attachments.js';
|
|
11
11
|
import { notifyRunFailed, notifyRunStopped } from '@/modules/notifications/index.js';
|
|
12
|
-
import { createCompleteMessage, createNormalizedMessage, flattenPromptForWindowsShell, getOpenCodeDatabasePath } from '@/shared/utils.js';
|
|
12
|
+
import { createCompleteMessage, createNormalizedMessage, flattenPromptForWindowsShell, getOpenCodeDatabasePath, stripAnsiSequences } from '@/shared/utils.js';
|
|
13
13
|
|
|
14
14
|
// cross-spawn resolves .cmd shims/PATHEXT on Windows and delegates to
|
|
15
15
|
// child_process.spawn everywhere else.
|
|
@@ -308,7 +308,10 @@ async function spawnOpenCode(command, options = {}, ws, context) {
|
|
|
308
308
|
});
|
|
309
309
|
|
|
310
310
|
opencodeProcess.stderr.on('data', (data) => {
|
|
311
|
-
|
|
311
|
+
// opencode styles its stderr for a terminal; the chat renders plain
|
|
312
|
+
// text, so the escapes have to go before the text is surfaced. A chunk
|
|
313
|
+
// that was styling only cleans down to nothing and is not an error.
|
|
314
|
+
const stderrText = stripAnsiSequences(data.toString());
|
|
312
315
|
if (!stderrText.trim()) {
|
|
313
316
|
return;
|
|
314
317
|
}
|
|
@@ -28,17 +28,23 @@ type SynchronizeRowsResult = {
|
|
|
28
28
|
firstSessionId: string | null;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
type OpenCodeChildSessionRow = {
|
|
32
|
+
id: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
31
35
|
/**
|
|
32
36
|
* Session indexer for OpenCode's SQLite-backed session store.
|
|
33
37
|
*/
|
|
34
38
|
export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
35
39
|
private readonly provider = 'opencode' as const;
|
|
40
|
+
private childSessionsReconciled = false;
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
43
|
* Scans OpenCode's shared opencode.db and upserts active sessions into DB.
|
|
39
44
|
*/
|
|
40
45
|
async synchronize(since?: Date): Promise<number> {
|
|
41
|
-
|
|
46
|
+
// The first provider-wide scan also reconciles child rows indexed by older versions.
|
|
47
|
+
const result = this.synchronizeRows(since, undefined, !this.childSessionsReconciled);
|
|
42
48
|
return result.processed;
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -54,7 +60,11 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
54
60
|
return result.firstSessionId;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
private synchronizeRows(
|
|
63
|
+
private synchronizeRows(
|
|
64
|
+
since?: Date,
|
|
65
|
+
limit?: number,
|
|
66
|
+
pruneChildSessions = false,
|
|
67
|
+
): SynchronizeRowsResult {
|
|
58
68
|
const dbPath = getOpenCodeDatabasePath();
|
|
59
69
|
if (!fsSync.existsSync(dbPath)) {
|
|
60
70
|
return { processed: 0, firstSessionId: null };
|
|
@@ -62,6 +72,11 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
62
72
|
|
|
63
73
|
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
64
74
|
try {
|
|
75
|
+
if (pruneChildSessions) {
|
|
76
|
+
this.pruneChildSessions(db);
|
|
77
|
+
this.childSessionsReconciled = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
const sinceMillis = since?.getTime() ?? null;
|
|
66
81
|
const limitClause = limit ? 'LIMIT ?' : '';
|
|
67
82
|
const params = limit ? [sinceMillis, sinceMillis, limit] : [sinceMillis, sinceMillis];
|
|
@@ -76,6 +91,7 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
76
91
|
FROM session s
|
|
77
92
|
LEFT JOIN project p ON p.id = s.project_id
|
|
78
93
|
WHERE s.time_archived IS NULL
|
|
94
|
+
AND s.parent_id IS NULL
|
|
79
95
|
AND (? IS NULL OR COALESCE(s.time_updated, s.time_created, 0) >= ?)
|
|
80
96
|
ORDER BY COALESCE(s.time_updated, s.time_created, 0) DESC, s.id DESC
|
|
81
97
|
${limitClause}
|
|
@@ -105,6 +121,18 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
105
121
|
}
|
|
106
122
|
}
|
|
107
123
|
|
|
124
|
+
private pruneChildSessions(db: Database.Database): void {
|
|
125
|
+
const childSessions = db.prepare(`
|
|
126
|
+
SELECT id
|
|
127
|
+
FROM session
|
|
128
|
+
WHERE parent_id IS NOT NULL
|
|
129
|
+
`).all() as OpenCodeChildSessionRow[];
|
|
130
|
+
|
|
131
|
+
for (const childSession of childSessions) {
|
|
132
|
+
sessionsDb.deleteSessionByProviderSessionId(childSession.id, this.provider);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
108
136
|
private upsertSession(db: Database.Database, row: OpenCodeSessionRow): string | null {
|
|
109
137
|
const sessionId = readOptionalString(row.id);
|
|
110
138
|
const projectPath = readOptionalString(row.directory) ?? readOptionalString(row.worktree);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import express, { type Request, type Response } from 'express';
|
|
2
2
|
|
|
3
3
|
import { providerAuthService } from '@/modules/providers/services/provider-auth.service.js';
|
|
4
|
+
import { claudeSettingsPermissionsService } from '@/modules/providers/services/claude-settings-permissions.service.js';
|
|
4
5
|
import { providerCapabilitiesService } from '@/modules/providers/services/provider-capabilities.service.js';
|
|
5
6
|
import { providerMcpService } from '@/modules/providers/services/mcp.service.js';
|
|
6
7
|
import { providerModelsService } from '@/modules/providers/services/provider-models.service.js';
|
|
@@ -9,6 +10,7 @@ import { providerSkillsService } from '@/modules/providers/services/skills.servi
|
|
|
9
10
|
import { sessionConversationsSearchService } from '@/modules/providers/services/session-conversations-search.service.js';
|
|
10
11
|
import { sessionsService } from '@/modules/providers/services/sessions.service.js';
|
|
11
12
|
import type {
|
|
13
|
+
CustomProviderModelEffort,
|
|
12
14
|
CustomProviderModelInput,
|
|
13
15
|
LLMProvider,
|
|
14
16
|
McpScope,
|
|
@@ -53,6 +55,35 @@ const parseSessionId = (value: unknown): string => {
|
|
|
53
55
|
return sessionId;
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
/** A workflow run id as the harness mints one (`wf_16fbf852-274`). */
|
|
59
|
+
const WORKFLOW_RUN_ID_PATTERN = /^wf_[A-Za-z0-9-]+$/;
|
|
60
|
+
/** A workflow agent id as the CLI mints one: `a` and sixteen hex digits. */
|
|
61
|
+
const WORKFLOW_AGENT_ID_PATTERN = /^a[0-9a-f]{16}$/;
|
|
62
|
+
|
|
63
|
+
// Both ids name a file under the session's transcript directory, so nothing
|
|
64
|
+
// outside these shapes may reach the filesystem.
|
|
65
|
+
const parseWorkflowRunId = (value: unknown): string => {
|
|
66
|
+
const runId = readPathParam(value, 'runId');
|
|
67
|
+
if (!WORKFLOW_RUN_ID_PATTERN.test(runId)) {
|
|
68
|
+
throw new AppError('Invalid workflow run id.', {
|
|
69
|
+
code: 'INVALID_WORKFLOW_RUN_ID',
|
|
70
|
+
statusCode: 400,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return runId;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const parseWorkflowAgentId = (value: unknown): string => {
|
|
77
|
+
const agentId = readPathParam(value, 'agentId');
|
|
78
|
+
if (!WORKFLOW_AGENT_ID_PATTERN.test(agentId)) {
|
|
79
|
+
throw new AppError('Invalid workflow agent id.', {
|
|
80
|
+
code: 'INVALID_WORKFLOW_AGENT_ID',
|
|
81
|
+
statusCode: 400,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return agentId;
|
|
85
|
+
};
|
|
86
|
+
|
|
56
87
|
const readOptionalQueryString = (value: unknown): string | undefined => {
|
|
57
88
|
if (typeof value !== 'string') {
|
|
58
89
|
return undefined;
|
|
@@ -465,6 +496,78 @@ const parseModelRecordId = (value: unknown): number => {
|
|
|
465
496
|
return recordId;
|
|
466
497
|
};
|
|
467
498
|
|
|
499
|
+
const invalidModelEffort = (message: string): AppError => new AppError(message, {
|
|
500
|
+
code: 'INVALID_MODEL_EFFORT',
|
|
501
|
+
statusCode: 400,
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Upper bound on declared effort levels, far above any provider's level set.
|
|
506
|
+
* Checked before the per-entry loop so an oversized body (the JSON parser
|
|
507
|
+
* accepts up to 50 MB) is rejected at once instead of being walked in full.
|
|
508
|
+
*/
|
|
509
|
+
const MAX_MODEL_EFFORT_LEVELS = 32;
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Parses the optional `effort` field of a custom-model payload.
|
|
513
|
+
*
|
|
514
|
+
* Only the shape is checked here (unique, trimmed, non-empty level ids and a
|
|
515
|
+
* default drawn from them); which levels a provider accepts is decided by the
|
|
516
|
+
* Providers service. `undefined` means "not sent" and `null` or an empty
|
|
517
|
+
* `values` list means "no effort levels".
|
|
518
|
+
*/
|
|
519
|
+
const parseCustomProviderModelEffort = (
|
|
520
|
+
value: unknown,
|
|
521
|
+
): CustomProviderModelEffort | null | undefined => {
|
|
522
|
+
if (value === undefined) {
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
if (value === null) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
529
|
+
throw invalidModelEffort('effort must be an object with a values array.');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const effort = value as Record<string, unknown>;
|
|
533
|
+
if (!Array.isArray(effort.values)) {
|
|
534
|
+
throw invalidModelEffort('effort.values must be an array of effort levels.');
|
|
535
|
+
}
|
|
536
|
+
if (effort.values.length > MAX_MODEL_EFFORT_LEVELS) {
|
|
537
|
+
throw invalidModelEffort(`effort.values can list at most ${MAX_MODEL_EFFORT_LEVELS} effort levels.`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const values: string[] = [];
|
|
541
|
+
const seenLevels = new Set<string>();
|
|
542
|
+
for (const entry of effort.values) {
|
|
543
|
+
const level = typeof entry === 'string' ? entry.trim() : '';
|
|
544
|
+
if (!level || level.length > 40) {
|
|
545
|
+
throw invalidModelEffort('effort.values must contain non-empty strings of 40 characters or fewer.');
|
|
546
|
+
}
|
|
547
|
+
if (seenLevels.has(level)) {
|
|
548
|
+
throw invalidModelEffort(`effort.values contains "${level}" more than once.`);
|
|
549
|
+
}
|
|
550
|
+
seenLevels.add(level);
|
|
551
|
+
values.push(level);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (effort.default !== undefined && effort.default !== null && typeof effort.default !== 'string') {
|
|
555
|
+
throw invalidModelEffort('effort.default must be a string.');
|
|
556
|
+
}
|
|
557
|
+
const defaultLevel = readOptionalQueryString(effort.default);
|
|
558
|
+
if (values.length === 0) {
|
|
559
|
+
if (defaultLevel) {
|
|
560
|
+
throw invalidModelEffort('effort.default requires at least one effort level.');
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
if (defaultLevel && !values.includes(defaultLevel)) {
|
|
565
|
+
throw invalidModelEffort('effort.default must be one of effort.values.');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return defaultLevel ? { values, default: defaultLevel } : { values };
|
|
569
|
+
};
|
|
570
|
+
|
|
468
571
|
const parseCustomProviderModelPayload = (payload: unknown): CustomProviderModelInput => {
|
|
469
572
|
if (!payload || typeof payload !== 'object') {
|
|
470
573
|
throw new AppError('Request body must be an object.', {
|
|
@@ -501,7 +604,8 @@ const parseCustomProviderModelPayload = (payload: unknown): CustomProviderModelI
|
|
|
501
604
|
});
|
|
502
605
|
}
|
|
503
606
|
|
|
504
|
-
|
|
607
|
+
const effort = parseCustomProviderModelEffort(body.effort);
|
|
608
|
+
return effort === undefined ? { model, id } : { model, id, effort };
|
|
505
609
|
};
|
|
506
610
|
|
|
507
611
|
router.get(
|
|
@@ -717,6 +821,15 @@ router.get(
|
|
|
717
821
|
}),
|
|
718
822
|
);
|
|
719
823
|
|
|
824
|
+
// Read-only: the rules Claude's own settings files add on top of the UI's lists.
|
|
825
|
+
router.get(
|
|
826
|
+
'/claude/settings-permissions',
|
|
827
|
+
asyncHandler(async (_req: Request, res: Response) => {
|
|
828
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources();
|
|
829
|
+
res.json(createApiSuccessResponse({ sources }));
|
|
830
|
+
}),
|
|
831
|
+
);
|
|
832
|
+
|
|
720
833
|
// ----------------- Session routes -----------------
|
|
721
834
|
/**
|
|
722
835
|
* Session gateway entry point: allocates the stable app-facing session id for
|
|
@@ -739,7 +852,7 @@ router.post(
|
|
|
739
852
|
router.get(
|
|
740
853
|
'/sessions/running',
|
|
741
854
|
asyncHandler(async (_req: Request, res: Response) => {
|
|
742
|
-
const sessions = sessionsService.listRunningSessions();
|
|
855
|
+
const sessions = await sessionsService.listRunningSessions();
|
|
743
856
|
res.json(createApiSuccessResponse({ sessions }));
|
|
744
857
|
}),
|
|
745
858
|
);
|
|
@@ -848,6 +961,22 @@ router.get(
|
|
|
848
961
|
}),
|
|
849
962
|
);
|
|
850
963
|
|
|
964
|
+
/**
|
|
965
|
+
* One workflow agent's timeline, read on demand when its row in the workflow
|
|
966
|
+
* card is opened. History does not carry it: a run can spawn a dozen agents
|
|
967
|
+
* with hundreds of tool calls each, and the card only lists them.
|
|
968
|
+
*/
|
|
969
|
+
router.get(
|
|
970
|
+
'/sessions/:sessionId/workflows/:runId/agents/:agentId',
|
|
971
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
972
|
+
const sessionId = parseSessionId(req.params.sessionId);
|
|
973
|
+
const runId = parseWorkflowRunId(req.params.runId);
|
|
974
|
+
const agentId = parseWorkflowAgentId(req.params.agentId);
|
|
975
|
+
const result = await sessionsService.readWorkflowAgentActivity(sessionId, runId, agentId);
|
|
976
|
+
res.json(createApiSuccessResponse(result));
|
|
977
|
+
}),
|
|
978
|
+
);
|
|
979
|
+
|
|
851
980
|
router.get('/search/sessions', asyncHandler(async (req: Request, res: Response) => {
|
|
852
981
|
const query = parseSessionSearchQuery(req.query.q);
|
|
853
982
|
const limit = parseSessionSearchLimit(req.query.limit);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One Claude CLI process that is currently producing a response.
|
|
7
|
+
*
|
|
8
|
+
* `providerSessionId` is Claude's own session id, which is what the registry
|
|
9
|
+
* records; callers map it onto an app session themselves.
|
|
10
|
+
*/
|
|
11
|
+
type LiveClaudeCliSession = {
|
|
12
|
+
providerSessionId: string;
|
|
13
|
+
startedAt: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Claude Code keeps one file per running session here, named `<pid>.json`.
|
|
18
|
+
* The directory belongs to the CLI, not to CloudCLI: it is only ever read.
|
|
19
|
+
*/
|
|
20
|
+
const registryDirectory = () => path.join(os.homedir(), '.claude', 'sessions');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Upper bound on registry files inspected per poll.
|
|
24
|
+
*
|
|
25
|
+
* The running-sessions endpoint is polled continuously, so a directory left
|
|
26
|
+
* full of stale files by repeated crashes must not turn every poll into
|
|
27
|
+
* unbounded filesystem work. Real installations hold a handful of entries.
|
|
28
|
+
*/
|
|
29
|
+
const MAX_REGISTRY_FILES = 256;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether `pid` is still the process the registry recorded.
|
|
33
|
+
*
|
|
34
|
+
* A registry file outlives a crash, so its mere presence proves nothing. The
|
|
35
|
+
* signal-0 probe answers "does this pid exist" (EPERM means it exists but is
|
|
36
|
+
* owned by someone else, which still counts), and on Linux the recorded
|
|
37
|
+
* `procStart` is compared against the kernel's start-time for that pid so a
|
|
38
|
+
* reused pid cannot be mistaken for the original process.
|
|
39
|
+
*/
|
|
40
|
+
async function isRecordedProcessAlive(pid: number, procStart: unknown): Promise<boolean> {
|
|
41
|
+
try {
|
|
42
|
+
process.kill(pid, 0);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if ((error as NodeJS.ErrnoException)?.code !== 'EPERM') {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (process.platform !== 'linux' || typeof procStart !== 'string' || !procStart) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const stat = await fsp.readFile(`/proc/${pid}/stat`, 'utf8');
|
|
55
|
+
// The comm field is parenthesised and may itself contain spaces, so the
|
|
56
|
+
// fields are counted from the last ')'. starttime is the 22nd field
|
|
57
|
+
// overall, i.e. the 20th of what follows the comm field.
|
|
58
|
+
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
59
|
+
return fields[19] === procStart;
|
|
60
|
+
} catch {
|
|
61
|
+
// No procfs entry means the process is gone between the two checks.
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Reads one registry file, returning null for anything that is not a live busy session. */
|
|
67
|
+
async function readLiveBusySession(filePath: string): Promise<LiveClaudeCliSession | null> {
|
|
68
|
+
let parsed: unknown;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(await fsp.readFile(filePath, 'utf8'));
|
|
71
|
+
} catch {
|
|
72
|
+
// Unreadable, half-written or malformed entries are simply not evidence
|
|
73
|
+
// of a running session.
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const record = parsed as Record<string, unknown> | null;
|
|
78
|
+
if (!record || typeof record !== 'object') {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const providerSessionId = typeof record.sessionId === 'string' ? record.sessionId : '';
|
|
83
|
+
const pid = typeof record.pid === 'number' ? record.pid : NaN;
|
|
84
|
+
if (!providerSessionId || !Number.isInteger(pid) || pid <= 0 || record.status !== 'busy') {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!(await isRecordedProcessAlive(pid, record.procStart))) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const startedAt = typeof record.startedAt === 'number' ? record.startedAt : Date.now();
|
|
93
|
+
return { providerSessionId, startedAt };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Lists the Claude CLI sessions that are mid-turn right now.
|
|
98
|
+
*
|
|
99
|
+
* Used by the providers module's sessions service so the running-sessions poll
|
|
100
|
+
* also reports turns driven outside CloudCLI's own chat runs — including the
|
|
101
|
+
* `claude` processes CloudCLI itself spawns for the Shell view, which live in
|
|
102
|
+
* the PTY map rather than the chat-run registry. Liveness is ephemeral, so it
|
|
103
|
+
* is recomputed on every read instead of being cached or persisted.
|
|
104
|
+
*/
|
|
105
|
+
export async function listBusyClaudeCliSessions(): Promise<LiveClaudeCliSession[]> {
|
|
106
|
+
let entries: string[];
|
|
107
|
+
try {
|
|
108
|
+
entries = await fsp.readdir(registryDirectory());
|
|
109
|
+
} catch {
|
|
110
|
+
// No registry directory at all is the normal case for an install that has
|
|
111
|
+
// never run the CLI, and an unreadable one is not worth failing a poll for.
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const registryFiles = entries.filter((entry) => entry.endsWith('.json')).slice(0, MAX_REGISTRY_FILES);
|
|
116
|
+
const sessions = await Promise.all(
|
|
117
|
+
registryFiles.map((entry) => readLiveBusySession(path.join(registryDirectory(), entry))),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return sessions.filter((session): session is LiveClaudeCliSession => session !== null);
|
|
121
|
+
}
|