@cloudcli-ai/cloudcli 1.37.1 → 1.37.3
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 +1 -0
- package/dist/assets/arc-CIfH4uMN.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-_IpEkukg.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-BttxrpgZ.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-g51dSRr1.js +10 -0
- package/dist/assets/channel-C5xU9jMw.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-D_-LbQUa.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-BtlHzwaO.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-UzkpvpAT.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-BKwx5Gag.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-D_KEBH5f.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-rK2DGyto.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-CeBl_N9i.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-BiQLWVIr.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-C1knVGV5.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-CtKbJkvF.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-DjX8y40Z.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-DM6YeRba.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-wZJ-vy5B.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-BzP0ejkH.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-BTL_j23J.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-Cf1Norgu.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-DPiUEKr-.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-CTcTpiz5.js +24 -0
- package/dist/assets/diagram-UB23O5K3-DPMSdYm0.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-7yvD6uiF.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-CfgeDoV6.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-B27ng5zu.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-CcC6b_7t.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-BX5kPGrO.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-BTDHQ6Id.css +32 -0
- package/dist/assets/index-sFfz8iBR.js +4931 -0
- package/dist/assets/infoDiagram-6WML65LV-CUP-SlN4.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-BTTDhOpX.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-ARXcc61m.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-BieMpqMA.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-61vxjRnP.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-nhnwkfQx.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-Cm6x5vT1.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-CcYBq_9H.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-CfvIjfzo.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-SgYoWPNb.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-D2Mu5E_f.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-Azwe_k4Q.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-DN1Ju3PA.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-Bj4qC34S.js +162 -0
- package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Co4PdklG.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-nOvL9dRC.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-CCK3wMid.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-BC29RNFm.js +120 -0
- package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
- package/dist/assets/vennDiagram-T6HMQDX7-pZlZCtEo.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-De8k2-zM.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-Bk8ykH0w.js +7 -0
- package/dist/index.html +3 -3
- package/dist-server/server/index.js +8 -5
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +2 -1
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/agent/agent.routes.js +2 -2
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +2 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -1
- package/dist-server/server/modules/cli/cli.js +9 -3
- package/dist-server/server/modules/cli/cli.js.map +1 -1
- package/dist-server/server/modules/cli/cli.module.js +0 -1
- package/dist-server/server/modules/cli/cli.module.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +0 -18
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
- package/dist-server/server/modules/database/connection.js +1 -1
- package/dist-server/server/modules/database/connection.js.map +1 -1
- package/dist-server/server/modules/database/index.js +5 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +32 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
- package/dist-server/server/modules/database/schema.js +92 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
- 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 +62 -11
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +15 -7
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
- package/dist-server/server/modules/notifications/index.js +3 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
- package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +3 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
- 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 +422 -86
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -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 +60 -49
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +29 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
- 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 +87 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
- package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/scheduled-messages/index.js +5 -0
- package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
- package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
- package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
- package/dist-server/server/modules/user/user.module.js +10 -1
- package/dist-server/server/modules/user/user.module.js.map +1 -1
- package/dist-server/server/modules/user/user.routes.js +44 -0
- package/dist-server/server/modules/user/user.routes.js.map +1 -1
- package/dist-server/server/modules/user/user.service.js +75 -0
- package/dist-server/server/modules/user/user.service.js.map +1 -1
- package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
- package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +7 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
- package/dist-server/server/shared/claude-cli-path.js +22 -5
- package/dist-server/server/shared/claude-cli-path.js.map +1 -1
- package/dist-server/server/shared/index.js +3 -0
- package/dist-server/server/shared/index.js.map +1 -0
- package/dist-server/server/shared/message-unification.js +376 -0
- package/dist-server/server/shared/message-unification.js.map +1 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
- package/dist-server/server/shared/tests/message-unification.test.js +174 -0
- package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +42 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +23 -16
- package/scripts/promote-dist-server.mjs +50 -0
- package/server/index.ts +12 -6
- package/server/modules/agent/agent.module.ts +2 -1
- package/server/modules/agent/agent.routes.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +33 -1
- package/server/modules/auth/auth.middleware.ts +2 -2
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -33
- package/server/modules/browser-use/browser-use.service.ts +5 -8
- package/server/modules/browser-use/index.ts +3 -0
- package/server/modules/cli/cli.module.ts +0 -1
- package/server/modules/cli/cli.service.ts +0 -20
- package/server/modules/cli/cli.ts +9 -3
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +0 -3
- package/server/modules/database/connection.ts +2 -1
- package/server/modules/database/index.ts +10 -0
- package/server/modules/database/migrations.ts +37 -0
- package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
- package/server/modules/database/repositories/session-drafts.db.ts +171 -0
- package/server/modules/database/repositories/sessions.db.ts +196 -3
- package/server/modules/database/repositories/user-preferences.db.ts +80 -0
- package/server/modules/database/schema.ts +96 -0
- package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
- package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
- package/server/modules/file-tree/file-tree.service.ts +21 -10
- package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git.routes.ts +16 -9
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +83 -0
- package/server/modules/notifications/index.ts +2 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
- package/server/modules/projects/services/project-delete.service.ts +13 -1
- package/server/modules/projects/services/project-star.service.ts +2 -2
- package/server/modules/providers/index.ts +4 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
- package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
- package/server/modules/providers/list/claude/claude-runtime.provider.js +463 -93
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
- package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
- package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
- package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
- package/server/modules/providers/provider.routes.ts +36 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
- package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
- package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
- package/server/modules/providers/services/session-history-cache.service.ts +139 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
- package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
- package/server/modules/providers/services/sessions.service.ts +201 -9
- package/server/modules/providers/tests/claude-auth.test.ts +58 -0
- package/server/modules/providers/tests/claude-models.test.ts +87 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
- package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
- package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
- package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
- package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
- package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
- package/server/modules/providers/tests/opencode-models.test.ts +166 -8
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
- package/server/modules/providers/tests/provider.routes.test.ts +61 -0
- package/server/modules/providers/tests/session-fork.test.ts +173 -0
- package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
- package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
- package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
- package/server/modules/providers/tests/sessions.service.test.ts +67 -1
- package/server/modules/providers/tests/skills.test.ts +21 -5
- package/server/modules/scheduled-messages/index.ts +8 -0
- package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
- package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
- package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
- package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
- package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
- package/server/modules/user/tests/user.service.test.ts +90 -0
- package/server/modules/user/user.module.ts +10 -1
- package/server/modules/user/user.routes.ts +44 -0
- package/server/modules/user/user.service.ts +105 -0
- package/server/modules/voice/tests/voice.service.test.ts +3 -2
- package/server/modules/websocket/index.ts +8 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
- package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
- package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
- package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
- package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
- package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
- package/server/shared/claude-cli-path.ts +24 -6
- package/server/shared/index.ts +3 -0
- package/server/shared/interfaces.ts +62 -0
- package/server/shared/message-unification.ts +449 -0
- package/server/shared/tests/claude-cli-path.test.ts +58 -1
- package/server/shared/tests/message-unification.test.ts +218 -0
- package/server/shared/types.ts +123 -3
- package/server/shared/utils.ts +50 -0
- package/server/tsconfig.json +6 -2
- package/README.de.md +0 -257
- package/README.ja.md +0 -249
- package/README.ko.md +0 -249
- package/README.ru.md +0 -257
- package/README.tr.md +0 -258
- package/README.zh-CN.md +0 -249
- package/README.zh-TW.md +0 -249
- package/dist/assets/index-D3jfzuKN.css +0 -32
- package/dist/assets/index-DTMWjJj8.js +0 -1550
- package/dist-server/server/constants/config.js +0 -6
- package/dist-server/server/constants/config.js.map +0 -1
- package/server/constants/config.ts +0 -5
|
@@ -1,59 +1,146 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { access } from 'node:fs/promises';
|
|
3
|
+
|
|
4
|
+
import { scanStateDb, sessionsDb } from '@/modules/database/index.js';
|
|
2
5
|
import { providerRegistry } from '@/modules/providers/provider.registry.js';
|
|
3
6
|
import type { LLMProvider } from '@/shared/types.js';
|
|
4
7
|
|
|
5
8
|
type SessionSynchronizeResult = {
|
|
6
9
|
processedByProvider: Record<LLMProvider, number>;
|
|
10
|
+
/** Indexed sessions dropped because their transcript file no longer exists. */
|
|
11
|
+
prunedOrphans: number;
|
|
7
12
|
failures: string[];
|
|
8
13
|
};
|
|
9
14
|
|
|
15
|
+
const pathExists = async (target: string): Promise<boolean> => {
|
|
16
|
+
try {
|
|
17
|
+
await access(target);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Removes indexed sessions whose transcript file has disappeared from disk.
|
|
26
|
+
*
|
|
27
|
+
* Nothing else deletes these rows: the synchronizers only ever upsert, and the
|
|
28
|
+
* watcher reacts to `add`/`change` but not `unlink`. A transcript removed by
|
|
29
|
+
* hand — or written by a test run that pointed at the real `~/.claude` — left a
|
|
30
|
+
* permanent sidebar entry that opened an empty "Untitled" session.
|
|
31
|
+
*
|
|
32
|
+
* A row is only dropped when its *containing directory* still exists. That
|
|
33
|
+
* keeps an unmounted or not-yet-created home from being read as "every
|
|
34
|
+
* transcript was deleted" and wiping the whole index.
|
|
35
|
+
*/
|
|
36
|
+
const pruneOrphanedSessions = async (): Promise<number> => {
|
|
37
|
+
const knownDirectoryExists = new Map<string, boolean>();
|
|
38
|
+
let pruned = 0;
|
|
39
|
+
|
|
40
|
+
for (const { session_id: sessionId, jsonl_path: jsonlPath } of sessionsDb.getSessionsWithTranscriptPath()) {
|
|
41
|
+
if (await pathExists(jsonlPath)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const directory = path.dirname(jsonlPath);
|
|
46
|
+
let directoryExists = knownDirectoryExists.get(directory);
|
|
47
|
+
if (directoryExists === undefined) {
|
|
48
|
+
directoryExists = await pathExists(directory);
|
|
49
|
+
knownDirectoryExists.set(directory, directoryExists);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!directoryExists) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (sessionsDb.deleteSessionById(sessionId)) {
|
|
57
|
+
pruned += 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return pruned;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The scan that every `synchronizeSessions()` caller shares while it runs.
|
|
66
|
+
*
|
|
67
|
+
* Opening the UI fires `/api/projects` and `/api/projects/archived` at once,
|
|
68
|
+
* and each used to start its own full provider scan from the same
|
|
69
|
+
* `last_scanned_at` cursor: identical work over identical transcripts,
|
|
70
|
+
* contending for the same synchronous SQLite writes and doubling how long the
|
|
71
|
+
* sidebar sits on its loading screen. Callers that arrive while a scan is
|
|
72
|
+
* already running now await that scan instead of starting another.
|
|
73
|
+
*/
|
|
74
|
+
let inFlightSynchronization: Promise<SessionSynchronizeResult> | null = null;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Runs all provider synchronizers and updates scan_state.last_scanned_at.
|
|
78
|
+
*/
|
|
79
|
+
async function runSessionSynchronization(): Promise<SessionSynchronizeResult> {
|
|
80
|
+
const lastScanAt = scanStateDb.getLastScannedAt();
|
|
81
|
+
const scanBoundary = new Date();
|
|
82
|
+
const processedByProvider: Record<LLMProvider, number> = {
|
|
83
|
+
claude: 0,
|
|
84
|
+
codex: 0,
|
|
85
|
+
cursor: 0,
|
|
86
|
+
opencode: 0,
|
|
87
|
+
};
|
|
88
|
+
const failures: string[] = [];
|
|
89
|
+
|
|
90
|
+
const results = await Promise.allSettled(
|
|
91
|
+
providerRegistry.listProviders().map(async (provider) => ({
|
|
92
|
+
provider: provider.id,
|
|
93
|
+
processed: await provider.sessionSynchronizer.synchronize(lastScanAt ?? undefined),
|
|
94
|
+
}))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
for (const result of results) {
|
|
98
|
+
if (result.status === 'fulfilled') {
|
|
99
|
+
processedByProvider[result.value.provider] = result.value.processed;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
104
|
+
failures.push(reason);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Pruning is skipped after a partial sync: a provider that just failed may
|
|
108
|
+
// not have re-indexed transcripts it would otherwise have re-created.
|
|
109
|
+
const prunedOrphans = failures.length === 0 ? await pruneOrphanedSessions() : 0;
|
|
110
|
+
|
|
111
|
+
if (failures.length === 0) {
|
|
112
|
+
scanStateDb.updateLastScannedAt(scanBoundary);
|
|
113
|
+
} else {
|
|
114
|
+
console.warn(
|
|
115
|
+
`[Sessions] Skipping scan_state cursor advance because ${failures.length} provider sync(s) failed.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
processedByProvider,
|
|
121
|
+
prunedOrphans,
|
|
122
|
+
failures,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
10
126
|
/**
|
|
11
127
|
* Orchestrates provider-specific session indexers and indexed-session lifecycle operations.
|
|
12
128
|
*/
|
|
13
129
|
export const sessionSynchronizerService = {
|
|
14
130
|
/**
|
|
15
|
-
*
|
|
131
|
+
* Scans every provider for new or changed sessions, coalescing concurrent
|
|
132
|
+
* callers onto a single scan.
|
|
16
133
|
*/
|
|
17
134
|
async synchronizeSessions(): Promise<SessionSynchronizeResult> {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const processedByProvider: Record<LLMProvider, number> = {
|
|
21
|
-
claude: 0,
|
|
22
|
-
codex: 0,
|
|
23
|
-
cursor: 0,
|
|
24
|
-
opencode: 0,
|
|
25
|
-
};
|
|
26
|
-
const failures: string[] = [];
|
|
27
|
-
|
|
28
|
-
const results = await Promise.allSettled(
|
|
29
|
-
providerRegistry.listProviders().map(async (provider) => ({
|
|
30
|
-
provider: provider.id,
|
|
31
|
-
processed: await provider.sessionSynchronizer.synchronize(lastScanAt ?? undefined),
|
|
32
|
-
}))
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
for (const result of results) {
|
|
36
|
-
if (result.status === 'fulfilled') {
|
|
37
|
-
processedByProvider[result.value.provider] = result.value.processed;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
42
|
-
failures.push(reason);
|
|
135
|
+
if (inFlightSynchronization) {
|
|
136
|
+
return inFlightSynchronization;
|
|
43
137
|
}
|
|
44
138
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
console.warn(
|
|
49
|
-
`[Sessions] Skipping scan_state cursor advance because ${failures.length} provider sync(s) failed.`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
139
|
+
inFlightSynchronization = runSessionSynchronization().finally(() => {
|
|
140
|
+
inFlightSynchronization = null;
|
|
141
|
+
});
|
|
52
142
|
|
|
53
|
-
return
|
|
54
|
-
processedByProvider,
|
|
55
|
-
failures,
|
|
56
|
-
};
|
|
143
|
+
return inFlightSynchronization;
|
|
57
144
|
},
|
|
58
145
|
|
|
59
146
|
/**
|
|
@@ -4,11 +4,9 @@ import { promises as fsPromises } from 'node:fs';
|
|
|
4
4
|
|
|
5
5
|
import chokidar, { type FSWatcher } from 'chokidar';
|
|
6
6
|
|
|
7
|
-
import { projectsDb, sessionsDb } from '@/modules/database/index.js';
|
|
8
7
|
import { sessionSynchronizerService } from '@/modules/providers/services/session-synchronizer.service.js';
|
|
9
|
-
import {
|
|
8
|
+
import { broadcastSessionUpsertedBatch } from '@/modules/websocket/index.js';
|
|
10
9
|
import type { LLMProvider } from '@/shared/types.js';
|
|
11
|
-
import { generateDisplayName } from '@/modules/projects/index.js';
|
|
12
10
|
|
|
13
11
|
type WatcherEventType = 'add' | 'change';
|
|
14
12
|
|
|
@@ -125,50 +123,6 @@ function queuePendingWatcherUpdate(
|
|
|
125
123
|
schedulePendingWatcherFlush();
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
/**
|
|
129
|
-
* Builds one `session_upserted` delta event for a provider-native session id.
|
|
130
|
-
*
|
|
131
|
-
* The event carries everything a sidebar needs to upsert the session in place
|
|
132
|
-
* (session summary plus owning-project metadata), so clients never need a full
|
|
133
|
-
* project-list refetch when a transcript file changes on disk. Returns `null`
|
|
134
|
-
* when the id cannot be resolved to an indexed session row.
|
|
135
|
-
*/
|
|
136
|
-
async function buildSessionUpsertedEvent(updatedProviderSessionId: string): Promise<string | null> {
|
|
137
|
-
const row = sessionsDb.getSessionByProviderSessionId(updatedProviderSessionId)
|
|
138
|
-
?? sessionsDb.getSessionById(updatedProviderSessionId);
|
|
139
|
-
if (!row || row.isArchived) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const projectPath = row.project_path;
|
|
144
|
-
const project = projectPath ? projectsDb.getProjectPath(projectPath) : null;
|
|
145
|
-
const displayName = project?.custom_project_name?.trim()
|
|
146
|
-
? project.custom_project_name
|
|
147
|
-
: await generateDisplayName(path.basename(projectPath ?? '') || (projectPath ?? ''), projectPath);
|
|
148
|
-
|
|
149
|
-
return JSON.stringify({
|
|
150
|
-
kind: 'session_upserted',
|
|
151
|
-
sessionId: row.session_id,
|
|
152
|
-
provider: row.provider,
|
|
153
|
-
session: {
|
|
154
|
-
id: row.session_id,
|
|
155
|
-
summary: row.custom_name || '',
|
|
156
|
-
messageCount: 0,
|
|
157
|
-
lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
|
|
158
|
-
},
|
|
159
|
-
project: project
|
|
160
|
-
? {
|
|
161
|
-
projectId: project.project_id,
|
|
162
|
-
path: project.project_path,
|
|
163
|
-
fullPath: project.project_path,
|
|
164
|
-
displayName,
|
|
165
|
-
isStarred: Boolean(project.isStarred),
|
|
166
|
-
}
|
|
167
|
-
: null,
|
|
168
|
-
timestamp: new Date().toISOString(),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
126
|
async function flushPendingWatcherUpdate(): Promise<void> {
|
|
173
127
|
clearPendingWatcherFlushTimer();
|
|
174
128
|
|
|
@@ -190,23 +144,7 @@ async function flushPendingWatcherUpdate(): Promise<void> {
|
|
|
190
144
|
// Per-session deltas instead of full project snapshots: an upsert of one
|
|
191
145
|
// session can never clobber unrelated client state, so the frontend needs
|
|
192
146
|
// no "suppress updates while a run is active" protection logic.
|
|
193
|
-
|
|
194
|
-
for (const updatedSessionId of queuedUpdate.updatedSessionIds) {
|
|
195
|
-
const event = await buildSessionUpsertedEvent(updatedSessionId);
|
|
196
|
-
if (event) {
|
|
197
|
-
events.push(event);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (events.length > 0) {
|
|
202
|
-
connectedClients.forEach(client => {
|
|
203
|
-
if (client.readyState === WS_OPEN_STATE) {
|
|
204
|
-
for (const event of events) {
|
|
205
|
-
client.send(event);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
}
|
|
147
|
+
await broadcastSessionUpsertedBatch(queuedUpdate.updatedSessionIds);
|
|
210
148
|
} catch (error) {
|
|
211
149
|
const message = error instanceof Error ? error.message : String(error);
|
|
212
150
|
console.error('Session watcher refresh failed while broadcasting session_upserted', { error: message });
|
|
@@ -262,6 +200,7 @@ export async function initializeSessionsWatcher(): Promise<void> {
|
|
|
262
200
|
const initialSync = await sessionSynchronizerService.synchronizeSessions();
|
|
263
201
|
console.log('Initial session synchronization complete', {
|
|
264
202
|
processedByProvider: initialSync.processedByProvider,
|
|
203
|
+
prunedOrphans: initialSync.prunedOrphans,
|
|
265
204
|
failures: initialSync.failures,
|
|
266
205
|
});
|
|
267
206
|
|
|
@@ -3,15 +3,16 @@ import fsp from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { projectsDb, sessionsDb } from '@/modules/database/index.js';
|
|
6
|
-
import { chatRunRegistry } from '@/modules/websocket/index.js';
|
|
6
|
+
import { broadcastSessionUpserted, chatRunRegistry } from '@/modules/websocket/index.js';
|
|
7
7
|
import { providerRegistry } from '@/modules/providers/provider.registry.js';
|
|
8
|
+
import { sessionHistoryCache } from '@/modules/providers/services/session-history-cache.service.js';
|
|
8
9
|
import type {
|
|
9
10
|
FetchHistoryOptions,
|
|
10
11
|
FetchHistoryResult,
|
|
11
12
|
LLMProvider,
|
|
12
13
|
NormalizedMessage,
|
|
13
14
|
} from '@/shared/types.js';
|
|
14
|
-
import { AppError } from '@/shared/utils.js';
|
|
15
|
+
import { AppError, sliceTailPage } from '@/shared/utils.js';
|
|
15
16
|
|
|
16
17
|
type CreateAppSessionResult = {
|
|
17
18
|
sessionId: string;
|
|
@@ -236,6 +237,79 @@ export const sessionsService = {
|
|
|
236
237
|
};
|
|
237
238
|
},
|
|
238
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Branches a session into an independent one containing its conversation up
|
|
242
|
+
* to `upToAnchorId` (the whole thing when omitted).
|
|
243
|
+
*
|
|
244
|
+
* The source is left completely untouched — this is the "try two approaches"
|
|
245
|
+
* action, not a destructive one.
|
|
246
|
+
*/
|
|
247
|
+
async forkSessionById(
|
|
248
|
+
sessionId: string,
|
|
249
|
+
options: { upToAnchorId?: string; title?: string } = {},
|
|
250
|
+
): Promise<CreateAppSessionResult> {
|
|
251
|
+
const source = sessionsDb.getSessionById(sessionId);
|
|
252
|
+
if (!source) {
|
|
253
|
+
throw new AppError(`Session "${sessionId}" was not found.`, {
|
|
254
|
+
code: 'SESSION_NOT_FOUND',
|
|
255
|
+
statusCode: 404,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const provider = source.provider as LLMProvider;
|
|
260
|
+
const fork = providerRegistry.resolveProvider(provider).fork;
|
|
261
|
+
if (!fork) {
|
|
262
|
+
throw new AppError(`Sessions cannot be forked for provider "${provider}".`, {
|
|
263
|
+
code: 'FORK_NOT_SUPPORTED',
|
|
264
|
+
statusCode: 409,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// A session that has never run has no transcript to copy, so there is
|
|
269
|
+
// nothing a fork of it could resume from.
|
|
270
|
+
if (!source.provider_session_id || !source.jsonl_path) {
|
|
271
|
+
throw new AppError('This session has not produced a transcript yet.', {
|
|
272
|
+
code: 'FORK_SOURCE_NOT_READY',
|
|
273
|
+
statusCode: 409,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const sessionName = options.title?.trim()
|
|
278
|
+
|| `${source.custom_name?.trim() || 'Session'} (fork)`;
|
|
279
|
+
|
|
280
|
+
const forked = await fork.forkSession({
|
|
281
|
+
providerSessionId: source.provider_session_id,
|
|
282
|
+
jsonlPath: source.jsonl_path,
|
|
283
|
+
projectPath: source.project_path ?? '',
|
|
284
|
+
upToAnchorId: options.upToAnchorId,
|
|
285
|
+
title: sessionName,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const forkSessionId = randomUUID();
|
|
289
|
+
sessionsDb.createForkedSession({
|
|
290
|
+
sessionId: forkSessionId,
|
|
291
|
+
provider,
|
|
292
|
+
projectPath: source.project_path ?? '',
|
|
293
|
+
customName: sessionName,
|
|
294
|
+
providerSessionId: forked.providerSessionId,
|
|
295
|
+
jsonlPath: forked.jsonlPath,
|
|
296
|
+
forkedFromSessionId: sessionId,
|
|
297
|
+
// A fork that silently dropped to the catalog default would answer
|
|
298
|
+
// differently from the conversation it was branched from.
|
|
299
|
+
model: source.model,
|
|
300
|
+
effort: source.effort,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await broadcastSessionUpserted(forkSessionId);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
sessionId: forkSessionId,
|
|
307
|
+
provider,
|
|
308
|
+
projectPath: source.project_path ?? '',
|
|
309
|
+
sessionName,
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
|
|
239
313
|
/**
|
|
240
314
|
* Resolves the provider-native id only for an explicit user copy action.
|
|
241
315
|
* Normal session payloads continue to expose only the stable app id.
|
|
@@ -268,6 +342,73 @@ export const sessionsService = {
|
|
|
268
342
|
* and every returned message is remapped back to the app session id so
|
|
269
343
|
* provider ids never reach the frontend.
|
|
270
344
|
*/
|
|
345
|
+
/**
|
|
346
|
+
* Resolves where a conversation must resume from so that one already-sent
|
|
347
|
+
* message, and everything after it, is replaced.
|
|
348
|
+
*
|
|
349
|
+
* Returns `null` when the provider cannot do this at all, which is how the
|
|
350
|
+
* chat gateway knows to refuse the request rather than silently sending the
|
|
351
|
+
* edit as a new message at the end of the conversation.
|
|
352
|
+
*/
|
|
353
|
+
async resolveEditAnchor(
|
|
354
|
+
sessionId: string,
|
|
355
|
+
anchorId: string,
|
|
356
|
+
): Promise<{ found: boolean; resumeThroughId: string | null } | null> {
|
|
357
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
358
|
+
if (!session) {
|
|
359
|
+
throw new AppError(`Session "${sessionId}" was not found.`, {
|
|
360
|
+
code: 'SESSION_NOT_FOUND',
|
|
361
|
+
statusCode: 404,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const sessions = providerRegistry.resolveProvider(session.provider as LLMProvider).sessions;
|
|
366
|
+
if (!sessions.resolveEditAnchor) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return sessions.resolveEditAnchor(sessionId, anchorId);
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Whether editing a message on this session's provider means rewinding it on
|
|
375
|
+
* disk first, rather than handing the anchor to the runtime as a resume
|
|
376
|
+
* option.
|
|
377
|
+
*
|
|
378
|
+
* Answering this without doing anything is the point: the rewind moves the
|
|
379
|
+
* session onto a different provider transcript and cannot be undone, so the
|
|
380
|
+
* gateway has to know which shape the run takes before it commits to one.
|
|
381
|
+
*/
|
|
382
|
+
providerRewindsForEdit(sessionId: string): boolean {
|
|
383
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
384
|
+
if (!session) {
|
|
385
|
+
throw new AppError(`Session "${sessionId}" was not found.`, {
|
|
386
|
+
code: 'SESSION_NOT_FOUND',
|
|
387
|
+
statusCode: 404,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return Boolean(providerRegistry.resolveProvider(session.provider as LLMProvider).sessions.rewindSession);
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Rewinds a session on disk so `keepThroughId` is the last row it holds.
|
|
396
|
+
*
|
|
397
|
+
* Only call this once the run is admitted — see `providerRewindsForEdit`.
|
|
398
|
+
*/
|
|
399
|
+
async rewindSessionForEdit(sessionId: string, keepThroughId: string | null): Promise<void> {
|
|
400
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
401
|
+
if (!session) {
|
|
402
|
+
throw new AppError(`Session "${sessionId}" was not found.`, {
|
|
403
|
+
code: 'SESSION_NOT_FOUND',
|
|
404
|
+
statusCode: 404,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const sessions = providerRegistry.resolveProvider(session.provider as LLMProvider).sessions;
|
|
409
|
+
await sessions.rewindSession?.(sessionId, keepThroughId);
|
|
410
|
+
},
|
|
411
|
+
|
|
271
412
|
async fetchHistory(
|
|
272
413
|
sessionId: string,
|
|
273
414
|
options: Pick<FetchHistoryOptions, 'limit' | 'offset'> = {},
|
|
@@ -293,13 +434,52 @@ export const sessionsService = {
|
|
|
293
434
|
}
|
|
294
435
|
|
|
295
436
|
const provider = session.provider as LLMProvider;
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
437
|
+
const providerSessions = providerRegistry.resolveProvider(provider).sessions;
|
|
438
|
+
const providerSessionId = session.provider_session_id;
|
|
439
|
+
const projectPath = session.project_path ?? '';
|
|
440
|
+
const requestedLimit = options.limit ?? null;
|
|
441
|
+
const requestedOffset = options.offset ?? 0;
|
|
442
|
+
|
|
443
|
+
// Claude and Codex history readers parse `jsonl_path` itself, so a page
|
|
444
|
+
// can be sliced from the stat-validated full-transcript cache instead of
|
|
445
|
+
// re-parsing the whole file per request. Cursor and OpenCode read their
|
|
446
|
+
// messages from elsewhere (store.db / shared SQLite), so that file's stat
|
|
447
|
+
// says nothing about their history — they stay on the direct path.
|
|
448
|
+
const transcriptPath = provider === 'claude' || provider === 'codex'
|
|
449
|
+
? session.jsonl_path
|
|
450
|
+
: null;
|
|
451
|
+
const fullHistory = await sessionHistoryCache.getFullHistory({
|
|
452
|
+
sessionId,
|
|
453
|
+
transcriptPath,
|
|
454
|
+
loadFull: () => providerSessions.fetchHistory(sessionId, {
|
|
455
|
+
limit: null,
|
|
456
|
+
offset: 0,
|
|
457
|
+
projectPath,
|
|
458
|
+
providerSessionId,
|
|
459
|
+
}),
|
|
301
460
|
});
|
|
302
461
|
|
|
462
|
+
let result: FetchHistoryResult;
|
|
463
|
+
if (fullHistory) {
|
|
464
|
+
// Providers slice with this same helper, so a cached page is identical
|
|
465
|
+
// to what a direct `(limit, offset)` read would have returned.
|
|
466
|
+
const { page, hasMore } = sliceTailPage(fullHistory.messages, requestedLimit, Math.max(0, requestedOffset));
|
|
467
|
+
result = {
|
|
468
|
+
...fullHistory,
|
|
469
|
+
messages: page,
|
|
470
|
+
hasMore,
|
|
471
|
+
offset: requestedOffset,
|
|
472
|
+
limit: requestedLimit,
|
|
473
|
+
};
|
|
474
|
+
} else {
|
|
475
|
+
result = await providerSessions.fetchHistory(sessionId, {
|
|
476
|
+
limit: requestedLimit,
|
|
477
|
+
offset: requestedOffset,
|
|
478
|
+
projectPath,
|
|
479
|
+
providerSessionId,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
303
483
|
return {
|
|
304
484
|
...result,
|
|
305
485
|
messages: result.messages.map((message) => ({
|
|
@@ -418,10 +598,22 @@ export const sessionsService = {
|
|
|
418
598
|
}
|
|
419
599
|
|
|
420
600
|
let removedFromDisk = false;
|
|
421
|
-
if (options.deletedFromDisk
|
|
422
|
-
|
|
601
|
+
if (options.deletedFromDisk) {
|
|
602
|
+
// Every file the conversation has lived in, not just the one the row
|
|
603
|
+
// points at now: editing a message on a provider that rewinds by
|
|
604
|
+
// branching moves the session onto a copy and leaves the earlier
|
|
605
|
+
// transcript behind. Deleting only the current one would leave the
|
|
606
|
+
// replaced turns on disk, and unreachable through the app.
|
|
607
|
+
const transcripts = [
|
|
608
|
+
...(session.jsonl_path ? [session.jsonl_path] : []),
|
|
609
|
+
...sessionsDb.getSupersededTranscriptPaths(sessionId),
|
|
610
|
+
];
|
|
611
|
+
for (const transcript of transcripts) {
|
|
612
|
+
removedFromDisk = (await removeFileIfExists(transcript)) || removedFromDisk;
|
|
613
|
+
}
|
|
423
614
|
}
|
|
424
615
|
|
|
616
|
+
sessionsDb.clearSupersededProviderSessions(sessionId);
|
|
425
617
|
const deleted = sessionsDb.deleteSessionById(sessionId);
|
|
426
618
|
if (!deleted) {
|
|
427
619
|
throw new AppError(`Session "${sessionId}" was not found.`, {
|
|
@@ -136,6 +136,64 @@ test('checkCredentials: no CLAUDE_CODE_OAUTH_TOKEN, expired credentials file rep
|
|
|
136
136
|
});
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
+
test('checkCredentials: expired access token with a live refresh token is still authenticated', async () => {
|
|
140
|
+
await withTempHome(async (homeDir) => {
|
|
141
|
+
await writeCredentialsFile(homeDir, {
|
|
142
|
+
claudeAiOauth: {
|
|
143
|
+
accessToken: 'stale-token',
|
|
144
|
+
refreshToken: 'live-refresh-token',
|
|
145
|
+
expiresAt: 1_000_000_000_000,
|
|
146
|
+
refreshTokenExpiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000,
|
|
147
|
+
},
|
|
148
|
+
email: 'someone@example.com',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await withEnv({}, async () => {
|
|
152
|
+
const status = await checkCredentials(new ClaudeProviderAuth());
|
|
153
|
+
assert.equal(status.authenticated, true);
|
|
154
|
+
assert.equal(status.method, 'credentials_file');
|
|
155
|
+
assert.equal(status.email, 'someone@example.com');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('checkCredentials: expired access token with a refresh token that has no recorded expiry is authenticated', async () => {
|
|
161
|
+
await withTempHome(async (homeDir) => {
|
|
162
|
+
await writeCredentialsFile(homeDir, {
|
|
163
|
+
claudeAiOauth: {
|
|
164
|
+
accessToken: 'stale-token',
|
|
165
|
+
refreshToken: 'live-refresh-token',
|
|
166
|
+
expiresAt: 1_000_000_000_000,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await withEnv({}, async () => {
|
|
171
|
+
const status = await checkCredentials(new ClaudeProviderAuth());
|
|
172
|
+
assert.equal(status.authenticated, true);
|
|
173
|
+
assert.equal(status.method, 'credentials_file');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('checkCredentials: expired access token with an expired refresh token reports not authenticated', async () => {
|
|
179
|
+
await withTempHome(async (homeDir) => {
|
|
180
|
+
await writeCredentialsFile(homeDir, {
|
|
181
|
+
claudeAiOauth: {
|
|
182
|
+
accessToken: 'stale-token',
|
|
183
|
+
refreshToken: 'stale-refresh-token',
|
|
184
|
+
expiresAt: 1_000_000_000_000,
|
|
185
|
+
refreshTokenExpiresAt: 1_000_000_000_000,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await withEnv({}, async () => {
|
|
190
|
+
const status = await checkCredentials(new ClaudeProviderAuth());
|
|
191
|
+
assert.equal(status.authenticated, false);
|
|
192
|
+
assert.match(status.error ?? '', /expired/i);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
139
197
|
test('checkCredentials: ANTHROPIC_API_KEY takes precedence over CLAUDE_CODE_OAUTH_TOKEN', async () => {
|
|
140
198
|
await withTempHome(async () => {
|
|
141
199
|
await withEnv(
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { extractClaudeEventModel } from '@/modules/providers/list/claude/claude-models.provider.js';
|
|
5
|
+
|
|
6
|
+
const SESSION_ID = 'session-1';
|
|
7
|
+
|
|
8
|
+
test('ignores the <synthetic> placeholder Claude Code stamps on synthesized rows', () => {
|
|
9
|
+
assert.equal(
|
|
10
|
+
extractClaudeEventModel(
|
|
11
|
+
{ sessionId: SESSION_ID, message: { model: '<synthetic>' } },
|
|
12
|
+
SESSION_ID,
|
|
13
|
+
),
|
|
14
|
+
null,
|
|
15
|
+
);
|
|
16
|
+
assert.equal(
|
|
17
|
+
extractClaudeEventModel({ sessionId: SESSION_ID, model: '<synthetic>' }, SESSION_ID),
|
|
18
|
+
null,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('still surfaces real model ids from message and event fields', () => {
|
|
23
|
+
assert.equal(
|
|
24
|
+
extractClaudeEventModel(
|
|
25
|
+
{ sessionId: SESSION_ID, message: { model: 'claude-sonnet-5' } },
|
|
26
|
+
SESSION_ID,
|
|
27
|
+
),
|
|
28
|
+
'claude-sonnet-5',
|
|
29
|
+
);
|
|
30
|
+
assert.equal(
|
|
31
|
+
extractClaudeEventModel({ sessionId: SESSION_ID, model: 'opus' }, SESSION_ID),
|
|
32
|
+
'opus',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('skips a placeholder content part so a later real model tag still wins', () => {
|
|
37
|
+
assert.equal(
|
|
38
|
+
extractClaudeEventModel(
|
|
39
|
+
{
|
|
40
|
+
sessionId: SESSION_ID,
|
|
41
|
+
message: {
|
|
42
|
+
content: [
|
|
43
|
+
{ text: '<model><synthetic></model>' },
|
|
44
|
+
{ text: '<model>claude-sonnet-5</model>' },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
SESSION_ID,
|
|
49
|
+
),
|
|
50
|
+
'claude-sonnet-5',
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a placeholder stdout hit does not shadow a real <model> tag in the same text', () => {
|
|
55
|
+
const text = '<local-command-stdout>Set model to <synthetic></local-command-stdout>'
|
|
56
|
+
+ '<model>claude-sonnet-5</model>';
|
|
57
|
+
assert.equal(
|
|
58
|
+
extractClaudeEventModel(
|
|
59
|
+
{ sessionId: SESSION_ID, message: { content: text } },
|
|
60
|
+
SESSION_ID,
|
|
61
|
+
),
|
|
62
|
+
'claude-sonnet-5',
|
|
63
|
+
);
|
|
64
|
+
assert.equal(
|
|
65
|
+
extractClaudeEventModel(
|
|
66
|
+
{ sessionId: SESSION_ID, message: { content: [{ text }] } },
|
|
67
|
+
SESSION_ID,
|
|
68
|
+
),
|
|
69
|
+
'claude-sonnet-5',
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('falls back to the message model when every content hit is a placeholder', () => {
|
|
74
|
+
assert.equal(
|
|
75
|
+
extractClaudeEventModel(
|
|
76
|
+
{
|
|
77
|
+
sessionId: SESSION_ID,
|
|
78
|
+
message: {
|
|
79
|
+
content: '<model><synthetic></model>',
|
|
80
|
+
model: 'claude-sonnet-5',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
SESSION_ID,
|
|
84
|
+
),
|
|
85
|
+
'claude-sonnet-5',
|
|
86
|
+
);
|
|
87
|
+
});
|