@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
|
@@ -36,9 +36,14 @@ import {
|
|
|
36
36
|
notifyRunStopped,
|
|
37
37
|
notifyUserIfEnabled
|
|
38
38
|
} from '@/modules/notifications/index.js';
|
|
39
|
+
import { sessionHistoryCache } from '@/modules/providers/services/session-history-cache.service.js';
|
|
39
40
|
import { createCompleteMessage, createNormalizedMessage } from '@/shared/utils.js';
|
|
40
41
|
|
|
41
42
|
const activeSessions = new Map();
|
|
43
|
+
// Outstanding background tasks per live session, keyed like activeSessions. An
|
|
44
|
+
// entry lives exactly as long as the map entry it shadows: cleared when the
|
|
45
|
+
// session is removed, and reset when a newer run takes the key over.
|
|
46
|
+
const backgroundWork = createBackgroundWorkTracker();
|
|
42
47
|
const pendingToolApprovals = new Map();
|
|
43
48
|
// Sessions cancelled via abort-session. The abort handler already sent the
|
|
44
49
|
// terminal `complete` (aborted: true) to the client, so the run loop must not
|
|
@@ -332,6 +337,9 @@ function addSession(sessionId, queryInstance, writer = null, releaseInput = null
|
|
|
332
337
|
console.error(`Error interrupting superseded run for session ${sessionId}:`, error?.message || error);
|
|
333
338
|
});
|
|
334
339
|
existing.releaseInput?.();
|
|
340
|
+
// Whatever the superseded process had outstanding dies with it and will
|
|
341
|
+
// never report, so the new run starts from an empty task set.
|
|
342
|
+
backgroundWork.clear(sessionId);
|
|
335
343
|
}
|
|
336
344
|
const carried = superseding ? null : existing;
|
|
337
345
|
activeSessions.set(sessionId, {
|
|
@@ -342,6 +350,44 @@ function addSession(sessionId, queryInstance, writer = null, releaseInput = null
|
|
|
342
350
|
// Re-registered mid-run once the provider session id lands; keep the closer.
|
|
343
351
|
releaseInput: releaseInput || carried?.releaseInput || null
|
|
344
352
|
});
|
|
353
|
+
// The history reader reports a background agent as running or stopped by
|
|
354
|
+
// whether this entry exists, and the cached history does not see this map.
|
|
355
|
+
sessionHistoryCache.invalidate(sessionId);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Lets go of the process an earlier run left holding a session open, so the
|
|
360
|
+
* next turn can take the session over.
|
|
361
|
+
*
|
|
362
|
+
* Closing stdin is not enough on its own: the CLI's wind-down waits for the
|
|
363
|
+
* background agents the process still has going — for up to the ceiling it
|
|
364
|
+
* was started with (BG_WAIT_CEILING_MS) — so an abandoned process with an
|
|
365
|
+
* agent outstanding would run next to the process that replaced it for as
|
|
366
|
+
* long as the agent does, both appending to the same session file. Stop the
|
|
367
|
+
* tasks first, while stdin is still open: the SDK drops control requests
|
|
368
|
+
* written after stdin has ended, which is also why the interrupt a later
|
|
369
|
+
* supersede sends to this process cannot do the job.
|
|
370
|
+
*
|
|
371
|
+
* The stops are not awaited — each request is written to the CLI before this
|
|
372
|
+
* returns, and a reply is only missing when the process has already gone.
|
|
373
|
+
* @param {string} sessionId - Session identifier
|
|
374
|
+
*/
|
|
375
|
+
function releaseHeldSession(sessionId) {
|
|
376
|
+
const held = getSession(sessionId);
|
|
377
|
+
if (!held) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
for (const task of backgroundWork.outstanding(sessionId)) {
|
|
381
|
+
// A stop that cannot be delivered changes nothing about what happens
|
|
382
|
+
// next: the process is on its way out either way. Guard the synchronous
|
|
383
|
+
// call too, so a failing stop can never skip the release below.
|
|
384
|
+
try {
|
|
385
|
+
Promise.resolve(held.instance?.stopTask?.(task.taskId)).catch(() => {});
|
|
386
|
+
} catch {
|
|
387
|
+
// Same as a rejected stop.
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
held.releaseInput?.();
|
|
345
391
|
}
|
|
346
392
|
|
|
347
393
|
/**
|
|
@@ -350,6 +396,11 @@ function addSession(sessionId, queryInstance, writer = null, releaseInput = null
|
|
|
350
396
|
*/
|
|
351
397
|
function removeSession(sessionId) {
|
|
352
398
|
activeSessions.delete(sessionId);
|
|
399
|
+
// No process, no background work: anything still tracked was killed with it.
|
|
400
|
+
backgroundWork.clear(sessionId);
|
|
401
|
+
// See addSession: a page cached while the process was up still says
|
|
402
|
+
// `running` for any agent that never reported back.
|
|
403
|
+
sessionHistoryCache.invalidate(sessionId);
|
|
353
404
|
}
|
|
354
405
|
|
|
355
406
|
/**
|
|
@@ -539,9 +590,12 @@ function extractCumulativeTokenBudget(sdkMessage) {
|
|
|
539
590
|
};
|
|
540
591
|
}
|
|
541
592
|
|
|
542
|
-
// Tool calls that leave work running past the end of a turn. Bash only
|
|
543
|
-
// when
|
|
544
|
-
|
|
593
|
+
// Tool calls that leave work running past the end of a turn. Bash and Agent only
|
|
594
|
+
// count when they are backgrounded; the rest defer or watch work by nature.
|
|
595
|
+
// Workflow belongs here rather than in a branch of its own: its input schema has
|
|
596
|
+
// no foreground option at all, so every call returns a task id immediately and
|
|
597
|
+
// reports back in a later turn.
|
|
598
|
+
const DEFERRED_WORK_TOOLS = new Set(['Monitor', 'ScheduleWakeup', 'CronCreate', 'TaskCreate', 'Workflow']);
|
|
545
599
|
|
|
546
600
|
/**
|
|
547
601
|
* Detects tool calls that keep working after the turn's `result` arrives.
|
|
@@ -549,10 +603,14 @@ const DEFERRED_WORK_TOOLS = new Set(['Monitor', 'ScheduleWakeup', 'CronCreate',
|
|
|
549
603
|
* Only turns that start background work need their CLI process held open; every
|
|
550
604
|
* other turn can let it exit immediately, as it did before the hold existed.
|
|
551
605
|
*
|
|
606
|
+
* Used by the providers module's tests, which pin the tool matching directly:
|
|
607
|
+
* the alternative is driving a whole SDK run to observe whether stdin was held,
|
|
608
|
+
* and the cost of getting this wrong is silently killed background work.
|
|
609
|
+
*
|
|
552
610
|
* @param {Object} sdkMessage - SDK stream message
|
|
553
611
|
* @returns {boolean} True when the message launches work that outlives the turn
|
|
554
612
|
*/
|
|
555
|
-
function startsBackgroundWork(sdkMessage) {
|
|
613
|
+
export function startsBackgroundWork(sdkMessage) {
|
|
556
614
|
const content = sdkMessage?.message?.content;
|
|
557
615
|
if (!Array.isArray(content)) {
|
|
558
616
|
return false;
|
|
@@ -565,10 +623,162 @@ function startsBackgroundWork(sdkMessage) {
|
|
|
565
623
|
if (block.name === 'Bash') {
|
|
566
624
|
return block.input?.run_in_background === true;
|
|
567
625
|
}
|
|
626
|
+
// A backgrounded subagent outlives the turn exactly like a backgrounded
|
|
627
|
+
// Bash does, so the process has to be held open for it to report back.
|
|
628
|
+
// Agents background by default — `run_in_background` is optional and only
|
|
629
|
+
// an explicit `false` opts out — hence `!== false` rather than `=== true`.
|
|
630
|
+
// A foreground agent must stay out of DEFERRED_WORK_TOOLS: it never pushes
|
|
631
|
+
// a follow-up turn, so it would pin the process for the full ceiling.
|
|
632
|
+
if (block.name === 'Agent') {
|
|
633
|
+
return block.input?.run_in_background !== false;
|
|
634
|
+
}
|
|
568
635
|
return DEFERRED_WORK_TOOLS.has(block.name);
|
|
569
636
|
});
|
|
570
637
|
}
|
|
571
638
|
|
|
639
|
+
// `task_updated` patch statuses after which a task is gone for good. `pending`,
|
|
640
|
+
// `running` and `paused` are still outstanding; `killed` is what the CLI
|
|
641
|
+
// writes when it stops a task itself (the task notification spells it
|
|
642
|
+
// `stopped`).
|
|
643
|
+
const TERMINAL_TASK_STATUSES = new Set(['completed', 'failed', 'killed']);
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Tracks the background tasks each live session still has outstanding, folded
|
|
647
|
+
* from the `system` task events the SDK stream already carries.
|
|
648
|
+
*
|
|
649
|
+
* `startsBackgroundWork` above only knows that a turn *launched* something
|
|
650
|
+
* lasting; this knows what is still running and which task ids it answers to,
|
|
651
|
+
* which is what the running-sessions list and a stop request need once the
|
|
652
|
+
* turn's `result` has gone out and nothing else remembers the session is busy.
|
|
653
|
+
*
|
|
654
|
+
* Verified against a real query (SDK 0.3.165): `task_started` carries
|
|
655
|
+
* `task_id`, `tool_use_id`, `task_type` and `description` for every agent,
|
|
656
|
+
* workflow and backgrounded command (a foreground Bash emits nothing);
|
|
657
|
+
* `task_notification` settles a task with any status; `task_updated` carries
|
|
658
|
+
* only `task_id` and a patch, and is terminal when the patch's `status` is.
|
|
659
|
+
* Housekeeping tasks the CLI starts on its own have no `tool_use_id` and are
|
|
660
|
+
* not tracked — nothing in the transcript could show them.
|
|
661
|
+
*
|
|
662
|
+
* Exported so the folding can be driven with the four event shapes directly;
|
|
663
|
+
* the runtime keeps one instance keyed like `activeSessions`.
|
|
664
|
+
*
|
|
665
|
+
* @returns {{
|
|
666
|
+
* apply: (sessionKey: string, message: Object) => void,
|
|
667
|
+
* hasOutstanding: (sessionKey: string) => boolean,
|
|
668
|
+
* has: (sessionKey: string, taskId: string) => boolean,
|
|
669
|
+
* outstanding: (sessionKey: string) => Array<import('@/shared/types.js').BackgroundTaskSummary>,
|
|
670
|
+
* clear: (sessionKey: string) => void,
|
|
671
|
+
* list: () => Array<{ sessionId: string, tasks: Array<import('@/shared/types.js').BackgroundTaskSummary> }>
|
|
672
|
+
* }}
|
|
673
|
+
*/
|
|
674
|
+
export function createBackgroundWorkTracker() {
|
|
675
|
+
/** @type {Map<string, Map<string, import('@/shared/types.js').BackgroundTaskSummary>>} */
|
|
676
|
+
const sessions = new Map();
|
|
677
|
+
/**
|
|
678
|
+
* Tool-use ids the session's own turns issued. A task started for a call an
|
|
679
|
+
* agent made inside its own transcript — a workflow agent's backgrounded
|
|
680
|
+
* command, say — reaches this stream too, and nothing in the parent
|
|
681
|
+
* transcript could show it; it is kept for stopping but flagged `nested`.
|
|
682
|
+
* @type {Map<string, Set<string>>}
|
|
683
|
+
*/
|
|
684
|
+
const ownToolUseIds = new Map();
|
|
685
|
+
|
|
686
|
+
const remove = (sessionKey, taskId) => {
|
|
687
|
+
const tasks = sessions.get(sessionKey);
|
|
688
|
+
if (!tasks) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
tasks.delete(taskId);
|
|
692
|
+
if (tasks.size === 0) {
|
|
693
|
+
sessions.delete(sessionKey);
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
apply(sessionKey, message) {
|
|
699
|
+
if (message?.type === 'assistant' && !message.parent_tool_use_id) {
|
|
700
|
+
const content = message.message?.content;
|
|
701
|
+
if (Array.isArray(content)) {
|
|
702
|
+
for (const block of content) {
|
|
703
|
+
if (block?.type === 'tool_use' && typeof block.id === 'string') {
|
|
704
|
+
let ids = ownToolUseIds.get(sessionKey);
|
|
705
|
+
if (!ids) {
|
|
706
|
+
ids = new Set();
|
|
707
|
+
ownToolUseIds.set(sessionKey, ids);
|
|
708
|
+
}
|
|
709
|
+
ids.add(block.id);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (message?.type !== 'system' || typeof message.task_id !== 'string') {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
switch (message.subtype) {
|
|
719
|
+
case 'task_started': {
|
|
720
|
+
if (typeof message.tool_use_id !== 'string') {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const task = {
|
|
724
|
+
taskId: message.task_id,
|
|
725
|
+
toolUseId: message.tool_use_id,
|
|
726
|
+
taskType: message.task_type,
|
|
727
|
+
description: message.description,
|
|
728
|
+
startedAt: Date.now()
|
|
729
|
+
};
|
|
730
|
+
if (typeof message.workflow_name === 'string') {
|
|
731
|
+
task.workflowName = message.workflow_name;
|
|
732
|
+
}
|
|
733
|
+
if (!ownToolUseIds.get(sessionKey)?.has(message.tool_use_id)) {
|
|
734
|
+
task.nested = true;
|
|
735
|
+
}
|
|
736
|
+
let tasks = sessions.get(sessionKey);
|
|
737
|
+
if (!tasks) {
|
|
738
|
+
tasks = new Map();
|
|
739
|
+
sessions.set(sessionKey, tasks);
|
|
740
|
+
}
|
|
741
|
+
tasks.set(task.taskId, task);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
case 'task_notification':
|
|
745
|
+
remove(sessionKey, message.task_id);
|
|
746
|
+
return;
|
|
747
|
+
case 'task_updated':
|
|
748
|
+
if (TERMINAL_TASK_STATUSES.has(message.patch?.status)) {
|
|
749
|
+
remove(sessionKey, message.task_id);
|
|
750
|
+
}
|
|
751
|
+
return;
|
|
752
|
+
default:
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
hasOutstanding(sessionKey) {
|
|
757
|
+
return sessions.has(sessionKey);
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
has(sessionKey, taskId) {
|
|
761
|
+
return Boolean(sessions.get(sessionKey)?.has(taskId));
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
outstanding(sessionKey) {
|
|
765
|
+
return Array.from(sessions.get(sessionKey)?.values() ?? []);
|
|
766
|
+
},
|
|
767
|
+
|
|
768
|
+
clear(sessionKey) {
|
|
769
|
+
sessions.delete(sessionKey);
|
|
770
|
+
ownToolUseIds.delete(sessionKey);
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
list() {
|
|
774
|
+
return Array.from(sessions, ([sessionId, tasks]) => ({
|
|
775
|
+
sessionId,
|
|
776
|
+
tasks: Array.from(tasks.values())
|
|
777
|
+
}));
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
572
782
|
/**
|
|
573
783
|
* Builds the SDK user messages for one turn.
|
|
574
784
|
*
|
|
@@ -724,6 +934,12 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
724
934
|
// Set when a turn starts background work, cleared when the next `result`
|
|
725
935
|
// arrives — only turns with work still outstanding hold their process open.
|
|
726
936
|
let backgroundWorkPending = false;
|
|
937
|
+
// Set when the stream reports a task starting during this turn. Task events
|
|
938
|
+
// are the exact word on what is still running, so when the turn produced
|
|
939
|
+
// any, the tracker decides the hold; `startsBackgroundWork` is the fallback
|
|
940
|
+
// for tools that emit none (Monitor, ScheduleWakeup, CronCreate, TaskCreate)
|
|
941
|
+
// and for an SDK that does not report tasks at all.
|
|
942
|
+
let sawTaskEventThisTurn = false;
|
|
727
943
|
// True while the process is being held open for background work, so a later
|
|
728
944
|
// `result` can be recognised as that work reporting back.
|
|
729
945
|
let heldForBackgroundWork = false;
|
|
@@ -734,7 +950,7 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
734
950
|
// A new turn supersedes any earlier one still holding this session's process
|
|
735
951
|
// open, so held runs cannot stack up across a conversation.
|
|
736
952
|
if (sessionKey()) {
|
|
737
|
-
|
|
953
|
+
releaseHeldSession(sessionKey());
|
|
738
954
|
}
|
|
739
955
|
|
|
740
956
|
// Arms (or re-arms) the idle countdown that eventually closes stdin.
|
|
@@ -889,10 +1105,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
889
1105
|
return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
|
|
890
1106
|
};
|
|
891
1107
|
|
|
1108
|
+
// The SDK's own `query`, unless the caller supplies one (tests script the
|
|
1109
|
+
// stream to drive the hold logic below without a CLI process).
|
|
1110
|
+
const createQuery = context.createQuery ?? query;
|
|
892
1111
|
let heldPrompt = createHeldPromptStream(promptMessages);
|
|
893
1112
|
releasePromptStream = heldPrompt.release;
|
|
894
1113
|
try {
|
|
895
|
-
queryInstance =
|
|
1114
|
+
queryInstance = createQuery({
|
|
896
1115
|
prompt: heldPrompt.stream,
|
|
897
1116
|
options: sdkOptions
|
|
898
1117
|
});
|
|
@@ -905,7 +1124,7 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
905
1124
|
heldPrompt.release();
|
|
906
1125
|
heldPrompt = createHeldPromptStream(promptMessages);
|
|
907
1126
|
releasePromptStream = heldPrompt.release;
|
|
908
|
-
queryInstance =
|
|
1127
|
+
queryInstance = createQuery({
|
|
909
1128
|
prompt: heldPrompt.stream,
|
|
910
1129
|
options: sdkOptions
|
|
911
1130
|
});
|
|
@@ -971,10 +1190,32 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
971
1190
|
if (startsBackgroundWork(message)) {
|
|
972
1191
|
backgroundWorkPending = true;
|
|
973
1192
|
}
|
|
1193
|
+
if (message.type === 'system' && message.subtype === 'task_started') {
|
|
1194
|
+
sawTaskEventThisTurn = true;
|
|
1195
|
+
}
|
|
1196
|
+
backgroundWork.apply(sessionKey(), message);
|
|
1197
|
+
|
|
1198
|
+
// A task the user stopped gets no follow-up turn from the CLI — only its
|
|
1199
|
+
// `stopped` notification — so when that was the last outstanding task
|
|
1200
|
+
// nothing will ever push the `result` the release below waits for, and
|
|
1201
|
+
// the process would sit until the idle ceiling. Release it here. A
|
|
1202
|
+
// completed task is different: the CLI relays its result in a turn of
|
|
1203
|
+
// its own, which closing stdin now would cut short.
|
|
1204
|
+
if (
|
|
1205
|
+
heldForBackgroundWork
|
|
1206
|
+
&& message.type === 'system'
|
|
1207
|
+
&& message.subtype === 'task_notification'
|
|
1208
|
+
&& message.status === 'stopped'
|
|
1209
|
+
&& !backgroundWork.hasOutstanding(sessionKey())
|
|
1210
|
+
) {
|
|
1211
|
+
heldForBackgroundWork = false;
|
|
1212
|
+
releasePromptStream();
|
|
1213
|
+
}
|
|
974
1214
|
|
|
975
1215
|
if (message.type === 'result') {
|
|
976
1216
|
// The turn is done as far as the client is concerned.
|
|
977
1217
|
const abortPending = sessionKey() ? abortedSessionIds.has(sessionKey()) : false;
|
|
1218
|
+
const stillOutstanding = backgroundWork.hasOutstanding(sessionKey());
|
|
978
1219
|
if (!turnCompleteSent && !abortPending) {
|
|
979
1220
|
turnCompleteSent = true;
|
|
980
1221
|
ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
|
|
@@ -985,9 +1226,10 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
985
1226
|
sessionName: sessionSummary,
|
|
986
1227
|
stopReason: 'completed'
|
|
987
1228
|
});
|
|
988
|
-
} else if (heldForBackgroundWork && !abortPending) {
|
|
1229
|
+
} else if (heldForBackgroundWork && !abortPending && !stillOutstanding) {
|
|
989
1230
|
// A result after the turn already reported complete means the work we
|
|
990
|
-
// held the process open for has finished and pushed a follow-up turn
|
|
1231
|
+
// held the process open for has finished and pushed a follow-up turn
|
|
1232
|
+
// — the last of it, when nothing else is still running.
|
|
991
1233
|
notifyBackgroundWorkCompleted({
|
|
992
1234
|
userId: ws?.userId || null,
|
|
993
1235
|
provider: 'claude',
|
|
@@ -995,11 +1237,26 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
995
1237
|
sessionName: sessionSummary
|
|
996
1238
|
});
|
|
997
1239
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1240
|
+
// Work started during this turn, or work from an earlier turn that
|
|
1241
|
+
// has not settled yet (a follow-up turn reports one task in while
|
|
1242
|
+
// another is still going), is still running. Hold the process open
|
|
1243
|
+
// so it can finish and report back in a follow-up turn; the ceiling
|
|
1244
|
+
// is only a backstop for work that never reports.
|
|
1245
|
+
//
|
|
1246
|
+
// The release when the last task settles is this same branch on the
|
|
1247
|
+
// follow-up turn the CLI pushes for it, not the settling event
|
|
1248
|
+
// itself: closing stdin at that moment would cut the turn that
|
|
1249
|
+
// relays the task's result.
|
|
1250
|
+
//
|
|
1251
|
+
// When the turn reported its tasks, the tracker is the whole truth: an
|
|
1252
|
+
// Agent call without `run_in_background` is scored as background by
|
|
1253
|
+
// `startsBackgroundWork`, but the CLI runs it in the foreground and
|
|
1254
|
+
// it has settled before this `result` — holding for it kept a process
|
|
1255
|
+
// alive for the full ceiling with nothing outstanding.
|
|
1256
|
+
const holdForTurn = sawTaskEventThisTurn ? stillOutstanding : backgroundWorkPending || stillOutstanding;
|
|
1257
|
+
backgroundWorkPending = false;
|
|
1258
|
+
sawTaskEventThisTurn = false;
|
|
1259
|
+
if (holdForTurn) {
|
|
1003
1260
|
heldForBackgroundWork = true;
|
|
1004
1261
|
scheduleRelease();
|
|
1005
1262
|
} else {
|
|
@@ -1139,6 +1396,36 @@ async function abortClaudeSDKSession(sessionId) {
|
|
|
1139
1396
|
}
|
|
1140
1397
|
}
|
|
1141
1398
|
|
|
1399
|
+
/**
|
|
1400
|
+
* Sessions whose background tasks are still outstanding, with the tasks.
|
|
1401
|
+
*
|
|
1402
|
+
* A session stays here after its turn's `result` for as long as the process
|
|
1403
|
+
* is held open for the work — which is exactly the window in which nothing
|
|
1404
|
+
* else (the chat run registry marks the run completed at `result`) knows the
|
|
1405
|
+
* session is still busy.
|
|
1406
|
+
* @returns {Array<{ sessionId: string, tasks: Array<import('@/shared/types.js').BackgroundTaskSummary> }>}
|
|
1407
|
+
*/
|
|
1408
|
+
function listClaudeSDKBackgroundWork() {
|
|
1409
|
+
return backgroundWork.list();
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Stops one outstanding background task through the SDK, which then emits a
|
|
1414
|
+
* `task_notification` with status `stopped` — the same event that drops the
|
|
1415
|
+
* task from the tracker and settles its card.
|
|
1416
|
+
* @param {string} sessionId - Session identifier
|
|
1417
|
+
* @param {string} taskId - The task's `task_id` as reported on `task_started`
|
|
1418
|
+
* @returns {Promise<boolean>} False when no live process is tracking the task
|
|
1419
|
+
*/
|
|
1420
|
+
async function stopClaudeSDKTask(sessionId, taskId) {
|
|
1421
|
+
const session = getSession(sessionId);
|
|
1422
|
+
if (!session || !backgroundWork.has(sessionId, taskId)) {
|
|
1423
|
+
return false;
|
|
1424
|
+
}
|
|
1425
|
+
await session.instance.stopTask(taskId);
|
|
1426
|
+
return true;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1142
1429
|
/**
|
|
1143
1430
|
* Checks if an SDK session is currently active
|
|
1144
1431
|
* @param {string} sessionId - Session identifier
|
|
@@ -1146,7 +1433,22 @@ async function abortClaudeSDKSession(sessionId) {
|
|
|
1146
1433
|
*/
|
|
1147
1434
|
function isClaudeSDKSessionActive(sessionId) {
|
|
1148
1435
|
const session = getSession(sessionId);
|
|
1149
|
-
return session && session.status === 'active';
|
|
1436
|
+
return Boolean(session && session.status === 'active');
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* When the run behind a session started, or null when no run is up.
|
|
1441
|
+
*
|
|
1442
|
+
* The history reader uses this to tell a background agent launched by the
|
|
1443
|
+
* live process (still able to report back) from one launched by an earlier
|
|
1444
|
+
* process that has since exited (never will): a launch row older than the
|
|
1445
|
+
* live run cannot belong to it.
|
|
1446
|
+
* @param {string} sessionId - Session identifier
|
|
1447
|
+
* @returns {number|null} Epoch milliseconds the live run started, or null
|
|
1448
|
+
*/
|
|
1449
|
+
function getClaudeSDKSessionStartTime(sessionId) {
|
|
1450
|
+
const session = getSession(sessionId);
|
|
1451
|
+
return session && session.status === 'active' ? session.startTime : null;
|
|
1150
1452
|
}
|
|
1151
1453
|
|
|
1152
1454
|
/**
|
|
@@ -1201,13 +1503,18 @@ export const claudeRuntime = {
|
|
|
1201
1503
|
resolve: resolveToolApproval,
|
|
1202
1504
|
listPending: getPendingApprovalsForSession,
|
|
1203
1505
|
},
|
|
1506
|
+
listBackgroundWork: listClaudeSDKBackgroundWork,
|
|
1507
|
+
stopBackgroundTask: stopClaudeSDKTask,
|
|
1204
1508
|
};
|
|
1205
1509
|
|
|
1206
1510
|
// Export public API
|
|
1207
1511
|
export {
|
|
1208
1512
|
queryClaudeSDK,
|
|
1209
1513
|
abortClaudeSDKSession,
|
|
1514
|
+
listClaudeSDKBackgroundWork,
|
|
1515
|
+
stopClaudeSDKTask,
|
|
1210
1516
|
isClaudeSDKSessionActive,
|
|
1517
|
+
getClaudeSDKSessionStartTime,
|
|
1211
1518
|
getActiveClaudeSDKSessions,
|
|
1212
1519
|
resolveToolApproval,
|
|
1213
1520
|
getPendingApprovalsForSession,
|
|
@@ -147,9 +147,9 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
let sessionName =
|
|
150
|
+
let sessionName = await this.extractSessionTitle(filePath, parsed.sessionId);
|
|
151
151
|
if (!sessionName) {
|
|
152
|
-
sessionName =
|
|
152
|
+
sessionName = nameMap.get(parsed.sessionId);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
return {
|
|
@@ -158,7 +158,17 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Returns the best available title for one session from its transcript.
|
|
163
|
+
*
|
|
164
|
+
* Scans forward keeping the last match of each event type, then prefers
|
|
165
|
+
* `custom-title` (a manual `/rename`) over `ai-title` over `last-prompt`.
|
|
166
|
+
* Claude writes `custom-title` immediately before `ai-title`, so a reverse
|
|
167
|
+
* scan that returns its first hit would always lose the manual rename.
|
|
168
|
+
*
|
|
169
|
+
* Returns undefined on a missing or unreadable file so sync can continue.
|
|
170
|
+
*/
|
|
171
|
+
private async extractSessionTitle(
|
|
162
172
|
filePath: string,
|
|
163
173
|
sessionId: string
|
|
164
174
|
): Promise<string | undefined> {
|
|
@@ -166,7 +176,11 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
166
176
|
const content = await readFile(filePath, 'utf8');
|
|
167
177
|
const lines = content.split(/\r?\n/);
|
|
168
178
|
|
|
169
|
-
|
|
179
|
+
let foundCustomTitle: string | undefined;
|
|
180
|
+
let foundAiTitle: string | undefined;
|
|
181
|
+
let foundLastPrompt: string | undefined;
|
|
182
|
+
|
|
183
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
170
184
|
const line = lines[index]?.trim();
|
|
171
185
|
if (!line) {
|
|
172
186
|
continue;
|
|
@@ -182,18 +196,30 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
182
196
|
const data = parsed as Record<string, unknown>;
|
|
183
197
|
const eventType = typeof data.type === 'string' ? data.type : undefined;
|
|
184
198
|
const eventSessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
(
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
|
|
200
|
+
if (eventSessionId !== sessionId) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (eventType === 'custom-title') {
|
|
205
|
+
const title = typeof data.customTitle === 'string' ? data.customTitle : undefined;
|
|
206
|
+
if (title?.trim()) {
|
|
207
|
+
foundCustomTitle = title;
|
|
208
|
+
}
|
|
209
|
+
} else if (eventType === 'ai-title') {
|
|
210
|
+
const title = typeof data.aiTitle === 'string' ? data.aiTitle : undefined;
|
|
211
|
+
if (title?.trim()) {
|
|
212
|
+
foundAiTitle = title;
|
|
213
|
+
}
|
|
214
|
+
} else if (eventType === 'last-prompt') {
|
|
215
|
+
const prompt = typeof data.lastPrompt === 'string' ? data.lastPrompt : undefined;
|
|
216
|
+
if (prompt?.trim()) {
|
|
217
|
+
foundLastPrompt = prompt;
|
|
218
|
+
}
|
|
195
219
|
}
|
|
196
220
|
}
|
|
221
|
+
|
|
222
|
+
return foundCustomTitle || foundAiTitle || foundLastPrompt;
|
|
197
223
|
} catch {
|
|
198
224
|
// Ignore missing/unreadable files so sync can continue.
|
|
199
225
|
}
|