@cloudcli-ai/cloudcli 1.33.3 → 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 (329) 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 +26 -6
  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-models.provider.js +5 -0
  113. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  114. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
  115. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  116. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
  117. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  118. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
  119. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  120. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
  121. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  122. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
  123. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  124. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
  125. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
  126. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
  127. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  128. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
  129. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
  130. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
  131. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
  132. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
  133. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
  134. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
  135. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
  136. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
  137. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  138. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
  139. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  140. package/dist-server/server/modules/providers/provider.routes.js +123 -1
  141. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  142. package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
  143. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
  144. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
  145. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
  146. package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
  147. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  148. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
  149. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  150. package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
  151. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  152. package/dist-server/server/modules/providers/services/skills.service.js +11 -0
  153. package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
  154. package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
  155. package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
  156. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
  157. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  158. package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
  159. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  160. package/dist-server/server/modules/websocket/index.js +1 -0
  161. package/dist-server/server/modules/websocket/index.js.map +1 -1
  162. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
  163. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
  164. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
  165. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
  166. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
  167. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  168. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js +39 -0
  169. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js.map +1 -0
  170. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
  171. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  172. package/dist-server/server/modules/websocket/services/websocket-server.service.js +10 -0
  173. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
  174. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
  175. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
  176. package/dist-server/server/openai-codex.js +24 -14
  177. package/dist-server/server/openai-codex.js.map +1 -1
  178. package/dist-server/server/opencode-cli.js +21 -8
  179. package/dist-server/server/opencode-cli.js.map +1 -1
  180. package/dist-server/server/opencode-cli.test.js +19 -1
  181. package/dist-server/server/opencode-cli.test.js.map +1 -1
  182. package/dist-server/server/routes/agent.js +1 -1
  183. package/dist-server/server/routes/settings.js +1 -1
  184. package/dist-server/server/routes/settings.js.map +1 -1
  185. package/dist-server/server/services/notification-orchestrator.js +1 -176
  186. package/dist-server/server/services/notification-orchestrator.js.map +1 -1
  187. package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
  188. package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
  189. package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
  190. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
  191. package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
  192. package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
  193. package/dist-server/server/shared/utils.js +73 -1
  194. package/dist-server/server/shared/utils.js.map +1 -1
  195. package/dist-server/server/voice-proxy.js +226 -0
  196. package/dist-server/server/voice-proxy.js.map +1 -0
  197. package/electron/assets/logo-macos.icns +0 -0
  198. package/electron/assets/logo-macos.png +0 -0
  199. package/electron/assets/logo-windows.ico +0 -0
  200. package/electron/cloud.js +260 -0
  201. package/electron/computerAgent.js +290 -0
  202. package/electron/desktopNotifications.js +378 -0
  203. package/electron/desktopWindow.js +781 -0
  204. package/electron/launcher/index.html +14 -0
  205. package/electron/launcher/launcher.css +801 -0
  206. package/electron/launcher/launcher.js +829 -0
  207. package/electron/localServer.js +550 -0
  208. package/electron/main.js +1060 -0
  209. package/electron/preload.cjs +63 -0
  210. package/electron/scripts/generate-macos-icon.js +62 -0
  211. package/electron/serverInstaller.js +277 -0
  212. package/electron/tabs.js +87 -0
  213. package/electron/viewHost.js +331 -0
  214. package/package.json +86 -5
  215. package/public/api-docs.html +36 -18
  216. package/scripts/build-computer-semantics.mjs +133 -0
  217. package/scripts/copy-computer-semantics-bin.mjs +24 -0
  218. package/scripts/release/build-server-bundle.js +176 -0
  219. package/scripts/release/prepare-desktop-app.js +158 -0
  220. package/server/browser-use-mcp.ts +384 -0
  221. package/server/claude-sdk.js +28 -6
  222. package/server/cli.js +15 -6
  223. package/server/computer-use-agent.ts +279 -0
  224. package/server/computer-use-mcp.ts +574 -0
  225. package/server/cursor-cli.js +27 -11
  226. package/server/gemini-cli.js +17 -2
  227. package/server/index.js +151 -47
  228. package/server/load-env.js +1 -1
  229. package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
  230. package/server/modules/browser-use/browser-use.routes.ts +96 -0
  231. package/server/modules/browser-use/browser-use.service.ts +836 -0
  232. package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
  233. package/server/modules/computer-use/actions/raw-action-dispatcher.ts +67 -0
  234. package/server/modules/computer-use/actions/raw-action-types.ts +28 -0
  235. package/server/modules/computer-use/computer-executor.ts +242 -0
  236. package/server/modules/computer-use/computer-semantics.service.ts +460 -0
  237. package/server/modules/computer-use/computer-use-mcp.routes.ts +141 -0
  238. package/server/modules/computer-use/computer-use.routes.ts +211 -0
  239. package/server/modules/computer-use/computer-use.service.ts +920 -0
  240. package/server/modules/computer-use/desktop-agent-relay.service.ts +158 -0
  241. package/server/modules/computer-use/index.ts +2 -0
  242. package/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.ts +82 -0
  243. package/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.ts +5 -0
  244. package/server/modules/computer-use/semantics/adapters/semantic-adapter.ts +23 -0
  245. package/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.ts +5 -0
  246. package/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
  247. package/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
  248. package/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
  249. package/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
  250. package/server/modules/computer-use/semantics/helpers/macos/CloudCLISemantics.swift +467 -0
  251. package/server/modules/computer-use/semantics/helpers/semantic-helper-process.ts +124 -0
  252. package/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.ts +97 -0
  253. package/server/modules/computer-use/semantics/helpers/windows/CloudCLISemantics.csproj +11 -0
  254. package/server/modules/computer-use/semantics/helpers/windows/Program.cs +534 -0
  255. package/server/modules/computer-use/semantics/semantic-session-store.ts +87 -0
  256. package/server/modules/computer-use/semantics/semantic-tool-dispatcher.ts +17 -0
  257. package/server/modules/computer-use/semantics/semantic-types.ts +58 -0
  258. package/server/modules/database/index.ts +1 -0
  259. package/server/modules/database/migrations.ts +25 -0
  260. package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
  261. package/server/modules/database/repositories/notification-preferences.ts +12 -1
  262. package/server/modules/database/repositories/sessions.db.ts +227 -23
  263. package/server/modules/database/schema.ts +27 -0
  264. package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
  265. package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
  266. package/server/modules/notifications/index.ts +13 -0
  267. package/server/modules/notifications/notifications.routes.ts +127 -0
  268. package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
  269. package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
  270. package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
  271. package/server/modules/projects/services/project-management.service.ts +0 -8
  272. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
  273. package/server/modules/providers/README.md +1 -1
  274. package/server/modules/providers/index.ts +1 -0
  275. package/server/modules/providers/list/claude/claude-models.provider.ts +5 -0
  276. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
  277. package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
  278. package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
  279. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
  280. package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
  281. package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
  282. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
  283. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
  284. package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
  285. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
  286. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
  287. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
  288. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
  289. package/server/modules/providers/provider.routes.ts +167 -1
  290. package/server/modules/providers/services/mcp.service.ts +26 -0
  291. package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
  292. package/server/modules/providers/services/provider-models.service.ts +1 -1
  293. package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
  294. package/server/modules/providers/services/sessions.service.ts +79 -4
  295. package/server/modules/providers/services/skills.service.ts +25 -1
  296. package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
  297. package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
  298. package/server/modules/providers/tests/skills.test.ts +212 -0
  299. package/server/modules/websocket/README.md +33 -27
  300. package/server/modules/websocket/index.ts +1 -0
  301. package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
  302. package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
  303. package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
  304. package/server/modules/websocket/services/desktop-agent-websocket.service.ts +51 -0
  305. package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
  306. package/server/modules/websocket/services/websocket-server.service.ts +12 -0
  307. package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
  308. package/server/openai-codex.js +24 -14
  309. package/server/opencode-cli.js +21 -8
  310. package/server/opencode-cli.test.js +20 -1
  311. package/server/routes/agent.js +1 -1
  312. package/server/routes/settings.js +6 -1
  313. package/server/services/notification-orchestrator.js +3 -224
  314. package/server/services/tests/notification-orchestrator.test.js +80 -0
  315. package/server/shared/interfaces.ts +15 -0
  316. package/server/shared/tests/slice-tail-page.test.ts +42 -0
  317. package/server/shared/types.ts +84 -1
  318. package/server/shared/utils.ts +95 -1
  319. package/server/voice-proxy.js +224 -0
  320. package/dist/assets/index-73Xw3i9v.css +0 -32
  321. package/dist/assets/index-9n7A21AT.js +0 -1361
  322. package/dist/modelConstants.js +0 -848
  323. package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
  324. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
  325. package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
  326. package/public/modelConstants.js +0 -848
  327. /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
  328. /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
  329. /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
