@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
@@ -0,0 +1,467 @@
1
+ import AppKit
2
+ import ApplicationServices
3
+ import Foundation
4
+
5
+ typealias JSON = [String: Any]
6
+
7
+ struct ElementRecord {
8
+ let index: String
9
+ let role: String
10
+ let title: String?
11
+ let value: String?
12
+ let bounds: [String: Double]?
13
+ let actions: [String]
14
+ }
15
+
16
+ var stateElements: [String: [ElementRecord]] = [:]
17
+ var stateAxElements: [String: [String: AXUIElement]] = [:]
18
+ var stateOrder: [String] = []
19
+ let maxStoredStates = 100
20
+
21
+ func jsonLine(_ object: Any) {
22
+ guard JSONSerialization.isValidJSONObject(object),
23
+ let data = try? JSONSerialization.data(withJSONObject: object),
24
+ let text = String(data: data, encoding: .utf8)
25
+ else {
26
+ print("{\"error\":\"Failed to encode JSON\"}")
27
+ fflush(stdout)
28
+ return
29
+ }
30
+ print(text)
31
+ fflush(stdout)
32
+ }
33
+
34
+ func respond(id: Any?, result: Any) {
35
+ jsonLine(["id": id ?? NSNull(), "result": result])
36
+ }
37
+
38
+ func respondError(id: Any?, _ message: String) {
39
+ jsonLine(["id": id ?? NSNull(), "error": message])
40
+ }
41
+
42
+ func stringAttr(_ element: AXUIElement, _ attr: CFString) -> String? {
43
+ var value: CFTypeRef?
44
+ guard AXUIElementCopyAttributeValue(element, attr, &value) == .success else { return nil }
45
+ return value as? String
46
+ }
47
+
48
+ func boolAttr(_ element: AXUIElement, _ attr: CFString) -> Bool? {
49
+ var value: CFTypeRef?
50
+ guard AXUIElementCopyAttributeValue(element, attr, &value) == .success else { return nil }
51
+ return value as? Bool
52
+ }
53
+
54
+ func arrayAttr(_ element: AXUIElement, _ attr: CFString) -> [AXUIElement] {
55
+ var value: CFTypeRef?
56
+ guard AXUIElementCopyAttributeValue(element, attr, &value) == .success else { return [] }
57
+ return value as? [AXUIElement] ?? []
58
+ }
59
+
60
+ func actions(_ element: AXUIElement) -> [String] {
61
+ var names: CFArray?
62
+ guard AXUIElementCopyActionNames(element, &names) == .success else { return [] }
63
+ return names as? [String] ?? []
64
+ }
65
+
66
+ func bounds(_ element: AXUIElement) -> [String: Double]? {
67
+ var positionRef: CFTypeRef?
68
+ var sizeRef: CFTypeRef?
69
+ guard AXUIElementCopyAttributeValue(element, kAXPositionAttribute as CFString, &positionRef) == .success,
70
+ AXUIElementCopyAttributeValue(element, kAXSizeAttribute as CFString, &sizeRef) == .success,
71
+ let positionValue = positionRef,
72
+ let sizeValue = sizeRef
73
+ else { return nil }
74
+
75
+ var point = CGPoint.zero
76
+ var size = CGSize.zero
77
+ guard CFGetTypeID(positionValue) == AXValueGetTypeID(),
78
+ CFGetTypeID(sizeValue) == AXValueGetTypeID()
79
+ else { return nil }
80
+
81
+ let positionAxValue = positionValue as! AXValue
82
+ let sizeAxValue = sizeValue as! AXValue
83
+ guard AXValueGetValue(positionAxValue, .cgPoint, &point),
84
+ AXValueGetValue(sizeAxValue, .cgSize, &size)
85
+ else { return nil }
86
+
87
+ return [
88
+ "x": Double(point.x),
89
+ "y": Double(point.y),
90
+ "width": Double(size.width),
91
+ "height": Double(size.height),
92
+ ]
93
+ }
94
+
95
+ func record(_ element: AXUIElement, index: String) -> ElementRecord {
96
+ ElementRecord(
97
+ index: index,
98
+ role: stringAttr(element, kAXRoleAttribute as CFString) ?? "AXUnknown",
99
+ title: stringAttr(element, kAXTitleAttribute as CFString) ?? stringAttr(element, kAXDescriptionAttribute as CFString),
100
+ value: stringAttr(element, kAXValueAttribute as CFString),
101
+ bounds: bounds(element),
102
+ actions: actions(element)
103
+ )
104
+ }
105
+
106
+ func cachedElement(_ params: JSON) -> AXUIElement? {
107
+ guard let stateId = params["stateId"] as? String,
108
+ let elementIndex = params["element_index"] as? String
109
+ else {
110
+ return nil
111
+ }
112
+ return stateAxElements[stateId]?[elementIndex]
113
+ }
114
+
115
+ func dictionary(_ record: ElementRecord) -> JSON {
116
+ var output: JSON = [
117
+ "index": record.index,
118
+ "role": record.role,
119
+ "actions": record.actions,
120
+ ]
121
+ if let title = record.title { output["title"] = title }
122
+ if let value = record.value { output["value"] = value }
123
+ if let bounds = record.bounds { output["bounds"] = bounds }
124
+ return output
125
+ }
126
+
127
+ func pruneStoredStates() {
128
+ while stateOrder.count > maxStoredStates {
129
+ let evicted = stateOrder.removeFirst()
130
+ stateElements.removeValue(forKey: evicted)
131
+ stateAxElements.removeValue(forKey: evicted)
132
+ }
133
+ }
134
+
135
+ func resolveApp(_ query: String) throws -> NSRunningApplication {
136
+ let normalized = query.lowercased()
137
+ let apps = NSWorkspace.shared.runningApplications.filter { app in
138
+ app.activationPolicy == .regular
139
+ }
140
+ if let app = apps.first(where: { $0.bundleIdentifier?.lowercased() == normalized }) {
141
+ return app
142
+ }
143
+ if let app = apps.first(where: { ($0.localizedName ?? "").lowercased() == normalized }) {
144
+ return app
145
+ }
146
+ if let app = apps.first(where: { ($0.localizedName ?? "").lowercased().contains(normalized) }) {
147
+ return app
148
+ }
149
+ throw NSError(domain: "CloudCLISemantics", code: 404, userInfo: [NSLocalizedDescriptionKey: "App is not running: \(query)"])
150
+ }
151
+
152
+ func listApps() -> [[String: Any]] {
153
+ NSWorkspace.shared.runningApplications
154
+ .filter { $0.activationPolicy == .regular }
155
+ .map { app in
156
+ [
157
+ "id": app.bundleIdentifier ?? app.localizedName ?? "\(app.processIdentifier)",
158
+ "name": app.localizedName ?? app.bundleIdentifier ?? "Unknown",
159
+ "bundleIdentifier": app.bundleIdentifier ?? "",
160
+ "pid": Int(app.processIdentifier),
161
+ "running": true,
162
+ ]
163
+ }
164
+ }
165
+
166
+ func walk(_ element: AXUIElement, depth: Int, maxDepth: Int, records: inout [ElementRecord], axRecords: inout [String: AXUIElement], limit: Int) {
167
+ if depth > maxDepth || records.count >= limit { return }
168
+ let index = "\(records.count + 1)"
169
+ records.append(record(element, index: index))
170
+ axRecords[index] = element
171
+ for child in arrayAttr(element, kAXChildrenAttribute as CFString) {
172
+ walk(child, depth: depth + 1, maxDepth: maxDepth, records: &records, axRecords: &axRecords, limit: limit)
173
+ if records.count >= limit { return }
174
+ }
175
+ }
176
+
177
+ func pngDataUrlForMainDisplay() -> String? {
178
+ let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("cloudcli-semantics-\(UUID().uuidString).png")
179
+ let process = Process()
180
+ process.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
181
+ process.arguments = ["-x", "-t", "png", fileURL.path]
182
+
183
+ do {
184
+ try process.run()
185
+ process.waitUntilExit()
186
+ guard process.terminationStatus == 0 else { return nil }
187
+ let png = try Data(contentsOf: fileURL)
188
+ try? FileManager.default.removeItem(at: fileURL)
189
+ return png.isEmpty ? nil : "data:image/png;base64,\(png.base64EncodedString())"
190
+ } catch {
191
+ try? FileManager.default.removeItem(at: fileURL)
192
+ return nil
193
+ }
194
+ }
195
+
196
+ func getAppState(_ params: JSON) throws -> JSON {
197
+ let appName = params["app"] as? String ?? ""
198
+ let app = try resolveApp(appName)
199
+ let axApp = AXUIElementCreateApplication(app.processIdentifier)
200
+ let windows = arrayAttr(axApp, kAXWindowsAttribute as CFString)
201
+ let root = windows.first ?? axApp
202
+ var records: [ElementRecord] = []
203
+ var axRecords: [String: AXUIElement] = [:]
204
+ walk(root, depth: 0, maxDepth: 5, records: &records, axRecords: &axRecords, limit: 300)
205
+ let stateId = "state_\(UUID().uuidString)"
206
+ stateElements[stateId] = records
207
+ stateAxElements[stateId] = axRecords
208
+ stateOrder.append(stateId)
209
+ pruneStoredStates()
210
+
211
+ let elements = records.map(dictionary)
212
+ return [
213
+ "stateId": stateId,
214
+ "app": app.localizedName ?? app.bundleIdentifier ?? appName,
215
+ "platform": "darwin",
216
+ "screenshotDataUrl": pngDataUrlForMainDisplay() ?? NSNull(),
217
+ "displaySize": [
218
+ "width": Int(CGDisplayPixelsWide(CGMainDisplayID())),
219
+ "height": Int(CGDisplayPixelsHigh(CGMainDisplayID())),
220
+ ],
221
+ "elements": elements,
222
+ "accessibilityTree": elements,
223
+ "treeText": elements.map { "\($0["index"] ?? "") \($0["role"] ?? "") \($0["title"] ?? "")" }.joined(separator: "\n"),
224
+ ]
225
+ }
226
+
227
+ func cgMouseButton(_ value: Any?) -> CGMouseButton {
228
+ guard let button = value as? String else { return .left }
229
+ switch button {
230
+ case "right": return .right
231
+ case "middle": return .center
232
+ default: return .left
233
+ }
234
+ }
235
+
236
+ func mouseEventTypes(_ button: CGMouseButton) -> (CGEventType, CGEventType) {
237
+ switch button {
238
+ case .right: return (.rightMouseDown, .rightMouseUp)
239
+ case .center: return (.otherMouseDown, .otherMouseUp)
240
+ default: return (.leftMouseDown, .leftMouseUp)
241
+ }
242
+ }
243
+
244
+ func postMouseClick(point: CGPoint, button: CGMouseButton, clickCount: Int = 1) throws {
245
+ guard let source = CGEventSource(stateID: .combinedSessionState) else {
246
+ throw NSError(domain: "CloudCLISemantics", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to create CGEventSource"])
247
+ }
248
+ let eventTypes = mouseEventTypes(button)
249
+ for _ in 0..<max(1, clickCount) {
250
+ let down = CGEvent(mouseEventSource: source, mouseType: eventTypes.0, mouseCursorPosition: point, mouseButton: button)
251
+ let up = CGEvent(mouseEventSource: source, mouseType: eventTypes.1, mouseCursorPosition: point, mouseButton: button)
252
+ down?.post(tap: .cghidEventTap)
253
+ up?.post(tap: .cghidEventTap)
254
+ usleep(80_000)
255
+ }
256
+ }
257
+
258
+ func postDrag(from: CGPoint, to: CGPoint, button: CGMouseButton) throws {
259
+ guard let source = CGEventSource(stateID: .combinedSessionState) else {
260
+ throw NSError(domain: "CloudCLISemantics", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to create CGEventSource"])
261
+ }
262
+ let eventTypes = mouseEventTypes(button)
263
+ CGEvent(mouseEventSource: source, mouseType: eventTypes.0, mouseCursorPosition: from, mouseButton: button)?.post(tap: .cghidEventTap)
264
+ usleep(80_000)
265
+ CGEvent(mouseEventSource: source, mouseType: .leftMouseDragged, mouseCursorPosition: to, mouseButton: button)?.post(tap: .cghidEventTap)
266
+ usleep(80_000)
267
+ CGEvent(mouseEventSource: source, mouseType: eventTypes.1, mouseCursorPosition: to, mouseButton: button)?.post(tap: .cghidEventTap)
268
+ }
269
+
270
+ func runAppleScript(_ script: String) throws {
271
+ let process = Process()
272
+ process.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
273
+ process.arguments = ["-e", script]
274
+ process.standardOutput = Pipe()
275
+ let stderr = Pipe()
276
+ process.standardError = stderr
277
+ try process.run()
278
+ process.waitUntilExit()
279
+ if process.terminationStatus != 0 {
280
+ let data = stderr.fileHandleForReading.readDataToEndOfFile()
281
+ let message = String(data: data, encoding: .utf8) ?? "AppleScript failed."
282
+ throw NSError(domain: "CloudCLISemantics", code: Int(process.terminationStatus), userInfo: [NSLocalizedDescriptionKey: message])
283
+ }
284
+ }
285
+
286
+ func escapedAppleScriptString(_ value: String) -> String {
287
+ value.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"")
288
+ }
289
+
290
+ func pointForElement(_ params: JSON) -> CGPoint? {
291
+ if let x = params["x"] as? Double, let y = params["y"] as? Double {
292
+ return CGPoint(x: x, y: y)
293
+ }
294
+ guard let stateId = params["stateId"] as? String,
295
+ let elementIndex = params["element_index"] as? String,
296
+ let element = stateElements[stateId]?.first(where: { $0.index == elementIndex }),
297
+ let b = element.bounds,
298
+ let x = b["x"], let y = b["y"], let width = b["width"], let height = b["height"]
299
+ else {
300
+ return nil
301
+ }
302
+ return CGPoint(x: x + width / 2, y: y + height / 2)
303
+ }
304
+
305
+ func click(_ params: JSON) throws -> JSON {
306
+ if let element = cachedElement(params),
307
+ cgMouseButton(params["mouse_button"]) == .left,
308
+ (params["click_count"] as? Int ?? 1) == 1,
309
+ actions(element).contains(kAXPressAction as String),
310
+ AXUIElementPerformAction(element, kAXPressAction as CFString) == .success {
311
+ return try getAppState(params)
312
+ }
313
+
314
+ guard let point = pointForElement(params) else {
315
+ throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "click_element requires x/y or stateId + element_index"])
316
+ }
317
+ let clickCount = params["click_count"] as? Int ?? 1
318
+ try postMouseClick(point: point, button: cgMouseButton(params["mouse_button"]), clickCount: clickCount)
319
+ return try getAppState(params)
320
+ }
321
+
322
+ func performSecondaryAction(_ params: JSON) throws -> JSON {
323
+ if let element = cachedElement(params),
324
+ actions(element).contains(kAXShowMenuAction as String),
325
+ AXUIElementPerformAction(element, kAXShowMenuAction as CFString) == .success {
326
+ return try getAppState(params)
327
+ }
328
+ guard let point = pointForElement(params) else {
329
+ throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "perform_secondary_action requires x/y or stateId + element_index"])
330
+ }
331
+ try postMouseClick(point: point, button: .right)
332
+ return try getAppState(params)
333
+ }
334
+
335
+ func setValue(_ params: JSON) throws -> JSON {
336
+ guard let value = params["value"] as? String else {
337
+ throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "set_value requires value"])
338
+ }
339
+ if let element = cachedElement(params),
340
+ AXUIElementSetAttributeValue(element, kAXValueAttribute as CFString, value as CFTypeRef) == .success {
341
+ return try getAppState(params)
342
+ }
343
+ guard let point = pointForElement(params) else {
344
+ throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "set_value requires x/y or stateId + element_index"])
345
+ }
346
+ try postMouseClick(point: point, button: .left)
347
+ try runAppleScript("tell application \"System Events\" to keystroke \"a\" using command down")
348
+ try runAppleScript("tell application \"System Events\" to keystroke \"\(escapedAppleScriptString(value))\"")
349
+ return try getAppState(params)
350
+ }
351
+
352
+ func typeText(_ params: JSON) throws -> JSON {
353
+ let text = params["text"] as? String ?? ""
354
+ try runAppleScript("tell application \"System Events\" to keystroke \"\(escapedAppleScriptString(text))\"")
355
+ return try getAppState(params)
356
+ }
357
+
358
+ func appleScriptModifiers(_ parts: [String]) -> String {
359
+ let modifiers = parts.compactMap { part -> String? in
360
+ switch part.lowercased() {
361
+ case "cmd", "command", "meta": return "command down"
362
+ case "ctrl", "control": return "control down"
363
+ case "alt", "option": return "option down"
364
+ case "shift": return "shift down"
365
+ default: return nil
366
+ }
367
+ }
368
+ return modifiers.isEmpty ? "" : " using {\(modifiers.joined(separator: ", "))}"
369
+ }
370
+
371
+ func appleScriptKeyCode(_ key: String) -> Int? {
372
+ switch key.lowercased() {
373
+ case "return", "enter": return 36
374
+ case "tab": return 48
375
+ case "space": return 49
376
+ case "delete", "backspace": return 51
377
+ case "escape", "esc": return 53
378
+ case "left": return 123
379
+ case "right": return 124
380
+ case "down": return 125
381
+ case "up": return 126
382
+ default: return nil
383
+ }
384
+ }
385
+
386
+ func pressKey(_ params: JSON) throws -> JSON {
387
+ let raw = params["key"] as? String ?? ""
388
+ let parts = raw.split(separator: "+").map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty }
389
+ let key = parts.last ?? raw
390
+ let modifiers = appleScriptModifiers(Array(parts.dropLast()))
391
+ if let keyCode = appleScriptKeyCode(key) {
392
+ try runAppleScript("tell application \"System Events\" to key code \(keyCode)\(modifiers)")
393
+ } else {
394
+ try runAppleScript("tell application \"System Events\" to keystroke \"\(escapedAppleScriptString(key))\"\(modifiers)")
395
+ }
396
+ return try getAppState(params)
397
+ }
398
+
399
+ func scrollElement(_ params: JSON) throws -> JSON {
400
+ guard let point = pointForElement(params) else {
401
+ throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "scroll_element requires x/y or stateId + element_index"])
402
+ }
403
+ CGWarpMouseCursorPosition(point)
404
+ let direction = params["direction"] as? String ?? "down"
405
+ let pages = params["pages"] as? Double ?? 1.0
406
+ let amount = Int32(max(1.0, abs(pages) * 8.0))
407
+ let vertical = direction == "up" ? amount : direction == "down" ? -amount : 0
408
+ let horizontal = direction == "left" ? amount : direction == "right" ? -amount : 0
409
+ CGEvent(scrollWheelEvent2Source: nil, units: .line, wheelCount: 2, wheel1: vertical, wheel2: horizontal, wheel3: 0)?.post(tap: .cghidEventTap)
410
+ return try getAppState(params)
411
+ }
412
+
413
+ func drag(_ params: JSON) throws -> JSON {
414
+ guard let fromX = params["from_x"] as? Double,
415
+ let fromY = params["from_y"] as? Double,
416
+ let toX = params["to_x"] as? Double,
417
+ let toY = params["to_y"] as? Double
418
+ else {
419
+ throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "drag requires from_x/from_y/to_x/to_y"])
420
+ }
421
+ try postDrag(from: CGPoint(x: fromX, y: fromY), to: CGPoint(x: toX, y: toY), button: cgMouseButton(params["mouse_button"]))
422
+ return try getAppState(params)
423
+ }
424
+
425
+ func handle(_ request: JSON) {
426
+ let id = request["id"]
427
+ let method = request["method"] as? String ?? ""
428
+ let params = request["params"] as? JSON ?? [:]
429
+
430
+ do {
431
+ switch method {
432
+ case "list_apps":
433
+ respond(id: id, result: listApps())
434
+ case "get_app_state":
435
+ respond(id: id, result: try getAppState(params))
436
+ case "click_element":
437
+ respond(id: id, result: try click(params))
438
+ case "perform_secondary_action":
439
+ respond(id: id, result: try performSecondaryAction(params))
440
+ case "set_value":
441
+ respond(id: id, result: try setValue(params))
442
+ case "type_text":
443
+ respond(id: id, result: try typeText(params))
444
+ case "press_key":
445
+ respond(id: id, result: try pressKey(params))
446
+ case "scroll_element":
447
+ respond(id: id, result: try scrollElement(params))
448
+ case "drag":
449
+ respond(id: id, result: try drag(params))
450
+ default:
451
+ respondError(id: id, "Method is not implemented yet: \(method)")
452
+ }
453
+ } catch {
454
+ respondError(id: id, error.localizedDescription)
455
+ }
456
+ }
457
+
458
+ while let line = readLine() {
459
+ guard let data = line.data(using: .utf8),
460
+ let object = try? JSONSerialization.jsonObject(with: data),
461
+ let request = object as? JSON
462
+ else {
463
+ respondError(id: nil, "Invalid JSON request")
464
+ continue
465
+ }
466
+ handle(request)
467
+ }
@@ -0,0 +1,124 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ import readline from 'node:readline';
3
+
4
+ type JsonRecord = Record<string, unknown>;
5
+
6
+ type PendingRequest = {
7
+ resolve: (value: unknown) => void;
8
+ reject: (error: Error) => void;
9
+ timer: ReturnType<typeof setTimeout>;
10
+ };
11
+
12
+ const DEFAULT_TIMEOUT_MS = Number.parseInt(process.env.CLOUDCLI_SEMANTICS_HELPER_TIMEOUT_MS || '60000', 10);
13
+
14
+ function timeoutMs(): number {
15
+ return Number.isFinite(DEFAULT_TIMEOUT_MS) && DEFAULT_TIMEOUT_MS > 0 ? DEFAULT_TIMEOUT_MS : 60000;
16
+ }
17
+
18
+ function errorMessage(error: unknown): string {
19
+ return error instanceof Error ? error.message : String(error);
20
+ }
21
+
22
+ export class SemanticHelperProcess {
23
+ private child: ChildProcessWithoutNullStreams | null = null;
24
+ private reader: readline.Interface | null = null;
25
+ private nextId = 1;
26
+ private pending = new Map<number, PendingRequest>();
27
+
28
+ constructor(private readonly executablePath: string) {}
29
+
30
+ async request(method: string, params: JsonRecord): Promise<unknown> {
31
+ this.ensureStarted();
32
+ const child = this.child;
33
+ if (!child?.stdin.writable) {
34
+ throw new Error('Semantic helper process is not running.');
35
+ }
36
+
37
+ const id = this.nextId++;
38
+ return new Promise((resolve, reject) => {
39
+ const timer = setTimeout(() => {
40
+ this.pending.delete(id);
41
+ reject(new Error(`Semantic helper request timed out: ${method}`));
42
+ }, timeoutMs());
43
+ this.pending.set(id, { resolve, reject, timer });
44
+ child.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
45
+ });
46
+ }
47
+
48
+ stop(): void {
49
+ const child = this.child;
50
+ this.child = null;
51
+ this.reader?.close();
52
+ this.reader = null;
53
+ this.rejectAll('Semantic helper stopped.');
54
+ if (child) {
55
+ try { child.kill('SIGTERM'); } catch { /* noop */ }
56
+ }
57
+ }
58
+
59
+ private ensureStarted(): void {
60
+ if (this.child) {
61
+ return;
62
+ }
63
+
64
+ this.child = spawn(this.executablePath, [], {
65
+ stdio: ['pipe', 'pipe', 'pipe'],
66
+ windowsHide: true,
67
+ });
68
+
69
+ this.reader = readline.createInterface({ input: this.child.stdout });
70
+ this.reader.on('line', (line) => this.handleLine(line));
71
+
72
+ this.child.stderr.on('data', (chunk) => {
73
+ const text = String(chunk).trim();
74
+ if (text) {
75
+ console.error('[SemanticHelper]', text);
76
+ }
77
+ });
78
+
79
+ this.child.once('error', (error) => {
80
+ this.child = null;
81
+ this.rejectAll(`Failed to start semantic helper: ${error.message}`);
82
+ });
83
+
84
+ this.child.once('exit', (code) => {
85
+ this.child = null;
86
+ this.rejectAll(`Semantic helper exited with code ${code ?? 'null'}.`);
87
+ });
88
+ }
89
+
90
+ private handleLine(line: string): void {
91
+ let message: JsonRecord;
92
+ try {
93
+ message = JSON.parse(line) as JsonRecord;
94
+ } catch (error) {
95
+ console.error('[SemanticHelper] Invalid JSON response:', errorMessage(error));
96
+ return;
97
+ }
98
+
99
+ const id = typeof message.id === 'number' ? message.id : null;
100
+ if (id === null) {
101
+ return;
102
+ }
103
+ const pending = this.pending.get(id);
104
+ if (!pending) {
105
+ return;
106
+ }
107
+ clearTimeout(pending.timer);
108
+ this.pending.delete(id);
109
+
110
+ if (message.error) {
111
+ pending.reject(new Error(typeof message.error === 'string' ? message.error : 'Semantic helper request failed.'));
112
+ return;
113
+ }
114
+ pending.resolve(message.result);
115
+ }
116
+
117
+ private rejectAll(reason: string): void {
118
+ for (const [id, request] of this.pending.entries()) {
119
+ clearTimeout(request.timer);
120
+ request.reject(new Error(reason));
121
+ this.pending.delete(id);
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export type SemanticHelperPlatform = 'darwin' | 'win32';
8
+
9
+ export type SemanticHelperResolution = {
10
+ available: boolean;
11
+ path: string | null;
12
+ source: 'bundled' | 'dev' | 'missing';
13
+ platform: NodeJS.Platform;
14
+ arch: NodeJS.Architecture;
15
+ reason?: string;
16
+ };
17
+
18
+ function helperExecutableName(platform: NodeJS.Platform): string | null {
19
+ if (platform === 'darwin') {
20
+ return 'CloudCLISemantics';
21
+ }
22
+ if (platform === 'win32') {
23
+ return 'CloudCLISemantics.exe';
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function pathExists(filePath: string): boolean {
29
+ try {
30
+ fs.accessSync(filePath, fs.constants.X_OK);
31
+ return true;
32
+ } catch {
33
+ try {
34
+ fs.accessSync(filePath, fs.constants.F_OK);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ }
41
+
42
+ function candidatePaths(platform: NodeJS.Platform, arch: NodeJS.Architecture): Array<{ source: 'bundled' | 'dev'; path: string }> {
43
+ const executable = helperExecutableName(platform);
44
+ if (!executable) {
45
+ return [];
46
+ }
47
+
48
+ const platformArch = `${platform}-${arch}`;
49
+ return [
50
+ {
51
+ source: 'bundled',
52
+ path: path.resolve(__dirname, '..', 'bin', platformArch, executable),
53
+ },
54
+ {
55
+ source: 'dev',
56
+ path: path.resolve(process.cwd(), 'server', 'modules', 'computer-use', 'semantics', 'bin', platformArch, executable),
57
+ },
58
+ ];
59
+ }
60
+
61
+ export function resolveSemanticHelper(
62
+ platform: NodeJS.Platform = process.platform,
63
+ arch: NodeJS.Architecture = process.arch,
64
+ ): SemanticHelperResolution {
65
+ const executable = helperExecutableName(platform);
66
+ if (!executable) {
67
+ return {
68
+ available: false,
69
+ path: null,
70
+ source: 'missing',
71
+ platform,
72
+ arch,
73
+ reason: `Semantic Computer Use helper is not supported on ${platform}.`,
74
+ };
75
+ }
76
+
77
+ for (const candidate of candidatePaths(platform, arch)) {
78
+ if (pathExists(candidate.path)) {
79
+ return {
80
+ available: true,
81
+ path: candidate.path,
82
+ source: candidate.source,
83
+ platform,
84
+ arch,
85
+ };
86
+ }
87
+ }
88
+
89
+ return {
90
+ available: false,
91
+ path: null,
92
+ source: 'missing',
93
+ platform,
94
+ arch,
95
+ reason: `Bundled semantic helper was not found for ${platform}-${arch} (${executable}).`,
96
+ };
97
+ }
@@ -0,0 +1,11 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+ <PropertyGroup>
3
+ <OutputType>Exe</OutputType>
4
+ <TargetFramework>net8.0-windows</TargetFramework>
5
+ <Nullable>enable</Nullable>
6
+ <ImplicitUsings>enable</ImplicitUsings>
7
+ <UseWindowsForms>true</UseWindowsForms>
8
+ <UseWPF>true</UseWPF>
9
+ <AssemblyName>CloudCLISemantics</AssemblyName>
10
+ </PropertyGroup>
11
+ </Project>