@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
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
PROJECTS_TABLE_SCHEMA_SQL,
|
|
8
8
|
PROVIDER_MODELS_TABLE_SCHEMA_SQL,
|
|
9
9
|
PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL,
|
|
10
|
+
SESSION_DRAFTS_TABLE_SCHEMA_SQL,
|
|
11
|
+
SUPERSEDED_PROVIDER_SESSIONS_TABLE_SCHEMA_SQL,
|
|
12
|
+
SCHEDULED_MESSAGES_TABLE_SCHEMA_SQL,
|
|
10
13
|
SESSIONS_TABLE_SCHEMA_SQL,
|
|
14
|
+
USER_PREFERENCES_TABLE_SCHEMA_SQL,
|
|
11
15
|
USER_NOTIFICATION_PREFERENCES_TABLE_SCHEMA_SQL,
|
|
12
16
|
VAPID_KEYS_TABLE_SCHEMA_SQL,
|
|
13
17
|
} from '@/modules/database/schema.js';
|
|
@@ -403,6 +407,29 @@ const addProviderSessionIdMapping = (db: Database): void => {
|
|
|
403
407
|
`);
|
|
404
408
|
};
|
|
405
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Adds the `forked_from_session_id` column recording where a branched session
|
|
412
|
+
* came from.
|
|
413
|
+
*
|
|
414
|
+
* Nothing is backfilled: a session that predates forking was not forked.
|
|
415
|
+
*/
|
|
416
|
+
/**
|
|
417
|
+
* Adds the transcript path to the superseded-session record.
|
|
418
|
+
*
|
|
419
|
+
* Only rows written before this column existed lack it, and there is nothing
|
|
420
|
+
* to backfill from — the session stopped pointing at that file when the row
|
|
421
|
+
* was created — so they keep a NULL and the delete path skips them.
|
|
422
|
+
*/
|
|
423
|
+
const addSupersededTranscriptPathColumn = (db: Database): void => {
|
|
424
|
+
const columnNames = getTableInfo(db, 'superseded_provider_sessions').map((column) => column.name);
|
|
425
|
+
addColumnToTableIfNotExists(db, 'superseded_provider_sessions', columnNames, 'jsonl_path', 'TEXT');
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const addForkedFromSessionIdColumn = (db: Database): void => {
|
|
429
|
+
const columnNames = getTableInfo(db, 'sessions').map((column) => column.name);
|
|
430
|
+
addColumnToTableIfNotExists(db, 'sessions', columnNames, 'forked_from_session_id', 'TEXT');
|
|
431
|
+
};
|
|
432
|
+
|
|
406
433
|
/**
|
|
407
434
|
* Adds the `model` column that records which model each session runs with.
|
|
408
435
|
*
|
|
@@ -477,6 +504,10 @@ export const runMigrations = (db: Database) => {
|
|
|
477
504
|
CREATE INDEX IF NOT EXISTS idx_provider_models_provider_order
|
|
478
505
|
ON provider_models(provider, sort_order, id)
|
|
479
506
|
`);
|
|
507
|
+
db.exec(USER_PREFERENCES_TABLE_SCHEMA_SQL);
|
|
508
|
+
db.exec(SESSION_DRAFTS_TABLE_SCHEMA_SQL);
|
|
509
|
+
db.exec(SUPERSEDED_PROVIDER_SESSIONS_TABLE_SCHEMA_SQL);
|
|
510
|
+
addSupersededTranscriptPathColumn(db);
|
|
480
511
|
|
|
481
512
|
db.exec(PROJECTS_TABLE_SCHEMA_SQL);
|
|
482
513
|
rebuildProjectsTableWithPrimaryKeySchema(db);
|
|
@@ -487,11 +518,17 @@ export const runMigrations = (db: Database) => {
|
|
|
487
518
|
addProviderSessionIdMapping(db);
|
|
488
519
|
addSessionModelColumn(db);
|
|
489
520
|
addSessionEffortColumn(db);
|
|
521
|
+
addForkedFromSessionIdColumn(db);
|
|
490
522
|
ensureProjectsForSessionPaths(db);
|
|
523
|
+
db.exec(SCHEDULED_MESSAGES_TABLE_SCHEMA_SQL);
|
|
491
524
|
|
|
492
525
|
db.exec('CREATE INDEX IF NOT EXISTS idx_session_ids_lookup ON sessions(session_id)');
|
|
493
526
|
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_provider_session_id ON sessions(provider_session_id)');
|
|
494
527
|
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_project_path ON sessions(project_path)');
|
|
528
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_forked_from ON sessions(forked_from_session_id)');
|
|
529
|
+
// The due-message poll runs on a timer; without this it table-scans.
|
|
530
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_scheduled_messages_due ON scheduled_messages(status, scheduled_for)');
|
|
531
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_scheduled_messages_session ON scheduled_messages(session_id)');
|
|
495
532
|
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_is_archived ON sessions(isArchived)');
|
|
496
533
|
db.exec('CREATE INDEX IF NOT EXISTS idx_projects_is_starred ON projects(isStarred)');
|
|
497
534
|
db.exec('CREATE INDEX IF NOT EXISTS idx_projects_is_archived ON projects(isArchived)');
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { getConnection } from '@/modules/database/connection.js';
|
|
4
|
+
|
|
5
|
+
export type ScheduledMessageStatus = 'pending' | 'sent' | 'failed' | 'cancelled';
|
|
6
|
+
|
|
7
|
+
export type ScheduledMessageRow = {
|
|
8
|
+
id: string;
|
|
9
|
+
user_id: number;
|
|
10
|
+
session_id: string;
|
|
11
|
+
content: string;
|
|
12
|
+
options: string;
|
|
13
|
+
scheduled_for: string;
|
|
14
|
+
status: ScheduledMessageStatus;
|
|
15
|
+
failure_reason: string | null;
|
|
16
|
+
created_at: string;
|
|
17
|
+
updated_at: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const COLUMNS =
|
|
21
|
+
'id, user_id, session_id, content, options, scheduled_for, status, failure_reason, created_at, updated_at';
|
|
22
|
+
|
|
23
|
+
export const scheduledMessagesDb = {
|
|
24
|
+
create(input: {
|
|
25
|
+
userId: number;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
content: string;
|
|
28
|
+
options: unknown;
|
|
29
|
+
scheduledFor: Date;
|
|
30
|
+
}): ScheduledMessageRow {
|
|
31
|
+
const db = getConnection();
|
|
32
|
+
const id = randomUUID();
|
|
33
|
+
|
|
34
|
+
db.prepare(
|
|
35
|
+
`INSERT INTO scheduled_messages (id, user_id, session_id, content, options, scheduled_for, status)
|
|
36
|
+
VALUES (?, ?, ?, ?, ?, ?, 'pending')`
|
|
37
|
+
).run(
|
|
38
|
+
id,
|
|
39
|
+
input.userId,
|
|
40
|
+
input.sessionId,
|
|
41
|
+
input.content,
|
|
42
|
+
JSON.stringify(input.options ?? {}),
|
|
43
|
+
input.scheduledFor.toISOString(),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return db.prepare(`SELECT ${COLUMNS} FROM scheduled_messages WHERE id = ?`).get(id) as ScheduledMessageRow;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/** Everything still to come or recently resolved, newest schedule first. */
|
|
50
|
+
listForSession(userId: number, sessionId: string): ScheduledMessageRow[] {
|
|
51
|
+
return getConnection()
|
|
52
|
+
.prepare(
|
|
53
|
+
`SELECT ${COLUMNS} FROM scheduled_messages
|
|
54
|
+
WHERE user_id = ? AND session_id = ?
|
|
55
|
+
ORDER BY scheduled_for ASC`
|
|
56
|
+
)
|
|
57
|
+
.all(userId, sessionId) as ScheduledMessageRow[];
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
listPendingForUser(userId: number): ScheduledMessageRow[] {
|
|
61
|
+
return getConnection()
|
|
62
|
+
.prepare(
|
|
63
|
+
`SELECT ${COLUMNS} FROM scheduled_messages
|
|
64
|
+
WHERE user_id = ? AND status = 'pending'
|
|
65
|
+
ORDER BY scheduled_for ASC`
|
|
66
|
+
)
|
|
67
|
+
.all(userId) as ScheduledMessageRow[];
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Claims every message whose time has passed, marking them in the same
|
|
72
|
+
* statement that selects them.
|
|
73
|
+
*
|
|
74
|
+
* Claiming is what makes a missed schedule work: the server can be down at
|
|
75
|
+
* the moment a message was due, and the next poll after it starts picks the
|
|
76
|
+
* message up instead of skipping it. Doing it in one transaction is what
|
|
77
|
+
* stops two overlapping polls from sending the same message twice.
|
|
78
|
+
*/
|
|
79
|
+
claimDue(now: Date): ScheduledMessageRow[] {
|
|
80
|
+
const db = getConnection();
|
|
81
|
+
const nowIso = now.toISOString();
|
|
82
|
+
|
|
83
|
+
return db.transaction(() => {
|
|
84
|
+
const due = db
|
|
85
|
+
.prepare(
|
|
86
|
+
`SELECT ${COLUMNS} FROM scheduled_messages
|
|
87
|
+
WHERE status = 'pending' AND scheduled_for <= ?
|
|
88
|
+
ORDER BY scheduled_for ASC`
|
|
89
|
+
)
|
|
90
|
+
.all(nowIso) as ScheduledMessageRow[];
|
|
91
|
+
|
|
92
|
+
for (const row of due) {
|
|
93
|
+
db.prepare(
|
|
94
|
+
`UPDATE scheduled_messages SET status = 'sent', updated_at = CURRENT_TIMESTAMP WHERE id = ?`
|
|
95
|
+
).run(row.id);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return due;
|
|
99
|
+
})();
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
markFailed(id: string, reason: string): void {
|
|
103
|
+
getConnection()
|
|
104
|
+
.prepare(
|
|
105
|
+
`UPDATE scheduled_messages
|
|
106
|
+
SET status = 'failed', failure_reason = ?, updated_at = CURRENT_TIMESTAMP
|
|
107
|
+
WHERE id = ?`
|
|
108
|
+
)
|
|
109
|
+
.run(reason.slice(0, 500), id);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Cancels a pending message, or dismisses a failed one so its banner goes
|
|
114
|
+
* away. Returns false when it had already fired successfully.
|
|
115
|
+
*/
|
|
116
|
+
cancel(userId: number, id: string): boolean {
|
|
117
|
+
const result = getConnection()
|
|
118
|
+
.prepare(
|
|
119
|
+
`UPDATE scheduled_messages
|
|
120
|
+
SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP
|
|
121
|
+
WHERE id = ? AND user_id = ? AND status IN ('pending', 'failed')`
|
|
122
|
+
)
|
|
123
|
+
.run(id, userId);
|
|
124
|
+
|
|
125
|
+
return result.changes > 0;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { getConnection } from '@/modules/database/connection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One chat scope's unsent state: the text still in the composer, plus the
|
|
5
|
+
* message queued behind an in-flight turn. Both are optional — a scope can
|
|
6
|
+
* hold only a draft, only a queued message, or both.
|
|
7
|
+
*/
|
|
8
|
+
export type SessionDraftRecord = {
|
|
9
|
+
scope: string;
|
|
10
|
+
text: string;
|
|
11
|
+
queuedMessage: unknown | null;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type DraftRow = {
|
|
16
|
+
draft_scope: string;
|
|
17
|
+
draft_text: string;
|
|
18
|
+
queued_message: string | null;
|
|
19
|
+
updated_at: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** A server-owned queued turn together with the exact stored value used to claim it once. */
|
|
23
|
+
export type QueuedSessionMessageRecord = {
|
|
24
|
+
userId: number;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
queuedMessage: unknown;
|
|
27
|
+
claimToken: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type QueuedMessageRow = {
|
|
31
|
+
user_id: number;
|
|
32
|
+
draft_scope: string;
|
|
33
|
+
queued_message: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** A queued message that no longer parses is treated as absent, not fatal. */
|
|
37
|
+
function parseQueuedMessage(raw: string | null): unknown | null {
|
|
38
|
+
if (!raw) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(raw) as unknown;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toRecord(row: DraftRow): SessionDraftRecord {
|
|
50
|
+
return {
|
|
51
|
+
scope: row.draft_scope,
|
|
52
|
+
text: row.draft_text,
|
|
53
|
+
queuedMessage: parseQueuedMessage(row.queued_message),
|
|
54
|
+
updatedAt: row.updated_at,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const sessionDraftsDb = {
|
|
59
|
+
/**
|
|
60
|
+
* Returns every draft the user has, newest first.
|
|
61
|
+
*
|
|
62
|
+
* The client pulls the whole set once per load: drafts are short strings, and
|
|
63
|
+
* having them all up front means switching sessions restores a draft written
|
|
64
|
+
* on another device without a round trip.
|
|
65
|
+
*/
|
|
66
|
+
getDrafts(userId: number): SessionDraftRecord[] {
|
|
67
|
+
const db = getConnection();
|
|
68
|
+
const rows = db
|
|
69
|
+
.prepare(
|
|
70
|
+
`SELECT draft_scope, draft_text, queued_message, updated_at
|
|
71
|
+
FROM session_drafts
|
|
72
|
+
WHERE user_id = ?
|
|
73
|
+
ORDER BY datetime(updated_at) DESC`
|
|
74
|
+
)
|
|
75
|
+
.all(userId) as DraftRow[];
|
|
76
|
+
|
|
77
|
+
return rows.map(toRecord);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/** Lists persisted queued turns whose scopes are real chat sessions. */
|
|
81
|
+
listQueuedMessages(): QueuedSessionMessageRecord[] {
|
|
82
|
+
const rows = getConnection()
|
|
83
|
+
.prepare(
|
|
84
|
+
`SELECT drafts.user_id, drafts.draft_scope, drafts.queued_message
|
|
85
|
+
FROM session_drafts AS drafts
|
|
86
|
+
INNER JOIN sessions ON sessions.session_id = drafts.draft_scope
|
|
87
|
+
WHERE drafts.queued_message IS NOT NULL`
|
|
88
|
+
)
|
|
89
|
+
.all() as QueuedMessageRow[];
|
|
90
|
+
|
|
91
|
+
return rows.map((row) => ({
|
|
92
|
+
userId: row.user_id,
|
|
93
|
+
sessionId: row.draft_scope,
|
|
94
|
+
queuedMessage: parseQueuedMessage(row.queued_message),
|
|
95
|
+
claimToken: row.queued_message,
|
|
96
|
+
}));
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/** Atomically removes a queued turn only if it has not been edited since listing. */
|
|
100
|
+
claimQueuedMessage(candidate: QueuedSessionMessageRecord): boolean {
|
|
101
|
+
const result = getConnection()
|
|
102
|
+
.prepare(
|
|
103
|
+
`UPDATE session_drafts
|
|
104
|
+
SET queued_message = NULL, updated_at = CURRENT_TIMESTAMP
|
|
105
|
+
WHERE user_id = ? AND draft_scope = ? AND queued_message = ?`
|
|
106
|
+
)
|
|
107
|
+
.run(candidate.userId, candidate.sessionId, candidate.claimToken);
|
|
108
|
+
return result.changes > 0;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/** Restores a claim lost to the narrow race where another run starts first. */
|
|
112
|
+
restoreQueuedMessage(candidate: QueuedSessionMessageRecord): void {
|
|
113
|
+
getConnection()
|
|
114
|
+
.prepare(
|
|
115
|
+
`UPDATE session_drafts
|
|
116
|
+
SET queued_message = ?, updated_at = CURRENT_TIMESTAMP
|
|
117
|
+
WHERE user_id = ? AND draft_scope = ? AND queued_message IS NULL`
|
|
118
|
+
)
|
|
119
|
+
.run(candidate.claimToken, candidate.userId, candidate.sessionId);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/** Removes the placeholder row left after its last queued turn is claimed. */
|
|
123
|
+
deleteEmptyDraft(userId: number, scope: string): void {
|
|
124
|
+
getConnection()
|
|
125
|
+
.prepare(
|
|
126
|
+
`DELETE FROM session_drafts
|
|
127
|
+
WHERE user_id = ? AND draft_scope = ? AND draft_text = '' AND queued_message IS NULL`
|
|
128
|
+
)
|
|
129
|
+
.run(userId, scope);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Writes one scope's draft, or deletes the row when nothing is left to keep.
|
|
134
|
+
*
|
|
135
|
+
* Deleting on empty is what stops the table growing a permanent row for every
|
|
136
|
+
* session the user ever opened and typed a character into.
|
|
137
|
+
*/
|
|
138
|
+
saveDraft(
|
|
139
|
+
userId: number,
|
|
140
|
+
scope: string,
|
|
141
|
+
draft: { text: string; queuedMessage: unknown | null }
|
|
142
|
+
): void {
|
|
143
|
+
const db = getConnection();
|
|
144
|
+
|
|
145
|
+
if (!draft.text && draft.queuedMessage === null) {
|
|
146
|
+
db.prepare('DELETE FROM session_drafts WHERE user_id = ? AND draft_scope = ?')
|
|
147
|
+
.run(userId, scope);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
db.prepare(
|
|
152
|
+
`INSERT INTO session_drafts (user_id, draft_scope, draft_text, queued_message, updated_at)
|
|
153
|
+
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
154
|
+
ON CONFLICT(user_id, draft_scope) DO UPDATE SET
|
|
155
|
+
draft_text = excluded.draft_text,
|
|
156
|
+
queued_message = excluded.queued_message,
|
|
157
|
+
updated_at = CURRENT_TIMESTAMP`
|
|
158
|
+
).run(
|
|
159
|
+
userId,
|
|
160
|
+
scope,
|
|
161
|
+
draft.text,
|
|
162
|
+
draft.queuedMessage === null ? null : JSON.stringify(draft.queuedMessage)
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
deleteDraft(userId: number, scope: string): void {
|
|
167
|
+
const db = getConnection();
|
|
168
|
+
db.prepare('DELETE FROM session_drafts WHERE user_id = ? AND draft_scope = ?')
|
|
169
|
+
.run(userId, scope);
|
|
170
|
+
},
|
|
171
|
+
};
|
|
@@ -13,6 +13,8 @@ type SessionRow = {
|
|
|
13
13
|
model: string | null;
|
|
14
14
|
/** Reasoning effort this session runs with; NULL until the app records one. */
|
|
15
15
|
effort: string | null;
|
|
16
|
+
/** The app session this one was branched from; NULL unless it is a fork. */
|
|
17
|
+
forked_from_session_id: string | null;
|
|
16
18
|
isArchived: number;
|
|
17
19
|
created_at: string;
|
|
18
20
|
updated_at: string;
|
|
@@ -24,7 +26,7 @@ type RecentSessionsPage = {
|
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const SESSION_ROW_COLUMNS =
|
|
27
|
-
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, model, effort, isArchived, created_at, updated_at';
|
|
29
|
+
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, model, effort, forked_from_session_id, isArchived, created_at, updated_at';
|
|
28
30
|
|
|
29
31
|
const SQLITE_UTC_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
30
32
|
|
|
@@ -111,7 +113,7 @@ export const sessionsDb = {
|
|
|
111
113
|
updated_at = COALESCE(?, CURRENT_TIMESTAMP),
|
|
112
114
|
project_path = ?,
|
|
113
115
|
jsonl_path = ?,
|
|
114
|
-
isArchived = 0,
|
|
116
|
+
isArchived = CASE WHEN ? IS NULL OR julianday(?) > julianday(updated_at) THEN 0 ELSE isArchived END,
|
|
115
117
|
custom_name = CASE
|
|
116
118
|
WHEN session_id <> provider_session_id AND custom_name IS NOT NULL THEN custom_name
|
|
117
119
|
ELSE COALESCE(?, custom_name)
|
|
@@ -122,6 +124,8 @@ export const sessionsDb = {
|
|
|
122
124
|
updatedAtValue,
|
|
123
125
|
normalizedProjectPath,
|
|
124
126
|
jsonlPath ?? null,
|
|
127
|
+
updatedAtValue,
|
|
128
|
+
updatedAtValue,
|
|
125
129
|
customName ?? null,
|
|
126
130
|
existing.session_id
|
|
127
131
|
);
|
|
@@ -141,7 +145,7 @@ export const sessionsDb = {
|
|
|
141
145
|
updated_at = excluded.updated_at,
|
|
142
146
|
project_path = excluded.project_path,
|
|
143
147
|
jsonl_path = excluded.jsonl_path,
|
|
144
|
-
isArchived = 0,
|
|
148
|
+
isArchived = CASE WHEN ? IS NULL OR julianday(excluded.updated_at) > julianday(sessions.updated_at) THEN 0 ELSE sessions.isArchived END,
|
|
145
149
|
custom_name = CASE
|
|
146
150
|
WHEN sessions.session_id <> sessions.provider_session_id AND sessions.custom_name IS NOT NULL
|
|
147
151
|
THEN sessions.custom_name
|
|
@@ -155,6 +159,7 @@ export const sessionsDb = {
|
|
|
155
159
|
normalizedProjectPath,
|
|
156
160
|
jsonlPath ?? null,
|
|
157
161
|
createdAtValue,
|
|
162
|
+
updatedAtValue,
|
|
158
163
|
updatedAtValue
|
|
159
164
|
);
|
|
160
165
|
|
|
@@ -189,6 +194,55 @@ export const sessionsDb = {
|
|
|
189
194
|
return sessionId;
|
|
190
195
|
},
|
|
191
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Inserts a session that already has its provider artifact on disk.
|
|
199
|
+
*
|
|
200
|
+
* Unlike `createAppSession` this writes `provider_session_id` and
|
|
201
|
+
* `jsonl_path` immediately, because a fork's transcript file exists before
|
|
202
|
+
* the row does — and the filesystem watcher would otherwise index it as an
|
|
203
|
+
* unrelated session under its own id.
|
|
204
|
+
*/
|
|
205
|
+
createForkedSession(input: {
|
|
206
|
+
sessionId: string;
|
|
207
|
+
provider: string;
|
|
208
|
+
projectPath: string;
|
|
209
|
+
customName: string | null;
|
|
210
|
+
providerSessionId: string;
|
|
211
|
+
jsonlPath: string;
|
|
212
|
+
forkedFromSessionId: string;
|
|
213
|
+
model: string | null;
|
|
214
|
+
effort: string | null;
|
|
215
|
+
}): string {
|
|
216
|
+
const db = getConnection();
|
|
217
|
+
const normalizedProjectPath = normalizeProjectPathForProvider(input.provider, input.projectPath);
|
|
218
|
+
|
|
219
|
+
projectsDb.createProjectPath(normalizedProjectPath);
|
|
220
|
+
|
|
221
|
+
// The watcher may already have created a row for the new transcript. Its
|
|
222
|
+
// id is the provider-native one, which is what this row claims, so replace
|
|
223
|
+
// it rather than leaving two sidebar entries for one conversation.
|
|
224
|
+
db.transaction(() => {
|
|
225
|
+
db.prepare('DELETE FROM sessions WHERE session_id = ? AND session_id <> ?')
|
|
226
|
+
.run(input.providerSessionId, input.sessionId);
|
|
227
|
+
db.prepare(
|
|
228
|
+
`INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, model, effort, forked_from_session_id, isArchived, created_at, updated_at)
|
|
229
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`
|
|
230
|
+
).run(
|
|
231
|
+
input.sessionId,
|
|
232
|
+
input.provider,
|
|
233
|
+
input.providerSessionId,
|
|
234
|
+
input.customName,
|
|
235
|
+
normalizedProjectPath,
|
|
236
|
+
input.jsonlPath,
|
|
237
|
+
input.model,
|
|
238
|
+
input.effort,
|
|
239
|
+
input.forkedFromSessionId,
|
|
240
|
+
);
|
|
241
|
+
})();
|
|
242
|
+
|
|
243
|
+
return input.sessionId;
|
|
244
|
+
},
|
|
245
|
+
|
|
192
246
|
/**
|
|
193
247
|
* Records the provider-native session id for one app-allocated session.
|
|
194
248
|
*
|
|
@@ -235,6 +289,125 @@ export const sessionsDb = {
|
|
|
235
289
|
merge();
|
|
236
290
|
},
|
|
237
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Moves one session onto a different provider session and transcript.
|
|
294
|
+
*
|
|
295
|
+
* Only editing a message on a provider that has to branch to rewind (Codex)
|
|
296
|
+
* does this — an ordinary run keeps the same provider session for its whole
|
|
297
|
+
* life. `assignProviderSessionId` cannot be used for it: that one keeps the
|
|
298
|
+
* existing `jsonl_path` on purpose, so a session repointed with it would
|
|
299
|
+
* claim the new thread while still reading the old transcript.
|
|
300
|
+
*
|
|
301
|
+
* The watcher may already have indexed the new transcript under its own id.
|
|
302
|
+
* That row is the same conversation this one is about to become, so it is
|
|
303
|
+
* replaced rather than left behind as a second sidebar entry.
|
|
304
|
+
*/
|
|
305
|
+
repointSessionToProviderSession(
|
|
306
|
+
sessionId: string,
|
|
307
|
+
input: { providerSessionId: string; jsonlPath: string },
|
|
308
|
+
): void {
|
|
309
|
+
const db = getConnection();
|
|
310
|
+
|
|
311
|
+
db.transaction(() => {
|
|
312
|
+
db.prepare('DELETE FROM sessions WHERE session_id = ? AND session_id <> ?')
|
|
313
|
+
.run(input.providerSessionId, sessionId);
|
|
314
|
+
db.prepare(
|
|
315
|
+
`UPDATE sessions SET
|
|
316
|
+
provider_session_id = ?,
|
|
317
|
+
jsonl_path = ?,
|
|
318
|
+
updated_at = CURRENT_TIMESTAMP
|
|
319
|
+
WHERE session_id = ?`
|
|
320
|
+
).run(input.providerSessionId, input.jsonlPath, sessionId);
|
|
321
|
+
})();
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Detaches a session from its provider session so the next run starts a new
|
|
326
|
+
* one.
|
|
327
|
+
*
|
|
328
|
+
* Used when an edit replaces the very first prompt: there is no conversation
|
|
329
|
+
* left to branch from, so the session starts over instead.
|
|
330
|
+
*/
|
|
331
|
+
detachProviderSession(sessionId: string): void {
|
|
332
|
+
const db = getConnection();
|
|
333
|
+
db.prepare(
|
|
334
|
+
`UPDATE sessions SET
|
|
335
|
+
provider_session_id = NULL,
|
|
336
|
+
jsonl_path = NULL,
|
|
337
|
+
updated_at = CURRENT_TIMESTAMP
|
|
338
|
+
WHERE session_id = ?`
|
|
339
|
+
).run(sessionId);
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Records that a session has left a provider session behind for good.
|
|
344
|
+
*
|
|
345
|
+
* The transcript stays on disk, which is deliberate — the abandoned attempt
|
|
346
|
+
* is recoverable — but the indexer must not offer it back, and on a session
|
|
347
|
+
* discovered from disk (whose app id *is* the provider id) rediscovering it
|
|
348
|
+
* would repoint the row at the conversation the user edited away from.
|
|
349
|
+
*/
|
|
350
|
+
markProviderSessionSuperseded(input: {
|
|
351
|
+
providerSessionId: string;
|
|
352
|
+
provider: string;
|
|
353
|
+
sessionId: string;
|
|
354
|
+
jsonlPath: string | null;
|
|
355
|
+
}): void {
|
|
356
|
+
const db = getConnection();
|
|
357
|
+
db.prepare(
|
|
358
|
+
`INSERT INTO superseded_provider_sessions (provider_session_id, provider, session_id, jsonl_path)
|
|
359
|
+
VALUES (?, ?, ?, ?)
|
|
360
|
+
ON CONFLICT(provider_session_id, provider) DO UPDATE SET
|
|
361
|
+
session_id = excluded.session_id,
|
|
362
|
+
jsonl_path = excluded.jsonl_path,
|
|
363
|
+
created_at = CURRENT_TIMESTAMP`
|
|
364
|
+
).run(input.providerSessionId, input.provider, input.sessionId, input.jsonlPath);
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
isProviderSessionSuperseded(providerSessionId: string, provider: string): boolean {
|
|
368
|
+
const db = getConnection();
|
|
369
|
+
const row = db
|
|
370
|
+
.prepare(
|
|
371
|
+
`SELECT 1 AS found FROM superseded_provider_sessions
|
|
372
|
+
WHERE provider_session_id = ? AND provider = ?
|
|
373
|
+
LIMIT 1`
|
|
374
|
+
)
|
|
375
|
+
.get(providerSessionId, provider) as { found: number } | undefined;
|
|
376
|
+
|
|
377
|
+
return Boolean(row);
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Transcripts one session has left behind, for the caller that deletes a
|
|
382
|
+
* conversation from disk.
|
|
383
|
+
*
|
|
384
|
+
* A conversation edited more than once has lived in more than one file, and
|
|
385
|
+
* the session row only ever points at the newest.
|
|
386
|
+
*/
|
|
387
|
+
getSupersededTranscriptPaths(sessionId: string): string[] {
|
|
388
|
+
const db = getConnection();
|
|
389
|
+
const rows = db
|
|
390
|
+
.prepare(
|
|
391
|
+
`SELECT jsonl_path FROM superseded_provider_sessions
|
|
392
|
+
WHERE session_id = ? AND jsonl_path IS NOT NULL`
|
|
393
|
+
)
|
|
394
|
+
.all(sessionId) as Array<{ jsonl_path: string }>;
|
|
395
|
+
|
|
396
|
+
return rows.map((row) => row.jsonl_path);
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Forgets what a session left behind, once the session itself is gone.
|
|
401
|
+
*
|
|
402
|
+
* Without this the record outlives the row it was written for and keeps the
|
|
403
|
+
* indexer refusing a transcript that no longer belongs to anything — a
|
|
404
|
+
* conversation invisible to the app and impossible to delete through it.
|
|
405
|
+
*/
|
|
406
|
+
clearSupersededProviderSessions(sessionId: string): void {
|
|
407
|
+
const db = getConnection();
|
|
408
|
+
db.prepare('DELETE FROM superseded_provider_sessions WHERE session_id = ?').run(sessionId);
|
|
409
|
+
},
|
|
410
|
+
|
|
238
411
|
/**
|
|
239
412
|
* Records the model one session runs with.
|
|
240
413
|
*
|
|
@@ -521,4 +694,24 @@ export const sessionsDb = {
|
|
|
521
694
|
const db = getConnection();
|
|
522
695
|
return db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId).changes > 0;
|
|
523
696
|
},
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Lists every indexed session that claims a transcript file on disk.
|
|
700
|
+
*
|
|
701
|
+
* Only rows with a `jsonl_path` are returned, which deliberately excludes
|
|
702
|
+
* app-created sessions still waiting for their first provider write and
|
|
703
|
+
* OpenCode rows (whose transcripts all live inside one shared sqlite file).
|
|
704
|
+
* Used by the session synchronizer to find rows whose transcript has been
|
|
705
|
+
* deleted underneath the index.
|
|
706
|
+
*/
|
|
707
|
+
getSessionsWithTranscriptPath(): Array<{ session_id: string; jsonl_path: string }> {
|
|
708
|
+
const db = getConnection();
|
|
709
|
+
return db
|
|
710
|
+
.prepare(
|
|
711
|
+
`SELECT session_id, jsonl_path
|
|
712
|
+
FROM sessions
|
|
713
|
+
WHERE jsonl_path IS NOT NULL AND jsonl_path <> ''`
|
|
714
|
+
)
|
|
715
|
+
.all() as Array<{ session_id: string; jsonl_path: string }>;
|
|
716
|
+
},
|
|
524
717
|
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getConnection } from '@/modules/database/connection.js';
|
|
2
|
+
|
|
3
|
+
type PreferenceRow = {
|
|
4
|
+
preference_key: string;
|
|
5
|
+
preference_value: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Decodes one stored preference, dropping any row whose JSON no longer parses
|
|
10
|
+
* rather than failing the whole read. A single corrupted value must not cost
|
|
11
|
+
* the user every other setting they have.
|
|
12
|
+
*/
|
|
13
|
+
function decodePreference(row: PreferenceRow): [string, unknown] | null {
|
|
14
|
+
try {
|
|
15
|
+
return [row.preference_key, JSON.parse(row.preference_value) as unknown];
|
|
16
|
+
} catch {
|
|
17
|
+
console.warn(`[UserPreferences] Dropping unreadable value for "${row.preference_key}"`);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const userPreferencesDb = {
|
|
23
|
+
/**
|
|
24
|
+
* Returns every preference the user has ever set, as one object.
|
|
25
|
+
*
|
|
26
|
+
* The client fetches this once on start-up, so absent keys simply mean "the
|
|
27
|
+
* user never changed this" and the client applies its own default.
|
|
28
|
+
*/
|
|
29
|
+
getPreferences(userId: number): Record<string, unknown> {
|
|
30
|
+
const db = getConnection();
|
|
31
|
+
const rows = db
|
|
32
|
+
.prepare(
|
|
33
|
+
`SELECT preference_key, preference_value
|
|
34
|
+
FROM user_preferences
|
|
35
|
+
WHERE user_id = ?`
|
|
36
|
+
)
|
|
37
|
+
.all(userId) as PreferenceRow[];
|
|
38
|
+
|
|
39
|
+
const preferences: Record<string, unknown> = {};
|
|
40
|
+
for (const row of rows) {
|
|
41
|
+
const decoded = decodePreference(row);
|
|
42
|
+
if (decoded) {
|
|
43
|
+
preferences[decoded[0]] = decoded[1];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return preferences;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Merge-patches preferences: keys present in `updates` are written, keys
|
|
52
|
+
* absent are left alone, and a key given as `undefined` is deleted.
|
|
53
|
+
*
|
|
54
|
+
* Runs in one transaction so a multi-key save from the settings dialog can
|
|
55
|
+
* never be observed half-applied.
|
|
56
|
+
*/
|
|
57
|
+
savePreferences(userId: number, updates: Record<string, unknown>): void {
|
|
58
|
+
const db = getConnection();
|
|
59
|
+
const upsert = db.prepare(
|
|
60
|
+
`INSERT INTO user_preferences (user_id, preference_key, preference_value, updated_at)
|
|
61
|
+
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
|
62
|
+
ON CONFLICT(user_id, preference_key) DO UPDATE SET
|
|
63
|
+
preference_value = excluded.preference_value,
|
|
64
|
+
updated_at = CURRENT_TIMESTAMP`
|
|
65
|
+
);
|
|
66
|
+
const remove = db.prepare(
|
|
67
|
+
'DELETE FROM user_preferences WHERE user_id = ? AND preference_key = ?'
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
db.transaction(() => {
|
|
71
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
72
|
+
if (value === undefined) {
|
|
73
|
+
remove.run(userId, key);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
upsert.run(userId, key, JSON.stringify(value));
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
},
|
|
80
|
+
};
|