@cloudcli-ai/cloudcli 1.34.0 → 1.35.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 (324) hide show
  1. package/README.de.md +9 -1
  2. package/README.ja.md +8 -0
  3. package/README.ko.md +9 -1
  4. package/README.md +16 -3
  5. package/README.ru.md +9 -1
  6. package/README.tr.md +8 -1
  7. package/README.zh-CN.md +9 -1
  8. package/README.zh-TW.md +9 -1
  9. package/dist/api-docs.html +36 -18
  10. package/dist/assets/index-CtwxqzGG.js +1407 -0
  11. package/dist/assets/index-DlvtxFyt.css +32 -0
  12. package/dist/index.html +2 -2
  13. package/dist-server/server/browser-use-mcp.js +350 -0
  14. package/dist-server/server/browser-use-mcp.js.map +1 -0
  15. package/dist-server/server/claude-sdk.js +25 -5
  16. package/dist-server/server/claude-sdk.js.map +1 -1
  17. package/dist-server/server/cli.js +14 -6
  18. package/dist-server/server/cli.js.map +1 -1
  19. package/dist-server/server/computer-use-agent.js +242 -0
  20. package/dist-server/server/computer-use-agent.js.map +1 -0
  21. package/dist-server/server/computer-use-mcp.js +519 -0
  22. package/dist-server/server/computer-use-mcp.js.map +1 -0
  23. package/dist-server/server/cursor-cli.js +27 -11
  24. package/dist-server/server/cursor-cli.js.map +1 -1
  25. package/dist-server/server/gemini-cli.js +17 -2
  26. package/dist-server/server/gemini-cli.js.map +1 -1
  27. package/dist-server/server/index.js +149 -40
  28. package/dist-server/server/index.js.map +1 -1
  29. package/dist-server/server/load-env.js +1 -1
  30. package/dist-server/server/load-env.js.map +1 -1
  31. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
  32. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
  33. package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
  34. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
  35. package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
  36. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
  37. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
  38. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
  39. package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js +55 -0
  40. package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js.map +1 -0
  41. package/dist-server/server/modules/computer-use/actions/raw-action-types.js +2 -0
  42. package/dist-server/server/modules/computer-use/actions/raw-action-types.js.map +1 -0
  43. package/dist-server/server/modules/computer-use/computer-executor.js +212 -0
  44. package/dist-server/server/modules/computer-use/computer-executor.js.map +1 -0
  45. package/dist-server/server/modules/computer-use/computer-semantics.service.js +422 -0
  46. package/dist-server/server/modules/computer-use/computer-semantics.service.js.map +1 -0
  47. package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js +126 -0
  48. package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js.map +1 -0
  49. package/dist-server/server/modules/computer-use/computer-use.routes.js +196 -0
  50. package/dist-server/server/modules/computer-use/computer-use.routes.js.map +1 -0
  51. package/dist-server/server/modules/computer-use/computer-use.service.js +759 -0
  52. package/dist-server/server/modules/computer-use/computer-use.service.js.map +1 -0
  53. package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js +126 -0
  54. package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js.map +1 -0
  55. package/dist-server/server/modules/computer-use/index.js +3 -0
  56. package/dist-server/server/modules/computer-use/index.js.map +1 -0
  57. package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js +59 -0
  58. package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js.map +1 -0
  59. package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js +5 -0
  60. package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js.map +1 -0
  61. package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js +2 -0
  62. package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js.map +1 -0
  63. package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js +5 -0
  64. package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js.map +1 -0
  65. package/dist-server/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
  66. package/dist-server/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
  67. package/dist-server/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
  68. package/dist-server/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
  69. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js +106 -0
  70. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js.map +1 -0
  71. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js +78 -0
  72. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js.map +1 -0
  73. package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js +68 -0
  74. package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js.map +1 -0
  75. package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js +16 -0
  76. package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js.map +1 -0
  77. package/dist-server/server/modules/computer-use/semantics/semantic-types.js +2 -0
  78. package/dist-server/server/modules/computer-use/semantics/semantic-types.js.map +1 -0
  79. package/dist-server/server/modules/database/index.js +1 -0
  80. package/dist-server/server/modules/database/index.js.map +1 -1
  81. package/dist-server/server/modules/database/migrations.js +23 -1
  82. package/dist-server/server/modules/database/migrations.js.map +1 -1
  83. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
  84. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
  85. package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
  86. package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
  87. package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
  88. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  89. package/dist-server/server/modules/database/schema.js +26 -0
  90. package/dist-server/server/modules/database/schema.js.map +1 -1
  91. package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
  92. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
  93. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
  94. package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
  95. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
  96. package/dist-server/server/modules/notifications/index.js +4 -0
  97. package/dist-server/server/modules/notifications/index.js.map +1 -0
  98. package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
  99. package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
  100. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
  101. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
  102. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
  103. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
  104. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
  105. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
  106. package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
  107. package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
  108. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
  109. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
  110. package/dist-server/server/modules/providers/index.js +1 -0
  111. package/dist-server/server/modules/providers/index.js.map +1 -1
  112. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
  113. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  114. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
  115. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  116. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
  117. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  118. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
  119. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  120. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
  121. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  122. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
  123. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
  124. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
  125. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  126. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
  127. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
  128. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
  129. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
  130. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
  131. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
  132. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
  133. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
  134. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
  135. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  136. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
  137. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  138. package/dist-server/server/modules/providers/provider.routes.js +123 -1
  139. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  140. package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
  141. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
  142. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
  143. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
  144. package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
  145. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  146. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
  147. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  148. package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
  149. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  150. package/dist-server/server/modules/providers/services/skills.service.js +11 -0
  151. package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
  152. package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
  153. package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
  154. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
  155. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  156. package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
  157. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  158. package/dist-server/server/modules/websocket/index.js +1 -0
  159. package/dist-server/server/modules/websocket/index.js.map +1 -1
  160. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
  161. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
  162. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
  163. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
  164. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
  165. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  166. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js +39 -0
  167. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js.map +1 -0
  168. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
  169. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  170. package/dist-server/server/modules/websocket/services/websocket-server.service.js +10 -0
  171. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
  172. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
  173. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
  174. package/dist-server/server/openai-codex.js +24 -14
  175. package/dist-server/server/openai-codex.js.map +1 -1
  176. package/dist-server/server/opencode-cli.js +21 -8
  177. package/dist-server/server/opencode-cli.js.map +1 -1
  178. package/dist-server/server/opencode-cli.test.js +19 -1
  179. package/dist-server/server/opencode-cli.test.js.map +1 -1
  180. package/dist-server/server/routes/settings.js +1 -1
  181. package/dist-server/server/routes/settings.js.map +1 -1
  182. package/dist-server/server/services/notification-orchestrator.js +1 -176
  183. package/dist-server/server/services/notification-orchestrator.js.map +1 -1
  184. package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
  185. package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
  186. package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
  187. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
  188. package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
  189. package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
  190. package/dist-server/server/shared/utils.js +73 -1
  191. package/dist-server/server/shared/utils.js.map +1 -1
  192. package/dist-server/server/voice-proxy.js +226 -0
  193. package/dist-server/server/voice-proxy.js.map +1 -0
  194. package/electron/assets/logo-macos.icns +0 -0
  195. package/electron/assets/logo-macos.png +0 -0
  196. package/electron/assets/logo-windows.ico +0 -0
  197. package/electron/cloud.js +260 -0
  198. package/electron/computerAgent.js +290 -0
  199. package/electron/desktopNotifications.js +378 -0
  200. package/electron/desktopWindow.js +781 -0
  201. package/electron/launcher/index.html +14 -0
  202. package/electron/launcher/launcher.css +801 -0
  203. package/electron/launcher/launcher.js +829 -0
  204. package/electron/localServer.js +550 -0
  205. package/electron/main.js +1060 -0
  206. package/electron/preload.cjs +63 -0
  207. package/electron/scripts/generate-macos-icon.js +62 -0
  208. package/electron/serverInstaller.js +277 -0
  209. package/electron/tabs.js +87 -0
  210. package/electron/viewHost.js +331 -0
  211. package/package.json +86 -5
  212. package/public/api-docs.html +36 -18
  213. package/scripts/build-computer-semantics.mjs +133 -0
  214. package/scripts/copy-computer-semantics-bin.mjs +24 -0
  215. package/scripts/release/build-server-bundle.js +176 -0
  216. package/scripts/release/prepare-desktop-app.js +158 -0
  217. package/server/browser-use-mcp.ts +384 -0
  218. package/server/claude-sdk.js +27 -5
  219. package/server/cli.js +15 -6
  220. package/server/computer-use-agent.ts +279 -0
  221. package/server/computer-use-mcp.ts +574 -0
  222. package/server/cursor-cli.js +27 -11
  223. package/server/gemini-cli.js +17 -2
  224. package/server/index.js +151 -47
  225. package/server/load-env.js +1 -1
  226. package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
  227. package/server/modules/browser-use/browser-use.routes.ts +96 -0
  228. package/server/modules/browser-use/browser-use.service.ts +836 -0
  229. package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
  230. package/server/modules/computer-use/actions/raw-action-dispatcher.ts +67 -0
  231. package/server/modules/computer-use/actions/raw-action-types.ts +28 -0
  232. package/server/modules/computer-use/computer-executor.ts +242 -0
  233. package/server/modules/computer-use/computer-semantics.service.ts +460 -0
  234. package/server/modules/computer-use/computer-use-mcp.routes.ts +141 -0
  235. package/server/modules/computer-use/computer-use.routes.ts +211 -0
  236. package/server/modules/computer-use/computer-use.service.ts +920 -0
  237. package/server/modules/computer-use/desktop-agent-relay.service.ts +158 -0
  238. package/server/modules/computer-use/index.ts +2 -0
  239. package/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.ts +82 -0
  240. package/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.ts +5 -0
  241. package/server/modules/computer-use/semantics/adapters/semantic-adapter.ts +23 -0
  242. package/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.ts +5 -0
  243. package/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
  244. package/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
  245. package/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
  246. package/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
  247. package/server/modules/computer-use/semantics/helpers/macos/CloudCLISemantics.swift +467 -0
  248. package/server/modules/computer-use/semantics/helpers/semantic-helper-process.ts +124 -0
  249. package/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.ts +97 -0
  250. package/server/modules/computer-use/semantics/helpers/windows/CloudCLISemantics.csproj +11 -0
  251. package/server/modules/computer-use/semantics/helpers/windows/Program.cs +534 -0
  252. package/server/modules/computer-use/semantics/semantic-session-store.ts +87 -0
  253. package/server/modules/computer-use/semantics/semantic-tool-dispatcher.ts +17 -0
  254. package/server/modules/computer-use/semantics/semantic-types.ts +58 -0
  255. package/server/modules/database/index.ts +1 -0
  256. package/server/modules/database/migrations.ts +25 -0
  257. package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
  258. package/server/modules/database/repositories/notification-preferences.ts +12 -1
  259. package/server/modules/database/repositories/sessions.db.ts +227 -23
  260. package/server/modules/database/schema.ts +27 -0
  261. package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
  262. package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
  263. package/server/modules/notifications/index.ts +13 -0
  264. package/server/modules/notifications/notifications.routes.ts +127 -0
  265. package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
  266. package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
  267. package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
  268. package/server/modules/projects/services/project-management.service.ts +0 -8
  269. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
  270. package/server/modules/providers/README.md +1 -1
  271. package/server/modules/providers/index.ts +1 -0
  272. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
  273. package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
  274. package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
  275. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
  276. package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
  277. package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
  278. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
  279. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
  280. package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
  281. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
  282. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
  283. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
  284. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
  285. package/server/modules/providers/provider.routes.ts +167 -1
  286. package/server/modules/providers/services/mcp.service.ts +26 -0
  287. package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
  288. package/server/modules/providers/services/provider-models.service.ts +1 -1
  289. package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
  290. package/server/modules/providers/services/sessions.service.ts +79 -4
  291. package/server/modules/providers/services/skills.service.ts +25 -1
  292. package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
  293. package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
  294. package/server/modules/providers/tests/skills.test.ts +212 -0
  295. package/server/modules/websocket/README.md +33 -27
  296. package/server/modules/websocket/index.ts +1 -0
  297. package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
  298. package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
  299. package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
  300. package/server/modules/websocket/services/desktop-agent-websocket.service.ts +51 -0
  301. package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
  302. package/server/modules/websocket/services/websocket-server.service.ts +12 -0
  303. package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
  304. package/server/openai-codex.js +24 -14
  305. package/server/opencode-cli.js +21 -8
  306. package/server/opencode-cli.test.js +20 -1
  307. package/server/routes/settings.js +6 -1
  308. package/server/services/notification-orchestrator.js +3 -224
  309. package/server/services/tests/notification-orchestrator.test.js +80 -0
  310. package/server/shared/interfaces.ts +15 -0
  311. package/server/shared/tests/slice-tail-page.test.ts +42 -0
  312. package/server/shared/types.ts +84 -1
  313. package/server/shared/utils.ts +95 -1
  314. package/server/voice-proxy.js +224 -0
  315. package/dist/assets/index-73Xw3i9v.css +0 -32
  316. package/dist/assets/index-BIHaviaA.js +0 -1361
  317. package/dist/modelConstants.js +0 -854
  318. package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
  319. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
  320. package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
  321. package/public/modelConstants.js +0 -854
  322. /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
  323. /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
  324. /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
