wind-agent-cli 1.37.2 → 1.38.0
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/LICENSE +718 -718
- package/README.md +17 -2
- package/dist/api-docs.html +901 -901
- package/dist/assets/{CodeEditor-B2Wf7Fg0.js → CodeEditor-FvgsnJ7J.js} +6 -6
- package/dist/assets/FileTree-NLULrBDi.js +1 -0
- package/dist/assets/GitPanel-51KUBbEW.js +4 -0
- package/dist/assets/{MarkdownPreview-C0tqYnVu.js → MarkdownPreview-PEEWVWeS.js} +17 -27
- package/dist/assets/Shell-BEx66cW_.js +76 -0
- package/dist/assets/Shell-DrlLKa8f.css +1 -0
- package/dist/assets/StandaloneShell-BkuUCPZP.js +1 -0
- package/dist/assets/TaskMasterPanel-DfQG80SF.js +2 -0
- package/dist/assets/abnfDiagram-N423BO3Z-Cc0ul87U.js +1 -0
- package/dist/assets/arc-BAuugHw_.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-DA3G7QZD.js +36 -0
- package/dist/assets/{blockDiagram-VBNYF7ZC-DpaEf3mv.js → blockDiagram-VBNYF7ZC-tJ12IJs3.js} +4 -4
- package/dist/assets/c4Diagram-5PPSVZJV-5BTMCXL4.js +10 -0
- package/dist/assets/channel-BGDgNNQz.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-DoSdNo8u.js → chunk-2GRJ4B5K-BPaI06Ou.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-CE7I3uMn.js → chunk-2Q5K7J3B-HV4zmsEe.js} +1 -1
- package/dist/assets/chunk-5RXB4S5H-BAwutJoU.js +231 -0
- package/dist/assets/{chunk-5VM5RSS4-B55XgqHZ.js → chunk-5VM5RSS4-B9QAJPrv.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-BS1KpefW.js → chunk-6Q2QTUOP-BOIW8bzI.js} +1 -1
- package/dist/assets/chunk-GF5L2VYU-DD13o00-.js +206 -0
- package/dist/assets/{chunk-JWPE2WC7-DOtkNhMm.js → chunk-JWPE2WC7-CFBm23VK.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-BXLjviqM.js → chunk-KBJHAD2P-C0i0v8Op.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-C50fjNNz.js → chunk-RYQCIY6F-DfeK0taV.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-QBYqCkb7.js → chunk-XXDRQBXY-BKWvf-QE.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-C2SNM4oy.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-C2SNM4oy.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-CBE8oM7L.js +1 -0
- package/dist/assets/{cynefin-VYW2F7L2-DnJa_OlQ.js → cynefin-VYW2F7L2-DziGp35A.js} +11 -23
- package/dist/assets/cynefinDiagram-MW4NZA55-CNvtHl39.js +62 -0
- package/dist/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/assets/dagre-VZM6K2ZE-Bz9lsU7m.js +4 -0
- package/dist/assets/{diagram-7IWD3JNH-pFzs9HEu.js → diagram-7IWD3JNH-CV7D3VSd.js} +5 -5
- package/dist/assets/{diagram-B4RE2ZJO-CcehQC7x.js → diagram-B4RE2ZJO-Dj4EjDNC.js} +2 -2
- package/dist/assets/diagram-LBJQPF4R-wJ6FHvf-.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-DMFYMhOB.js +24 -0
- package/dist/assets/diagram-UB23O5K3-DeBSbWAI.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-2NnJSGhT.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-DrSlg8hr.js +85 -0
- package/dist/assets/feature-chat-CvOCyaLe.js +88 -0
- package/dist/assets/feature-workspace-CAB6bgPy.js +39 -0
- package/dist/assets/{flowDiagram-UKHOOZJN-CJV8fQPR.js → flowDiagram-UKHOOZJN-C0pejU6W.js} +4 -4
- package/dist/assets/ganttDiagram-PKOTCBZU-BM0VCUUz.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-SbLG0c5_.js +106 -0
- package/dist/assets/graph-DOmOIIwC.js +1 -0
- package/dist/assets/{index-GFhXZyl2.js → index-BflGfU1C.js} +3 -3
- package/dist/assets/index-Btkf0_rU.js +1 -0
- package/dist/assets/index-Cr5MbHuj.css +1 -0
- package/dist/assets/index-Gqazqfs3.js +1 -0
- package/dist/assets/index-nyrkoQK6.js +35 -0
- package/dist/assets/infoDiagram-6WML65LV-Cf5DSa66.js +2 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DwyFutKy.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-Df0FRhLo.js +139 -0
- package/dist/assets/{kanban-definition-27J2QSJJ-B3iL0ztJ.js → kanban-definition-27J2QSJJ-HmpbjrZz.js} +6 -6
- package/dist/assets/{layout-D6BstSzF.js → layout-D-LzfAck.js} +1 -1
- package/dist/assets/{linear-Cwe9dEKA.js → linear-DOM01LBe.js} +1 -1
- package/dist/assets/map-DxJ2ADlA.js +1 -0
- package/dist/assets/mermaid.core-C2Ma-_S6.js +306 -0
- package/dist/assets/{mindmap-definition-FAOFIHXS-BgfBYMzr.js → mindmap-definition-FAOFIHXS-B4GOIn76.js} +4 -4
- package/dist/assets/pegDiagram-VL7TDLO6-BHur7aH2.js +1 -0
- package/dist/assets/{pieDiagram-7S7Q4E2Y-DeSrxc4I.js → pieDiagram-7S7Q4E2Y-Cw0UELLV.js} +2 -2
- package/dist/assets/quadrantDiagram-CIZ2JOQS-CB2BrX3g.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-DPYZYmLX.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-DSX4IJ1n.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-TenC3ZQo.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-DltGsf3F.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-D7llwbJZ.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-C-hAvbnY.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DaYSFcRQ.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-DWD30fv6.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-LTkF2T6t.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-quWmimSE.js +120 -0
- package/dist/assets/vendor-data-DsEZeNPr.js +4 -0
- package/dist/assets/vendor-i18n-B6HjLCnN.js +1 -0
- package/dist/assets/vendor-markdown-DkITEES2.js +298 -0
- package/dist/assets/vendor-react-C2GTiUCy.js +10 -0
- package/dist/assets/vendor-ui-CAUHSABu.js +49 -0
- package/dist/assets/vennDiagram-T6HMQDX7-aUQcm8h5.js +34 -0
- package/dist/assets/{wardleyDiagram-T6FBY63Y-BNCpsFZV.js → wardleyDiagram-T6FBY63Y-BZ0bwsFQ.js} +2 -2
- package/dist/assets/workbox-window.prod.es5-BBnX5xw4.js +2 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-DOVzAq63.js +7 -0
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +8 -8
- package/dist/generate-icons.js +48 -48
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +11 -11
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +11 -11
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +11 -11
- package/dist/icons/icon-180x180.png +0 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +11 -11
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +11 -11
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +11 -11
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +11 -11
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +11 -11
- package/dist/icons/icon-maskable-192x192.png +0 -0
- package/dist/icons/icon-maskable-512x512.png +0 -0
- package/dist/icons/icon-template.svg +11 -11
- package/dist/icons/new-icon.png +0 -0
- package/dist/index.html +48 -58
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo.svg +17 -17
- package/dist/manifest.webmanifest +1 -0
- package/dist/push-sw.js +48 -0
- package/dist/screenshots/setup-screen-mobile.png +0 -0
- package/dist/screenshots/setup-screen.png +0 -0
- package/dist/sw.js +1 -124
- package/dist/workbox-ae5c0bb2.js +1 -0
- package/dist-server/server/index.js +1 -1
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +4 -2
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/agent/agent.routes.js +1 -1
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use.service.js +1 -1
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +37 -37
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/sandbox.service.js +43 -43
- package/dist-server/server/modules/cli/sandbox.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +74 -74
- package/dist-server/server/modules/commands/commands.routes.js +25 -25
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
- package/dist-server/server/modules/commands/tests/commands.test.js +1 -1
- package/dist-server/server/modules/commands/tests/commands.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/migrations.js +190 -190
- package/dist-server/server/modules/database/repositories/api-keys.js +3 -3
- package/dist-server/server/modules/database/repositories/github-tokens.js +2 -2
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +28 -28
- package/dist-server/server/modules/database/repositories/notification-preferences.js +4 -4
- package/dist-server/server/modules/database/repositories/projects.db.js +61 -61
- package/dist-server/server/modules/database/repositories/provider-models.js +35 -35
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +5 -5
- package/dist-server/server/modules/database/repositories/scan-state.db.js +5 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js +93 -93
- package/dist-server/server/modules/database/schema.js +177 -177
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +3 -2
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +6 -6
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +4 -3
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +2 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/git/git.routes.js +17 -17
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/notifications/vapid-keys.service.js +1 -1
- package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -1
- package/dist-server/server/modules/projects/tests/project-management.service.test.js +2 -1
- package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +2 -2
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.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 +13 -13
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +9 -9
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +25 -25
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +27 -27
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +9 -9
- package/dist-server/server/modules/providers/services/sessions.service.js +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +8 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/mcp.test.js +3 -3
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +98 -98
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +18 -18
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/modules/system/system.service.js +1 -1
- package/dist-server/server/modules/system/system.service.js.map +1 -1
- package/dist-server/server/modules/system/tests/system.service.test.js +1 -1
- package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -1
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +423 -422
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js +1 -1
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js +1 -1
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +1 -1
- package/electron/cloud.js +260 -260
- package/electron/desktopNotifications.js +378 -378
- package/electron/desktopWindow.js +766 -766
- package/electron/launcher/index.html +14 -14
- package/electron/launcher/launcher.css +801 -801
- package/electron/launcher/launcher.js +687 -687
- package/electron/localServer.js +549 -549
- package/electron/main.js +944 -944
- package/electron/preload.cjs +60 -60
- package/electron/scripts/generate-macos-icon.js +62 -62
- package/electron/serverInstaller.js +277 -277
- package/electron/tabs.js +87 -87
- package/electron/viewHost.js +331 -331
- package/package.json +262 -247
- package/public/api-docs.html +901 -901
- package/scripts/build-client.mjs +30 -0
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/generate-pwa-icons.ps1 +33 -0
- package/scripts/promote-dist-server.mjs +50 -50
- package/scripts/release/build-server-bundle.js +176 -176
- package/scripts/release/prepare-desktop-app.js +152 -152
- package/scripts/verify-pwa-build.mjs +37 -0
- package/server/index.ts +399 -399
- package/server/load-env.ts +49 -47
- package/server/modules/agent/agent.module.ts +52 -52
- package/server/modules/agent/agent.routes.ts +1287 -1287
- package/server/modules/agent/index.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +205 -205
- package/server/modules/assets/assets.routes.ts +150 -150
- package/server/modules/assets/index.ts +3 -3
- package/server/modules/assets/services/image-assets.service.ts +127 -127
- package/server/modules/assets/tests/image-assets.service.test.ts +74 -74
- package/server/modules/auth/auth.middleware.ts +163 -163
- package/server/modules/auth/auth.module.ts +38 -38
- package/server/modules/auth/auth.routes.ts +57 -57
- package/server/modules/auth/auth.service.ts +155 -155
- package/server/modules/auth/index.ts +9 -9
- package/server/modules/auth/tests/auth.service.test.ts +95 -95
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -120
- package/server/modules/browser-use/browser-use-mcp.ts +386 -386
- package/server/modules/browser-use/browser-use-runtime.ts +10 -10
- package/server/modules/browser-use/browser-use.routes.ts +96 -96
- package/server/modules/browser-use/browser-use.service.ts +836 -836
- package/server/modules/browser-use/index.ts +11 -11
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -10
- package/server/modules/cli/cli.module.ts +93 -93
- package/server/modules/cli/cli.service.ts +248 -248
- package/server/modules/cli/cli.ts +19 -19
- package/server/modules/cli/index.ts +2 -2
- package/server/modules/cli/sandbox.service.ts +398 -398
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -186
- package/server/modules/cli/tests/cli.service.test.ts +86 -86
- package/server/modules/cli/tests/sandbox.service.test.ts +58 -58
- package/server/modules/commands/commands.module.ts +22 -22
- package/server/modules/commands/commands.routes.ts +587 -587
- package/server/modules/commands/index.ts +2 -2
- package/server/modules/commands/tests/commands.test.ts +110 -110
- package/server/modules/database/connection.ts +144 -143
- package/server/modules/database/index.ts +17 -17
- package/server/modules/database/init-db.ts +17 -17
- package/server/modules/database/migrations.ts +515 -515
- package/server/modules/database/repositories/api-keys.ts +119 -119
- package/server/modules/database/repositories/app-config.ts +53 -53
- package/server/modules/database/repositories/credentials.ts +106 -106
- package/server/modules/database/repositories/github-tokens.ts +100 -100
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -153
- package/server/modules/database/repositories/notification-preferences.ts +117 -117
- package/server/modules/database/repositories/projects.db.ts +196 -196
- package/server/modules/database/repositories/provider-models.ts +168 -168
- package/server/modules/database/repositories/push-subscriptions.ts +80 -80
- package/server/modules/database/repositories/scan-state.db.ts +42 -42
- package/server/modules/database/repositories/sessions.db.ts +524 -524
- package/server/modules/database/repositories/users.ts +140 -140
- package/server/modules/database/repositories/vapid-keys.ts +57 -57
- package/server/modules/database/schema.ts +210 -210
- package/server/modules/database/tests/projects.db.integration.test.ts +73 -72
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -125
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +110 -109
- package/server/modules/database/tests/sessions.db.integration.test.ts +117 -116
- package/server/modules/file-tree/file-tree.module.ts +116 -116
- package/server/modules/file-tree/file-tree.routes.ts +258 -258
- package/server/modules/file-tree/file-tree.service.ts +675 -675
- package/server/modules/file-tree/index.ts +2 -2
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -157
- package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -371
- package/server/modules/git/git-branch.service.ts +43 -43
- package/server/modules/git/git-parsing.service.ts +66 -66
- package/server/modules/git/git.module.ts +22 -22
- package/server/modules/git/git.routes.ts +1596 -1596
- package/server/modules/git/index.ts +2 -2
- package/server/modules/git/tests/git-branch.service.test.ts +60 -60
- package/server/modules/git/tests/git-init.routes.test.ts +132 -132
- package/server/modules/git/tests/git.test.ts +106 -106
- package/server/modules/notifications/index.ts +24 -24
- package/server/modules/notifications/notifications.routes.ts +127 -127
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -124
- package/server/modules/notifications/services/notification-orchestrator.service.js +310 -310
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -55
- package/server/modules/notifications/vapid-keys.service.ts +38 -38
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -109
- package/server/modules/plugins/index.ts +9 -9
- package/server/modules/plugins/plugin-process.service.ts +217 -217
- package/server/modules/plugins/plugin-registry.service.ts +459 -459
- package/server/modules/plugins/plugins.module.ts +33 -33
- package/server/modules/plugins/plugins.routes.ts +66 -66
- package/server/modules/plugins/plugins.service.ts +147 -147
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -32
- package/server/modules/projects/index.ts +11 -11
- package/server/modules/projects/projects.routes.ts +273 -273
- package/server/modules/projects/services/project-clone.service.ts +323 -323
- package/server/modules/projects/services/project-delete.service.ts +90 -90
- package/server/modules/projects/services/project-management.service.ts +144 -144
- package/server/modules/projects/services/project-star.service.ts +78 -78
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -248
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -314
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -183
- package/server/modules/projects/tests/project-management.service.test.ts +118 -117
- package/server/modules/projects/tests/project-star.service.test.ts +123 -123
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -105
- package/server/modules/providers/README.md +380 -380
- package/server/modules/providers/index.ts +10 -10
- package/server/modules/providers/list/claude/claude-auth.provider.ts +160 -160
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-models.provider.ts +294 -294
- package/server/modules/providers/list/claude/claude-runtime.provider.js +1068 -1068
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +684 -684
- package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -265
- package/server/modules/providers/list/claude/claude.provider.ts +30 -30
- package/server/modules/providers/list/codex/codex-app-server.client.ts +2 -2
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -100
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-models.provider.ts +123 -123
- package/server/modules/providers/list/codex/codex-runtime.provider.js +533 -533
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +938 -938
- package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -73
- package/server/modules/providers/list/codex/codex.provider.ts +28 -28
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -119
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +385 -385
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +158 -159
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -654
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -39
- package/server/modules/providers/list/cursor/cursor.provider.ts +30 -30
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -110
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -228
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +203 -203
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -434
- package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -261
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -179
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -500
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -78
- package/server/modules/providers/list/opencode/opencode.provider.ts +30 -30
- package/server/modules/providers/provider.registry.ts +36 -36
- package/server/modules/providers/provider.routes.ts +877 -877
- package/server/modules/providers/services/mcp.service.ts +109 -109
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/provider-capabilities.service.ts +97 -97
- package/server/modules/providers/services/provider-models.service.ts +401 -401
- package/server/modules/providers/services/provider-runtime.service.ts +108 -108
- package/server/modules/providers/services/provider-token-usage.service.ts +357 -357
- package/server/modules/providers/services/session-conversations-search.service.ts +1321 -1321
- package/server/modules/providers/services/session-synchronizer.service.ts +74 -74
- package/server/modules/providers/services/sessions-watcher.service.ts +247 -247
- package/server/modules/providers/services/sessions.service.ts +471 -471
- package/server/modules/providers/services/skills.service.ts +39 -39
- package/server/modules/providers/shared/base/abstract.provider.ts +32 -32
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/skills/skills.provider.ts +287 -287
- package/server/modules/providers/tests/claude-auth.test.ts +157 -150
- package/server/modules/providers/tests/claude-sessions.test.ts +61 -61
- package/server/modules/providers/tests/codex-sessions.test.ts +231 -231
- package/server/modules/providers/tests/mcp.test.ts +349 -349
- package/server/modules/providers/tests/opencode-models.test.ts +41 -41
- package/server/modules/providers/tests/opencode-sessions.test.ts +522 -522
- package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -247
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -353
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -138
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -186
- package/server/modules/providers/tests/provider.routes.test.ts +244 -244
- package/server/modules/providers/tests/sessions-details.test.ts +73 -73
- package/server/modules/providers/tests/sessions.service.test.ts +127 -127
- package/server/modules/providers/tests/skills.test.ts +692 -692
- package/server/modules/settings/index.ts +2 -2
- package/server/modules/settings/settings.module.ts +50 -50
- package/server/modules/settings/settings.routes.ts +51 -51
- package/server/modules/settings/settings.service.ts +186 -186
- package/server/modules/settings/tests/settings.service.test.ts +54 -54
- package/server/modules/system/index.ts +2 -2
- package/server/modules/system/system.module.ts +62 -62
- package/server/modules/system/system.routes.ts +21 -21
- package/server/modules/system/system.service.ts +76 -76
- package/server/modules/system/tests/system.service.test.ts +118 -118
- package/server/modules/taskmaster/index.ts +2 -2
- package/server/modules/taskmaster/taskmaster.module.ts +24 -24
- package/server/modules/taskmaster/taskmaster.routes.ts +1483 -1482
- package/server/modules/taskmaster/taskmaster.service.ts +132 -132
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -144
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -97
- package/server/modules/user/index.ts +2 -2
- package/server/modules/user/tests/user.service.test.ts +61 -61
- package/server/modules/user/user.module.ts +58 -58
- package/server/modules/user/user.routes.ts +50 -50
- package/server/modules/user/user.service.ts +85 -85
- package/server/modules/voice/index.ts +2 -2
- package/server/modules/voice/tests/voice.service.test.ts +98 -98
- package/server/modules/voice/voice.module.ts +47 -47
- package/server/modules/voice/voice.routes.ts +113 -113
- package/server/modules/voice/voice.service.ts +192 -192
- package/server/modules/websocket/README.md +273 -273
- package/server/modules/websocket/index.ts +3 -3
- package/server/modules/websocket/services/chat-run-registry.service.ts +343 -343
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -145
- package/server/modules/websocket/services/chat-websocket.service.ts +425 -425
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -65
- package/server/modules/websocket/services/shell-websocket.service.ts +503 -503
- package/server/modules/websocket/services/websocket-auth.service.ts +59 -59
- package/server/modules/websocket/services/websocket-server.service.ts +124 -124
- package/server/modules/websocket/services/websocket-state.service.ts +16 -16
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -38
- package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -59
- package/server/modules/websocket/tests/chat-run-registry.test.ts +282 -282
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +100 -100
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -78
- package/server/modules/worktrees/index.ts +2 -2
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -53
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -104
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -207
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -143
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -157
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -85
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -88
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -68
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -142
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -84
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -45
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -213
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -122
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -227
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -199
- package/server/modules/worktrees/worktrees.module.ts +113 -113
- package/server/modules/worktrees/worktrees.routes.ts +116 -116
- package/server/shared/claude-cli-path.ts +139 -139
- package/server/shared/frontmatter.ts +18 -18
- package/server/shared/image-attachments.ts +435 -435
- package/server/shared/interfaces.ts +179 -179
- package/server/shared/tests/claude-cli-path.test.ts +61 -61
- package/server/shared/tests/image-attachments.test.ts +353 -353
- package/server/shared/tests/slice-tail-page.test.ts +42 -42
- package/server/shared/types.ts +1228 -1228
- package/server/shared/utils.ts +1161 -1161
- package/server/tsconfig.json +40 -40
- package/shared/networkHosts.js +22 -22
- package/dist/assets/FileTree-N2IkkHlv.js +0 -127
- package/dist/assets/GitPanel-DZCeuFZ2.js +0 -29
- package/dist/assets/PluginTabContent-Bnuf-fb8.js +0 -1
- package/dist/assets/Shell-BfFnMZ-f.js +0 -81
- package/dist/assets/Shell-qxJ8_QYu.css +0 -32
- package/dist/assets/StandaloneShell-QmyYwNAl.js +0 -1
- package/dist/assets/TaskMasterPanel-fMsmvN9L.js +0 -32
- package/dist/assets/abnfDiagram-N423BO3Z-DjPU7HLs.js +0 -1
- package/dist/assets/arc-Pdq6Tvr3.js +0 -1
- package/dist/assets/architectureDiagram-T3A2C74G-D0JnT0Vh.js +0 -36
- package/dist/assets/c4Diagram-5PPSVZJV-MpqlPGFi.js +0 -10
- package/dist/assets/channel-Bm0ZLE4z.js +0 -1
- package/dist/assets/chunk-5RXB4S5H-B4UQ8snD.js +0 -231
- package/dist/assets/chunk-GF5L2VYU-DkEPedJP.js +0 -206
- package/dist/assets/classDiagram-JCYQIIEL-DqEOcNxw.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-DqEOcNxw.js +0 -1
- package/dist/assets/code-xml-VK1YfShP.js +0 -6
- package/dist/assets/cose-bilkent-JH36ORCC-DrVTo1Nh.js +0 -1
- package/dist/assets/cynefinDiagram-MW4NZA55-CCxBjw9E.js +0 -62
- package/dist/assets/cytoscape.esm-C10L9Icf.js +0 -331
- package/dist/assets/dagre-VZM6K2ZE-C_GvE4lu.js +0 -4
- package/dist/assets/diagram-LBJQPF4R-hfKcrrxe.js +0 -24
- package/dist/assets/diagram-Q27KOJAE-C1semJfK.js +0 -24
- package/dist/assets/diagram-UB23O5K3-57hvXQXO.js +0 -41
- package/dist/assets/ebnfDiagram-BXEA7PRR-9KIAlAVw.js +0 -1
- package/dist/assets/erDiagram-JOGREHBK-B7OzJ767.js +0 -85
- package/dist/assets/ganttDiagram-PKOTCBZU-CbMNWWQu.js +0 -292
- package/dist/assets/gitGraphDiagram-DS77QQ5N-I_vZ5waO.js +0 -106
- package/dist/assets/graph-Cx-EqisW.js +0 -1
- package/dist/assets/index-DFnVoqpx.js +0 -1174
- package/dist/assets/index-DlSeAkw_.js +0 -11
- package/dist/assets/index-ZHwIojzi.js +0 -1
- package/dist/assets/index-t1w8KNxn.css +0 -1
- package/dist/assets/infoDiagram-6WML65LV-Dh20Tsbq.js +0 -2
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DH2Sm3ko.js +0 -70
- package/dist/assets/journeyDiagram-NVQOT4AX-BNFQqlVu.js +0 -139
- package/dist/assets/map-CV9uif0g.js +0 -1
- package/dist/assets/mermaid.core-BYRQSshi.js +0 -312
- package/dist/assets/pegDiagram-VL7TDLO6-CNYpECr8.js +0 -1
- package/dist/assets/quadrantDiagram-CIZ2JOQS-BXVVQsa6.js +0 -7
- package/dist/assets/railroadDiagram-AXF67PYL-JTroIW8F.js +0 -1
- package/dist/assets/requirementDiagram-LRYGKXZP-C1Z_inLz.js +0 -84
- package/dist/assets/sankeyDiagram-W5VNT64P-DCJgKFUk.js +0 -40
- package/dist/assets/sequenceDiagram-SI44F4Z6-D8Wrx5Vc.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-CXqD68vD.js +0 -1
- package/dist/assets/stateDiagram-OKZ733FA-CF0Ekqjo.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DARLpEFJ.js +0 -1
- package/dist/assets/swimlanes-SLNWSIFB-DVIax_k4.js +0 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CrgDgaY8.js +0 -8
- package/dist/assets/timeline-definition-Z64GVDOM-DSqk3ApG.js +0 -120
- package/dist/assets/vendor-react-whEHfigN.js +0 -59
- package/dist/assets/vennDiagram-T6HMQDX7-DNbTqyG5.js +0 -34
- package/dist/assets/xychartDiagram-ELKLHX3M-XsVZphHr.js +0 -7
- package/dist/manifest.json +0 -61
|
@@ -1,524 +1,524 @@
|
|
|
1
|
-
import { getConnection } from '@/modules/database/connection.js';
|
|
2
|
-
import { projectsDb } from '@/modules/database/repositories/projects.db.js';
|
|
3
|
-
import { normalizeProjectPath } from '@/shared/utils.js';
|
|
4
|
-
|
|
5
|
-
type SessionRow = {
|
|
6
|
-
session_id: string;
|
|
7
|
-
provider: string;
|
|
8
|
-
provider_session_id: string | null;
|
|
9
|
-
project_path: string | null;
|
|
10
|
-
jsonl_path: string | null;
|
|
11
|
-
custom_name: string | null;
|
|
12
|
-
/** Model this session runs with; NULL until the app records one for it. */
|
|
13
|
-
model: string | null;
|
|
14
|
-
/** Reasoning effort this session runs with; NULL until the app records one. */
|
|
15
|
-
effort: string | null;
|
|
16
|
-
isArchived: number;
|
|
17
|
-
created_at: string;
|
|
18
|
-
updated_at: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type RecentSessionsPage = {
|
|
22
|
-
sessions: SessionRow[];
|
|
23
|
-
total: number;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const SESSION_ROW_COLUMNS =
|
|
27
|
-
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, model, effort, isArchived, created_at, updated_at';
|
|
28
|
-
|
|
29
|
-
const SQLITE_UTC_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
30
|
-
|
|
31
|
-
function normalizeTimestamp(value?: string): string | null {
|
|
32
|
-
if (!value) return null;
|
|
33
|
-
|
|
34
|
-
// SQLite CURRENT_TIMESTAMP is stored as UTC without a timezone suffix.
|
|
35
|
-
// Normalize it here so every session reader returns canonical ISO strings
|
|
36
|
-
// and the sidebar never interprets fresh rows as local-time "hours old".
|
|
37
|
-
const normalizedValue = SQLITE_UTC_TIMESTAMP_REGEX.test(value)
|
|
38
|
-
? `${value.replace(' ', 'T')}Z`
|
|
39
|
-
: value;
|
|
40
|
-
|
|
41
|
-
const parsed = new Date(normalizedValue);
|
|
42
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return parsed.toISOString();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function normalizeSessionRow<T extends SessionRow | null | undefined>(row: T): T {
|
|
50
|
-
if (!row) {
|
|
51
|
-
return row;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
...row,
|
|
56
|
-
created_at: normalizeTimestamp(row.created_at) ?? row.created_at,
|
|
57
|
-
updated_at: normalizeTimestamp(row.updated_at) ?? row.updated_at,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function normalizeSessionRows(rows: SessionRow[]): SessionRow[] {
|
|
62
|
-
return rows.map((row) => normalizeSessionRow(row) as SessionRow);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function normalizeProjectPathForProvider(provider: string, projectPath: string): string {
|
|
66
|
-
void provider;
|
|
67
|
-
return normalizeProjectPath(projectPath);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const sessionsDb = {
|
|
71
|
-
/**
|
|
72
|
-
* Upserts one session row discovered on disk by a provider synchronizer.
|
|
73
|
-
*
|
|
74
|
-
* The given id is the provider-native session id. Rows are keyed by
|
|
75
|
-
* `provider_session_id` so a session that was first created by the app
|
|
76
|
-
* (with an app-allocated `session_id`) is updated in place once its
|
|
77
|
-
* transcript shows up on disk, instead of producing a duplicate row. An
|
|
78
|
-
* app-created row keeps its existing name; synchronizer names only update
|
|
79
|
-
* rows that were themselves created by indexing provider storage.
|
|
80
|
-
*/
|
|
81
|
-
createSession(
|
|
82
|
-
providerSessionId: string,
|
|
83
|
-
provider: string,
|
|
84
|
-
projectPath: string,
|
|
85
|
-
customName?: string,
|
|
86
|
-
createdAt?: string,
|
|
87
|
-
updatedAt?: string,
|
|
88
|
-
jsonlPath?: string | null
|
|
89
|
-
): string {
|
|
90
|
-
const db = getConnection();
|
|
91
|
-
const createdAtValue = normalizeTimestamp(createdAt);
|
|
92
|
-
const updatedAtValue = normalizeTimestamp(updatedAt);
|
|
93
|
-
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
94
|
-
|
|
95
|
-
// First, ensure the project path is recorded in the projects table,
|
|
96
|
-
// since it's a foreign key in the sessions table.
|
|
97
|
-
projectsDb.createProjectPath(normalizedProjectPath);
|
|
98
|
-
|
|
99
|
-
const existing = db
|
|
100
|
-
.prepare(
|
|
101
|
-
`SELECT session_id FROM sessions
|
|
102
|
-
WHERE provider_session_id = ? AND provider = ?
|
|
103
|
-
LIMIT 1`
|
|
104
|
-
)
|
|
105
|
-
.get(providerSessionId, provider) as { session_id: string } | undefined;
|
|
106
|
-
|
|
107
|
-
if (existing) {
|
|
108
|
-
db.prepare(
|
|
109
|
-
`UPDATE sessions SET
|
|
110
|
-
provider = ?,
|
|
111
|
-
updated_at = COALESCE(?, CURRENT_TIMESTAMP),
|
|
112
|
-
project_path = ?,
|
|
113
|
-
jsonl_path = ?,
|
|
114
|
-
isArchived = 0,
|
|
115
|
-
custom_name = CASE
|
|
116
|
-
WHEN session_id <> provider_session_id AND custom_name IS NOT NULL THEN custom_name
|
|
117
|
-
ELSE COALESCE(?, custom_name)
|
|
118
|
-
END
|
|
119
|
-
WHERE session_id = ?`
|
|
120
|
-
).run(
|
|
121
|
-
provider,
|
|
122
|
-
updatedAtValue,
|
|
123
|
-
normalizedProjectPath,
|
|
124
|
-
jsonlPath ?? null,
|
|
125
|
-
customName ?? null,
|
|
126
|
-
existing.session_id
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
return existing.session_id;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Sessions created outside the app (directly via the provider CLI) are
|
|
133
|
-
// keyed by the provider-native id for both columns. The ON CONFLICT path
|
|
134
|
-
// covers legacy rows that predate the provider_session_id mapping.
|
|
135
|
-
db.prepare(
|
|
136
|
-
`INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
|
137
|
-
VALUES (?, ?, ?, ?, ?, ?, 0, COALESCE(?, CURRENT_TIMESTAMP), COALESCE(?, CURRENT_TIMESTAMP))
|
|
138
|
-
ON CONFLICT(session_id) DO UPDATE SET
|
|
139
|
-
provider = excluded.provider,
|
|
140
|
-
provider_session_id = excluded.provider_session_id,
|
|
141
|
-
updated_at = excluded.updated_at,
|
|
142
|
-
project_path = excluded.project_path,
|
|
143
|
-
jsonl_path = excluded.jsonl_path,
|
|
144
|
-
isArchived = 0,
|
|
145
|
-
custom_name = CASE
|
|
146
|
-
WHEN sessions.session_id <> sessions.provider_session_id AND sessions.custom_name IS NOT NULL
|
|
147
|
-
THEN sessions.custom_name
|
|
148
|
-
ELSE COALESCE(excluded.custom_name, sessions.custom_name)
|
|
149
|
-
END`
|
|
150
|
-
).run(
|
|
151
|
-
providerSessionId,
|
|
152
|
-
provider,
|
|
153
|
-
providerSessionId,
|
|
154
|
-
customName ?? null,
|
|
155
|
-
normalizedProjectPath,
|
|
156
|
-
jsonlPath ?? null,
|
|
157
|
-
createdAtValue,
|
|
158
|
-
updatedAtValue
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
return providerSessionId;
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Inserts one app-allocated session row before any provider run happens.
|
|
166
|
-
*
|
|
167
|
-
* The session gateway uses this when the frontend starts a brand-new chat:
|
|
168
|
-
* `session_id` is the stable app-facing id, while `provider_session_id`
|
|
169
|
-
* stays NULL until the provider runtime announces its own id and
|
|
170
|
-
* `assignProviderSessionId` records the mapping. `customName` is derived
|
|
171
|
-
* from the first visible
|
|
172
|
-
*/
|
|
173
|
-
createAppSession(
|
|
174
|
-
sessionId: string,
|
|
175
|
-
provider: string,
|
|
176
|
-
projectPath: string,
|
|
177
|
-
customName?: string,
|
|
178
|
-
): string {
|
|
179
|
-
const db = getConnection();
|
|
180
|
-
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
181
|
-
|
|
182
|
-
projectsDb.createProjectPath(normalizedProjectPath);
|
|
183
|
-
|
|
184
|
-
db.prepare(
|
|
185
|
-
`INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
|
186
|
-
VALUES (?, ?, NULL, ?, ?, NULL, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`
|
|
187
|
-
).run(sessionId, provider, customName ?? null, normalizedProjectPath);
|
|
188
|
-
|
|
189
|
-
return sessionId;
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Records the provider-native session id for one app-allocated session.
|
|
194
|
-
*
|
|
195
|
-
* If the filesystem watcher indexed the provider transcript before this
|
|
196
|
-
* mapping was recorded (a duplicate row keyed by the provider id exists),
|
|
197
|
-
* the duplicate is merged into the app row: its transcript path and name
|
|
198
|
-
* are adopted and the duplicate row is removed. Runs in a transaction so
|
|
199
|
-
* the sidebar can never observe both rows at once.
|
|
200
|
-
*/
|
|
201
|
-
assignProviderSessionId(sessionId: string, providerSessionId: string): void {
|
|
202
|
-
const db = getConnection();
|
|
203
|
-
|
|
204
|
-
const merge = db.transaction(() => {
|
|
205
|
-
const duplicate = db
|
|
206
|
-
.prepare(
|
|
207
|
-
`SELECT ${SESSION_ROW_COLUMNS} FROM sessions
|
|
208
|
-
WHERE (session_id = ? OR provider_session_id = ?)
|
|
209
|
-
AND session_id <> ?
|
|
210
|
-
LIMIT 1`
|
|
211
|
-
)
|
|
212
|
-
.get(providerSessionId, providerSessionId, sessionId) as SessionRow | undefined;
|
|
213
|
-
|
|
214
|
-
if (duplicate) {
|
|
215
|
-
db.prepare('DELETE FROM sessions WHERE session_id = ?').run(duplicate.session_id);
|
|
216
|
-
db.prepare(
|
|
217
|
-
`UPDATE sessions SET
|
|
218
|
-
provider_session_id = ?,
|
|
219
|
-
jsonl_path = COALESCE(jsonl_path, ?),
|
|
220
|
-
custom_name = COALESCE(custom_name, ?),
|
|
221
|
-
updated_at = CURRENT_TIMESTAMP
|
|
222
|
-
WHERE session_id = ?`
|
|
223
|
-
).run(providerSessionId, duplicate.jsonl_path, duplicate.custom_name, sessionId);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
db.prepare(
|
|
228
|
-
`UPDATE sessions SET
|
|
229
|
-
provider_session_id = ?,
|
|
230
|
-
updated_at = CURRENT_TIMESTAMP
|
|
231
|
-
WHERE session_id = ?`
|
|
232
|
-
).run(providerSessionId, sessionId);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
merge();
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Records the model one session runs with.
|
|
240
|
-
*
|
|
241
|
-
* Called both when the user picks a model for the session and on every send,
|
|
242
|
-
* so the row always reflects what the session last ran with and reopening it
|
|
243
|
-
* restores that model instead of a catalog default.
|
|
244
|
-
*/
|
|
245
|
-
setSessionModel(sessionId: string, model: string): void {
|
|
246
|
-
const db = getConnection();
|
|
247
|
-
db.prepare(
|
|
248
|
-
`UPDATE sessions
|
|
249
|
-
SET model = ?
|
|
250
|
-
WHERE session_id = ?`
|
|
251
|
-
).run(model, sessionId);
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Records the reasoning effort one session runs with.
|
|
256
|
-
*
|
|
257
|
-
* `default` is stored as an explicit choice rather than NULL so reopening
|
|
258
|
-
* the session does not inherit a later per-provider effort preference.
|
|
259
|
-
*/
|
|
260
|
-
setSessionEffort(sessionId: string, effort: string): void {
|
|
261
|
-
const db = getConnection();
|
|
262
|
-
db.prepare(
|
|
263
|
-
`UPDATE sessions
|
|
264
|
-
SET effort = ?
|
|
265
|
-
WHERE session_id = ?`
|
|
266
|
-
).run(effort, sessionId);
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
updateSessionCustomName(sessionId: string, customName: string): void {
|
|
270
|
-
const db = getConnection();
|
|
271
|
-
db.prepare(
|
|
272
|
-
`UPDATE sessions
|
|
273
|
-
SET custom_name = ?
|
|
274
|
-
WHERE session_id = ?`
|
|
275
|
-
).run(customName, sessionId);
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
getSessionById(sessionId: string): SessionRow | null {
|
|
279
|
-
const db = getConnection();
|
|
280
|
-
const row = db
|
|
281
|
-
.prepare(
|
|
282
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
283
|
-
FROM sessions
|
|
284
|
-
WHERE session_id = ?
|
|
285
|
-
ORDER BY updated_at DESC
|
|
286
|
-
LIMIT 1`
|
|
287
|
-
)
|
|
288
|
-
.get(sessionId) as SessionRow | undefined;
|
|
289
|
-
|
|
290
|
-
return normalizeSessionRow(row) ?? null;
|
|
291
|
-
},
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Resolves one session row through the provider-native id.
|
|
295
|
-
*
|
|
296
|
-
* The filesystem watcher only knows provider ids (they come from transcript
|
|
297
|
-
* file names), so it uses this lookup to translate disk artifacts back to
|
|
298
|
-
* the app-facing session row before broadcasting sidebar updates.
|
|
299
|
-
*/
|
|
300
|
-
getSessionByProviderSessionId(providerSessionId: string): SessionRow | null {
|
|
301
|
-
const db = getConnection();
|
|
302
|
-
const row = db
|
|
303
|
-
.prepare(
|
|
304
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
305
|
-
FROM sessions
|
|
306
|
-
WHERE provider_session_id = ?
|
|
307
|
-
ORDER BY updated_at DESC
|
|
308
|
-
LIMIT 1`
|
|
309
|
-
)
|
|
310
|
-
.get(providerSessionId) as SessionRow | undefined;
|
|
311
|
-
|
|
312
|
-
return normalizeSessionRow(row) ?? null;
|
|
313
|
-
},
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Finds the newest app-created session for a project that is still waiting
|
|
317
|
-
* for its provider-native id to be recorded.
|
|
318
|
-
*
|
|
319
|
-
* Primary intention: OpenCode can expose a new session in its shared
|
|
320
|
-
* `opencode.db` before the websocket runtime reports that same provider id
|
|
321
|
-
* back to our app. At that moment the sidebar already has an optimistic
|
|
322
|
-
* app-owned session row, but the watcher only knows the provider-native id.
|
|
323
|
-
*
|
|
324
|
-
* Without this lookup, the synchronizer would insert a second row keyed by
|
|
325
|
-
* the provider id, then `assignProviderSessionId()` would merge it a moment
|
|
326
|
-
* later. That eventually self-heals, but on slow networks the user can still
|
|
327
|
-
* briefly see two sidebar sessions for the same conversation.
|
|
328
|
-
*
|
|
329
|
-
* This helper lets the synchronizer claim the pending app row first, so the
|
|
330
|
-
* provider id is attached before any watcher-created row exists. The result
|
|
331
|
-
* is simpler than frontend dedupe and keeps the race resolved at the source.
|
|
332
|
-
*/
|
|
333
|
-
findLatestPendingAppSession(provider: string, projectPath: string): SessionRow | null {
|
|
334
|
-
const db = getConnection();
|
|
335
|
-
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
336
|
-
const row = db
|
|
337
|
-
.prepare(
|
|
338
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
339
|
-
FROM sessions
|
|
340
|
-
WHERE provider = ?
|
|
341
|
-
AND project_path = ?
|
|
342
|
-
AND provider_session_id IS NULL
|
|
343
|
-
AND isArchived = 0
|
|
344
|
-
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC
|
|
345
|
-
LIMIT 1`
|
|
346
|
-
)
|
|
347
|
-
.get(provider, normalizedProjectPath) as SessionRow | undefined;
|
|
348
|
-
|
|
349
|
-
return normalizeSessionRow(row) ?? null;
|
|
350
|
-
},
|
|
351
|
-
|
|
352
|
-
getAllSessions(): SessionRow[] {
|
|
353
|
-
const db = getConnection();
|
|
354
|
-
const rows = db
|
|
355
|
-
.prepare(
|
|
356
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
357
|
-
FROM sessions
|
|
358
|
-
WHERE isArchived = 0`
|
|
359
|
-
)
|
|
360
|
-
.all() as SessionRow[];
|
|
361
|
-
|
|
362
|
-
return normalizeSessionRows(rows);
|
|
363
|
-
},
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Returns one globally ordered page of visible conversations.
|
|
367
|
-
*
|
|
368
|
-
* Pagination happens after archived sessions and sessions belonging to an
|
|
369
|
-
* archived project have been excluded. This keeps the sidebar feed complete
|
|
370
|
-
* and correctly ordered across projects instead of flattening only the
|
|
371
|
-
* per-project slices already loaded by the client.
|
|
372
|
-
*/
|
|
373
|
-
getRecentSessionsPage(limit: number, offset: number): RecentSessionsPage {
|
|
374
|
-
const db = getConnection();
|
|
375
|
-
const visibilityClause = `
|
|
376
|
-
sessions.isArchived = 0
|
|
377
|
-
AND (projects.isArchived IS NULL OR projects.isArchived = 0)
|
|
378
|
-
`;
|
|
379
|
-
const rows = db
|
|
380
|
-
.prepare(
|
|
381
|
-
`SELECT sessions.*
|
|
382
|
-
FROM sessions
|
|
383
|
-
LEFT JOIN projects ON projects.project_path = sessions.project_path
|
|
384
|
-
WHERE ${visibilityClause}
|
|
385
|
-
ORDER BY julianday(COALESCE(sessions.updated_at, sessions.created_at)) DESC,
|
|
386
|
-
sessions.session_id DESC
|
|
387
|
-
LIMIT ? OFFSET ?`
|
|
388
|
-
)
|
|
389
|
-
.all(limit, offset) as SessionRow[];
|
|
390
|
-
const countRow = db
|
|
391
|
-
.prepare(
|
|
392
|
-
`SELECT COUNT(*) AS count
|
|
393
|
-
FROM sessions
|
|
394
|
-
LEFT JOIN projects ON projects.project_path = sessions.project_path
|
|
395
|
-
WHERE ${visibilityClause}`
|
|
396
|
-
)
|
|
397
|
-
.get() as { count: number } | undefined;
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
sessions: normalizeSessionRows(rows),
|
|
401
|
-
total: Number(countRow?.count ?? 0),
|
|
402
|
-
};
|
|
403
|
-
},
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Archived rows are intentionally queried separately so the caller can render
|
|
407
|
-
* them in a dedicated view without reintroducing them into active session lists.
|
|
408
|
-
*/
|
|
409
|
-
getArchivedSessions(): SessionRow[] {
|
|
410
|
-
const db = getConnection();
|
|
411
|
-
const rows = db
|
|
412
|
-
.prepare(
|
|
413
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
414
|
-
FROM sessions
|
|
415
|
-
WHERE isArchived = 1
|
|
416
|
-
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC`
|
|
417
|
-
)
|
|
418
|
-
.all() as SessionRow[];
|
|
419
|
-
|
|
420
|
-
return normalizeSessionRows(rows);
|
|
421
|
-
},
|
|
422
|
-
|
|
423
|
-
getSessionsByProjectPath(projectPath: string): SessionRow[] {
|
|
424
|
-
const db = getConnection();
|
|
425
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
426
|
-
const rows = db
|
|
427
|
-
.prepare(
|
|
428
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
429
|
-
FROM sessions
|
|
430
|
-
WHERE project_path = ?
|
|
431
|
-
AND isArchived = 0`
|
|
432
|
-
)
|
|
433
|
-
.all(normalizedProjectPath) as SessionRow[];
|
|
434
|
-
|
|
435
|
-
return normalizeSessionRows(rows);
|
|
436
|
-
},
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Permanent project deletion must see every session row for the path,
|
|
440
|
-
* including archived ones, so their transcript files can be cleaned up.
|
|
441
|
-
*/
|
|
442
|
-
getSessionsByProjectPathIncludingArchived(projectPath: string): SessionRow[] {
|
|
443
|
-
const db = getConnection();
|
|
444
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
445
|
-
const rows = db
|
|
446
|
-
.prepare(
|
|
447
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
448
|
-
FROM sessions
|
|
449
|
-
WHERE project_path = ?`
|
|
450
|
-
)
|
|
451
|
-
.all(normalizedProjectPath) as SessionRow[];
|
|
452
|
-
|
|
453
|
-
return normalizeSessionRows(rows);
|
|
454
|
-
},
|
|
455
|
-
|
|
456
|
-
getSessionsByProjectPathPage(projectPath: string, limit: number, offset: number): SessionRow[] {
|
|
457
|
-
const db = getConnection();
|
|
458
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
459
|
-
const rows = db
|
|
460
|
-
.prepare(
|
|
461
|
-
`SELECT ${SESSION_ROW_COLUMNS}
|
|
462
|
-
FROM sessions
|
|
463
|
-
WHERE project_path = ?
|
|
464
|
-
AND isArchived = 0
|
|
465
|
-
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC
|
|
466
|
-
LIMIT ? OFFSET ?`
|
|
467
|
-
)
|
|
468
|
-
.all(normalizedProjectPath, limit, offset) as SessionRow[];
|
|
469
|
-
|
|
470
|
-
return normalizeSessionRows(rows);
|
|
471
|
-
},
|
|
472
|
-
|
|
473
|
-
countSessionsByProjectPath(projectPath: string): number {
|
|
474
|
-
const db = getConnection();
|
|
475
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
476
|
-
const row = db
|
|
477
|
-
.prepare(
|
|
478
|
-
`SELECT COUNT(*) AS count
|
|
479
|
-
FROM sessions
|
|
480
|
-
WHERE project_path = ?
|
|
481
|
-
AND isArchived = 0`
|
|
482
|
-
)
|
|
483
|
-
.get(normalizedProjectPath) as { count: number } | undefined;
|
|
484
|
-
|
|
485
|
-
return Number(row?.count ?? 0);
|
|
486
|
-
},
|
|
487
|
-
|
|
488
|
-
deleteSessionsByProjectPath(projectPath: string): void {
|
|
489
|
-
const db = getConnection();
|
|
490
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
491
|
-
db.prepare(`DELETE FROM sessions WHERE project_path = ?`).run(normalizedProjectPath);
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
getSessionName(sessionId: string, provider: string): string | null {
|
|
495
|
-
const db = getConnection();
|
|
496
|
-
const row = db
|
|
497
|
-
.prepare(
|
|
498
|
-
`SELECT custom_name
|
|
499
|
-
FROM sessions
|
|
500
|
-
WHERE session_id = ? AND provider = ?`
|
|
501
|
-
)
|
|
502
|
-
.get(sessionId, provider) as { custom_name: string | null } | undefined;
|
|
503
|
-
|
|
504
|
-
return row?.custom_name ?? null;
|
|
505
|
-
},
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Soft-delete and restore both use the same flag update so callers keep the
|
|
509
|
-
* row, metadata, and file path intact while toggling visibility.
|
|
510
|
-
*/
|
|
511
|
-
updateSessionIsArchived(sessionId: string, isArchived: boolean): void {
|
|
512
|
-
const db = getConnection();
|
|
513
|
-
db.prepare(
|
|
514
|
-
`UPDATE sessions
|
|
515
|
-
SET isArchived = ?
|
|
516
|
-
WHERE session_id = ?`
|
|
517
|
-
).run(isArchived ? 1 : 0, sessionId);
|
|
518
|
-
},
|
|
519
|
-
|
|
520
|
-
deleteSessionById(sessionId: string): boolean {
|
|
521
|
-
const db = getConnection();
|
|
522
|
-
return db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId).changes > 0;
|
|
523
|
-
},
|
|
524
|
-
};
|
|
1
|
+
import { getConnection } from '@/modules/database/connection.js';
|
|
2
|
+
import { projectsDb } from '@/modules/database/repositories/projects.db.js';
|
|
3
|
+
import { normalizeProjectPath } from '@/shared/utils.js';
|
|
4
|
+
|
|
5
|
+
type SessionRow = {
|
|
6
|
+
session_id: string;
|
|
7
|
+
provider: string;
|
|
8
|
+
provider_session_id: string | null;
|
|
9
|
+
project_path: string | null;
|
|
10
|
+
jsonl_path: string | null;
|
|
11
|
+
custom_name: string | null;
|
|
12
|
+
/** Model this session runs with; NULL until the app records one for it. */
|
|
13
|
+
model: string | null;
|
|
14
|
+
/** Reasoning effort this session runs with; NULL until the app records one. */
|
|
15
|
+
effort: string | null;
|
|
16
|
+
isArchived: number;
|
|
17
|
+
created_at: string;
|
|
18
|
+
updated_at: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type RecentSessionsPage = {
|
|
22
|
+
sessions: SessionRow[];
|
|
23
|
+
total: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const SESSION_ROW_COLUMNS =
|
|
27
|
+
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, model, effort, isArchived, created_at, updated_at';
|
|
28
|
+
|
|
29
|
+
const SQLITE_UTC_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
30
|
+
|
|
31
|
+
function normalizeTimestamp(value?: string): string | null {
|
|
32
|
+
if (!value) return null;
|
|
33
|
+
|
|
34
|
+
// SQLite CURRENT_TIMESTAMP is stored as UTC without a timezone suffix.
|
|
35
|
+
// Normalize it here so every session reader returns canonical ISO strings
|
|
36
|
+
// and the sidebar never interprets fresh rows as local-time "hours old".
|
|
37
|
+
const normalizedValue = SQLITE_UTC_TIMESTAMP_REGEX.test(value)
|
|
38
|
+
? `${value.replace(' ', 'T')}Z`
|
|
39
|
+
: value;
|
|
40
|
+
|
|
41
|
+
const parsed = new Date(normalizedValue);
|
|
42
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return parsed.toISOString();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeSessionRow<T extends SessionRow | null | undefined>(row: T): T {
|
|
50
|
+
if (!row) {
|
|
51
|
+
return row;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
...row,
|
|
56
|
+
created_at: normalizeTimestamp(row.created_at) ?? row.created_at,
|
|
57
|
+
updated_at: normalizeTimestamp(row.updated_at) ?? row.updated_at,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeSessionRows(rows: SessionRow[]): SessionRow[] {
|
|
62
|
+
return rows.map((row) => normalizeSessionRow(row) as SessionRow);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeProjectPathForProvider(provider: string, projectPath: string): string {
|
|
66
|
+
void provider;
|
|
67
|
+
return normalizeProjectPath(projectPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const sessionsDb = {
|
|
71
|
+
/**
|
|
72
|
+
* Upserts one session row discovered on disk by a provider synchronizer.
|
|
73
|
+
*
|
|
74
|
+
* The given id is the provider-native session id. Rows are keyed by
|
|
75
|
+
* `provider_session_id` so a session that was first created by the app
|
|
76
|
+
* (with an app-allocated `session_id`) is updated in place once its
|
|
77
|
+
* transcript shows up on disk, instead of producing a duplicate row. An
|
|
78
|
+
* app-created row keeps its existing name; synchronizer names only update
|
|
79
|
+
* rows that were themselves created by indexing provider storage.
|
|
80
|
+
*/
|
|
81
|
+
createSession(
|
|
82
|
+
providerSessionId: string,
|
|
83
|
+
provider: string,
|
|
84
|
+
projectPath: string,
|
|
85
|
+
customName?: string,
|
|
86
|
+
createdAt?: string,
|
|
87
|
+
updatedAt?: string,
|
|
88
|
+
jsonlPath?: string | null
|
|
89
|
+
): string {
|
|
90
|
+
const db = getConnection();
|
|
91
|
+
const createdAtValue = normalizeTimestamp(createdAt);
|
|
92
|
+
const updatedAtValue = normalizeTimestamp(updatedAt);
|
|
93
|
+
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
94
|
+
|
|
95
|
+
// First, ensure the project path is recorded in the projects table,
|
|
96
|
+
// since it's a foreign key in the sessions table.
|
|
97
|
+
projectsDb.createProjectPath(normalizedProjectPath);
|
|
98
|
+
|
|
99
|
+
const existing = db
|
|
100
|
+
.prepare(
|
|
101
|
+
`SELECT session_id FROM sessions
|
|
102
|
+
WHERE provider_session_id = ? AND provider = ?
|
|
103
|
+
LIMIT 1`
|
|
104
|
+
)
|
|
105
|
+
.get(providerSessionId, provider) as { session_id: string } | undefined;
|
|
106
|
+
|
|
107
|
+
if (existing) {
|
|
108
|
+
db.prepare(
|
|
109
|
+
`UPDATE sessions SET
|
|
110
|
+
provider = ?,
|
|
111
|
+
updated_at = COALESCE(?, CURRENT_TIMESTAMP),
|
|
112
|
+
project_path = ?,
|
|
113
|
+
jsonl_path = ?,
|
|
114
|
+
isArchived = 0,
|
|
115
|
+
custom_name = CASE
|
|
116
|
+
WHEN session_id <> provider_session_id AND custom_name IS NOT NULL THEN custom_name
|
|
117
|
+
ELSE COALESCE(?, custom_name)
|
|
118
|
+
END
|
|
119
|
+
WHERE session_id = ?`
|
|
120
|
+
).run(
|
|
121
|
+
provider,
|
|
122
|
+
updatedAtValue,
|
|
123
|
+
normalizedProjectPath,
|
|
124
|
+
jsonlPath ?? null,
|
|
125
|
+
customName ?? null,
|
|
126
|
+
existing.session_id
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return existing.session_id;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Sessions created outside the app (directly via the provider CLI) are
|
|
133
|
+
// keyed by the provider-native id for both columns. The ON CONFLICT path
|
|
134
|
+
// covers legacy rows that predate the provider_session_id mapping.
|
|
135
|
+
db.prepare(
|
|
136
|
+
`INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
|
137
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, COALESCE(?, CURRENT_TIMESTAMP), COALESCE(?, CURRENT_TIMESTAMP))
|
|
138
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
139
|
+
provider = excluded.provider,
|
|
140
|
+
provider_session_id = excluded.provider_session_id,
|
|
141
|
+
updated_at = excluded.updated_at,
|
|
142
|
+
project_path = excluded.project_path,
|
|
143
|
+
jsonl_path = excluded.jsonl_path,
|
|
144
|
+
isArchived = 0,
|
|
145
|
+
custom_name = CASE
|
|
146
|
+
WHEN sessions.session_id <> sessions.provider_session_id AND sessions.custom_name IS NOT NULL
|
|
147
|
+
THEN sessions.custom_name
|
|
148
|
+
ELSE COALESCE(excluded.custom_name, sessions.custom_name)
|
|
149
|
+
END`
|
|
150
|
+
).run(
|
|
151
|
+
providerSessionId,
|
|
152
|
+
provider,
|
|
153
|
+
providerSessionId,
|
|
154
|
+
customName ?? null,
|
|
155
|
+
normalizedProjectPath,
|
|
156
|
+
jsonlPath ?? null,
|
|
157
|
+
createdAtValue,
|
|
158
|
+
updatedAtValue
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return providerSessionId;
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Inserts one app-allocated session row before any provider run happens.
|
|
166
|
+
*
|
|
167
|
+
* The session gateway uses this when the frontend starts a brand-new chat:
|
|
168
|
+
* `session_id` is the stable app-facing id, while `provider_session_id`
|
|
169
|
+
* stays NULL until the provider runtime announces its own id and
|
|
170
|
+
* `assignProviderSessionId` records the mapping. `customName` is derived
|
|
171
|
+
* from the first visible WindCli message by the sessions service.
|
|
172
|
+
*/
|
|
173
|
+
createAppSession(
|
|
174
|
+
sessionId: string,
|
|
175
|
+
provider: string,
|
|
176
|
+
projectPath: string,
|
|
177
|
+
customName?: string,
|
|
178
|
+
): string {
|
|
179
|
+
const db = getConnection();
|
|
180
|
+
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
181
|
+
|
|
182
|
+
projectsDb.createProjectPath(normalizedProjectPath);
|
|
183
|
+
|
|
184
|
+
db.prepare(
|
|
185
|
+
`INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
|
186
|
+
VALUES (?, ?, NULL, ?, ?, NULL, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`
|
|
187
|
+
).run(sessionId, provider, customName ?? null, normalizedProjectPath);
|
|
188
|
+
|
|
189
|
+
return sessionId;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Records the provider-native session id for one app-allocated session.
|
|
194
|
+
*
|
|
195
|
+
* If the filesystem watcher indexed the provider transcript before this
|
|
196
|
+
* mapping was recorded (a duplicate row keyed by the provider id exists),
|
|
197
|
+
* the duplicate is merged into the app row: its transcript path and name
|
|
198
|
+
* are adopted and the duplicate row is removed. Runs in a transaction so
|
|
199
|
+
* the sidebar can never observe both rows at once.
|
|
200
|
+
*/
|
|
201
|
+
assignProviderSessionId(sessionId: string, providerSessionId: string): void {
|
|
202
|
+
const db = getConnection();
|
|
203
|
+
|
|
204
|
+
const merge = db.transaction(() => {
|
|
205
|
+
const duplicate = db
|
|
206
|
+
.prepare(
|
|
207
|
+
`SELECT ${SESSION_ROW_COLUMNS} FROM sessions
|
|
208
|
+
WHERE (session_id = ? OR provider_session_id = ?)
|
|
209
|
+
AND session_id <> ?
|
|
210
|
+
LIMIT 1`
|
|
211
|
+
)
|
|
212
|
+
.get(providerSessionId, providerSessionId, sessionId) as SessionRow | undefined;
|
|
213
|
+
|
|
214
|
+
if (duplicate) {
|
|
215
|
+
db.prepare('DELETE FROM sessions WHERE session_id = ?').run(duplicate.session_id);
|
|
216
|
+
db.prepare(
|
|
217
|
+
`UPDATE sessions SET
|
|
218
|
+
provider_session_id = ?,
|
|
219
|
+
jsonl_path = COALESCE(jsonl_path, ?),
|
|
220
|
+
custom_name = COALESCE(custom_name, ?),
|
|
221
|
+
updated_at = CURRENT_TIMESTAMP
|
|
222
|
+
WHERE session_id = ?`
|
|
223
|
+
).run(providerSessionId, duplicate.jsonl_path, duplicate.custom_name, sessionId);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
db.prepare(
|
|
228
|
+
`UPDATE sessions SET
|
|
229
|
+
provider_session_id = ?,
|
|
230
|
+
updated_at = CURRENT_TIMESTAMP
|
|
231
|
+
WHERE session_id = ?`
|
|
232
|
+
).run(providerSessionId, sessionId);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
merge();
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Records the model one session runs with.
|
|
240
|
+
*
|
|
241
|
+
* Called both when the user picks a model for the session and on every send,
|
|
242
|
+
* so the row always reflects what the session last ran with and reopening it
|
|
243
|
+
* restores that model instead of a catalog default.
|
|
244
|
+
*/
|
|
245
|
+
setSessionModel(sessionId: string, model: string): void {
|
|
246
|
+
const db = getConnection();
|
|
247
|
+
db.prepare(
|
|
248
|
+
`UPDATE sessions
|
|
249
|
+
SET model = ?
|
|
250
|
+
WHERE session_id = ?`
|
|
251
|
+
).run(model, sessionId);
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Records the reasoning effort one session runs with.
|
|
256
|
+
*
|
|
257
|
+
* `default` is stored as an explicit choice rather than NULL so reopening
|
|
258
|
+
* the session does not inherit a later per-provider effort preference.
|
|
259
|
+
*/
|
|
260
|
+
setSessionEffort(sessionId: string, effort: string): void {
|
|
261
|
+
const db = getConnection();
|
|
262
|
+
db.prepare(
|
|
263
|
+
`UPDATE sessions
|
|
264
|
+
SET effort = ?
|
|
265
|
+
WHERE session_id = ?`
|
|
266
|
+
).run(effort, sessionId);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
updateSessionCustomName(sessionId: string, customName: string): void {
|
|
270
|
+
const db = getConnection();
|
|
271
|
+
db.prepare(
|
|
272
|
+
`UPDATE sessions
|
|
273
|
+
SET custom_name = ?
|
|
274
|
+
WHERE session_id = ?`
|
|
275
|
+
).run(customName, sessionId);
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
getSessionById(sessionId: string): SessionRow | null {
|
|
279
|
+
const db = getConnection();
|
|
280
|
+
const row = db
|
|
281
|
+
.prepare(
|
|
282
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
283
|
+
FROM sessions
|
|
284
|
+
WHERE session_id = ?
|
|
285
|
+
ORDER BY updated_at DESC
|
|
286
|
+
LIMIT 1`
|
|
287
|
+
)
|
|
288
|
+
.get(sessionId) as SessionRow | undefined;
|
|
289
|
+
|
|
290
|
+
return normalizeSessionRow(row) ?? null;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Resolves one session row through the provider-native id.
|
|
295
|
+
*
|
|
296
|
+
* The filesystem watcher only knows provider ids (they come from transcript
|
|
297
|
+
* file names), so it uses this lookup to translate disk artifacts back to
|
|
298
|
+
* the app-facing session row before broadcasting sidebar updates.
|
|
299
|
+
*/
|
|
300
|
+
getSessionByProviderSessionId(providerSessionId: string): SessionRow | null {
|
|
301
|
+
const db = getConnection();
|
|
302
|
+
const row = db
|
|
303
|
+
.prepare(
|
|
304
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
305
|
+
FROM sessions
|
|
306
|
+
WHERE provider_session_id = ?
|
|
307
|
+
ORDER BY updated_at DESC
|
|
308
|
+
LIMIT 1`
|
|
309
|
+
)
|
|
310
|
+
.get(providerSessionId) as SessionRow | undefined;
|
|
311
|
+
|
|
312
|
+
return normalizeSessionRow(row) ?? null;
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Finds the newest app-created session for a project that is still waiting
|
|
317
|
+
* for its provider-native id to be recorded.
|
|
318
|
+
*
|
|
319
|
+
* Primary intention: OpenCode can expose a new session in its shared
|
|
320
|
+
* `opencode.db` before the websocket runtime reports that same provider id
|
|
321
|
+
* back to our app. At that moment the sidebar already has an optimistic
|
|
322
|
+
* app-owned session row, but the watcher only knows the provider-native id.
|
|
323
|
+
*
|
|
324
|
+
* Without this lookup, the synchronizer would insert a second row keyed by
|
|
325
|
+
* the provider id, then `assignProviderSessionId()` would merge it a moment
|
|
326
|
+
* later. That eventually self-heals, but on slow networks the user can still
|
|
327
|
+
* briefly see two sidebar sessions for the same conversation.
|
|
328
|
+
*
|
|
329
|
+
* This helper lets the synchronizer claim the pending app row first, so the
|
|
330
|
+
* provider id is attached before any watcher-created row exists. The result
|
|
331
|
+
* is simpler than frontend dedupe and keeps the race resolved at the source.
|
|
332
|
+
*/
|
|
333
|
+
findLatestPendingAppSession(provider: string, projectPath: string): SessionRow | null {
|
|
334
|
+
const db = getConnection();
|
|
335
|
+
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
336
|
+
const row = db
|
|
337
|
+
.prepare(
|
|
338
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
339
|
+
FROM sessions
|
|
340
|
+
WHERE provider = ?
|
|
341
|
+
AND project_path = ?
|
|
342
|
+
AND provider_session_id IS NULL
|
|
343
|
+
AND isArchived = 0
|
|
344
|
+
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC
|
|
345
|
+
LIMIT 1`
|
|
346
|
+
)
|
|
347
|
+
.get(provider, normalizedProjectPath) as SessionRow | undefined;
|
|
348
|
+
|
|
349
|
+
return normalizeSessionRow(row) ?? null;
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
getAllSessions(): SessionRow[] {
|
|
353
|
+
const db = getConnection();
|
|
354
|
+
const rows = db
|
|
355
|
+
.prepare(
|
|
356
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
357
|
+
FROM sessions
|
|
358
|
+
WHERE isArchived = 0`
|
|
359
|
+
)
|
|
360
|
+
.all() as SessionRow[];
|
|
361
|
+
|
|
362
|
+
return normalizeSessionRows(rows);
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Returns one globally ordered page of visible conversations.
|
|
367
|
+
*
|
|
368
|
+
* Pagination happens after archived sessions and sessions belonging to an
|
|
369
|
+
* archived project have been excluded. This keeps the sidebar feed complete
|
|
370
|
+
* and correctly ordered across projects instead of flattening only the
|
|
371
|
+
* per-project slices already loaded by the client.
|
|
372
|
+
*/
|
|
373
|
+
getRecentSessionsPage(limit: number, offset: number): RecentSessionsPage {
|
|
374
|
+
const db = getConnection();
|
|
375
|
+
const visibilityClause = `
|
|
376
|
+
sessions.isArchived = 0
|
|
377
|
+
AND (projects.isArchived IS NULL OR projects.isArchived = 0)
|
|
378
|
+
`;
|
|
379
|
+
const rows = db
|
|
380
|
+
.prepare(
|
|
381
|
+
`SELECT sessions.*
|
|
382
|
+
FROM sessions
|
|
383
|
+
LEFT JOIN projects ON projects.project_path = sessions.project_path
|
|
384
|
+
WHERE ${visibilityClause}
|
|
385
|
+
ORDER BY julianday(COALESCE(sessions.updated_at, sessions.created_at)) DESC,
|
|
386
|
+
sessions.session_id DESC
|
|
387
|
+
LIMIT ? OFFSET ?`
|
|
388
|
+
)
|
|
389
|
+
.all(limit, offset) as SessionRow[];
|
|
390
|
+
const countRow = db
|
|
391
|
+
.prepare(
|
|
392
|
+
`SELECT COUNT(*) AS count
|
|
393
|
+
FROM sessions
|
|
394
|
+
LEFT JOIN projects ON projects.project_path = sessions.project_path
|
|
395
|
+
WHERE ${visibilityClause}`
|
|
396
|
+
)
|
|
397
|
+
.get() as { count: number } | undefined;
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
sessions: normalizeSessionRows(rows),
|
|
401
|
+
total: Number(countRow?.count ?? 0),
|
|
402
|
+
};
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Archived rows are intentionally queried separately so the caller can render
|
|
407
|
+
* them in a dedicated view without reintroducing them into active session lists.
|
|
408
|
+
*/
|
|
409
|
+
getArchivedSessions(): SessionRow[] {
|
|
410
|
+
const db = getConnection();
|
|
411
|
+
const rows = db
|
|
412
|
+
.prepare(
|
|
413
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
414
|
+
FROM sessions
|
|
415
|
+
WHERE isArchived = 1
|
|
416
|
+
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC`
|
|
417
|
+
)
|
|
418
|
+
.all() as SessionRow[];
|
|
419
|
+
|
|
420
|
+
return normalizeSessionRows(rows);
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
getSessionsByProjectPath(projectPath: string): SessionRow[] {
|
|
424
|
+
const db = getConnection();
|
|
425
|
+
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
426
|
+
const rows = db
|
|
427
|
+
.prepare(
|
|
428
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
429
|
+
FROM sessions
|
|
430
|
+
WHERE project_path = ?
|
|
431
|
+
AND isArchived = 0`
|
|
432
|
+
)
|
|
433
|
+
.all(normalizedProjectPath) as SessionRow[];
|
|
434
|
+
|
|
435
|
+
return normalizeSessionRows(rows);
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Permanent project deletion must see every session row for the path,
|
|
440
|
+
* including archived ones, so their transcript files can be cleaned up.
|
|
441
|
+
*/
|
|
442
|
+
getSessionsByProjectPathIncludingArchived(projectPath: string): SessionRow[] {
|
|
443
|
+
const db = getConnection();
|
|
444
|
+
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
445
|
+
const rows = db
|
|
446
|
+
.prepare(
|
|
447
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
448
|
+
FROM sessions
|
|
449
|
+
WHERE project_path = ?`
|
|
450
|
+
)
|
|
451
|
+
.all(normalizedProjectPath) as SessionRow[];
|
|
452
|
+
|
|
453
|
+
return normalizeSessionRows(rows);
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
getSessionsByProjectPathPage(projectPath: string, limit: number, offset: number): SessionRow[] {
|
|
457
|
+
const db = getConnection();
|
|
458
|
+
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
459
|
+
const rows = db
|
|
460
|
+
.prepare(
|
|
461
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
462
|
+
FROM sessions
|
|
463
|
+
WHERE project_path = ?
|
|
464
|
+
AND isArchived = 0
|
|
465
|
+
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC
|
|
466
|
+
LIMIT ? OFFSET ?`
|
|
467
|
+
)
|
|
468
|
+
.all(normalizedProjectPath, limit, offset) as SessionRow[];
|
|
469
|
+
|
|
470
|
+
return normalizeSessionRows(rows);
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
countSessionsByProjectPath(projectPath: string): number {
|
|
474
|
+
const db = getConnection();
|
|
475
|
+
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
476
|
+
const row = db
|
|
477
|
+
.prepare(
|
|
478
|
+
`SELECT COUNT(*) AS count
|
|
479
|
+
FROM sessions
|
|
480
|
+
WHERE project_path = ?
|
|
481
|
+
AND isArchived = 0`
|
|
482
|
+
)
|
|
483
|
+
.get(normalizedProjectPath) as { count: number } | undefined;
|
|
484
|
+
|
|
485
|
+
return Number(row?.count ?? 0);
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
deleteSessionsByProjectPath(projectPath: string): void {
|
|
489
|
+
const db = getConnection();
|
|
490
|
+
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
491
|
+
db.prepare(`DELETE FROM sessions WHERE project_path = ?`).run(normalizedProjectPath);
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
getSessionName(sessionId: string, provider: string): string | null {
|
|
495
|
+
const db = getConnection();
|
|
496
|
+
const row = db
|
|
497
|
+
.prepare(
|
|
498
|
+
`SELECT custom_name
|
|
499
|
+
FROM sessions
|
|
500
|
+
WHERE session_id = ? AND provider = ?`
|
|
501
|
+
)
|
|
502
|
+
.get(sessionId, provider) as { custom_name: string | null } | undefined;
|
|
503
|
+
|
|
504
|
+
return row?.custom_name ?? null;
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Soft-delete and restore both use the same flag update so callers keep the
|
|
509
|
+
* row, metadata, and file path intact while toggling visibility.
|
|
510
|
+
*/
|
|
511
|
+
updateSessionIsArchived(sessionId: string, isArchived: boolean): void {
|
|
512
|
+
const db = getConnection();
|
|
513
|
+
db.prepare(
|
|
514
|
+
`UPDATE sessions
|
|
515
|
+
SET isArchived = ?
|
|
516
|
+
WHERE session_id = ?`
|
|
517
|
+
).run(isArchived ? 1 : 0, sessionId);
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
deleteSessionById(sessionId: string): boolean {
|
|
521
|
+
const db = getConnection();
|
|
522
|
+
return db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId).changes > 0;
|
|
523
|
+
},
|
|
524
|
+
};
|