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.
Files changed (510) hide show
  1. package/LICENSE +718 -718
  2. package/README.md +17 -2
  3. package/dist/api-docs.html +901 -901
  4. package/dist/assets/{CodeEditor-B2Wf7Fg0.js → CodeEditor-FvgsnJ7J.js} +6 -6
  5. package/dist/assets/FileTree-NLULrBDi.js +1 -0
  6. package/dist/assets/GitPanel-51KUBbEW.js +4 -0
  7. package/dist/assets/{MarkdownPreview-C0tqYnVu.js → MarkdownPreview-PEEWVWeS.js} +17 -27
  8. package/dist/assets/Shell-BEx66cW_.js +76 -0
  9. package/dist/assets/Shell-DrlLKa8f.css +1 -0
  10. package/dist/assets/StandaloneShell-BkuUCPZP.js +1 -0
  11. package/dist/assets/TaskMasterPanel-DfQG80SF.js +2 -0
  12. package/dist/assets/abnfDiagram-N423BO3Z-Cc0ul87U.js +1 -0
  13. package/dist/assets/arc-BAuugHw_.js +1 -0
  14. package/dist/assets/architectureDiagram-T3A2C74G-DA3G7QZD.js +36 -0
  15. package/dist/assets/{blockDiagram-VBNYF7ZC-DpaEf3mv.js → blockDiagram-VBNYF7ZC-tJ12IJs3.js} +4 -4
  16. package/dist/assets/c4Diagram-5PPSVZJV-5BTMCXL4.js +10 -0
  17. package/dist/assets/channel-BGDgNNQz.js +1 -0
  18. package/dist/assets/{chunk-2GRJ4B5K-DoSdNo8u.js → chunk-2GRJ4B5K-BPaI06Ou.js} +1 -1
  19. package/dist/assets/{chunk-2Q5K7J3B-CE7I3uMn.js → chunk-2Q5K7J3B-HV4zmsEe.js} +1 -1
  20. package/dist/assets/chunk-5RXB4S5H-BAwutJoU.js +231 -0
  21. package/dist/assets/{chunk-5VM5RSS4-B55XgqHZ.js → chunk-5VM5RSS4-B9QAJPrv.js} +1 -1
  22. package/dist/assets/{chunk-6Q2QTUOP-BS1KpefW.js → chunk-6Q2QTUOP-BOIW8bzI.js} +1 -1
  23. package/dist/assets/chunk-GF5L2VYU-DD13o00-.js +206 -0
  24. package/dist/assets/{chunk-JWPE2WC7-DOtkNhMm.js → chunk-JWPE2WC7-CFBm23VK.js} +1 -1
  25. package/dist/assets/{chunk-KBJHAD2P-BXLjviqM.js → chunk-KBJHAD2P-C0i0v8Op.js} +1 -1
  26. package/dist/assets/{chunk-RYQCIY6F-C50fjNNz.js → chunk-RYQCIY6F-DfeK0taV.js} +1 -1
  27. package/dist/assets/{chunk-XXDRQBXY-QBYqCkb7.js → chunk-XXDRQBXY-BKWvf-QE.js} +1 -1
  28. package/dist/assets/classDiagram-JCYQIIEL-C2SNM4oy.js +1 -0
  29. package/dist/assets/classDiagram-v2-OCEON4UE-C2SNM4oy.js +1 -0
  30. package/dist/assets/cose-bilkent-JH36ORCC-CBE8oM7L.js +1 -0
  31. package/dist/assets/{cynefin-VYW2F7L2-DnJa_OlQ.js → cynefin-VYW2F7L2-DziGp35A.js} +11 -23
  32. package/dist/assets/cynefinDiagram-MW4NZA55-CNvtHl39.js +62 -0
  33. package/dist/assets/cytoscape.esm-yzknjiTM.js +321 -0
  34. package/dist/assets/dagre-VZM6K2ZE-Bz9lsU7m.js +4 -0
  35. package/dist/assets/{diagram-7IWD3JNH-pFzs9HEu.js → diagram-7IWD3JNH-CV7D3VSd.js} +5 -5
  36. package/dist/assets/{diagram-B4RE2ZJO-CcehQC7x.js → diagram-B4RE2ZJO-Dj4EjDNC.js} +2 -2
  37. package/dist/assets/diagram-LBJQPF4R-wJ6FHvf-.js +24 -0
  38. package/dist/assets/diagram-Q27KOJAE-DMFYMhOB.js +24 -0
  39. package/dist/assets/diagram-UB23O5K3-DeBSbWAI.js +41 -0
  40. package/dist/assets/ebnfDiagram-BXEA7PRR-2NnJSGhT.js +1 -0
  41. package/dist/assets/erDiagram-JOGREHBK-DrSlg8hr.js +85 -0
  42. package/dist/assets/feature-chat-CvOCyaLe.js +88 -0
  43. package/dist/assets/feature-workspace-CAB6bgPy.js +39 -0
  44. package/dist/assets/{flowDiagram-UKHOOZJN-CJV8fQPR.js → flowDiagram-UKHOOZJN-C0pejU6W.js} +4 -4
  45. package/dist/assets/ganttDiagram-PKOTCBZU-BM0VCUUz.js +292 -0
  46. package/dist/assets/gitGraphDiagram-DS77QQ5N-SbLG0c5_.js +106 -0
  47. package/dist/assets/graph-DOmOIIwC.js +1 -0
  48. package/dist/assets/{index-GFhXZyl2.js → index-BflGfU1C.js} +3 -3
  49. package/dist/assets/index-Btkf0_rU.js +1 -0
  50. package/dist/assets/index-Cr5MbHuj.css +1 -0
  51. package/dist/assets/index-Gqazqfs3.js +1 -0
  52. package/dist/assets/index-nyrkoQK6.js +35 -0
  53. package/dist/assets/infoDiagram-6WML65LV-Cf5DSa66.js +2 -0
  54. package/dist/assets/ishikawaDiagram-WSZJBQD7-DwyFutKy.js +70 -0
  55. package/dist/assets/journeyDiagram-NVQOT4AX-Df0FRhLo.js +139 -0
  56. package/dist/assets/{kanban-definition-27J2QSJJ-B3iL0ztJ.js → kanban-definition-27J2QSJJ-HmpbjrZz.js} +6 -6
  57. package/dist/assets/{layout-D6BstSzF.js → layout-D-LzfAck.js} +1 -1
  58. package/dist/assets/{linear-Cwe9dEKA.js → linear-DOM01LBe.js} +1 -1
  59. package/dist/assets/map-DxJ2ADlA.js +1 -0
  60. package/dist/assets/mermaid.core-C2Ma-_S6.js +306 -0
  61. package/dist/assets/{mindmap-definition-FAOFIHXS-BgfBYMzr.js → mindmap-definition-FAOFIHXS-B4GOIn76.js} +4 -4
  62. package/dist/assets/pegDiagram-VL7TDLO6-BHur7aH2.js +1 -0
  63. package/dist/assets/{pieDiagram-7S7Q4E2Y-DeSrxc4I.js → pieDiagram-7S7Q4E2Y-Cw0UELLV.js} +2 -2
  64. package/dist/assets/quadrantDiagram-CIZ2JOQS-CB2BrX3g.js +7 -0
  65. package/dist/assets/railroadDiagram-AXF67PYL-DPYZYmLX.js +1 -0
  66. package/dist/assets/requirementDiagram-LRYGKXZP-DSX4IJ1n.js +84 -0
  67. package/dist/assets/sankeyDiagram-W5VNT64P-TenC3ZQo.js +40 -0
  68. package/dist/assets/sequenceDiagram-SI44F4Z6-DltGsf3F.js +162 -0
  69. package/dist/assets/sizeCapture-X5ZJPWSS-D7llwbJZ.js +1 -0
  70. package/dist/assets/stateDiagram-OKZ733FA-C-hAvbnY.js +1 -0
  71. package/dist/assets/stateDiagram-v2-UEYNNEHI-DaYSFcRQ.js +1 -0
  72. package/dist/assets/swimlanes-SLNWSIFB-DWD30fv6.js +2 -0
  73. package/dist/assets/swimlanesDiagram-ULZ7WXOC-LTkF2T6t.js +8 -0
  74. package/dist/assets/timeline-definition-Z64GVDOM-quWmimSE.js +120 -0
  75. package/dist/assets/vendor-data-DsEZeNPr.js +4 -0
  76. package/dist/assets/vendor-i18n-B6HjLCnN.js +1 -0
  77. package/dist/assets/vendor-markdown-DkITEES2.js +298 -0
  78. package/dist/assets/vendor-react-C2GTiUCy.js +10 -0
  79. package/dist/assets/vendor-ui-CAUHSABu.js +49 -0
  80. package/dist/assets/vennDiagram-T6HMQDX7-aUQcm8h5.js +34 -0
  81. package/dist/assets/{wardleyDiagram-T6FBY63Y-BNCpsFZV.js → wardleyDiagram-T6FBY63Y-BZ0bwsFQ.js} +2 -2
  82. package/dist/assets/workbox-window.prod.es5-BBnX5xw4.js +2 -0
  83. package/dist/assets/xychartDiagram-ELKLHX3M-DOVzAq63.js +7 -0
  84. package/dist/clear-cache.html +85 -85
  85. package/dist/convert-icons.md +52 -52
  86. package/dist/favicon.png +0 -0
  87. package/dist/favicon.svg +8 -8
  88. package/dist/generate-icons.js +48 -48
  89. package/dist/icons/codex-white.svg +3 -3
  90. package/dist/icons/codex.svg +3 -3
  91. package/dist/icons/cursor-white.svg +11 -11
  92. package/dist/icons/icon-128x128.png +0 -0
  93. package/dist/icons/icon-128x128.svg +11 -11
  94. package/dist/icons/icon-144x144.png +0 -0
  95. package/dist/icons/icon-144x144.svg +11 -11
  96. package/dist/icons/icon-152x152.png +0 -0
  97. package/dist/icons/icon-152x152.svg +11 -11
  98. package/dist/icons/icon-180x180.png +0 -0
  99. package/dist/icons/icon-192x192.png +0 -0
  100. package/dist/icons/icon-192x192.svg +11 -11
  101. package/dist/icons/icon-384x384.png +0 -0
  102. package/dist/icons/icon-384x384.svg +11 -11
  103. package/dist/icons/icon-512x512.png +0 -0
  104. package/dist/icons/icon-512x512.svg +11 -11
  105. package/dist/icons/icon-72x72.png +0 -0
  106. package/dist/icons/icon-72x72.svg +11 -11
  107. package/dist/icons/icon-96x96.png +0 -0
  108. package/dist/icons/icon-96x96.svg +11 -11
  109. package/dist/icons/icon-maskable-192x192.png +0 -0
  110. package/dist/icons/icon-maskable-512x512.png +0 -0
  111. package/dist/icons/icon-template.svg +11 -11
  112. package/dist/icons/new-icon.png +0 -0
  113. package/dist/index.html +48 -58
  114. package/dist/logo-128.png +0 -0
  115. package/dist/logo-256.png +0 -0
  116. package/dist/logo.svg +17 -17
  117. package/dist/manifest.webmanifest +1 -0
  118. package/dist/push-sw.js +48 -0
  119. package/dist/screenshots/setup-screen-mobile.png +0 -0
  120. package/dist/screenshots/setup-screen.png +0 -0
  121. package/dist/sw.js +1 -124
  122. package/dist/workbox-ae5c0bb2.js +1 -0
  123. package/dist-server/server/index.js +1 -1
  124. package/dist-server/server/index.js.map +1 -1
  125. package/dist-server/server/load-env.js +4 -2
  126. package/dist-server/server/load-env.js.map +1 -1
  127. package/dist-server/server/modules/agent/agent.routes.js +1 -1
  128. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  129. package/dist-server/server/modules/browser-use/browser-use.service.js +1 -1
  130. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  131. package/dist-server/server/modules/cli/cli.service.js +37 -37
  132. package/dist-server/server/modules/cli/cli.service.js.map +1 -1
  133. package/dist-server/server/modules/cli/sandbox.service.js +43 -43
  134. package/dist-server/server/modules/cli/sandbox.service.js.map +1 -1
  135. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +74 -74
  136. package/dist-server/server/modules/commands/commands.routes.js +25 -25
  137. package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
  138. package/dist-server/server/modules/commands/tests/commands.test.js +1 -1
  139. package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -1
  140. package/dist-server/server/modules/database/connection.js +1 -1
  141. package/dist-server/server/modules/database/connection.js.map +1 -1
  142. package/dist-server/server/modules/database/migrations.js +190 -190
  143. package/dist-server/server/modules/database/repositories/api-keys.js +3 -3
  144. package/dist-server/server/modules/database/repositories/github-tokens.js +2 -2
  145. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +28 -28
  146. package/dist-server/server/modules/database/repositories/notification-preferences.js +4 -4
  147. package/dist-server/server/modules/database/repositories/projects.db.js +61 -61
  148. package/dist-server/server/modules/database/repositories/provider-models.js +35 -35
  149. package/dist-server/server/modules/database/repositories/push-subscriptions.js +5 -5
  150. package/dist-server/server/modules/database/repositories/scan-state.db.js +5 -5
  151. package/dist-server/server/modules/database/repositories/sessions.db.js +93 -93
  152. package/dist-server/server/modules/database/schema.js +177 -177
  153. package/dist-server/server/modules/database/tests/projects.db.integration.test.js +3 -2
  154. package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
  155. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +6 -6
  156. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +4 -3
  157. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
  158. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +2 -1
  159. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  160. package/dist-server/server/modules/git/git.routes.js +17 -17
  161. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +1 -1
  162. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  163. package/dist-server/server/modules/notifications/vapid-keys.service.js +1 -1
  164. package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -1
  165. package/dist-server/server/modules/projects/tests/project-management.service.test.js +2 -1
  166. package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -1
  167. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +2 -2
  168. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -1
  169. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +1 -1
  170. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  171. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +1 -1
  172. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
  173. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  174. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +13 -13
  175. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +9 -9
  176. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +17 -17
  177. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +25 -25
  178. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +27 -27
  179. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +9 -9
  180. package/dist-server/server/modules/providers/services/sessions.service.js +1 -1
  181. package/dist-server/server/modules/providers/tests/claude-auth.test.js +8 -0
  182. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  183. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +1 -1
  184. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  185. package/dist-server/server/modules/providers/tests/mcp.test.js +3 -3
  186. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +98 -98
  187. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  188. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +18 -18
  189. package/dist-server/server/modules/providers/tests/provider.routes.test.js +1 -1
  190. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  191. package/dist-server/server/modules/system/system.service.js +1 -1
  192. package/dist-server/server/modules/system/system.service.js.map +1 -1
  193. package/dist-server/server/modules/system/tests/system.service.test.js +1 -1
  194. package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -1
  195. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +423 -422
  196. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  197. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +1 -1
  198. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  199. package/dist-server/server/modules/worktrees/services/worktree-list.service.js +1 -1
  200. package/dist-server/server/modules/worktrees/services/worktree-open.service.js +1 -1
  201. package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +1 -1
  202. package/electron/cloud.js +260 -260
  203. package/electron/desktopNotifications.js +378 -378
  204. package/electron/desktopWindow.js +766 -766
  205. package/electron/launcher/index.html +14 -14
  206. package/electron/launcher/launcher.css +801 -801
  207. package/electron/launcher/launcher.js +687 -687
  208. package/electron/localServer.js +549 -549
  209. package/electron/main.js +944 -944
  210. package/electron/preload.cjs +60 -60
  211. package/electron/scripts/generate-macos-icon.js +62 -62
  212. package/electron/serverInstaller.js +277 -277
  213. package/electron/tabs.js +87 -87
  214. package/electron/viewHost.js +331 -331
  215. package/package.json +262 -247
  216. package/public/api-docs.html +901 -901
  217. package/scripts/build-client.mjs +30 -0
  218. package/scripts/fix-node-pty.js +67 -67
  219. package/scripts/generate-pwa-icons.ps1 +33 -0
  220. package/scripts/promote-dist-server.mjs +50 -50
  221. package/scripts/release/build-server-bundle.js +176 -176
  222. package/scripts/release/prepare-desktop-app.js +152 -152
  223. package/scripts/verify-pwa-build.mjs +37 -0
  224. package/server/index.ts +399 -399
  225. package/server/load-env.ts +49 -47
  226. package/server/modules/agent/agent.module.ts +52 -52
  227. package/server/modules/agent/agent.routes.ts +1287 -1287
  228. package/server/modules/agent/index.ts +2 -2
  229. package/server/modules/agent/tests/agent.routes.test.ts +205 -205
  230. package/server/modules/assets/assets.routes.ts +150 -150
  231. package/server/modules/assets/index.ts +3 -3
  232. package/server/modules/assets/services/image-assets.service.ts +127 -127
  233. package/server/modules/assets/tests/image-assets.service.test.ts +74 -74
  234. package/server/modules/auth/auth.middleware.ts +163 -163
  235. package/server/modules/auth/auth.module.ts +38 -38
  236. package/server/modules/auth/auth.routes.ts +57 -57
  237. package/server/modules/auth/auth.service.ts +155 -155
  238. package/server/modules/auth/index.ts +9 -9
  239. package/server/modules/auth/tests/auth.service.test.ts +95 -95
  240. package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -120
  241. package/server/modules/browser-use/browser-use-mcp.ts +386 -386
  242. package/server/modules/browser-use/browser-use-runtime.ts +10 -10
  243. package/server/modules/browser-use/browser-use.routes.ts +96 -96
  244. package/server/modules/browser-use/browser-use.service.ts +836 -836
  245. package/server/modules/browser-use/index.ts +11 -11
  246. package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -10
  247. package/server/modules/cli/cli.module.ts +93 -93
  248. package/server/modules/cli/cli.service.ts +248 -248
  249. package/server/modules/cli/cli.ts +19 -19
  250. package/server/modules/cli/index.ts +2 -2
  251. package/server/modules/cli/sandbox.service.ts +398 -398
  252. package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -186
  253. package/server/modules/cli/tests/cli.service.test.ts +86 -86
  254. package/server/modules/cli/tests/sandbox.service.test.ts +58 -58
  255. package/server/modules/commands/commands.module.ts +22 -22
  256. package/server/modules/commands/commands.routes.ts +587 -587
  257. package/server/modules/commands/index.ts +2 -2
  258. package/server/modules/commands/tests/commands.test.ts +110 -110
  259. package/server/modules/database/connection.ts +144 -143
  260. package/server/modules/database/index.ts +17 -17
  261. package/server/modules/database/init-db.ts +17 -17
  262. package/server/modules/database/migrations.ts +515 -515
  263. package/server/modules/database/repositories/api-keys.ts +119 -119
  264. package/server/modules/database/repositories/app-config.ts +53 -53
  265. package/server/modules/database/repositories/credentials.ts +106 -106
  266. package/server/modules/database/repositories/github-tokens.ts +100 -100
  267. package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -153
  268. package/server/modules/database/repositories/notification-preferences.ts +117 -117
  269. package/server/modules/database/repositories/projects.db.ts +196 -196
  270. package/server/modules/database/repositories/provider-models.ts +168 -168
  271. package/server/modules/database/repositories/push-subscriptions.ts +80 -80
  272. package/server/modules/database/repositories/scan-state.db.ts +42 -42
  273. package/server/modules/database/repositories/sessions.db.ts +524 -524
  274. package/server/modules/database/repositories/users.ts +140 -140
  275. package/server/modules/database/repositories/vapid-keys.ts +57 -57
  276. package/server/modules/database/schema.ts +210 -210
  277. package/server/modules/database/tests/projects.db.integration.test.ts +73 -72
  278. package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -125
  279. package/server/modules/database/tests/sessions-provider-mapping.test.ts +110 -109
  280. package/server/modules/database/tests/sessions.db.integration.test.ts +117 -116
  281. package/server/modules/file-tree/file-tree.module.ts +116 -116
  282. package/server/modules/file-tree/file-tree.routes.ts +258 -258
  283. package/server/modules/file-tree/file-tree.service.ts +675 -675
  284. package/server/modules/file-tree/index.ts +2 -2
  285. package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -157
  286. package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -371
  287. package/server/modules/git/git-branch.service.ts +43 -43
  288. package/server/modules/git/git-parsing.service.ts +66 -66
  289. package/server/modules/git/git.module.ts +22 -22
  290. package/server/modules/git/git.routes.ts +1596 -1596
  291. package/server/modules/git/index.ts +2 -2
  292. package/server/modules/git/tests/git-branch.service.test.ts +60 -60
  293. package/server/modules/git/tests/git-init.routes.test.ts +132 -132
  294. package/server/modules/git/tests/git.test.ts +106 -106
  295. package/server/modules/notifications/index.ts +24 -24
  296. package/server/modules/notifications/notifications.routes.ts +127 -127
  297. package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -124
  298. package/server/modules/notifications/services/notification-orchestrator.service.js +310 -310
  299. package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -55
  300. package/server/modules/notifications/vapid-keys.service.ts +38 -38
  301. package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -109
  302. package/server/modules/plugins/index.ts +9 -9
  303. package/server/modules/plugins/plugin-process.service.ts +217 -217
  304. package/server/modules/plugins/plugin-registry.service.ts +459 -459
  305. package/server/modules/plugins/plugins.module.ts +33 -33
  306. package/server/modules/plugins/plugins.routes.ts +66 -66
  307. package/server/modules/plugins/plugins.service.ts +147 -147
  308. package/server/modules/plugins/tests/plugins.service.test.ts +32 -32
  309. package/server/modules/projects/index.ts +11 -11
  310. package/server/modules/projects/projects.routes.ts +273 -273
  311. package/server/modules/projects/services/project-clone.service.ts +323 -323
  312. package/server/modules/projects/services/project-delete.service.ts +90 -90
  313. package/server/modules/projects/services/project-management.service.ts +144 -144
  314. package/server/modules/projects/services/project-star.service.ts +78 -78
  315. package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -248
  316. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -314
  317. package/server/modules/projects/tests/project-clone.service.test.ts +183 -183
  318. package/server/modules/projects/tests/project-management.service.test.ts +118 -117
  319. package/server/modules/projects/tests/project-star.service.test.ts +123 -123
  320. package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -105
  321. package/server/modules/providers/README.md +380 -380
  322. package/server/modules/providers/index.ts +10 -10
  323. package/server/modules/providers/list/claude/claude-auth.provider.ts +160 -160
  324. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  325. package/server/modules/providers/list/claude/claude-models.provider.ts +294 -294
  326. package/server/modules/providers/list/claude/claude-runtime.provider.js +1068 -1068
  327. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -203
  328. package/server/modules/providers/list/claude/claude-sessions.provider.ts +684 -684
  329. package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -265
  330. package/server/modules/providers/list/claude/claude.provider.ts +30 -30
  331. package/server/modules/providers/list/codex/codex-app-server.client.ts +2 -2
  332. package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -100
  333. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  334. package/server/modules/providers/list/codex/codex-models.provider.ts +123 -123
  335. package/server/modules/providers/list/codex/codex-runtime.provider.js +533 -533
  336. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +203 -203
  337. package/server/modules/providers/list/codex/codex-sessions.provider.ts +938 -938
  338. package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -73
  339. package/server/modules/providers/list/codex/codex.provider.ts +28 -28
  340. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
  341. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  342. package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -119
  343. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +385 -385
  344. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +158 -159
  345. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -654
  346. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -39
  347. package/server/modules/providers/list/cursor/cursor.provider.ts +30 -30
  348. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -110
  349. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -228
  350. package/server/modules/providers/list/opencode/opencode-models.provider.ts +203 -203
  351. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -434
  352. package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -261
  353. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -179
  354. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -500
  355. package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -78
  356. package/server/modules/providers/list/opencode/opencode.provider.ts +30 -30
  357. package/server/modules/providers/provider.registry.ts +36 -36
  358. package/server/modules/providers/provider.routes.ts +877 -877
  359. package/server/modules/providers/services/mcp.service.ts +109 -109
  360. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  361. package/server/modules/providers/services/provider-capabilities.service.ts +97 -97
  362. package/server/modules/providers/services/provider-models.service.ts +401 -401
  363. package/server/modules/providers/services/provider-runtime.service.ts +108 -108
  364. package/server/modules/providers/services/provider-token-usage.service.ts +357 -357
  365. package/server/modules/providers/services/session-conversations-search.service.ts +1321 -1321
  366. package/server/modules/providers/services/session-synchronizer.service.ts +74 -74
  367. package/server/modules/providers/services/sessions-watcher.service.ts +247 -247
  368. package/server/modules/providers/services/sessions.service.ts +471 -471
  369. package/server/modules/providers/services/skills.service.ts +39 -39
  370. package/server/modules/providers/shared/base/abstract.provider.ts +32 -32
  371. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  372. package/server/modules/providers/shared/skills/skills.provider.ts +287 -287
  373. package/server/modules/providers/tests/claude-auth.test.ts +157 -150
  374. package/server/modules/providers/tests/claude-sessions.test.ts +61 -61
  375. package/server/modules/providers/tests/codex-sessions.test.ts +231 -231
  376. package/server/modules/providers/tests/mcp.test.ts +349 -349
  377. package/server/modules/providers/tests/opencode-models.test.ts +41 -41
  378. package/server/modules/providers/tests/opencode-sessions.test.ts +522 -522
  379. package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -247
  380. package/server/modules/providers/tests/provider-models.service.test.ts +353 -353
  381. package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -138
  382. package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -186
  383. package/server/modules/providers/tests/provider.routes.test.ts +244 -244
  384. package/server/modules/providers/tests/sessions-details.test.ts +73 -73
  385. package/server/modules/providers/tests/sessions.service.test.ts +127 -127
  386. package/server/modules/providers/tests/skills.test.ts +692 -692
  387. package/server/modules/settings/index.ts +2 -2
  388. package/server/modules/settings/settings.module.ts +50 -50
  389. package/server/modules/settings/settings.routes.ts +51 -51
  390. package/server/modules/settings/settings.service.ts +186 -186
  391. package/server/modules/settings/tests/settings.service.test.ts +54 -54
  392. package/server/modules/system/index.ts +2 -2
  393. package/server/modules/system/system.module.ts +62 -62
  394. package/server/modules/system/system.routes.ts +21 -21
  395. package/server/modules/system/system.service.ts +76 -76
  396. package/server/modules/system/tests/system.service.test.ts +118 -118
  397. package/server/modules/taskmaster/index.ts +2 -2
  398. package/server/modules/taskmaster/taskmaster.module.ts +24 -24
  399. package/server/modules/taskmaster/taskmaster.routes.ts +1483 -1482
  400. package/server/modules/taskmaster/taskmaster.service.ts +132 -132
  401. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -144
  402. package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -97
  403. package/server/modules/user/index.ts +2 -2
  404. package/server/modules/user/tests/user.service.test.ts +61 -61
  405. package/server/modules/user/user.module.ts +58 -58
  406. package/server/modules/user/user.routes.ts +50 -50
  407. package/server/modules/user/user.service.ts +85 -85
  408. package/server/modules/voice/index.ts +2 -2
  409. package/server/modules/voice/tests/voice.service.test.ts +98 -98
  410. package/server/modules/voice/voice.module.ts +47 -47
  411. package/server/modules/voice/voice.routes.ts +113 -113
  412. package/server/modules/voice/voice.service.ts +192 -192
  413. package/server/modules/websocket/README.md +273 -273
  414. package/server/modules/websocket/index.ts +3 -3
  415. package/server/modules/websocket/services/chat-run-registry.service.ts +343 -343
  416. package/server/modules/websocket/services/chat-session-writer.service.ts +145 -145
  417. package/server/modules/websocket/services/chat-websocket.service.ts +425 -425
  418. package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -65
  419. package/server/modules/websocket/services/shell-websocket.service.ts +503 -503
  420. package/server/modules/websocket/services/websocket-auth.service.ts +59 -59
  421. package/server/modules/websocket/services/websocket-server.service.ts +124 -124
  422. package/server/modules/websocket/services/websocket-state.service.ts +16 -16
  423. package/server/modules/websocket/services/websocket-writer.service.ts +38 -38
  424. package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -59
  425. package/server/modules/websocket/tests/chat-run-registry.test.ts +282 -282
  426. package/server/modules/websocket/tests/shell-websocket.service.test.ts +100 -100
  427. package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -78
  428. package/server/modules/worktrees/index.ts +2 -2
  429. package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -53
  430. package/server/modules/worktrees/services/worktree-create.service.ts +104 -104
  431. package/server/modules/worktrees/services/worktree-git.service.ts +207 -207
  432. package/server/modules/worktrees/services/worktree-list.service.ts +143 -143
  433. package/server/modules/worktrees/services/worktree-merge.service.ts +157 -157
  434. package/server/modules/worktrees/services/worktree-open.service.ts +85 -85
  435. package/server/modules/worktrees/services/worktree-remove.service.ts +88 -88
  436. package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -68
  437. package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -142
  438. package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -84
  439. package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -45
  440. package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -213
  441. package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -122
  442. package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -227
  443. package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -199
  444. package/server/modules/worktrees/worktrees.module.ts +113 -113
  445. package/server/modules/worktrees/worktrees.routes.ts +116 -116
  446. package/server/shared/claude-cli-path.ts +139 -139
  447. package/server/shared/frontmatter.ts +18 -18
  448. package/server/shared/image-attachments.ts +435 -435
  449. package/server/shared/interfaces.ts +179 -179
  450. package/server/shared/tests/claude-cli-path.test.ts +61 -61
  451. package/server/shared/tests/image-attachments.test.ts +353 -353
  452. package/server/shared/tests/slice-tail-page.test.ts +42 -42
  453. package/server/shared/types.ts +1228 -1228
  454. package/server/shared/utils.ts +1161 -1161
  455. package/server/tsconfig.json +40 -40
  456. package/shared/networkHosts.js +22 -22
  457. package/dist/assets/FileTree-N2IkkHlv.js +0 -127
  458. package/dist/assets/GitPanel-DZCeuFZ2.js +0 -29
  459. package/dist/assets/PluginTabContent-Bnuf-fb8.js +0 -1
  460. package/dist/assets/Shell-BfFnMZ-f.js +0 -81
  461. package/dist/assets/Shell-qxJ8_QYu.css +0 -32
  462. package/dist/assets/StandaloneShell-QmyYwNAl.js +0 -1
  463. package/dist/assets/TaskMasterPanel-fMsmvN9L.js +0 -32
  464. package/dist/assets/abnfDiagram-N423BO3Z-DjPU7HLs.js +0 -1
  465. package/dist/assets/arc-Pdq6Tvr3.js +0 -1
  466. package/dist/assets/architectureDiagram-T3A2C74G-D0JnT0Vh.js +0 -36
  467. package/dist/assets/c4Diagram-5PPSVZJV-MpqlPGFi.js +0 -10
  468. package/dist/assets/channel-Bm0ZLE4z.js +0 -1
  469. package/dist/assets/chunk-5RXB4S5H-B4UQ8snD.js +0 -231
  470. package/dist/assets/chunk-GF5L2VYU-DkEPedJP.js +0 -206
  471. package/dist/assets/classDiagram-JCYQIIEL-DqEOcNxw.js +0 -1
  472. package/dist/assets/classDiagram-v2-OCEON4UE-DqEOcNxw.js +0 -1
  473. package/dist/assets/code-xml-VK1YfShP.js +0 -6
  474. package/dist/assets/cose-bilkent-JH36ORCC-DrVTo1Nh.js +0 -1
  475. package/dist/assets/cynefinDiagram-MW4NZA55-CCxBjw9E.js +0 -62
  476. package/dist/assets/cytoscape.esm-C10L9Icf.js +0 -331
  477. package/dist/assets/dagre-VZM6K2ZE-C_GvE4lu.js +0 -4
  478. package/dist/assets/diagram-LBJQPF4R-hfKcrrxe.js +0 -24
  479. package/dist/assets/diagram-Q27KOJAE-C1semJfK.js +0 -24
  480. package/dist/assets/diagram-UB23O5K3-57hvXQXO.js +0 -41
  481. package/dist/assets/ebnfDiagram-BXEA7PRR-9KIAlAVw.js +0 -1
  482. package/dist/assets/erDiagram-JOGREHBK-B7OzJ767.js +0 -85
  483. package/dist/assets/ganttDiagram-PKOTCBZU-CbMNWWQu.js +0 -292
  484. package/dist/assets/gitGraphDiagram-DS77QQ5N-I_vZ5waO.js +0 -106
  485. package/dist/assets/graph-Cx-EqisW.js +0 -1
  486. package/dist/assets/index-DFnVoqpx.js +0 -1174
  487. package/dist/assets/index-DlSeAkw_.js +0 -11
  488. package/dist/assets/index-ZHwIojzi.js +0 -1
  489. package/dist/assets/index-t1w8KNxn.css +0 -1
  490. package/dist/assets/infoDiagram-6WML65LV-Dh20Tsbq.js +0 -2
  491. package/dist/assets/ishikawaDiagram-WSZJBQD7-DH2Sm3ko.js +0 -70
  492. package/dist/assets/journeyDiagram-NVQOT4AX-BNFQqlVu.js +0 -139
  493. package/dist/assets/map-CV9uif0g.js +0 -1
  494. package/dist/assets/mermaid.core-BYRQSshi.js +0 -312
  495. package/dist/assets/pegDiagram-VL7TDLO6-CNYpECr8.js +0 -1
  496. package/dist/assets/quadrantDiagram-CIZ2JOQS-BXVVQsa6.js +0 -7
  497. package/dist/assets/railroadDiagram-AXF67PYL-JTroIW8F.js +0 -1
  498. package/dist/assets/requirementDiagram-LRYGKXZP-C1Z_inLz.js +0 -84
  499. package/dist/assets/sankeyDiagram-W5VNT64P-DCJgKFUk.js +0 -40
  500. package/dist/assets/sequenceDiagram-SI44F4Z6-D8Wrx5Vc.js +0 -162
  501. package/dist/assets/sizeCapture-X5ZJPWSS-CXqD68vD.js +0 -1
  502. package/dist/assets/stateDiagram-OKZ733FA-CF0Ekqjo.js +0 -1
  503. package/dist/assets/stateDiagram-v2-UEYNNEHI-DARLpEFJ.js +0 -1
  504. package/dist/assets/swimlanes-SLNWSIFB-DVIax_k4.js +0 -2
  505. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CrgDgaY8.js +0 -8
  506. package/dist/assets/timeline-definition-Z64GVDOM-DSqk3ApG.js +0 -120
  507. package/dist/assets/vendor-react-whEHfigN.js +0 -59
  508. package/dist/assets/vennDiagram-T6HMQDX7-DNbTqyG5.js +0 -34
  509. package/dist/assets/xychartDiagram-ELKLHX3M-XsVZphHr.js +0 -7
  510. 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 CloudCLI 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
- };
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
+ };