@@ -0,0 +1,109 @@
1
+ import type { WebSocket } from 'ws';
2
+
3
+ import {
4
+ registerDesktopNotificationClient,
5
+ unregisterDesktopNotificationClient,
6
+ } from '@/modules/notifications/services/desktop-notification-clients.service.js';
7
+ import type { AuthenticatedWebSocketRequest } from '@/shared/types.js';
8
+ import { parseIncomingJsonObject } from '@/shared/utils.js';
9
+
10
+ type DesktopNotificationRegisterMessage = {
11
+ type?: unknown;
12
+ kind?: unknown;
13
+ deviceId?: unknown;
14
+ label?: unknown;
15
+ platform?: unknown;
16
+ appVersion?: unknown;
17
+ };
18
+
19
+ function readRequestUserId(request: AuthenticatedWebSocketRequest): number | null {
20
+ const user = request.user;
21
+ const rawUserId = typeof user?.id === 'number' || typeof user?.id === 'string'
22
+ ? user.id
23
+ : typeof user?.userId === 'number' || typeof user?.userId === 'string'
24
+ ? user.userId
25
+ : null;
26
+ const numericUserId = Number(rawUserId);
27
+ return Number.isInteger(numericUserId) && numericUserId > 0 ? numericUserId : null;
28
+ }
29
+
30
+ function readOptionalString(value: unknown): string | null {
31
+ if (typeof value !== 'string') return null;
32
+ const normalized = value.trim();
33
+ return normalized || null;
34
+ }
35
+
36
+ function sendJson(ws: WebSocket, payload: unknown): void {
37
+ if (ws.readyState === ws.OPEN) {
38
+ ws.send(JSON.stringify(payload));
39
+ }
40
+ }
41
+
42
+ export function handleDesktopNotificationsConnection(
43
+ ws: WebSocket,
44
+ request: AuthenticatedWebSocketRequest
45
+ ): void {
46
+ const userId = readRequestUserId(request);
47
+ if (!userId) {
48
+ ws.close(1008, 'Missing authenticated user');
49
+ return;
50
+ }
51
+
52
+ let registered = false;
53
+
54
+ ws.on('message', (rawMessage) => {
55
+ const data = parseIncomingJsonObject(rawMessage) as DesktopNotificationRegisterMessage | null;
56
+ if (!data) {
57
+ return;
58
+ }
59
+
60
+ const type = typeof data.type === 'string' ? data.type : typeof data.kind === 'string' ? data.kind : '';
61
+ if (type === 'notification_ack') {
62
+ return;
63
+ }
64
+
65
+ if (type !== 'register' || registered) {
66
+ return;
67
+ }
68
+
69
+ const deviceId = readOptionalString(data.deviceId);
70
+ if (!deviceId) {
71
+ sendJson(ws, {
72
+ type: 'error',
73
+ code: 'DEVICE_ID_REQUIRED',
74
+ message: 'Desktop notification registration requires deviceId.',
75
+ });
76
+ ws.close(1008, 'Missing deviceId');
77
+ return;
78
+ }
79
+
80
+ const device = registerDesktopNotificationClient({
81
+ userId,
82
+ deviceId,
83
+ label: readOptionalString(data.label),
84
+ platform: readOptionalString(data.platform),
85
+ appVersion: readOptionalString(data.appVersion),
86
+ ws,
87
+ });
88
+
89
+ if (!device) {
90
+ ws.close(1011, 'Registration failed');
91
+ return;
92
+ }
93
+
94
+ registered = true;
95
+ sendJson(ws, {
96
+ type: 'registered',
97
+ deviceId: device.endpoint_id,
98
+ enabled: Boolean(device.enabled),
99
+ });
100
+ });
101
+
102
+ ws.on('close', () => {
103
+ unregisterDesktopNotificationClient(ws);
104
+ });
105
+
106
+ ws.on('error', () => {
107
+ unregisterDesktopNotificationClient(ws);
108
+ });
109
+ }
@@ -30,10 +30,6 @@ type ProjectApiView = {
30
30
  isArchived: boolean;
31
31
  isStarred: boolean;
32
32
  sessions: [];
33
- cursorSessions: [];
34
- codexSessions: [];
35
- geminiSessions: [];
36
- opencodeSessions: [];
37
33
  sessionMeta: {
38
34
  hasMore: false;
39
35
  total: 0;
@@ -82,10 +78,6 @@ function mapProjectRowToApiView(projectRow: ProjectRepositoryRow): ProjectApiVie
82
78
  isArchived: Boolean(projectRow.isArchived),
83
79
  isStarred: Boolean(projectRow.isStarred),
84
80
  sessions: [],
85
- cursorSessions: [],
86
- codexSessions: [],
87
- geminiSessions: [],
88
- opencodeSessions: [],
89
81
  sessionMeta: {
90
82
  hasMore: false,
91
83
  total: 0,
@@ -9,13 +9,12 @@ import { AppError } from '@/shared/utils.js';
9
9
 
10
10
  type SessionSummary = {
11
11
  id: string;
12
+ provider: string;
12
13
  summary: string;
13
14
  messageCount: number;
14
15
  lastActivity: string;
15
16
  };
16
17
 
17
- type SessionsByProvider = Record<'claude' | 'cursor' | 'codex' | 'gemini' | 'opencode', SessionSummary[]>;
18
-
19
18
  type SessionRepositoryRow = {
20
19
  provider: string;
21
20
  session_id: string;
@@ -31,10 +30,6 @@ export type ProjectListItem = {
31
30
  fullPath: string;
32
31
  isStarred: boolean;
33
32
  sessions: SessionSummary[];
34
- cursorSessions: SessionSummary[];
35
- codexSessions: SessionSummary[];
36
- geminiSessions: SessionSummary[];
37
- opencodeSessions: SessionSummary[];
38
33
  sessionMeta: {
39
34
  hasMore: boolean;
40
35
  total: number;
@@ -64,7 +59,7 @@ type SessionPaginationOptions = {
64
59
  };
65
60
 
66
61
  type ProjectSessionsPageResult = {
67
- sessionsByProvider: SessionsByProvider;
62
+ sessions: SessionSummary[];
68
63
  total: number;
69
64
  hasMore: boolean;
70
65
  };
@@ -72,10 +67,6 @@ type ProjectSessionsPageResult = {
72
67
  export type ProjectSessionsPageApiView = {
73
68
  projectId: string;
74
69
  sessions: SessionSummary[];
75
- cursorSessions: SessionSummary[];
76
- codexSessions: SessionSummary[];
77
- geminiSessions: SessionSummary[];
78
- opencodeSessions: SessionSummary[];
79
70
  sessionMeta: {
80
71
  hasMore: boolean;
81
72
  total: number;
@@ -129,39 +120,18 @@ function normalizeSessionPagination(options: SessionPaginationOptions = {}): { l
129
120
  function mapSessionRowToSummary(row: SessionRepositoryRow): SessionSummary {
130
121
  return {
131
122
  id: row.session_id,
123
+ provider: row.provider,
132
124
  summary: row.custom_name || '',
133
125
  messageCount: 0,
134
126
  lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
135
127
  };
136
128
  }
137
129
 
138
- function bucketSessionRowsByProvider(rows: SessionRepositoryRow[]): SessionsByProvider {
139
- const byProvider: SessionsByProvider = {
140
- claude: [],
141
- cursor: [],
142
- codex: [],
143
- gemini: [],
144
- opencode: [],
145
- };
146
-
147
- for (const row of rows) {
148
- const provider = row.provider as keyof SessionsByProvider;
149
- const bucket = byProvider[provider];
150
- if (!bucket) {
151
- continue;
152
- }
153
-
154
- bucket.push(mapSessionRowToSummary(row));
155
- }
156
-
157
- return byProvider;
158
- }
159
-
160
130
  function readProjectSessionsIncludingArchived(projectPath: string): ProjectSessionsPageResult {
161
131
  const rows = sessionsDb.getSessionsByProjectPathIncludingArchived(projectPath) as SessionRepositoryRow[];
162
132
 
163
133
  return {
164
- sessionsByProvider: bucketSessionRowsByProvider(rows),
134
+ sessions: rows.map(mapSessionRowToSummary),
165
135
  total: rows.length,
166
136
  hasMore: false,
167
137
  };
@@ -183,16 +153,17 @@ function readProjectSessionsPageByPath(
183
153
  const total = sessionsDb.countSessionsByProjectPath(projectPath);
184
154
 
185
155
  return {
186
- sessionsByProvider: bucketSessionRowsByProvider(rows),
156
+ sessions: rows.map(mapSessionRowToSummary),
187
157
  total,
188
158
  hasMore: pagination.offset + rows.length < total,
189
159
  };
190
160
  }
191
161
 
192
- // Broadcast progress to all connected WebSocket clients
162
+ // Broadcast progress to all connected WebSocket clients.
163
+ // Uses the unified `kind` envelope like every other websocket frame.
193
164
  function broadcastProgress(progress: ProgressUpdate) {
194
165
  const message = JSON.stringify({
195
- type: 'loading_progress',
166
+ kind: 'loading_progress',
196
167
  ...progress,
197
168
  });
198
169
 
@@ -204,7 +175,7 @@ function broadcastProgress(progress: ProgressUpdate) {
204
175
  }
205
176
 
206
177
  /**
207
- * Reads all projects from DB and returns provider-bucketed session summaries.
178
+ * Reads all projects from DB and returns normalized session summaries.
208
179
  */
209
180
  export async function getProjectsWithSessions(
210
181
  options: GetProjectsWithSessionsOptions = {}
@@ -252,11 +223,7 @@ export async function getProjectsWithSessions(
252
223
  displayName,
253
224
  fullPath: projectPath,
254
225
  isStarred: Boolean(row.isStarred),
255
- sessions: sessionsPage.sessionsByProvider.claude,
256
- cursorSessions: sessionsPage.sessionsByProvider.cursor,
257
- codexSessions: sessionsPage.sessionsByProvider.codex,
258
- geminiSessions: sessionsPage.sessionsByProvider.gemini,
259
- opencodeSessions: sessionsPage.sessionsByProvider.opencode,
226
+ sessions: sessionsPage.sessions,
260
227
  sessionMeta: {
261
228
  hasMore: sessionsPage.hasMore,
262
229
  total: sessionsPage.total,
@@ -309,11 +276,7 @@ export async function getArchivedProjectsWithSessions(
309
276
  fullPath: row.project_path,
310
277
  isStarred: Boolean(row.isStarred),
311
278
  isArchived: true,
312
- sessions: sessionsPage.sessionsByProvider.claude,
313
- cursorSessions: sessionsPage.sessionsByProvider.cursor,
314
- codexSessions: sessionsPage.sessionsByProvider.codex,
315
- geminiSessions: sessionsPage.sessionsByProvider.gemini,
316
- opencodeSessions: sessionsPage.sessionsByProvider.opencode,
279
+ sessions: sessionsPage.sessions,
317
280
  sessionMeta: {
318
281
  hasMore: sessionsPage.hasMore,
319
282
  total: sessionsPage.total,
@@ -342,11 +305,7 @@ export async function getProjectSessionsPage(
342
305
  const sessionsPage = readProjectSessionsPageByPath(projectRow.project_path, options);
343
306
  return {
344
307
  projectId: projectRow.project_id,
345
- sessions: sessionsPage.sessionsByProvider.claude,
346
- cursorSessions: sessionsPage.sessionsByProvider.cursor,
347
- codexSessions: sessionsPage.sessionsByProvider.codex,
348
- geminiSessions: sessionsPage.sessionsByProvider.gemini,
349
- opencodeSessions: sessionsPage.sessionsByProvider.opencode,
308
+ sessions: sessionsPage.sessions,
350
309
  sessionMeta: {
351
310
  hasMore: sessionsPage.hasMore,
352
311
  total: sessionsPage.total,
@@ -83,7 +83,7 @@ The existing provider folders are `claude`, `codex`, `cursor`, `gemini`, and
83
83
  - Update `server/modules/providers/provider.routes.ts`.
84
84
  - Update `server/routes/agent.js` if the provider is launchable from the agent runtime.
85
85
  - Update `server/index.js` if the provider needs runtime boot or shutdown wiring.
86
- - Update `public/modelConstants.js` if the provider appears in README or public API docs.
86
+ - Update the `PROVIDER_ORDER` list in `public/api-docs.html` if the provider should appear in the public API docs.
87
87
  - Update `src/components/chat/hooks/useChatProviderState.ts` and
88
88
  `src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx` if
89
89
  the provider should be selectable in chat.
@@ -1,5 +1,6 @@
1
1
  export { sessionSynchronizerService } from './services/session-synchronizer.service.js';
2
2
  export { providerSkillsService } from './services/skills.service.js';
3
+ export { providerMcpService } from './services/mcp.service.js';
3
4
 
4
5
  export { initializeSessionsWatcher } from './services/sessions-watcher.service.js';
5
6
  export { closeSessionsWatcher } from './services/sessions-watcher.service.js';
@@ -25,6 +25,21 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
25
25
  private readonly provider = 'claude' as const;
26
26
  private readonly claudeHome = path.join(os.homedir(), '.claude');
27
27
 
28
+ /**
29
+ * Returns true when a JSONL file is a subagent transcript rather than a
30
+ * top-level session.
31
+ *
32
+ * Claude stores subagent transcripts under a `subagents/` directory, e.g.
33
+ * `~/.claude/projects/<encoded-cwd>/<session-id>/subagents/agent-<id>.jsonl`.
34
+ * Those files repeat the parent session's `sessionId`, so indexing them as
35
+ * standalone sessions overwrites the parent row's `jsonl_path` and corrupts
36
+ * the main session record. The recursive scan in `synchronize()` reaches
37
+ * them, so both entry points must skip them.
38
+ */
39
+ private isSubagentTranscript(filePath: string): boolean {
40
+ return path.normalize(filePath).split(path.sep).includes('subagents');
41
+ }
42
+
28
43
  /**
29
44
  * Scans ~/.claude/projects and upserts discovered sessions into DB.
30
45
  */
@@ -38,6 +53,10 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
38
53
 
39
54
  let processed = 0;
40
55
  for (const filePath of files) {
56
+ if (this.isSubagentTranscript(filePath)) {
57
+ continue;
58
+ }
59
+
41
60
  const parsed = await this.processSessionFile(filePath, nameMap);
42
61
  if (!parsed) {
43
62
  continue;
@@ -66,6 +85,9 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
66
85
  if (!filePath.endsWith('.jsonl')) {
67
86
  return null;
68
87
  }
88
+ if (this.isSubagentTranscript(filePath)) {
89
+ return null;
90
+ }
69
91
 
70
92
  const nameMap = await buildLookupMap(path.join(this.claudeHome, 'history.jsonl'), 'sessionId', 'display');
71
93
  const parsed = await this.processSessionFile(filePath, nameMap);
@@ -111,7 +133,10 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
111
133
  return null;
112
134
  }
113
135
 
114
- const existingSession = sessionsDb.getSessionById(parsed.sessionId);
136
+ // App-created sessions are keyed by an app id, so disk-discovered provider
137
+ // ids must be resolved through the provider-id mapping first.
138
+ const existingSession = sessionsDb.getSessionByProviderSessionId(parsed.sessionId)
139
+ ?? sessionsDb.getSessionById(parsed.sessionId);
115
140
  const existingSessionName = existingSession?.custom_name;
116
141
  if (existingSessionName && existingSessionName !== 'Untitled Claude Session') {
117
142
  return {
@@ -5,7 +5,7 @@ import readline from 'node:readline';
5
5
 
6
6
  import type { IProviderSessions } from '@/shared/interfaces.js';
7
7
  import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
8
- import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
8
+ import { createNormalizedMessage, generateMessageId, readObjectRecord, sliceTailPage } from '@/shared/utils.js';
9
9
  import { sessionsDb } from '@/modules/database/index.js';
10
10
 
11
11
  const PROVIDER = 'claude';
@@ -103,10 +103,13 @@ async function parseAgentTools(filePath: string): Promise<AnyRecord[]> {
103
103
 
104
104
  async function getSessionMessages(
105
105
  sessionId: string,
106
+ providerSessionId: string,
106
107
  limit: number | null,
107
108
  offset: number,
108
109
  ): Promise<ClaudeHistoryMessagesResult> {
109
110
  try {
111
+ // The DB row is keyed by the app-facing session id, while the JSONL rows
112
+ // on disk carry the provider-native id — both ids are needed here.
110
113
  const jsonLPath = sessionsDb.getSessionById(sessionId)?.jsonl_path;
111
114
 
112
115
  if (!jsonLPath) {
@@ -133,7 +136,7 @@ async function getSessionMessages(
133
136
 
134
137
  try {
135
138
  const entry = JSON.parse(line) as AnyRecord;
136
- if (entry.sessionId === sessionId) {
139
+ if (entry.sessionId === providerSessionId) {
137
140
  messages.push(entry);
138
141
  }
139
142
  } catch {
@@ -553,12 +556,13 @@ export class ClaudeSessionsProvider implements IProviderSessions {
553
556
  options: FetchHistoryOptions = {},
554
557
  ): Promise<FetchHistoryResult> {
555
558
  const { limit = null, offset = 0 } = options;
559
+ const providerSessionId = options.providerSessionId ?? sessionId;
556
560
 
557
561
  let result: ClaudeHistoryResult;
558
562
  try {
559
563
  // Load full history first so `total` reflects frontend-normalized messages,
560
564
  // not raw JSONL records.
561
- result = await getSessionMessages(sessionId, null, 0);
565
+ result = await getSessionMessages(sessionId, providerSessionId, null, 0);
562
566
  } catch (error) {
563
567
  const message = error instanceof Error ? error.message : String(error);
564
568
  console.warn(`[ClaudeProvider] Failed to load session ${sessionId}:`, message);
@@ -606,7 +610,6 @@ export class ClaudeSessionsProvider implements IProviderSessions {
606
610
  }
607
611
  }
608
612
 
609
- const totalNormalized = normalized.length;
610
613
  let total = 0;
611
614
  for (const msg of normalized) {
612
615
  if (msg.kind !== 'tool_result') {
@@ -615,18 +618,10 @@ export class ClaudeSessionsProvider implements IProviderSessions {
615
618
  }
616
619
  const normalizedOffset = Math.max(0, offset);
617
620
  const normalizedLimit = limit === null ? null : Math.max(0, limit);
618
- const messages = normalizedLimit === null
619
- ? normalized
620
- : normalized.slice(
621
- Math.max(0, totalNormalized - normalizedOffset - normalizedLimit),
622
- Math.max(0, totalNormalized - normalizedOffset),
623
- );
624
- const hasMore = normalizedLimit === null
625
- ? false
626
- : Math.max(0, totalNormalized - normalizedOffset - normalizedLimit) > 0;
621
+ const { page, hasMore } = sliceTailPage(normalized, normalizedLimit, normalizedOffset);
627
622
 
628
623
  return {
629
- messages,
624
+ messages: page,
630
625
  total,
631
626
  hasMore,
632
627
  offset: normalizedOffset,
@@ -99,6 +99,14 @@ export class ClaudeSkillsProvider extends SkillsProvider {
99
99
  ];
100
100
  }
101
101
 
102
+ protected async getGlobalSkillSource(): Promise<ProviderSkillSource> {
103
+ return {
104
+ scope: 'user',
105
+ rootDir: path.join(getClaudeHomePath(), 'skills'),
106
+ commandPrefix: '/',
107
+ };
108
+ }
109
+
102
110
  private async listPluginSkills(claudeHomePath: string): Promise<ProviderSkill[]> {
103
111
  const settings = await readJsonConfig(path.join(claudeHomePath, 'settings.json'));
104
112
  const enabledPlugins = readObjectRecord(settings.enabledPlugins);
@@ -43,11 +43,12 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
43
43
  continue;
44
44
  }
45
45
 
46
- const existingSession = sessionsDb.getSessionById(parsed.sessionId);
46
+ const existingSession = sessionsDb.getSessionByProviderSessionId(parsed.sessionId)
47
+ ?? sessionsDb.getSessionById(parsed.sessionId);
47
48
  if (existingSession) {
48
49
  // If session name is untitled and we now have a name, update it
49
50
  if (existingSession.custom_name === 'Untitled Codex Session' && parsed.sessionName && parsed.sessionName !== 'Untitled Codex Session') {
50
- sessionsDb.updateSessionCustomName(parsed.sessionId, parsed.sessionName);
51
+ sessionsDb.updateSessionCustomName(existingSession.session_id, parsed.sessionName);
51
52
  }
52
53
  }
53
54
 
@@ -120,7 +121,10 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
120
121
  return null;
121
122
  }
122
123
 
123
- const existingSession = sessionsDb.getSessionById(parsed.sessionId);
124
+ // App-created sessions are keyed by an app id, so disk-discovered provider
125
+ // ids must be resolved through the provider-id mapping first.
126
+ const existingSession = sessionsDb.getSessionByProviderSessionId(parsed.sessionId)
127
+ ?? sessionsDb.getSessionById(parsed.sessionId);
124
128
  const existingSessionName = existingSession?.custom_name;
125
129
  if (existingSessionName && existingSessionName !== 'Untitled Codex Session') {
126
130
  return {
@@ -4,7 +4,7 @@ import readline from 'node:readline';
4
4
  import { sessionsDb } from '@/modules/database/index.js';
5
5
  import type { IProviderSessions } from '@/shared/interfaces.js';
6
6
  import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
7
- import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
7
+ import { createNormalizedMessage, generateMessageId, readObjectRecord, sliceTailPage } from '@/shared/utils.js';
8
8
 
9
9
  const PROVIDER = 'codex';
10
10
 
@@ -552,7 +552,6 @@ export class CodexSessionsProvider implements IProviderSessions {
552
552
  }
553
553
  }
554
554
 
555
- const totalNormalized = normalized.length;
556
555
  let total = 0;
557
556
  for (const msg of normalized) {
558
557
  if (msg.kind !== 'tool_result') {
@@ -561,18 +560,10 @@ export class CodexSessionsProvider implements IProviderSessions {
561
560
  }
562
561
  const normalizedOffset = Math.max(0, offset);
563
562
  const normalizedLimit = limit === null ? null : Math.max(0, limit);
564
- const messages = normalizedLimit === null
565
- ? normalized
566
- : normalized.slice(
567
- Math.max(0, totalNormalized - normalizedOffset - normalizedLimit),
568
- Math.max(0, totalNormalized - normalizedOffset),
569
- );
570
- const hasMore = normalizedLimit === null
571
- ? false
572
- : Math.max(0, totalNormalized - normalizedOffset - normalizedLimit) > 0;
563
+ const { page, hasMore } = sliceTailPage(normalized, normalizedLimit, normalizedOffset);
573
564
 
574
565
  return {
575
- messages,
566
+ messages: page,
576
567
  total,
577
568
  hasMore,
578
569
  offset: normalizedOffset,
@@ -57,4 +57,12 @@ export class CodexSkillsProvider extends SkillsProvider {
57
57
 
58
58
  return sources;
59
59
  }
60
+
61
+ protected async getGlobalSkillSource(): Promise<ProviderSkillSource> {
62
+ return {
63
+ scope: 'user',
64
+ rootDir: path.join(os.homedir(), '.agents', 'skills'),
65
+ commandPrefix: '$',
66
+ };
67
+ }
60
68
  }
@@ -9,6 +9,7 @@ import {
9
9
  generateMessageId,
10
10
  readObjectRecord,
11
11
  sanitizeLeafDirectoryName,
12
+ sliceTailPage,
12
13
  } from '@/shared/utils.js';
13
14
 
14
15
  const PROVIDER = 'cursor';
@@ -363,42 +364,32 @@ export class CursorSessionsProvider implements IProviderSessions {
363
364
 
364
365
  /**
365
366
  * Fetches and paginates Cursor session history from its project-scoped store.db.
367
+ *
368
+ * Pagination follows the shared tail contract (`sliceTailPage`): offset 0 is
369
+ * the most recent page, matching every other provider.
366
370
  */
367
371
  async fetchHistory(
368
372
  sessionId: string,
369
373
  options: FetchHistoryOptions = {},
370
374
  ): Promise<FetchHistoryResult> {
371
375
  const { projectPath = '', limit = null, offset = 0 } = options;
376
+ // The store.db folder on disk is named after the provider-native id, not
377
+ // the app-facing session id this method is addressed with.
378
+ const providerSessionId = options.providerSessionId ?? sessionId;
372
379
 
373
380
  try {
374
- const blobs = await this.loadCursorBlobs(sessionId, projectPath);
381
+ const blobs = await this.loadCursorBlobs(providerSessionId, projectPath);
375
382
  const allNormalized = this.normalizeCursorBlobs(blobs, sessionId);
376
383
  const renderableMessages = allNormalized.filter((msg) => msg.kind !== 'tool_result');
377
384
  const total = renderableMessages.length;
378
-
379
- if (limit !== null) {
380
- const start = offset;
381
- const page = limit === 0
382
- ? []
383
- : renderableMessages.slice(start, start + limit);
384
- const hasMore = limit === 0
385
- ? start < total
386
- : start + limit < total;
387
- return {
388
- messages: page,
389
- total,
390
- hasMore,
391
- offset,
392
- limit,
393
- };
394
- }
385
+ const { page, hasMore } = sliceTailPage(renderableMessages, limit, offset);
395
386
 
396
387
  return {
397
- messages: renderableMessages,
388
+ messages: page,
398
389
  total,
399
- hasMore: false,
400
- offset: 0,
401
- limit: null,
390
+ hasMore,
391
+ offset,
392
+ limit,
402
393
  };
403
394
  } catch (error) {
404
395
  const message = error instanceof Error ? error.message : String(error);
@@ -28,4 +28,12 @@ export class CursorSkillsProvider extends SkillsProvider {
28
28
  },
29
29
  ];
30
30
  }
31
+
32
+ protected async getGlobalSkillSource(): Promise<ProviderSkillSource> {
33
+ return {
34
+ scope: 'user',
35
+ rootDir: path.join(os.homedir(), '.cursor', 'skills'),
36
+ commandPrefix: '/',
37
+ };
38
+ }
31
39
  }
@@ -12,17 +12,14 @@ import {
12
12
 
13
13
  export const GEMINI_FALLBACK_MODELS: ProviderModelsDefinition = {
14
14
  OPTIONS: [
15
- { value: 'gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro Preview' },
16
- { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
17
15
  { value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' },
16
+ { value: 'gemini-3.1-flash-lite-preview', label: 'Gemini 3.1 Flash Lite Preview' },
18
17
  { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
19
- { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
20
- { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash Lite' },
21
- { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' },
22
- { value: 'gemini-2.0-pro-exp', label: 'Gemini 2.0 Pro Experimental' },
23
- { value: 'gemini-2.0-flash-thinking-exp', label: 'Gemini 2.0 Flash Thinking' },
18
+ { value: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite' },
19
+ { value: 'gemma-4-31b-it', label: 'Gemma 4 31B IT' },
20
+ { value: 'gemma-4-26b-a4b-it', label: 'Gemma 4 26B A4B IT' },
24
21
  ],
25
- DEFAULT: 'gemini-3.1-pro-preview',
22
+ DEFAULT: 'gemini-3-flash-preview',
26
23
  };
27
24
 
28
25
  export class GeminiProviderModels implements IProviderModels {
@@ -5,7 +5,7 @@ import readline from 'node:readline';
5
5
  import { sessionsDb } from '@/modules/database/index.js';
6
6
  import type { IProviderSessions } from '@/shared/interfaces.js';
7
7
  import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
8
- import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
8
+ import { createNormalizedMessage, generateMessageId, readObjectRecord, sliceTailPage } from '@/shared/utils.js';
9
9
 
10
10
  const PROVIDER = 'gemini';
11
11
 
@@ -518,9 +518,9 @@ export class GeminiSessionsProvider implements IProviderSessions {
518
518
 
519
519
  const start = Math.max(0, offset);
520
520
  const pageLimit = limit === null ? null : Math.max(0, limit);
521
- const messages = pageLimit === null
522
- ? normalized.slice(start)
523
- : normalized.slice(start, start + pageLimit);
521
+ // Tail pagination via the shared contract: offset 0 returns the most
522
+ // recent page, matching every other provider.
523
+ const { page, hasMore } = sliceTailPage(normalized, pageLimit, start);
524
524
  let total = 0;
525
525
  for (const msg of normalized) {
526
526
  if (msg.kind !== 'tool_result') {
@@ -529,9 +529,9 @@ export class GeminiSessionsProvider implements IProviderSessions {
529
529
  }
530
530
 
531
531
  return {
532
- messages,
532
+ messages: page,
533
533
  total,
534
- hasMore: pageLimit === null ? false : start + pageLimit < normalized.length,
534
+ hasMore,
535
535
  offset: start,
536
536
  limit: pageLimit,
537
537
  tokenUsage: result.tokenUsage,