@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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import type { AddressInfo } from 'node:net';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import test from 'node:test';
|
|
8
|
+
|
|
9
|
+
import express from 'express';
|
|
10
|
+
|
|
11
|
+
import providerRouter from '@/modules/providers/provider.routes.js';
|
|
12
|
+
import { claudeSettingsPermissionsService } from '@/modules/providers/services/claude-settings-permissions.service.js';
|
|
13
|
+
|
|
14
|
+
// Issue #109: the Claude CLI applies the permission rules in its own settings
|
|
15
|
+
// files on top of the lists CloudCLI passes it, but the Settings panel never
|
|
16
|
+
// showed them. These tests pin the read-only reader behind that view.
|
|
17
|
+
|
|
18
|
+
type Source = Awaited<ReturnType<typeof claudeSettingsPermissionsService.listRuleSources>>[number];
|
|
19
|
+
|
|
20
|
+
const withHome = async (run: (homeDir: string) => Promise<void>) => {
|
|
21
|
+
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'claude-settings-permissions-'));
|
|
22
|
+
// os.homedir() reads HOME on POSIX and USERPROFILE on Windows.
|
|
23
|
+
const overridden = { HOME: homeDir, USERPROFILE: homeDir, CLAUDE_CONFIG_DIR: undefined };
|
|
24
|
+
const original = Object.fromEntries(Object.keys(overridden).map((key) => [key, process.env[key]]));
|
|
25
|
+
const setEnv = (values: Record<string, string | undefined>) => {
|
|
26
|
+
for (const [key, value] of Object.entries(values)) {
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
delete process.env[key];
|
|
29
|
+
} else {
|
|
30
|
+
process.env[key] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
setEnv(overridden);
|
|
35
|
+
try {
|
|
36
|
+
await run(homeDir);
|
|
37
|
+
} finally {
|
|
38
|
+
setEnv(original);
|
|
39
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const writeSettings = async (filePath: string, content: unknown) => {
|
|
44
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
45
|
+
await writeFile(
|
|
46
|
+
filePath,
|
|
47
|
+
typeof content === 'string' || Buffer.isBuffer(content) ? content : JSON.stringify(content),
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const bySource = (sources: Source[], scope: Source['scope']): Source => {
|
|
52
|
+
const source = sources.find((entry) => entry.scope === scope);
|
|
53
|
+
assert.ok(source, `expected a ${scope} source`);
|
|
54
|
+
return source;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
test('lists the allow, deny and ask rules of ~/.claude/settings.json and the managed file', async () => {
|
|
58
|
+
await withHome(async (homeDir) => {
|
|
59
|
+
const userFile = path.join(homeDir, '.claude', 'settings.json');
|
|
60
|
+
const managedFile = path.join(homeDir, 'managed', 'managed-settings.json');
|
|
61
|
+
await writeSettings(userFile, {
|
|
62
|
+
env: { ANTHROPIC_API_KEY: 'sk-secret' },
|
|
63
|
+
permissions: {
|
|
64
|
+
allow: ['Bash(npm run test:*)', 'Read', 'Read', ' ', 42],
|
|
65
|
+
deny: ['Bash(rm:*)'],
|
|
66
|
+
ask: ['WebFetch'],
|
|
67
|
+
defaultMode: 'acceptEdits',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
await writeSettings(managedFile, { permissions: { deny: ['WebSearch'] } });
|
|
71
|
+
|
|
72
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources(managedFile);
|
|
73
|
+
|
|
74
|
+
assert.deepEqual(bySource(sources, 'user'), {
|
|
75
|
+
scope: 'user',
|
|
76
|
+
path: userFile,
|
|
77
|
+
status: 'ok',
|
|
78
|
+
// Duplicates, blanks and non-strings are dropped; order is kept.
|
|
79
|
+
allow: ['Bash(npm run test:*)', 'Read'],
|
|
80
|
+
deny: ['Bash(rm:*)'],
|
|
81
|
+
ask: ['WebFetch'],
|
|
82
|
+
});
|
|
83
|
+
assert.deepEqual(bySource(sources, 'managed'), {
|
|
84
|
+
scope: 'managed',
|
|
85
|
+
path: managedFile,
|
|
86
|
+
status: 'ok',
|
|
87
|
+
allow: [],
|
|
88
|
+
deny: ['WebSearch'],
|
|
89
|
+
ask: [],
|
|
90
|
+
});
|
|
91
|
+
// Nothing but the rule lists leaves the server (the env block holds secrets).
|
|
92
|
+
assert.doesNotMatch(JSON.stringify(sources), /sk-secret|acceptEdits/);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('reports missing files as empty instead of failing', async () => {
|
|
97
|
+
await withHome(async (homeDir) => {
|
|
98
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources(
|
|
99
|
+
path.join(homeDir, 'nowhere', 'managed-settings.json'),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
for (const source of sources) {
|
|
103
|
+
assert.equal(source.status, 'missing');
|
|
104
|
+
assert.deepEqual([source.allow, source.deny, source.ask], [[], [], []]);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('reports invalid JSON, non-object JSON and unreadable paths as invalid without throwing', async () => {
|
|
110
|
+
await withHome(async (homeDir) => {
|
|
111
|
+
const userFile = path.join(homeDir, '.claude', 'settings.json');
|
|
112
|
+
const managedFile = path.join(homeDir, 'managed', 'managed-settings.json');
|
|
113
|
+
await writeSettings(userFile, '{ "permissions": { "allow": ["Read"], }');
|
|
114
|
+
await writeSettings(managedFile, '["Read"]');
|
|
115
|
+
|
|
116
|
+
let sources = await claudeSettingsPermissionsService.listRuleSources(managedFile);
|
|
117
|
+
assert.equal(bySource(sources, 'user').status, 'invalid');
|
|
118
|
+
assert.deepEqual(bySource(sources, 'user').allow, []);
|
|
119
|
+
assert.equal(bySource(sources, 'managed').status, 'invalid');
|
|
120
|
+
|
|
121
|
+
// A directory where the file should be cannot be read either.
|
|
122
|
+
await rm(userFile);
|
|
123
|
+
await mkdir(userFile);
|
|
124
|
+
sources = await claudeSettingsPermissionsService.listRuleSources(managedFile);
|
|
125
|
+
assert.equal(bySource(sources, 'user').status, 'invalid');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('reports a file under a path that is not a directory as missing', async () => {
|
|
130
|
+
await withHome(async (homeDir) => {
|
|
131
|
+
// ~/.claude is a plain file, so ~/.claude/settings.json cannot exist (ENOTDIR).
|
|
132
|
+
await writeFile(path.join(homeDir, '.claude'), 'not a directory');
|
|
133
|
+
|
|
134
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources(path.join(homeDir, 'none.json'));
|
|
135
|
+
|
|
136
|
+
assert.equal(bySource(sources, 'user').status, 'missing');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('reads files saved with a byte order mark, as the CLI does', async () => {
|
|
141
|
+
await withHome(async (homeDir) => {
|
|
142
|
+
const userFile = path.join(homeDir, '.claude', 'settings.json');
|
|
143
|
+
const managedFile = path.join(homeDir, 'managed', 'managed-settings.json');
|
|
144
|
+
const settings = JSON.stringify({ permissions: { allow: ['Bash(git status)'], deny: ['Bash(rm:*)'] } });
|
|
145
|
+
// UTF-8 with BOM (older Notepad) and UTF-16LE with BOM (Windows PowerShell 5.1 Out-File).
|
|
146
|
+
await writeSettings(userFile, Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), Buffer.from(settings, 'utf8')]));
|
|
147
|
+
await writeSettings(managedFile, Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(settings, 'utf16le')]));
|
|
148
|
+
|
|
149
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources(managedFile);
|
|
150
|
+
|
|
151
|
+
for (const scope of ['user', 'managed'] as const) {
|
|
152
|
+
const source = bySource(sources, scope);
|
|
153
|
+
assert.equal(source.status, 'ok', `${scope} file`);
|
|
154
|
+
assert.deepEqual(source.allow, ['Bash(git status)']);
|
|
155
|
+
assert.deepEqual(source.deny, ['Bash(rm:*)']);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('treats an empty or whitespace-only file as valid with no rules, as the CLI does', async () => {
|
|
161
|
+
await withHome(async (homeDir) => {
|
|
162
|
+
const userFile = path.join(homeDir, '.claude', 'settings.json');
|
|
163
|
+
const managedFile = path.join(homeDir, 'managed', 'managed-settings.json');
|
|
164
|
+
await writeSettings(userFile, '');
|
|
165
|
+
await writeSettings(managedFile, ' \n\t\r\n');
|
|
166
|
+
|
|
167
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources(managedFile);
|
|
168
|
+
|
|
169
|
+
for (const source of sources) {
|
|
170
|
+
assert.equal(source.status, 'ok', `${source.scope} file`);
|
|
171
|
+
assert.deepEqual([source.allow, source.deny, source.ask], [[], [], []]);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('reports a file over the CLI 2 MB limit as invalid, since the CLI skips it', async () => {
|
|
177
|
+
await withHome(async (homeDir) => {
|
|
178
|
+
const userFile = path.join(homeDir, '.claude', 'settings.json');
|
|
179
|
+
await writeSettings(userFile, { permissions: { allow: ['Read'] }, padding: 'x'.repeat(2 * 1024 * 1024) });
|
|
180
|
+
|
|
181
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources(path.join(homeDir, 'none.json'));
|
|
182
|
+
|
|
183
|
+
assert.equal(bySource(sources, 'user').status, 'invalid');
|
|
184
|
+
assert.deepEqual(bySource(sources, 'user').allow, []);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('a settings file without a permissions block is valid and adds no rules', async () => {
|
|
189
|
+
await withHome(async (homeDir) => {
|
|
190
|
+
const userFile = path.join(homeDir, '.claude', 'settings.json');
|
|
191
|
+
await writeSettings(userFile, { model: 'opus' });
|
|
192
|
+
|
|
193
|
+
let sources = await claudeSettingsPermissionsService.listRuleSources(path.join(homeDir, 'none.json'));
|
|
194
|
+
assert.equal(bySource(sources, 'user').status, 'ok');
|
|
195
|
+
assert.deepEqual(bySource(sources, 'user').allow, []);
|
|
196
|
+
|
|
197
|
+
// A rule list that is not an array adds no rules either.
|
|
198
|
+
await writeSettings(userFile, { permissions: { allow: 'Read' } });
|
|
199
|
+
sources = await claudeSettingsPermissionsService.listRuleSources(path.join(homeDir, 'none.json'));
|
|
200
|
+
assert.equal(bySource(sources, 'user').status, 'ok');
|
|
201
|
+
assert.deepEqual(bySource(sources, 'user').allow, []);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('reads the managed file from the fixed path the CLI uses on each OS', async () => {
|
|
206
|
+
const expected = {
|
|
207
|
+
darwin: '/Library/Application Support/ClaudeCode/managed-settings.json',
|
|
208
|
+
win32: 'C:\\Program Files\\ClaudeCode\\managed-settings.json',
|
|
209
|
+
linux: '/etc/claude-code/managed-settings.json',
|
|
210
|
+
};
|
|
211
|
+
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
|
|
212
|
+
assert.ok(originalPlatform);
|
|
213
|
+
await withHome(async () => {
|
|
214
|
+
try {
|
|
215
|
+
for (const [platform, managedPath] of Object.entries(expected)) {
|
|
216
|
+
Object.defineProperty(process, 'platform', { ...originalPlatform, value: platform });
|
|
217
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources();
|
|
218
|
+
assert.equal(bySource(sources, 'managed').path, managedPath, platform);
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
Object.defineProperty(process, 'platform', originalPlatform);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('follows CLAUDE_CONFIG_DIR like the CLI does', async () => {
|
|
227
|
+
await withHome(async (homeDir) => {
|
|
228
|
+
const configDir = path.join(homeDir, 'custom-claude');
|
|
229
|
+
await writeSettings(path.join(configDir, 'settings.json'), { permissions: { allow: ['Edit'] } });
|
|
230
|
+
process.env.CLAUDE_CONFIG_DIR = configDir;
|
|
231
|
+
|
|
232
|
+
const sources = await claudeSettingsPermissionsService.listRuleSources(path.join(homeDir, 'none.json'));
|
|
233
|
+
|
|
234
|
+
assert.equal(bySource(sources, 'user').path, path.join(configDir, 'settings.json'));
|
|
235
|
+
assert.deepEqual(bySource(sources, 'user').allow, ['Edit']);
|
|
236
|
+
|
|
237
|
+
// A relative value would resolve against the server's cwd, not the CLI's
|
|
238
|
+
// project folder, so the reader keeps the default location instead.
|
|
239
|
+
process.env.CLAUDE_CONFIG_DIR = 'custom-claude';
|
|
240
|
+
const fallback = await claudeSettingsPermissionsService.listRuleSources(path.join(homeDir, 'none.json'));
|
|
241
|
+
assert.equal(bySource(fallback, 'user').path, path.join(homeDir, '.claude', 'settings.json'));
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('GET /api/providers/claude/settings-permissions serves the sources and never writes the files', async () => {
|
|
246
|
+
await withHome(async (homeDir) => {
|
|
247
|
+
const userFile = path.join(homeDir, '.claude', 'settings.json');
|
|
248
|
+
const original = JSON.stringify({ permissions: { allow: ['Bash(git status)'] } });
|
|
249
|
+
await writeSettings(userFile, original);
|
|
250
|
+
|
|
251
|
+
const app = express().use('/api/providers', providerRouter);
|
|
252
|
+
const server = app.listen(0, '127.0.0.1');
|
|
253
|
+
await once(server, 'listening');
|
|
254
|
+
try {
|
|
255
|
+
const { port } = server.address() as AddressInfo;
|
|
256
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/providers/claude/settings-permissions`);
|
|
257
|
+
const payload = await response.json() as { success: boolean; data: { sources: Source[] } };
|
|
258
|
+
|
|
259
|
+
assert.equal(response.status, 200);
|
|
260
|
+
assert.equal(payload.success, true);
|
|
261
|
+
const user = bySource(payload.data.sources, 'user');
|
|
262
|
+
assert.equal(user.path, userFile);
|
|
263
|
+
assert.deepEqual(user.allow, ['Bash(git status)']);
|
|
264
|
+
assert.equal(await readFile(userFile, 'utf8'), original);
|
|
265
|
+
} finally {
|
|
266
|
+
await new Promise<void>((resolve, reject) => {
|
|
267
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
|
|
5
|
+
import { CODEX_PREDEFINED_MODELS } from '@/modules/providers/list/codex/codex-models.provider.js';
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
const findCodexModel = (value: string) =>
|
|
10
|
+
CODEX_PREDEFINED_MODELS.OPTIONS.find((option) => option.value === value);
|
|
11
|
+
|
|
12
|
+
test('lists GPT-6 Sol and GPT-6 Luna with the effort levels the Codex CLI accepts', () => {
|
|
13
|
+
const sol = findCodexModel('gpt-6-sol');
|
|
14
|
+
assert.equal(sol?.label, 'GPT-6 Sol');
|
|
15
|
+
assert.equal(sol?.effort?.default, 'medium');
|
|
16
|
+
assert.deepEqual(
|
|
17
|
+
sol?.effort?.values.map((effort) => effort.value),
|
|
18
|
+
['low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const luna = findCodexModel('gpt-6-luna');
|
|
22
|
+
assert.equal(luna?.label, 'GPT-6 Luna');
|
|
23
|
+
assert.equal(luna?.effort?.default, 'medium');
|
|
24
|
+
assert.deepEqual(
|
|
25
|
+
luna?.effort?.values.map((effort) => effort.value),
|
|
26
|
+
['low', 'medium', 'high', 'xhigh', 'max'],
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('bundles a Codex CLI new enough to know the GPT-6 Sol and Luna models', () => {
|
|
31
|
+
// Codex only ships metadata for gpt-6-sol / gpt-6-luna from 0.155.0 on. An
|
|
32
|
+
// older CLI still sends the request, but on fallback metadata: it warns
|
|
33
|
+
// "Model metadata ... not found" and quietly drops `ultra` to `medium`.
|
|
34
|
+
const { version } = require('@openai/codex/package.json') as { version: string };
|
|
35
|
+
const [major, minor] = version.split('.').map(Number);
|
|
36
|
+
assert.ok(major > 0 || minor >= 155, `bundled @openai/codex ${version} predates 0.155.0`);
|
|
37
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import test from 'node:test';
|
|
3
4
|
|
|
4
5
|
import { Codex } from '@openai/codex-sdk';
|
|
@@ -56,3 +57,38 @@ for (const resumed of [false, true]) {
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
|
|
61
|
+
for (const command of ['', ' \n\t']) {
|
|
62
|
+
test(`Codex supplies a prompt for an image-only turn (${JSON.stringify(command)})`, async (t) => {
|
|
63
|
+
let capturedPrompt: unknown;
|
|
64
|
+
const imagePath = path.join(process.cwd(), 'public', 'favicon.png');
|
|
65
|
+
const thread = {
|
|
66
|
+
id: 'native-thread',
|
|
67
|
+
async runStreamed(prompt: unknown) {
|
|
68
|
+
capturedPrompt = prompt;
|
|
69
|
+
return { events: (async function* () {
|
|
70
|
+
yield { type: 'thread.started', thread_id: 'native-thread' };
|
|
71
|
+
})() };
|
|
72
|
+
},
|
|
73
|
+
} as unknown as Thread;
|
|
74
|
+
|
|
75
|
+
t.mock.method(Codex.prototype, 'startThread', () => thread);
|
|
76
|
+
const context: ProviderRuntimeContext = {
|
|
77
|
+
resolveProviderSessionId: () => null,
|
|
78
|
+
resolveResumeModel: async () => 'test-model',
|
|
79
|
+
getProviderModels: async () => ({ OPTIONS: [], DEFAULT: 'test-model' }),
|
|
80
|
+
normalizeMessage: () => [],
|
|
81
|
+
isProviderInstalled: async () => true,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
await codexRuntime.run(command, {
|
|
85
|
+
cwd: process.cwd(),
|
|
86
|
+
images: [{ path: imagePath, mimeType: 'image/png' }],
|
|
87
|
+
}, { isWebSocketWriter: true, send: () => {} }, context);
|
|
88
|
+
|
|
89
|
+
assert.deepEqual(capturedPrompt, [
|
|
90
|
+
{ type: 'text', text: 'Please analyze the attached image(s).' },
|
|
91
|
+
{ type: 'local_image', path: imagePath },
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -551,3 +551,62 @@ test('an exec script that updates the plan yields the steps it set', () => {
|
|
|
551
551
|
],
|
|
552
552
|
}]);
|
|
553
553
|
});
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
test('Codex history restores user prompts from typed item_completed rows', { concurrency: false }, async () => {
|
|
557
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-typed-user-history-'));
|
|
558
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
559
|
+
await mkdir(workspacePath, { recursive: true });
|
|
560
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
const providerSessionId = 'codex-typed-1';
|
|
564
|
+
const prompt = 'Typed prompt that must survive a refresh';
|
|
565
|
+
const transcriptPath = path.join(tempRoot, '.codex', 'sessions', '2026', '07', '07', 'rollout-' + providerSessionId + '.jsonl');
|
|
566
|
+
await mkdir(path.dirname(transcriptPath), { recursive: true });
|
|
567
|
+
const lineEnd = String.fromCharCode(10);
|
|
568
|
+
const typedLine = JSON.stringify({
|
|
569
|
+
timestamp: '2026-09-07T17:59:17.000Z',
|
|
570
|
+
type: 'event_msg',
|
|
571
|
+
payload: {
|
|
572
|
+
type: 'item_completed',
|
|
573
|
+
turn_id: 'typed-turn-1',
|
|
574
|
+
item: {
|
|
575
|
+
type: 'UserMessage',
|
|
576
|
+
id: 'item-user-1',
|
|
577
|
+
content: [
|
|
578
|
+
{ type: 'text', text: prompt, text_elements: [] },
|
|
579
|
+
{ type: 'local_image', path: '/tmp/prompt-picture.png', detail: null },
|
|
580
|
+
],
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
const wireUserEcho = JSON.stringify({
|
|
585
|
+
timestamp: '2026-09-07T17:59:19.000Z',
|
|
586
|
+
type: 'response_item',
|
|
587
|
+
payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: prompt }] },
|
|
588
|
+
});
|
|
589
|
+
await writeFile(
|
|
590
|
+
transcriptPath,
|
|
591
|
+
[JSON.stringify({ type: 'session_meta', payload: { id: providerSessionId, cwd: workspacePath } }), typedLine, wireUserEcho].join(lineEnd) + lineEnd,
|
|
592
|
+
'utf8',
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
await withIsolatedDatabase(async () => {
|
|
596
|
+
sessionsDb.createAppSession('app-typed-1', 'codex', workspacePath);
|
|
597
|
+
sessionsDb.assignProviderSessionId('app-typed-1', providerSessionId);
|
|
598
|
+
await new CodexSessionSynchronizer().synchronize();
|
|
599
|
+
|
|
600
|
+
const history = await new CodexSessionsProvider().fetchHistory('app-typed-1');
|
|
601
|
+
const users = history.messages.filter((message) => message.role === 'user');
|
|
602
|
+
const images = users[0]?.images as Array<{ path?: string; data?: string }> | undefined;
|
|
603
|
+
|
|
604
|
+
assert.equal(users.length, 1, 'the wire user echo must not duplicate the typed prompt');
|
|
605
|
+
assert.equal(users[0]?.content, prompt);
|
|
606
|
+
assert.equal(images?.[0]?.path, '/tmp/prompt-picture.png');
|
|
607
|
+
});
|
|
608
|
+
} finally {
|
|
609
|
+
restoreHomeDir();
|
|
610
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
611
|
+
}
|
|
612
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import test, { mock } from 'node:test';
|
|
7
|
+
|
|
8
|
+
import { CursorProviderAuth } from '@/modules/providers/list/cursor/cursor-auth.provider.js';
|
|
9
|
+
|
|
10
|
+
const findEnvKey = (name: string) =>
|
|
11
|
+
Object.keys(process.env).find((key) => key.toLowerCase() === name.toLowerCase()) || name;
|
|
12
|
+
|
|
13
|
+
// What the real CLI prints when it is logged in and the account request succeeded.
|
|
14
|
+
const LOGGED_IN_OUTPUT = '✓ Logged in as cursor-user@example.com';
|
|
15
|
+
|
|
16
|
+
// A stand-in for the real CLI: `--version` answers at once, while `status` drops a started
|
|
17
|
+
// marker and keeps running (like the real one does while it fetches the account details from
|
|
18
|
+
// Cursor's API) until the test creates the release file, then prints the configured output.
|
|
19
|
+
// It records a SIGTERM so the tests can check that a timed-out check kills it.
|
|
20
|
+
const FAKE_CURSOR_AGENT_SCRIPT = `
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const command = process.argv[2];
|
|
24
|
+
const markerDir = process.env.CURSOR_FAKE_MARKER_DIR;
|
|
25
|
+
if (command === '--version') {
|
|
26
|
+
console.log('2026.07.20-test');
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
if (command === 'status') {
|
|
30
|
+
process.on('SIGTERM', () => {
|
|
31
|
+
fs.writeFileSync(path.join(markerDir, 'status-killed'), '');
|
|
32
|
+
process.exit(143);
|
|
33
|
+
});
|
|
34
|
+
fs.writeFileSync(path.join(markerDir, 'status-started'), '');
|
|
35
|
+
// Never outlive a broken test run (its cleanup deletes the marker dir).
|
|
36
|
+
const giveUpAt = Date.now() + 30000;
|
|
37
|
+
const poll = () => {
|
|
38
|
+
if (!fs.existsSync(markerDir) || Date.now() > giveUpAt) {
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (fs.existsSync(path.join(markerDir, 'release-status'))) {
|
|
42
|
+
process.stdout.write(fs.readFileSync(path.join(markerDir, 'status-output'), 'utf8') + '\\n');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setTimeout(poll, 20);
|
|
46
|
+
};
|
|
47
|
+
poll();
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
async function createFakeCursorAgent(binDir: string) {
|
|
52
|
+
await writeFile(path.join(binDir, 'cursor-agent.cjs'), FAKE_CURSOR_AGENT_SCRIPT, 'utf8');
|
|
53
|
+
|
|
54
|
+
if (process.platform === 'win32') {
|
|
55
|
+
const commandPath = path.join(binDir, 'cursor-agent.cmd');
|
|
56
|
+
await writeFile(commandPath, '@echo off\r\nnode "%~dp0cursor-agent.cjs" %*\r\n', 'utf8');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// exec so the provider's kill() on timeout reaches the node process itself.
|
|
61
|
+
const commandPath = path.join(binDir, 'cursor-agent');
|
|
62
|
+
await writeFile(commandPath, '#!/bin/sh\nexec node "$(dirname "$0")/cursor-agent.cjs" "$@"\n', 'utf8');
|
|
63
|
+
await chmod(commandPath, 0o755);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Waits in real time (the tests mock only setTimeout, not setImmediate or Date) and fails
|
|
67
|
+
// instead of hanging when the fake never gets there.
|
|
68
|
+
async function waitForFile(filePath: string, description: string) {
|
|
69
|
+
const giveUpAt = Date.now() + 15_000;
|
|
70
|
+
while (!existsSync(filePath)) {
|
|
71
|
+
if (Date.now() > giveUpAt) {
|
|
72
|
+
assert.fail(`timed out waiting for ${description}`);
|
|
73
|
+
}
|
|
74
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type FakeCursorAgent = {
|
|
79
|
+
releaseStatus: () => Promise<void>;
|
|
80
|
+
waitForStatusStart: () => Promise<void>;
|
|
81
|
+
waitForStatusKill: () => Promise<void>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const withFakeCursorAgent = async (statusOutput: string, fn: (fake: FakeCursorAgent) => Promise<void>) => {
|
|
85
|
+
const binDir = await mkdtemp(path.join(os.tmpdir(), 'cursor-auth-test-'));
|
|
86
|
+
const fake: FakeCursorAgent = {
|
|
87
|
+
releaseStatus: () => writeFile(path.join(binDir, 'release-status'), '', 'utf8'),
|
|
88
|
+
waitForStatusStart: () => waitForFile(path.join(binDir, 'status-started'), 'the fake `status` to start'),
|
|
89
|
+
waitForStatusKill: () => waitForFile(path.join(binDir, 'status-killed'), 'the fake `status` to get SIGTERM'),
|
|
90
|
+
};
|
|
91
|
+
const pathKey = findEnvKey('PATH');
|
|
92
|
+
const pathExtKey = findEnvKey('PATHEXT');
|
|
93
|
+
const previousPath = process.env[pathKey];
|
|
94
|
+
const previousPathExt = process.env[pathExtKey];
|
|
95
|
+
const previousMarkerDir = process.env.CURSOR_FAKE_MARKER_DIR;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await createFakeCursorAgent(binDir);
|
|
99
|
+
await writeFile(path.join(binDir, 'status-output'), statusOutput, 'utf8');
|
|
100
|
+
process.env[pathKey] = `${binDir}${path.delimiter}${previousPath || ''}`;
|
|
101
|
+
process.env.CURSOR_FAKE_MARKER_DIR = binDir;
|
|
102
|
+
if (process.platform === 'win32') {
|
|
103
|
+
process.env[pathExtKey] = previousPathExt?.toUpperCase().includes('.CMD')
|
|
104
|
+
? previousPathExt
|
|
105
|
+
: `.COM;.EXE;.BAT;.CMD${previousPathExt ? `;${previousPathExt}` : ''}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await fn(fake);
|
|
109
|
+
} finally {
|
|
110
|
+
// Lets a fake `status` that is still polling exit right away.
|
|
111
|
+
await fake.releaseStatus().catch(() => {});
|
|
112
|
+
process.env[pathKey] = previousPath;
|
|
113
|
+
if (previousPathExt === undefined) {
|
|
114
|
+
delete process.env[pathExtKey];
|
|
115
|
+
} else {
|
|
116
|
+
process.env[pathExtKey] = previousPathExt;
|
|
117
|
+
}
|
|
118
|
+
if (previousMarkerDir === undefined) {
|
|
119
|
+
delete process.env.CURSOR_FAKE_MARKER_DIR;
|
|
120
|
+
} else {
|
|
121
|
+
process.env.CURSOR_FAKE_MARKER_DIR = previousMarkerDir;
|
|
122
|
+
}
|
|
123
|
+
await rm(binDir, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
test('getStatus waits for a logged-in `cursor-agent status` that takes longer than 5 s (#551)', async () => {
|
|
128
|
+
await withFakeCursorAgent(LOGGED_IN_OUTPUT, async (fake) => {
|
|
129
|
+
// getStatus() arms its timeout before it spawns `status`, so it must see the mocked setTimeout.
|
|
130
|
+
mock.timers.enable({ apis: ['setTimeout'] });
|
|
131
|
+
try {
|
|
132
|
+
const pending = new CursorProviderAuth().getStatus();
|
|
133
|
+
// Move the clock only once `status` runs, i.e. once the timeout is armed.
|
|
134
|
+
await fake.waitForStatusStart();
|
|
135
|
+
|
|
136
|
+
// CLI startup alone took 3-6 s on a busy host; the account request adds a network round trip.
|
|
137
|
+
mock.timers.tick(10_000);
|
|
138
|
+
await fake.releaseStatus();
|
|
139
|
+
|
|
140
|
+
const status = await pending;
|
|
141
|
+
assert.equal(status.error, undefined);
|
|
142
|
+
assert.equal(status.authenticated, true);
|
|
143
|
+
assert.equal(status.email, 'cursor-user@example.com');
|
|
144
|
+
assert.equal(status.method, 'cli');
|
|
145
|
+
} finally {
|
|
146
|
+
mock.timers.reset();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('getStatus still gives up on, and kills, a `cursor-agent status` that never answers', async () => {
|
|
152
|
+
await withFakeCursorAgent(LOGGED_IN_OUTPUT, async (fake) => {
|
|
153
|
+
mock.timers.enable({ apis: ['setTimeout'] });
|
|
154
|
+
try {
|
|
155
|
+
const pending = new CursorProviderAuth().getStatus();
|
|
156
|
+
await fake.waitForStatusStart();
|
|
157
|
+
|
|
158
|
+
mock.timers.tick(60_000);
|
|
159
|
+
|
|
160
|
+
const status = await pending;
|
|
161
|
+
assert.equal(status.installed, true);
|
|
162
|
+
assert.equal(status.authenticated, false);
|
|
163
|
+
assert.equal(status.error, 'Command timeout');
|
|
164
|
+
// On Windows kill() ends the .cmd shim's cmd.exe, which does not signal node behind it.
|
|
165
|
+
if (process.platform !== 'win32') {
|
|
166
|
+
await fake.waitForStatusKill();
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
mock.timers.reset();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('getStatus reports no email when `cursor-agent status` could not fetch the account', async () => {
|
|
175
|
+
// What the real CLI prints when its account request fails: the stored login is still valid.
|
|
176
|
+
const output = '✓ Login successful!\nLogged in (unable to fetch user details)';
|
|
177
|
+
await withFakeCursorAgent(output, async (fake) => {
|
|
178
|
+
await fake.releaseStatus();
|
|
179
|
+
|
|
180
|
+
const status = await new CursorProviderAuth().getStatus();
|
|
181
|
+
assert.equal(status.error, undefined);
|
|
182
|
+
assert.equal(status.authenticated, true);
|
|
183
|
+
// The old 'Logged in' placeholder made the Settings card read "Logged in as Logged in".
|
|
184
|
+
assert.equal(status.email, null);
|
|
185
|
+
assert.equal(status.method, 'cli');
|
|
186
|
+
});
|
|
187
|
+
});
|