@@ -10,7 +10,7 @@ import GeminiResponseHandler from './gemini-response-handler.js';
10
10
  import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
11
11
  import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
12
12
  import { providerModelsService } from './modules/providers/services/provider-models.service.js';
13
- import { createNormalizedMessage } from './shared/utils.js';
13
+ import { createCompleteMessage, createNormalizedMessage } from './shared/utils.js';
14
14
 
15
15
  // Use cross-spawn on Windows for correct .cmd resolution (same pattern as cursor-cli.js)
16
16
  const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
@@ -129,6 +129,9 @@ async function spawnGemini(command, options = {}, ws) {
129
129
  let capturedSessionId = sessionId; // Track session ID throughout the process
130
130
  let sessionCreatedSent = false; // Track if we've already sent session-created event
131
131
  let assistantBlocks = []; // Accumulate the full response blocks including tools
132
+ // Unified lifecycle contract: exactly one terminal `complete` per run
133
+ // (close and error handlers can both fire for spawn failures).
134
+ let completeSent = false;
132
135
 
133
136
  // Use tools settings passed from frontend, or defaults
134
137
  const settings = toolsSettings || {
@@ -486,7 +489,12 @@ async function spawnGemini(command, options = {}, ws) {
486
489
  sessionManager.addMessage(finalSessionId, 'assistant', assistantBlocks);
487
490
  }
488
491
 
489
- ws.send(createNormalizedMessage({ kind: 'complete', exitCode: code, isNewSession: !sessionId && !!command, sessionId: finalSessionId, provider: 'gemini' }));
492
+ // Terminal complete skipped for aborted runs (abort-session
493
+ // already sent the aborted complete on this run's behalf).
494
+ if (!completeSent && !geminiProcess.aborted) {
495
+ completeSent = true;
496
+ ws.send(createCompleteMessage({ provider: 'gemini', sessionId: finalSessionId, exitCode: code }));
497
+ }
490
498
 
491
499
  // Clean up temporary image files if any
492
500
  if (geminiProcess.tempImagePaths && geminiProcess.tempImagePaths.length > 0) {
@@ -566,6 +574,10 @@ async function spawnGemini(command, options = {}, ws) {
566
574
 
567
575
  const errorSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : finalSessionId;
568
576
  ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: errorSessionId, provider: 'gemini' }));
577
+ if (!completeSent && !geminiProcess.aborted) {
578
+ completeSent = true;
579
+ ws.send(createCompleteMessage({ provider: 'gemini', sessionId: errorSessionId, exitCode: 1 }));
580
+ }
569
581
  notifyTerminalState({ error });
570
582
 
571
583
  reject(error);
@@ -590,6 +602,9 @@ function abortGeminiSession(sessionId) {
590
602
 
591
603
  if (geminiProc) {
592
604
  try {
605
+ // The abort handler sends the terminal complete (aborted: true);
606
+ // flag the process so its close handler does not emit a second one.
607
+ geminiProc.aborted = true;
593
608
  geminiProc.kill('SIGTERM');
594
609
  setTimeout(() => {
595
610
  if (activeGeminiProcesses.has(processKey)) {
package/server/index.js CHANGED
@@ -22,35 +22,24 @@ import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
22
22
  import {
23
23
  queryClaudeSDK,
24
24
  abortClaudeSDKSession,
25
- isClaudeSDKSessionActive,
26
- getActiveClaudeSDKSessions,
27
25
  resolveToolApproval,
28
26
  getPendingApprovalsForSession,
29
- reconnectSessionWriter,
30
27
  } from './claude-sdk.js';
31
28
  import {
32
29
  spawnCursor,
33
30
  abortCursorSession,
34
- isCursorSessionActive,
35
- getActiveCursorSessions,
36
31
  } from './cursor-cli.js';
37
32
  import {
38
33
  queryCodex,
39
34
  abortCodexSession,
40
- isCodexSessionActive,
41
- getActiveCodexSessions,
42
35
  } from './openai-codex.js';
43
36
  import {
44
37
  spawnGemini,
45
38
  abortGeminiSession,
46
- isGeminiSessionActive,
47
- getActiveGeminiSessions,
48
39
  } from './gemini-cli.js';
49
40
  import {
50
41
  spawnOpenCode,
51
42
  abortOpenCodeSession,
52
- isOpenCodeSessionActive,
53
- getActiveOpenCodeSessions,
54
43
  } from './opencode-cli.js';
55
44
  import sessionManager from './sessionManager.js';
56
45
  import {
@@ -68,10 +57,18 @@ import commandsRoutes from './routes/commands.js';
68
57
  import settingsRoutes from './routes/settings.js';
69
58
  import agentRoutes from './routes/agent.js';
70
59
  import projectModuleRoutes from './modules/projects/projects.routes.js';
60
+ import notificationRoutes from './modules/notifications/notifications.routes.js';
71
61
  import userRoutes from './routes/user.js';
72
62
  import geminiRoutes from './routes/gemini.js';
73
63
  import pluginsRoutes from './routes/plugins.js';
74
64
  import providerRoutes from './modules/providers/provider.routes.js';
65
+ import voiceRoutes from './voice-proxy.js';
66
+ import browserUseRoutes from './modules/browser-use/browser-use.routes.js';
67
+ import browserUseMcpRoutes from './modules/browser-use/browser-use-mcp.routes.js';
68
+ import { browserUseService } from './modules/browser-use/browser-use.service.js';
69
+ import computerUseRoutes from './modules/computer-use/computer-use.routes.js';
70
+ import computerUseMcpRoutes from './modules/computer-use/computer-use-mcp.routes.js';
71
+ import { computerUseService } from './modules/computer-use/computer-use.service.js';
75
72
  import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
76
73
  import { initializeDatabase, projectsDb, sessionsDb } from './modules/database/index.js';
77
74
  import { configureWebPush } from './services/vapid-keys.js';
@@ -84,6 +81,19 @@ const __dirname = getModuleDir(import.meta.url);
84
81
  // Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
85
82
  const APP_ROOT = findAppRoot(__dirname);
86
83
  const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
84
+ // Version of the code that is actually running, captured once at process
85
+ // startup. This intentionally does NOT re-read package.json per request: after
86
+ // an update replaces the files on disk, package.json reflects the NEW version
87
+ // while this long-lived process still runs the OLD code. The frontend bundle is
88
+ // rebuilt on update, so a mismatch between this value and the frontend's
89
+ // build-time version means the server was updated but not restarted.
90
+ const RUNNING_VERSION = (() => {
91
+ try {
92
+ return JSON.parse(fs.readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8')).version || null;
93
+ } catch {
94
+ return null;
95
+ }
96
+ })();
87
97
  const MAX_FILE_UPLOAD_SIZE_MB = 200;
88
98
  const MAX_FILE_UPLOAD_SIZE_BYTES = MAX_FILE_UPLOAD_SIZE_MB * 1024 * 1024;
89
99
  const MAX_FILE_UPLOAD_COUNT = 20;
@@ -105,32 +115,35 @@ const wss = createWebSocketServer(server, {
105
115
  authenticateWebSocket,
106
116
  },
107
117
  chat: {
108
- queryClaudeSDK,
109
- spawnCursor,
110
- queryCodex,
111
- spawnGemini,
112
- spawnOpenCode,
113
- abortClaudeSDKSession,
114
- abortCursorSession,
115
- abortCodexSession,
116
- abortGeminiSession,
117
- abortOpenCodeSession,
118
+ spawnFns: {
119
+ claude: queryClaudeSDK,
120
+ cursor: spawnCursor,
121
+ codex: queryCodex,
122
+ gemini: spawnGemini,
123
+ opencode: spawnOpenCode,
124
+ },
125
+ abortFns: {
126
+ claude: abortClaudeSDKSession,
127
+ cursor: abortCursorSession,
128
+ codex: abortCodexSession,
129
+ gemini: abortGeminiSession,
130
+ opencode: abortOpenCodeSession,
131
+ },
118
132
  resolveToolApproval,
119
- isClaudeSDKSessionActive,
120
- isCursorSessionActive,
121
- isCodexSessionActive,
122
- isGeminiSessionActive,
123
- isOpenCodeSessionActive,
124
- reconnectSessionWriter,
125
133
  getPendingApprovalsForSession,
126
- getActiveClaudeSDKSessions,
127
- getActiveCursorSessions,
128
- getActiveCodexSessions,
129
- getActiveGeminiSessions,
130
- getActiveOpenCodeSessions,
131
134
  },
132
135
  shell: {
133
- getSessionById: (sessionId) => sessionManager.getSession(sessionId),
136
+ resolveProviderSessionId: (sessionId, provider) => {
137
+ const dbSession = sessionsDb.getSessionById(sessionId);
138
+ const legacyGeminiSession =
139
+ provider === 'gemini' ? sessionManager.getSession(sessionId) : null;
140
+
141
+ if (dbSession) {
142
+ return dbSession.provider_session_id ?? legacyGeminiSession?.cliSessionId ?? null;
143
+ }
144
+
145
+ return legacyGeminiSession?.cliSessionId;
146
+ },
134
147
  stripAnsiSequences,
135
148
  normalizeDetectedUrl,
136
149
  extractUrlsFromText,
@@ -161,7 +174,8 @@ app.get('/health', (req, res) => {
161
174
  res.json({
162
175
  status: 'ok',
163
176
  timestamp: new Date().toISOString(),
164
- installMode
177
+ installMode,
178
+ version: RUNNING_VERSION
165
179
  });
166
180
  });
167
181
 
@@ -192,6 +206,8 @@ app.use('/api/commands', authenticateToken, commandsRoutes);
192
206
  // Settings API Routes (protected)
193
207
  app.use('/api/settings', authenticateToken, settingsRoutes);
194
208
 
209
+ app.use('/api/notifications', authenticateToken, notificationRoutes);
210
+
195
211
  // User API Routes (protected)
196
212
  app.use('/api/user', authenticateToken, userRoutes);
197
213
 
@@ -201,12 +217,26 @@ app.use('/api/gemini', authenticateToken, geminiRoutes);
201
217
  // Plugins API Routes (protected)
202
218
  app.use('/api/plugins', authenticateToken, pluginsRoutes);
203
219
 
220
+ // Browser MCP bridge API (local token protected)
221
+ app.use('/api/browser-use-mcp', browserUseMcpRoutes);
222
+
223
+ // Browser API Routes (protected)
224
+ app.use('/api/browser-use', authenticateToken, browserUseRoutes);
225
+
226
+ // Computer Use MCP bridge API (local token protected)
227
+ app.use('/api/computer-use-mcp', computerUseMcpRoutes);
228
+
229
+ // Computer Use API Routes (protected)
230
+ app.use('/api/computer-use', authenticateToken, computerUseRoutes);
231
+
204
232
  // Unified provider MCP routes (protected)
205
233
  app.use('/api/providers', authenticateToken, providerRoutes);
206
234
 
207
235
  // Agent API Routes (uses API key authentication)
208
236
  app.use('/api/agent', agentRoutes);
209
237
 
238
+ app.use('/api/voice', authenticateToken, voiceRoutes);
239
+
210
240
  // Serve public files (like api-docs.html)
211
241
  app.use(express.static(path.join(APP_ROOT, 'public')));
212
242
 
@@ -1143,7 +1173,6 @@ app.post('/api/projects/:projectId/upload-images', authenticateToken, async (req
1143
1173
  app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticateToken, async (req, res) => {
1144
1174
  try {
1145
1175
  const { projectId, sessionId } = req.params;
1146
- const { provider = 'claude' } = req.query;
1147
1176
  const homeDir = os.homedir();
1148
1177
 
1149
1178
  // Allow only safe characters in sessionId
@@ -1152,6 +1181,18 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
1152
1181
  return res.status(400).json({ error: 'Invalid sessionId' });
1153
1182
  }
1154
1183
 
1184
+ // Provider artifacts on disk (JSONL file names, OpenCode sqlite rows)
1185
+ // are keyed by the provider-native session id, while the caller sends
1186
+ // the app-facing id. Resolve provider and id mapping from the indexed
1187
+ // session row so the frontend does not choose provider-specific paths.
1188
+ const sessionRow = sessionsDb.getSessionById(safeSessionId);
1189
+ if (!sessionRow) {
1190
+ return res.status(404).json({ error: 'Session not found', sessionId: safeSessionId });
1191
+ }
1192
+
1193
+ const provider = sessionRow.provider || 'claude';
1194
+ const providerNativeSessionId = sessionRow?.provider_session_id || safeSessionId;
1195
+
1155
1196
  // Handle Cursor sessions - they use SQLite and don't have token usage info
1156
1197
  if (provider === 'cursor') {
1157
1198
  return res.json({
@@ -1252,7 +1293,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
1252
1293
  tokens_cache_write AS cacheWriteTokens
1253
1294
  FROM session
1254
1295
  WHERE id = ?
1255
- `).get(safeSessionId);
1296
+ `).get(providerNativeSessionId);
1256
1297
 
1257
1298
  if (!row) {
1258
1299
  return res.status(404).json({ error: 'OpenCode session not found', sessionId: safeSessionId });
@@ -1293,7 +1334,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
1293
1334
  if (entry.isDirectory()) {
1294
1335
  const found = await findSessionFile(fullPath);
1295
1336
  if (found) return found;
1296
- } else if (entry.name.includes(safeSessionId) && entry.name.endsWith('.jsonl')) {
1337
+ } else if (entry.name.includes(providerNativeSessionId) && entry.name.endsWith('.jsonl')) {
1297
1338
  return fullPath;
1298
1339
  }
1299
1340
  }
@@ -1377,12 +1418,19 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
1377
1418
  const encodedPath = projectPath.replace(/[^a-zA-Z0-9-]/g, '-');
1378
1419
  const projectDir = path.join(homeDir, '.claude', 'projects', encodedPath);
1379
1420
 
1380
- const jsonlPath = path.join(projectDir, `${safeSessionId}.jsonl`);
1381
-
1382
- // Constrain to projectDir
1383
- const rel = path.relative(path.resolve(projectDir), path.resolve(jsonlPath));
1384
- if (rel.startsWith('..') || path.isAbsolute(rel)) {
1385
- return res.status(400).json({ error: 'Invalid path' });
1421
+ // Prefer the indexed transcript path (already produced by the trusted
1422
+ // session synchronizer); fall back to the conventional location
1423
+ // derived from the provider-native session id.
1424
+ let jsonlPath = sessionRow?.jsonl_path;
1425
+ if (!jsonlPath) {
1426
+ jsonlPath = path.join(projectDir, `${providerNativeSessionId}.jsonl`);
1427
+
1428
+ // Constrain the constructed path to projectDir (the id is
1429
+ // caller-influenced in this fallback branch).
1430
+ const rel = path.relative(path.resolve(projectDir), path.resolve(jsonlPath));
1431
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
1432
+ return res.status(400).json({ error: 'Invalid path' });
1433
+ }
1386
1434
  }
1387
1435
 
1388
1436
  // Read and parse the JSONL file
@@ -1646,6 +1694,40 @@ const SERVER_PORT = process.env.SERVER_PORT || 3001;
1646
1694
  const HOST = process.env.HOST || '0.0.0.0';
1647
1695
  const DISPLAY_HOST = getConnectableHost(HOST);
1648
1696
  const VITE_PORT = process.env.VITE_PORT || 5173;
1697
+ const LOCAL_SERVER_MARKER_PATH = path.join(os.homedir(), '.cloudcli', 'local-server.json');
1698
+
1699
+ async function writeLocalServerMarker() {
1700
+ const marker = {
1701
+ pid: process.pid,
1702
+ host: HOST,
1703
+ port: Number.parseInt(String(SERVER_PORT), 10),
1704
+ url: `http://${DISPLAY_HOST}:${SERVER_PORT}`,
1705
+ installMode,
1706
+ appRoot: APP_ROOT,
1707
+ updatedAt: new Date().toISOString(),
1708
+ };
1709
+
1710
+ await fsPromises.mkdir(path.dirname(LOCAL_SERVER_MARKER_PATH), { recursive: true });
1711
+ await fsPromises.writeFile(LOCAL_SERVER_MARKER_PATH, JSON.stringify(marker, null, 2), 'utf8');
1712
+ }
1713
+
1714
+ async function removeLocalServerMarker() {
1715
+ try {
1716
+ const raw = await fsPromises.readFile(LOCAL_SERVER_MARKER_PATH, 'utf8');
1717
+ const marker = JSON.parse(raw);
1718
+ if (marker.pid && marker.pid !== process.pid) return;
1719
+ } catch (error) {
1720
+ if (error.code === 'ENOENT') return;
1721
+ }
1722
+
1723
+ try {
1724
+ await fsPromises.unlink(LOCAL_SERVER_MARKER_PATH);
1725
+ } catch (error) {
1726
+ if (error.code !== 'ENOENT') {
1727
+ console.warn('[WARN] Could not remove local server marker:', error.message);
1728
+ }
1729
+ }
1730
+ }
1649
1731
 
1650
1732
  // Initialize database and start server
1651
1733
  async function startServer() {
@@ -1672,6 +1754,9 @@ async function startServer() {
1672
1754
 
1673
1755
  server.listen(SERVER_PORT, HOST, async () => {
1674
1756
  const appInstallPath = APP_ROOT;
1757
+ await writeLocalServerMarker().catch((error) => {
1758
+ console.warn('[WARN] Could not write local server marker:', error.message);
1759
+ });
1675
1760
 
1676
1761
  console.log('');
1677
1762
  console.log(c.dim('═'.repeat(63)));
@@ -1694,12 +1779,31 @@ async function startServer() {
1694
1779
 
1695
1780
  await closeSessionsWatcher();
1696
1781
  // Clean up plugin processes on shutdown
1697
- const shutdownPlugins = async () => {
1698
- await stopAllPlugins();
1782
+ const shutdownRuntimeServices = async () => {
1783
+ try {
1784
+ await browserUseService.stopAllSessions();
1785
+ } catch (err) {
1786
+ console.error('[Browser] Error stopping sessions during shutdown:', err?.message || err);
1787
+ }
1788
+ try {
1789
+ await computerUseService.stopAllSessions();
1790
+ } catch (err) {
1791
+ console.error('[Computer Use] Error stopping sessions during shutdown:', err?.message || err);
1792
+ }
1793
+ try {
1794
+ await stopAllPlugins();
1795
+ } catch (err) {
1796
+ console.error('[Plugins] Error stopping plugins during shutdown:', err?.message || err);
1797
+ }
1798
+ try {
1799
+ await removeLocalServerMarker();
1800
+ } catch (err) {
1801
+ console.error('[Local Server] Error removing server marker during shutdown:', err?.message || err);
1802
+ }
1699
1803
  process.exit(0);
1700
1804
  };
1701
- process.on('SIGTERM', () => void shutdownPlugins());
1702
- process.on('SIGINT', () => void shutdownPlugins());
1805
+ process.on('SIGTERM', () => void shutdownRuntimeServices());
1806
+ process.on('SIGINT', () => void shutdownRuntimeServices());
1703
1807
  } catch (error) {
1704
1808
  console.error('[ERROR] Failed to start server:', error);
1705
1809
  process.exit(1);
@@ -22,7 +22,7 @@ try {
22
22
  }
23
23
  });
24
24
  } catch (e) {
25
- console.log('No .env file found or error reading it:', e.message);
25
+ console.error('No .env file found or error reading it:', e.message);
26
26
  }
27
27
 
28
28
  // Keep the default database in a stable user-level location so rebuilding dist-server
@@ -0,0 +1,120 @@
1
+ import express from 'express';
2
+
3
+ import { browserUseService } from '@/modules/browser-use/browser-use.service.js';
4
+
5
+ const router = express.Router();
6
+
7
+ function readBearerToken(header: unknown): string | null {
8
+ if (typeof header !== 'string') {
9
+ return null;
10
+ }
11
+ const match = /^Bearer\s+(\S.*)$/i.exec(header.trim());
12
+ return match?.[1]?.trim() || null;
13
+ }
14
+
15
+ router.use((req, res, next) => {
16
+ const expected = browserUseService.getMcpToken();
17
+ const token = readBearerToken(req.headers.authorization) || String(req.headers['x-browser-use-mcp-token'] || '');
18
+ if (!token || token !== expected) {
19
+ res.status(401).json({ success: false, error: 'Invalid Browser MCP token.' });
20
+ return;
21
+ }
22
+ next();
23
+ });
24
+
25
+ router.post('/tools/:toolName', async (req, res) => {
26
+ try {
27
+ const input = (req.body && typeof req.body === 'object' ? req.body : {}) as Record<string, unknown>;
28
+ const sessionId = typeof input.sessionId === 'string' ? input.sessionId : '';
29
+ const toolName = req.params.toolName;
30
+ let result: unknown;
31
+
32
+ switch (toolName) {
33
+ case 'browser_create_session':
34
+ result = await browserUseService.createAgentSession({
35
+ profileName: typeof input.profileName === 'string' ? input.profileName : null,
36
+ });
37
+ break;
38
+ case 'browser_list_sessions':
39
+ result = await browserUseService.listAgentSessions();
40
+ break;
41
+ case 'browser_snapshot':
42
+ case 'browser_take_screenshot':
43
+ result = await browserUseService.agentSnapshot(sessionId);
44
+ break;
45
+ case 'browser_navigate':
46
+ result = await browserUseService.agentNavigate(sessionId, String(input.url || ''));
47
+ break;
48
+ case 'browser_click':
49
+ result = await browserUseService.agentClick(sessionId, {
50
+ selector: typeof input.selector === 'string' ? input.selector : undefined,
51
+ text: typeof input.text === 'string' ? input.text : undefined,
52
+ x: typeof input.x === 'number' ? input.x : undefined,
53
+ y: typeof input.y === 'number' ? input.y : undefined,
54
+ });
55
+ break;
56
+ case 'browser_type':
57
+ result = await browserUseService.agentType(sessionId, {
58
+ selector: typeof input.selector === 'string' ? input.selector : undefined,
59
+ text: String(input.text || ''),
60
+ submit: input.submit === true,
61
+ });
62
+ break;
63
+ case 'browser_fill_form':
64
+ result = await browserUseService.agentFillForm(
65
+ sessionId,
66
+ Array.isArray(input.fields)
67
+ ? input.fields.map((field) => {
68
+ const record = field as Record<string, unknown>;
69
+ return {
70
+ selector: String(record.selector || ''),
71
+ value: String(record.value || ''),
72
+ };
73
+ })
74
+ : [],
75
+ );
76
+ break;
77
+ case 'browser_press_key':
78
+ result = await browserUseService.agentPressKey(sessionId, String(input.key || ''));
79
+ break;
80
+ case 'browser_select_option':
81
+ result = await browserUseService.agentSelectOption(
82
+ sessionId,
83
+ String(input.selector || ''),
84
+ Array.isArray(input.values) ? input.values.filter((value): value is string => typeof value === 'string') : [],
85
+ );
86
+ break;
87
+ case 'browser_wait_for':
88
+ result = await browserUseService.agentWaitFor(sessionId, {
89
+ text: typeof input.text === 'string' ? input.text : undefined,
90
+ url: typeof input.url === 'string' ? input.url : undefined,
91
+ timeoutMs: typeof input.timeoutMs === 'number' ? input.timeoutMs : undefined,
92
+ });
93
+ break;
94
+ case 'browser_tabs':
95
+ result = await browserUseService.agentTabs(sessionId, {
96
+ action: input.action === 'new' || input.action === 'select' || input.action === 'close' || input.action === 'list'
97
+ ? input.action
98
+ : undefined,
99
+ index: typeof input.index === 'number' ? input.index : undefined,
100
+ url: typeof input.url === 'string' ? input.url : undefined,
101
+ });
102
+ break;
103
+ case 'browser_close_session':
104
+ result = await browserUseService.agentStopSession(sessionId);
105
+ break;
106
+ default:
107
+ res.status(404).json({ success: false, error: `Unknown Browser MCP tool "${toolName}".` });
108
+ return;
109
+ }
110
+
111
+ res.json({ success: true, data: result });
112
+ } catch (error) {
113
+ res.status(400).json({
114
+ success: false,
115
+ error: error instanceof Error ? error.message : 'Browser MCP tool failed.',
116
+ });
117
+ }
118
+ });
119
+
120
+ export default router;
@@ -0,0 +1,96 @@
1
+ import express from 'express';
2
+
3
+ import { browserUseService } from '@/modules/browser-use/browser-use.service.js';
4
+
5
+ const router = express.Router();
6
+
7
+ function readParam(value: string | string[] | undefined): string {
8
+ return Array.isArray(value) ? value[0] || '' : value || '';
9
+ }
10
+
11
+ router.get('/status', async (_req, res) => {
12
+ try {
13
+ res.json({ success: true, data: await browserUseService.getStatus() });
14
+ } catch (error) {
15
+ res.status(500).json({
16
+ success: false,
17
+ error: error instanceof Error ? error.message : 'Failed to load Browser status.',
18
+ });
19
+ }
20
+ });
21
+
22
+ router.get('/settings', async (_req, res) => {
23
+ try {
24
+ res.json({ success: true, data: { settings: await browserUseService.getSettings() } });
25
+ } catch (error) {
26
+ res.status(500).json({
27
+ success: false,
28
+ error: error instanceof Error ? error.message : 'Failed to load Browser settings.',
29
+ });
30
+ }
31
+ });
32
+
33
+ router.put('/settings', async (req, res) => {
34
+ try {
35
+ const settings = await browserUseService.updateSettings(req.body || {});
36
+ res.json({ success: true, data: { settings } });
37
+ } catch (error) {
38
+ res.status(400).json({
39
+ success: false,
40
+ error: error instanceof Error ? error.message : 'Failed to save Browser settings.',
41
+ });
42
+ }
43
+ });
44
+
45
+ router.post('/runtime/install', async (_req, res) => {
46
+ try {
47
+ const result = await browserUseService.installRuntime();
48
+ res.status(result.success ? 200 : 500).json({
49
+ success: result.success,
50
+ data: result,
51
+ error: result.success ? undefined : result.message,
52
+ });
53
+ } catch (error) {
54
+ res.status(500).json({
55
+ success: false,
56
+ error: error instanceof Error ? error.message : 'Failed to install Browser runtime.',
57
+ });
58
+ }
59
+ });
60
+
61
+ router.get('/sessions', async (_req, res) => {
62
+ try {
63
+ res.json({ success: true, data: { sessions: await browserUseService.listSessions() } });
64
+ } catch (error) {
65
+ res.status(401).json({
66
+ success: false,
67
+ error: error instanceof Error ? error.message : 'Failed to list browser sessions.',
68
+ });
69
+ }
70
+ });
71
+
72
+ router.post('/sessions/:sessionId/stop', async (req, res) => {
73
+ try {
74
+ const result = await browserUseService.stopSession(readParam(req.params.sessionId));
75
+ res.json({ success: true, data: result });
76
+ } catch (error) {
77
+ res.status(400).json({
78
+ success: false,
79
+ error: error instanceof Error ? error.message : 'Failed to stop browser session.',
80
+ });
81
+ }
82
+ });
83
+
84
+ router.delete('/sessions/:sessionId', async (req, res) => {
85
+ try {
86
+ const result = await browserUseService.deleteSession(readParam(req.params.sessionId));
87
+ res.json({ success: true, data: result });
88
+ } catch (error) {
89
+ res.status(400).json({
90
+ success: false,
91
+ error: error instanceof Error ? error.message : 'Failed to delete browser session.',
92
+ });
93
+ }
94
+ });
95
+
96
+ export default router;