cowork-os 0.3.21

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 (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1638 -0
  3. package/bin/cowork.js +42 -0
  4. package/build/entitlements.mac.plist +16 -0
  5. package/build/icon.icns +0 -0
  6. package/build/icon.png +0 -0
  7. package/dist/electron/electron/activity/ActivityRepository.js +190 -0
  8. package/dist/electron/electron/agent/browser/browser-service.js +639 -0
  9. package/dist/electron/electron/agent/context-manager.js +225 -0
  10. package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
  11. package/dist/electron/electron/agent/daemon.js +975 -0
  12. package/dist/electron/electron/agent/executor.js +3561 -0
  13. package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
  14. package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
  15. package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
  16. package/dist/electron/electron/agent/llm/index.js +34 -0
  17. package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
  18. package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
  19. package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
  20. package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
  21. package/dist/electron/electron/agent/llm/pricing.js +160 -0
  22. package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
  23. package/dist/electron/electron/agent/llm/types.js +178 -0
  24. package/dist/electron/electron/agent/queue-manager.js +378 -0
  25. package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
  26. package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
  27. package/dist/electron/electron/agent/sandbox/runner.js +410 -0
  28. package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
  29. package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
  30. package/dist/electron/electron/agent/search/brave-provider.js +119 -0
  31. package/dist/electron/electron/agent/search/google-provider.js +100 -0
  32. package/dist/electron/electron/agent/search/index.js +28 -0
  33. package/dist/electron/electron/agent/search/provider-factory.js +395 -0
  34. package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
  35. package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
  36. package/dist/electron/electron/agent/search/types.js +40 -0
  37. package/dist/electron/electron/agent/security/index.js +12 -0
  38. package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
  39. package/dist/electron/electron/agent/security/output-filter.js +217 -0
  40. package/dist/electron/electron/agent/skill-eligibility.js +281 -0
  41. package/dist/electron/electron/agent/skill-registry.js +396 -0
  42. package/dist/electron/electron/agent/skills/document.js +878 -0
  43. package/dist/electron/electron/agent/skills/image-generator.js +225 -0
  44. package/dist/electron/electron/agent/skills/organizer.js +141 -0
  45. package/dist/electron/electron/agent/skills/presentation.js +367 -0
  46. package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
  47. package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
  48. package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
  49. package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
  50. package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
  51. package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
  52. package/dist/electron/electron/agent/tools/file-tools.js +719 -0
  53. package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
  54. package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
  55. package/dist/electron/electron/agent/tools/image-tools.js +111 -0
  56. package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
  57. package/dist/electron/electron/agent/tools/node-tools.js +476 -0
  58. package/dist/electron/electron/agent/tools/registry.js +2719 -0
  59. package/dist/electron/electron/agent/tools/search-tools.js +91 -0
  60. package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
  61. package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
  62. package/dist/electron/electron/agent/tools/system-tools.js +578 -0
  63. package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
  64. package/dist/electron/electron/agent/tools/x-tools.js +264 -0
  65. package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
  66. package/dist/electron/electron/agents/HeartbeatService.js +356 -0
  67. package/dist/electron/electron/agents/MentionRepository.js +197 -0
  68. package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
  69. package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
  70. package/dist/electron/electron/canvas/canvas-manager.js +714 -0
  71. package/dist/electron/electron/canvas/canvas-preload.js +53 -0
  72. package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
  73. package/dist/electron/electron/canvas/canvas-store.js +174 -0
  74. package/dist/electron/electron/canvas/index.js +13 -0
  75. package/dist/electron/electron/control-plane/client.js +364 -0
  76. package/dist/electron/electron/control-plane/handlers.js +572 -0
  77. package/dist/electron/electron/control-plane/index.js +41 -0
  78. package/dist/electron/electron/control-plane/node-manager.js +264 -0
  79. package/dist/electron/electron/control-plane/protocol.js +194 -0
  80. package/dist/electron/electron/control-plane/remote-client.js +437 -0
  81. package/dist/electron/electron/control-plane/server.js +640 -0
  82. package/dist/electron/electron/control-plane/settings.js +369 -0
  83. package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
  84. package/dist/electron/electron/cron/index.js +30 -0
  85. package/dist/electron/electron/cron/schedule.js +190 -0
  86. package/dist/electron/electron/cron/service.js +614 -0
  87. package/dist/electron/electron/cron/store.js +155 -0
  88. package/dist/electron/electron/cron/types.js +82 -0
  89. package/dist/electron/electron/cron/webhook.js +258 -0
  90. package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
  91. package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
  92. package/dist/electron/electron/database/repositories.js +1781 -0
  93. package/dist/electron/electron/database/schema.js +978 -0
  94. package/dist/electron/electron/extensions/index.js +33 -0
  95. package/dist/electron/electron/extensions/loader.js +313 -0
  96. package/dist/electron/electron/extensions/registry.js +485 -0
  97. package/dist/electron/electron/extensions/types.js +11 -0
  98. package/dist/electron/electron/gateway/channel-registry.js +1102 -0
  99. package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
  100. package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
  101. package/dist/electron/electron/gateway/channels/discord.js +975 -0
  102. package/dist/electron/electron/gateway/channels/email-client.js +593 -0
  103. package/dist/electron/electron/gateway/channels/email.js +443 -0
  104. package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
  105. package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
  106. package/dist/electron/electron/gateway/channels/imessage.js +465 -0
  107. package/dist/electron/electron/gateway/channels/index.js +36 -0
  108. package/dist/electron/electron/gateway/channels/line-client.js +470 -0
  109. package/dist/electron/electron/gateway/channels/line.js +479 -0
  110. package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
  111. package/dist/electron/electron/gateway/channels/matrix.js +592 -0
  112. package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
  113. package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
  114. package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
  115. package/dist/electron/electron/gateway/channels/signal.js +582 -0
  116. package/dist/electron/electron/gateway/channels/slack.js +415 -0
  117. package/dist/electron/electron/gateway/channels/teams.js +596 -0
  118. package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
  119. package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
  120. package/dist/electron/electron/gateway/channels/twitch.js +396 -0
  121. package/dist/electron/electron/gateway/channels/types.js +8 -0
  122. package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
  123. package/dist/electron/electron/gateway/context-policy.js +268 -0
  124. package/dist/electron/electron/gateway/index.js +1063 -0
  125. package/dist/electron/electron/gateway/infrastructure.js +496 -0
  126. package/dist/electron/electron/gateway/router.js +2700 -0
  127. package/dist/electron/electron/gateway/security.js +375 -0
  128. package/dist/electron/electron/gateway/session.js +115 -0
  129. package/dist/electron/electron/gateway/tunnel.js +503 -0
  130. package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
  131. package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
  132. package/dist/electron/electron/hooks/index.js +46 -0
  133. package/dist/electron/electron/hooks/mappings.js +381 -0
  134. package/dist/electron/electron/hooks/server.js +480 -0
  135. package/dist/electron/electron/hooks/settings.js +447 -0
  136. package/dist/electron/electron/hooks/types.js +41 -0
  137. package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
  138. package/dist/electron/electron/ipc/handlers.js +3138 -0
  139. package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
  140. package/dist/electron/electron/main.js +448 -0
  141. package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
  142. package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
  143. package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
  144. package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
  145. package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
  146. package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
  147. package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
  148. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
  149. package/dist/electron/electron/mcp/settings.js +446 -0
  150. package/dist/electron/electron/mcp/types.js +59 -0
  151. package/dist/electron/electron/memory/MemoryService.js +435 -0
  152. package/dist/electron/electron/notifications/index.js +17 -0
  153. package/dist/electron/electron/notifications/service.js +118 -0
  154. package/dist/electron/electron/notifications/store.js +144 -0
  155. package/dist/electron/electron/preload.js +842 -0
  156. package/dist/electron/electron/reports/StandupReportService.js +272 -0
  157. package/dist/electron/electron/security/concurrency.js +293 -0
  158. package/dist/electron/electron/security/index.js +15 -0
  159. package/dist/electron/electron/security/policy-manager.js +435 -0
  160. package/dist/electron/electron/settings/appearance-manager.js +193 -0
  161. package/dist/electron/electron/settings/personality-manager.js +724 -0
  162. package/dist/electron/electron/settings/x-manager.js +58 -0
  163. package/dist/electron/electron/tailscale/exposure.js +188 -0
  164. package/dist/electron/electron/tailscale/index.js +28 -0
  165. package/dist/electron/electron/tailscale/settings.js +205 -0
  166. package/dist/electron/electron/tailscale/tailscale.js +355 -0
  167. package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
  168. package/dist/electron/electron/tray/TrayManager.js +895 -0
  169. package/dist/electron/electron/tray/index.js +9 -0
  170. package/dist/electron/electron/updater/index.js +6 -0
  171. package/dist/electron/electron/updater/update-manager.js +418 -0
  172. package/dist/electron/electron/utils/env-migration.js +209 -0
  173. package/dist/electron/electron/utils/process.js +102 -0
  174. package/dist/electron/electron/utils/rate-limiter.js +104 -0
  175. package/dist/electron/electron/utils/validation.js +419 -0
  176. package/dist/electron/electron/utils/x-cli.js +177 -0
  177. package/dist/electron/electron/voice/VoiceService.js +507 -0
  178. package/dist/electron/electron/voice/index.js +14 -0
  179. package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
  180. package/dist/electron/shared/channelMessages.js +170 -0
  181. package/dist/electron/shared/types.js +1185 -0
  182. package/package.json +159 -0
  183. package/resources/skills/1password.json +10 -0
  184. package/resources/skills/add-documentation.json +31 -0
  185. package/resources/skills/analyze-csv.json +17 -0
  186. package/resources/skills/apple-notes.json +10 -0
  187. package/resources/skills/apple-reminders.json +10 -0
  188. package/resources/skills/auto-commenter.json +10 -0
  189. package/resources/skills/bear-notes.json +10 -0
  190. package/resources/skills/bird.json +35 -0
  191. package/resources/skills/blogwatcher.json +10 -0
  192. package/resources/skills/blucli.json +10 -0
  193. package/resources/skills/bluebubbles.json +10 -0
  194. package/resources/skills/camsnap.json +10 -0
  195. package/resources/skills/clean-imports.json +18 -0
  196. package/resources/skills/code-review.json +18 -0
  197. package/resources/skills/coding-agent.json +10 -0
  198. package/resources/skills/compare-files.json +23 -0
  199. package/resources/skills/convert-code.json +34 -0
  200. package/resources/skills/create-changelog.json +24 -0
  201. package/resources/skills/debug-error.json +17 -0
  202. package/resources/skills/dependency-check.json +10 -0
  203. package/resources/skills/discord.json +10 -0
  204. package/resources/skills/eightctl.json +10 -0
  205. package/resources/skills/explain-code.json +29 -0
  206. package/resources/skills/extract-todos.json +18 -0
  207. package/resources/skills/food-order.json +10 -0
  208. package/resources/skills/gemini.json +10 -0
  209. package/resources/skills/generate-readme.json +10 -0
  210. package/resources/skills/gifgrep.json +10 -0
  211. package/resources/skills/git-commit.json +10 -0
  212. package/resources/skills/github.json +10 -0
  213. package/resources/skills/gog.json +10 -0
  214. package/resources/skills/goplaces.json +10 -0
  215. package/resources/skills/himalaya.json +10 -0
  216. package/resources/skills/imsg.json +10 -0
  217. package/resources/skills/karpathy-guidelines.json +12 -0
  218. package/resources/skills/last30days.json +26 -0
  219. package/resources/skills/local-places.json +10 -0
  220. package/resources/skills/mcporter.json +10 -0
  221. package/resources/skills/model-usage.json +10 -0
  222. package/resources/skills/nano-banana-pro.json +10 -0
  223. package/resources/skills/nano-pdf.json +10 -0
  224. package/resources/skills/notion.json +10 -0
  225. package/resources/skills/obsidian.json +10 -0
  226. package/resources/skills/openai-image-gen.json +10 -0
  227. package/resources/skills/openai-whisper-api.json +10 -0
  228. package/resources/skills/openai-whisper.json +10 -0
  229. package/resources/skills/openhue.json +10 -0
  230. package/resources/skills/oracle.json +10 -0
  231. package/resources/skills/ordercli.json +10 -0
  232. package/resources/skills/peekaboo.json +10 -0
  233. package/resources/skills/project-structure.json +10 -0
  234. package/resources/skills/proofread.json +17 -0
  235. package/resources/skills/refactor-code.json +31 -0
  236. package/resources/skills/rename-symbol.json +23 -0
  237. package/resources/skills/sag.json +10 -0
  238. package/resources/skills/security-audit.json +18 -0
  239. package/resources/skills/session-logs.json +10 -0
  240. package/resources/skills/sherpa-onnx-tts.json +10 -0
  241. package/resources/skills/skill-creator.json +15 -0
  242. package/resources/skills/skill-hub.json +29 -0
  243. package/resources/skills/slack.json +10 -0
  244. package/resources/skills/songsee.json +10 -0
  245. package/resources/skills/sonoscli.json +10 -0
  246. package/resources/skills/spotify-player.json +10 -0
  247. package/resources/skills/startup-cfo.json +55 -0
  248. package/resources/skills/summarize-folder.json +18 -0
  249. package/resources/skills/summarize.json +10 -0
  250. package/resources/skills/things-mac.json +10 -0
  251. package/resources/skills/tmux.json +10 -0
  252. package/resources/skills/translate.json +36 -0
  253. package/resources/skills/trello.json +10 -0
  254. package/resources/skills/video-frames.json +10 -0
  255. package/resources/skills/voice-call.json +10 -0
  256. package/resources/skills/wacli.json +10 -0
  257. package/resources/skills/weather.json +10 -0
  258. package/resources/skills/write-tests.json +31 -0
  259. package/src/electron/activity/ActivityRepository.ts +238 -0
  260. package/src/electron/agent/browser/browser-service.ts +721 -0
  261. package/src/electron/agent/context-manager.ts +257 -0
  262. package/src/electron/agent/custom-skill-loader.ts +634 -0
  263. package/src/electron/agent/daemon.ts +1097 -0
  264. package/src/electron/agent/executor.ts +4017 -0
  265. package/src/electron/agent/llm/anthropic-provider.ts +175 -0
  266. package/src/electron/agent/llm/bedrock-provider.ts +236 -0
  267. package/src/electron/agent/llm/gemini-provider.ts +422 -0
  268. package/src/electron/agent/llm/index.ts +9 -0
  269. package/src/electron/agent/llm/ollama-provider.ts +347 -0
  270. package/src/electron/agent/llm/openai-oauth.ts +127 -0
  271. package/src/electron/agent/llm/openai-provider.ts +686 -0
  272. package/src/electron/agent/llm/openrouter-provider.ts +273 -0
  273. package/src/electron/agent/llm/pricing.ts +180 -0
  274. package/src/electron/agent/llm/provider-factory.ts +971 -0
  275. package/src/electron/agent/llm/types.ts +291 -0
  276. package/src/electron/agent/queue-manager.ts +408 -0
  277. package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
  278. package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
  279. package/src/electron/agent/sandbox/runner.ts +453 -0
  280. package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
  281. package/src/electron/agent/sandbox/security-utils.ts +251 -0
  282. package/src/electron/agent/search/brave-provider.ts +141 -0
  283. package/src/electron/agent/search/google-provider.ts +131 -0
  284. package/src/electron/agent/search/index.ts +6 -0
  285. package/src/electron/agent/search/provider-factory.ts +450 -0
  286. package/src/electron/agent/search/serpapi-provider.ts +138 -0
  287. package/src/electron/agent/search/tavily-provider.ts +108 -0
  288. package/src/electron/agent/search/types.ts +118 -0
  289. package/src/electron/agent/security/index.ts +20 -0
  290. package/src/electron/agent/security/input-sanitizer.ts +380 -0
  291. package/src/electron/agent/security/output-filter.ts +259 -0
  292. package/src/electron/agent/skill-eligibility.ts +334 -0
  293. package/src/electron/agent/skill-registry.ts +457 -0
  294. package/src/electron/agent/skills/document.ts +1070 -0
  295. package/src/electron/agent/skills/image-generator.ts +272 -0
  296. package/src/electron/agent/skills/organizer.ts +131 -0
  297. package/src/electron/agent/skills/presentation.ts +418 -0
  298. package/src/electron/agent/skills/spreadsheet.ts +166 -0
  299. package/src/electron/agent/tools/browser-tools.ts +546 -0
  300. package/src/electron/agent/tools/builtin-settings.ts +422 -0
  301. package/src/electron/agent/tools/canvas-tools.ts +572 -0
  302. package/src/electron/agent/tools/cron-tools.ts +723 -0
  303. package/src/electron/agent/tools/edit-tools.ts +196 -0
  304. package/src/electron/agent/tools/file-tools.ts +811 -0
  305. package/src/electron/agent/tools/glob-tools.ts +303 -0
  306. package/src/electron/agent/tools/grep-tools.ts +432 -0
  307. package/src/electron/agent/tools/image-tools.ts +126 -0
  308. package/src/electron/agent/tools/mention-tools.ts +371 -0
  309. package/src/electron/agent/tools/node-tools.ts +550 -0
  310. package/src/electron/agent/tools/registry.ts +3052 -0
  311. package/src/electron/agent/tools/search-tools.ts +111 -0
  312. package/src/electron/agent/tools/shell-tools.ts +651 -0
  313. package/src/electron/agent/tools/skill-tools.ts +340 -0
  314. package/src/electron/agent/tools/system-tools.ts +665 -0
  315. package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
  316. package/src/electron/agent/tools/x-tools.ts +267 -0
  317. package/src/electron/agents/AgentRoleRepository.ts +557 -0
  318. package/src/electron/agents/HeartbeatService.ts +469 -0
  319. package/src/electron/agents/MentionRepository.ts +242 -0
  320. package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
  321. package/src/electron/agents/WorkingStateRepository.ts +278 -0
  322. package/src/electron/canvas/canvas-manager.ts +818 -0
  323. package/src/electron/canvas/canvas-preload.ts +102 -0
  324. package/src/electron/canvas/canvas-protocol.ts +174 -0
  325. package/src/electron/canvas/canvas-store.ts +200 -0
  326. package/src/electron/canvas/index.ts +8 -0
  327. package/src/electron/control-plane/client.ts +527 -0
  328. package/src/electron/control-plane/handlers.ts +723 -0
  329. package/src/electron/control-plane/index.ts +51 -0
  330. package/src/electron/control-plane/node-manager.ts +322 -0
  331. package/src/electron/control-plane/protocol.ts +269 -0
  332. package/src/electron/control-plane/remote-client.ts +517 -0
  333. package/src/electron/control-plane/server.ts +853 -0
  334. package/src/electron/control-plane/settings.ts +401 -0
  335. package/src/electron/control-plane/ssh-tunnel.ts +624 -0
  336. package/src/electron/cron/index.ts +9 -0
  337. package/src/electron/cron/schedule.ts +217 -0
  338. package/src/electron/cron/service.ts +743 -0
  339. package/src/electron/cron/store.ts +165 -0
  340. package/src/electron/cron/types.ts +291 -0
  341. package/src/electron/cron/webhook.ts +303 -0
  342. package/src/electron/database/SecureSettingsRepository.ts +514 -0
  343. package/src/electron/database/TaskLabelRepository.ts +148 -0
  344. package/src/electron/database/repositories.ts +2397 -0
  345. package/src/electron/database/schema.ts +1017 -0
  346. package/src/electron/extensions/index.ts +18 -0
  347. package/src/electron/extensions/loader.ts +336 -0
  348. package/src/electron/extensions/registry.ts +546 -0
  349. package/src/electron/extensions/types.ts +372 -0
  350. package/src/electron/gateway/channel-registry.ts +1267 -0
  351. package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
  352. package/src/electron/gateway/channels/bluebubbles.ts +509 -0
  353. package/src/electron/gateway/channels/discord.ts +1150 -0
  354. package/src/electron/gateway/channels/email-client.ts +708 -0
  355. package/src/electron/gateway/channels/email.ts +516 -0
  356. package/src/electron/gateway/channels/google-chat.ts +760 -0
  357. package/src/electron/gateway/channels/imessage-client.ts +473 -0
  358. package/src/electron/gateway/channels/imessage.ts +520 -0
  359. package/src/electron/gateway/channels/index.ts +21 -0
  360. package/src/electron/gateway/channels/line-client.ts +598 -0
  361. package/src/electron/gateway/channels/line.ts +559 -0
  362. package/src/electron/gateway/channels/matrix-client.ts +632 -0
  363. package/src/electron/gateway/channels/matrix.ts +655 -0
  364. package/src/electron/gateway/channels/mattermost-client.ts +526 -0
  365. package/src/electron/gateway/channels/mattermost.ts +550 -0
  366. package/src/electron/gateway/channels/signal-client.ts +722 -0
  367. package/src/electron/gateway/channels/signal.ts +666 -0
  368. package/src/electron/gateway/channels/slack.ts +458 -0
  369. package/src/electron/gateway/channels/teams.ts +681 -0
  370. package/src/electron/gateway/channels/telegram.ts +1727 -0
  371. package/src/electron/gateway/channels/twitch-client.ts +665 -0
  372. package/src/electron/gateway/channels/twitch.ts +468 -0
  373. package/src/electron/gateway/channels/types.ts +1002 -0
  374. package/src/electron/gateway/channels/whatsapp.ts +1101 -0
  375. package/src/electron/gateway/context-policy.ts +382 -0
  376. package/src/electron/gateway/index.ts +1274 -0
  377. package/src/electron/gateway/infrastructure.ts +645 -0
  378. package/src/electron/gateway/router.ts +3206 -0
  379. package/src/electron/gateway/security.ts +422 -0
  380. package/src/electron/gateway/session.ts +144 -0
  381. package/src/electron/gateway/tunnel.ts +626 -0
  382. package/src/electron/guardrails/guardrail-manager.ts +380 -0
  383. package/src/electron/hooks/gmail-watcher.ts +355 -0
  384. package/src/electron/hooks/index.ts +30 -0
  385. package/src/electron/hooks/mappings.ts +404 -0
  386. package/src/electron/hooks/server.ts +574 -0
  387. package/src/electron/hooks/settings.ts +466 -0
  388. package/src/electron/hooks/types.ts +245 -0
  389. package/src/electron/ipc/canvas-handlers.ts +223 -0
  390. package/src/electron/ipc/handlers.ts +3661 -0
  391. package/src/electron/ipc/mission-control-handlers.ts +182 -0
  392. package/src/electron/main.ts +496 -0
  393. package/src/electron/mcp/client/MCPClientManager.ts +406 -0
  394. package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
  395. package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
  396. package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
  397. package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
  398. package/src/electron/mcp/host/MCPHostServer.ts +388 -0
  399. package/src/electron/mcp/host/ToolAdapter.ts +140 -0
  400. package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
  401. package/src/electron/mcp/settings.ts +468 -0
  402. package/src/electron/mcp/types.ts +371 -0
  403. package/src/electron/memory/MemoryService.ts +523 -0
  404. package/src/electron/notifications/index.ts +16 -0
  405. package/src/electron/notifications/service.ts +161 -0
  406. package/src/electron/notifications/store.ts +163 -0
  407. package/src/electron/preload.ts +2845 -0
  408. package/src/electron/reports/StandupReportService.ts +356 -0
  409. package/src/electron/security/concurrency.ts +333 -0
  410. package/src/electron/security/index.ts +17 -0
  411. package/src/electron/security/policy-manager.ts +539 -0
  412. package/src/electron/settings/appearance-manager.ts +182 -0
  413. package/src/electron/settings/personality-manager.ts +800 -0
  414. package/src/electron/settings/x-manager.ts +62 -0
  415. package/src/electron/tailscale/exposure.ts +262 -0
  416. package/src/electron/tailscale/index.ts +34 -0
  417. package/src/electron/tailscale/settings.ts +218 -0
  418. package/src/electron/tailscale/tailscale.ts +379 -0
  419. package/src/electron/tray/QuickInputWindow.ts +609 -0
  420. package/src/electron/tray/TrayManager.ts +1005 -0
  421. package/src/electron/tray/index.ts +6 -0
  422. package/src/electron/updater/index.ts +1 -0
  423. package/src/electron/updater/update-manager.ts +447 -0
  424. package/src/electron/utils/env-migration.ts +203 -0
  425. package/src/electron/utils/process.ts +124 -0
  426. package/src/electron/utils/rate-limiter.ts +130 -0
  427. package/src/electron/utils/validation.ts +493 -0
  428. package/src/electron/utils/x-cli.ts +198 -0
  429. package/src/electron/voice/VoiceService.ts +583 -0
  430. package/src/electron/voice/index.ts +9 -0
  431. package/src/electron/voice/voice-settings-manager.ts +403 -0
  432. package/src/renderer/App.tsx +775 -0
  433. package/src/renderer/components/ActivityFeed.tsx +407 -0
  434. package/src/renderer/components/ActivityFeedItem.tsx +285 -0
  435. package/src/renderer/components/AgentRoleCard.tsx +343 -0
  436. package/src/renderer/components/AgentRoleEditor.tsx +805 -0
  437. package/src/renderer/components/AgentSquadSettings.tsx +295 -0
  438. package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
  439. package/src/renderer/components/AppearanceSettings.tsx +122 -0
  440. package/src/renderer/components/ApprovalDialog.tsx +100 -0
  441. package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
  442. package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
  443. package/src/renderer/components/CanvasPreview.tsx +1189 -0
  444. package/src/renderer/components/CommandOutput.tsx +202 -0
  445. package/src/renderer/components/ContextPolicySettings.tsx +523 -0
  446. package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
  447. package/src/renderer/components/DisclaimerModal.tsx +124 -0
  448. package/src/renderer/components/DiscordSettings.tsx +436 -0
  449. package/src/renderer/components/EmailSettings.tsx +606 -0
  450. package/src/renderer/components/ExtensionsSettings.tsx +542 -0
  451. package/src/renderer/components/FileViewer.tsx +224 -0
  452. package/src/renderer/components/GoogleChatSettings.tsx +535 -0
  453. package/src/renderer/components/GuardrailSettings.tsx +487 -0
  454. package/src/renderer/components/HooksSettings.tsx +581 -0
  455. package/src/renderer/components/ImessageSettings.tsx +484 -0
  456. package/src/renderer/components/LineSettings.tsx +483 -0
  457. package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
  458. package/src/renderer/components/MCPSettings.tsx +943 -0
  459. package/src/renderer/components/MainContent.tsx +2433 -0
  460. package/src/renderer/components/MatrixSettings.tsx +510 -0
  461. package/src/renderer/components/MattermostSettings.tsx +473 -0
  462. package/src/renderer/components/MemorySettings.tsx +247 -0
  463. package/src/renderer/components/MentionBadge.tsx +87 -0
  464. package/src/renderer/components/MentionInput.tsx +409 -0
  465. package/src/renderer/components/MentionList.tsx +476 -0
  466. package/src/renderer/components/MissionControlPanel.tsx +1995 -0
  467. package/src/renderer/components/NodesSettings.tsx +316 -0
  468. package/src/renderer/components/NotificationPanel.tsx +481 -0
  469. package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
  470. package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
  471. package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
  472. package/src/renderer/components/Onboarding/index.ts +3 -0
  473. package/src/renderer/components/OnboardingModal.tsx +698 -0
  474. package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
  475. package/src/renderer/components/PersonalitySettings.tsx +597 -0
  476. package/src/renderer/components/QueueSettings.tsx +119 -0
  477. package/src/renderer/components/QuickTaskFAB.tsx +71 -0
  478. package/src/renderer/components/RightPanel.tsx +413 -0
  479. package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
  480. package/src/renderer/components/SearchSettings.tsx +328 -0
  481. package/src/renderer/components/Settings.tsx +1504 -0
  482. package/src/renderer/components/Sidebar.tsx +344 -0
  483. package/src/renderer/components/SignalSettings.tsx +673 -0
  484. package/src/renderer/components/SkillHubBrowser.tsx +458 -0
  485. package/src/renderer/components/SkillParameterModal.tsx +185 -0
  486. package/src/renderer/components/SkillsSettings.tsx +451 -0
  487. package/src/renderer/components/SlackSettings.tsx +442 -0
  488. package/src/renderer/components/StandupReportViewer.tsx +614 -0
  489. package/src/renderer/components/TaskBoard.tsx +498 -0
  490. package/src/renderer/components/TaskBoardCard.tsx +357 -0
  491. package/src/renderer/components/TaskBoardColumn.tsx +211 -0
  492. package/src/renderer/components/TaskLabelManager.tsx +472 -0
  493. package/src/renderer/components/TaskQueuePanel.tsx +144 -0
  494. package/src/renderer/components/TaskQuickActions.tsx +492 -0
  495. package/src/renderer/components/TaskTimeline.tsx +216 -0
  496. package/src/renderer/components/TaskView.tsx +162 -0
  497. package/src/renderer/components/TeamsSettings.tsx +518 -0
  498. package/src/renderer/components/TelegramSettings.tsx +421 -0
  499. package/src/renderer/components/Toast.tsx +76 -0
  500. package/src/renderer/components/TraySettings.tsx +189 -0
  501. package/src/renderer/components/TwitchSettings.tsx +511 -0
  502. package/src/renderer/components/UpdateSettings.tsx +295 -0
  503. package/src/renderer/components/VoiceIndicator.tsx +270 -0
  504. package/src/renderer/components/VoiceSettings.tsx +867 -0
  505. package/src/renderer/components/WhatsAppSettings.tsx +721 -0
  506. package/src/renderer/components/WorkingStateEditor.tsx +309 -0
  507. package/src/renderer/components/WorkingStateHistory.tsx +481 -0
  508. package/src/renderer/components/WorkspaceSelector.tsx +150 -0
  509. package/src/renderer/components/XSettings.tsx +311 -0
  510. package/src/renderer/global.d.ts +9 -0
  511. package/src/renderer/hooks/useAgentContext.ts +153 -0
  512. package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
  513. package/src/renderer/hooks/useVoiceInput.ts +268 -0
  514. package/src/renderer/index.html +12 -0
  515. package/src/renderer/main.tsx +10 -0
  516. package/src/renderer/public/cowork-os-logo.png +0 -0
  517. package/src/renderer/quick-input.html +164 -0
  518. package/src/renderer/styles/index.css +14504 -0
  519. package/src/renderer/utils/agentMessages.ts +749 -0
  520. package/src/renderer/utils/voice-directives.ts +169 -0
  521. package/src/shared/channelMessages.ts +213 -0
  522. package/src/shared/types.ts +3608 -0
  523. package/tsconfig.electron.json +26 -0
  524. package/tsconfig.json +26 -0
  525. package/tsconfig.node.json +10 -0
  526. package/vite.config.ts +23 -0
@@ -0,0 +1,853 @@
1
+ /**
2
+ * WebSocket Control Plane Server
3
+ *
4
+ * The main WebSocket server that handles client connections, authentication,
5
+ * and message routing for the control plane.
6
+ */
7
+
8
+ import http from 'http';
9
+ import { WebSocketServer, WebSocket } from 'ws';
10
+ import crypto from 'crypto';
11
+ import {
12
+ Frame,
13
+ RequestFrame,
14
+ FrameType,
15
+ parseFrame,
16
+ serializeFrame,
17
+ createResponseFrame,
18
+ createErrorResponse,
19
+ createEventFrame,
20
+ ErrorCodes,
21
+ Events,
22
+ Methods,
23
+ } from './protocol';
24
+ import {
25
+ ControlPlaneClient,
26
+ ClientRegistry,
27
+ type ClientScope,
28
+ } from './client';
29
+ import {
30
+ ControlPlaneSettingsManager,
31
+ type ControlPlaneSettings,
32
+ } from './settings';
33
+ import {
34
+ startTailscaleExposure,
35
+ stopTailscaleExposure,
36
+ getExposureStatus,
37
+ type TailscaleExposureResult,
38
+ } from '../tailscale';
39
+
40
+ /**
41
+ * Control plane server configuration
42
+ */
43
+ export interface ControlPlaneConfig {
44
+ /** Port to listen on (default: 18789) */
45
+ port?: number;
46
+ /** Host to bind to (default: 127.0.0.1) */
47
+ host?: string;
48
+ /** Authentication token */
49
+ token: string;
50
+ /** Handshake timeout in milliseconds (default: 10000) */
51
+ handshakeTimeoutMs?: number;
52
+ /** Heartbeat interval in milliseconds (default: 30000) */
53
+ heartbeatIntervalMs?: number;
54
+ /** Cleanup interval in milliseconds for disconnected clients (default: 60000) */
55
+ cleanupIntervalMs?: number;
56
+ /** Maximum payload size in bytes (default: 10MB) */
57
+ maxPayloadBytes?: number;
58
+ /** Maximum failed auth attempts before temporary ban (default: 5) */
59
+ maxAuthAttempts?: number;
60
+ /** Auth ban duration in milliseconds (default: 300000 = 5 minutes) */
61
+ authBanDurationMs?: number;
62
+ /** Event handler for server events */
63
+ onEvent?: (event: ControlPlaneServerEvent) => void;
64
+ }
65
+
66
+ /**
67
+ * Server events emitted for monitoring
68
+ */
69
+ export interface ControlPlaneServerEvent {
70
+ action: 'started' | 'stopped' | 'client_connected' | 'client_disconnected' | 'client_authenticated' | 'request' | 'error';
71
+ timestamp: number;
72
+ clientId?: string;
73
+ method?: string;
74
+ error?: string;
75
+ details?: unknown;
76
+ }
77
+
78
+ /**
79
+ * Method handler function signature
80
+ */
81
+ export type MethodHandler = (
82
+ client: ControlPlaneClient,
83
+ params?: unknown
84
+ ) => Promise<unknown>;
85
+
86
+ /**
87
+ * WebSocket Control Plane Server
88
+ */
89
+ export class ControlPlaneServer {
90
+ private httpServer: http.Server | null = null;
91
+ private wss: WebSocketServer | null = null;
92
+ private clients: ClientRegistry;
93
+ private config: Required<ControlPlaneConfig>;
94
+ private methods: Map<string, MethodHandler> = new Map();
95
+ private heartbeatInterval: NodeJS.Timeout | null = null;
96
+ private cleanupInterval: NodeJS.Timeout | null = null;
97
+ private tailscaleCleanup: (() => Promise<void>) | null = null;
98
+
99
+ // Rate limiting for auth attempts: Map<remoteAddress, { attempts: number, bannedUntil?: number }>
100
+ private authAttempts: Map<string, { attempts: number; bannedUntil?: number }> = new Map();
101
+
102
+ constructor(config: ControlPlaneConfig) {
103
+ this.config = {
104
+ port: config.port ?? 18789,
105
+ host: config.host ?? '127.0.0.1',
106
+ token: config.token,
107
+ handshakeTimeoutMs: config.handshakeTimeoutMs ?? 10000,
108
+ heartbeatIntervalMs: config.heartbeatIntervalMs ?? 30000,
109
+ cleanupIntervalMs: config.cleanupIntervalMs ?? 60000,
110
+ maxPayloadBytes: config.maxPayloadBytes ?? 10 * 1024 * 1024,
111
+ maxAuthAttempts: config.maxAuthAttempts ?? 5,
112
+ authBanDurationMs: config.authBanDurationMs ?? 5 * 60 * 1000, // 5 minutes
113
+ onEvent: config.onEvent ?? (() => {}),
114
+ };
115
+
116
+ this.clients = new ClientRegistry();
117
+ this.registerBuiltinMethods();
118
+ }
119
+
120
+ /**
121
+ * Check if the server is running
122
+ */
123
+ get isRunning(): boolean {
124
+ return this.httpServer !== null && this.wss !== null;
125
+ }
126
+
127
+ /**
128
+ * Get server address
129
+ */
130
+ getAddress(): { host: string; port: number; wsUrl: string } | null {
131
+ if (!this.httpServer) return null;
132
+ const addr = this.httpServer.address();
133
+ if (typeof addr === 'string' || !addr) return null;
134
+
135
+ return {
136
+ host: addr.address,
137
+ port: addr.port,
138
+ wsUrl: `ws://${addr.address}:${addr.port}`,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Start the control plane server
144
+ */
145
+ async start(): Promise<void> {
146
+ if (this.isRunning) {
147
+ console.info('[ControlPlane] Server already running');
148
+ return;
149
+ }
150
+
151
+ return new Promise((resolve, reject) => {
152
+ // Create HTTP server for WebSocket upgrade
153
+ this.httpServer = http.createServer((req, res) => {
154
+ // Health check endpoint
155
+ if (req.url === '/health' && req.method === 'GET') {
156
+ res.writeHead(200, { 'Content-Type': 'application/json' });
157
+ res.end(JSON.stringify({
158
+ status: 'ok',
159
+ timestamp: Date.now(),
160
+ clients: this.clients.count,
161
+ }));
162
+ return;
163
+ }
164
+
165
+ // Return 404 for other HTTP requests
166
+ res.writeHead(404, { 'Content-Type': 'application/json' });
167
+ res.end(JSON.stringify({ error: 'Not found' }));
168
+ });
169
+
170
+ // Create WebSocket server
171
+ this.wss = new WebSocketServer({
172
+ server: this.httpServer,
173
+ maxPayload: this.config.maxPayloadBytes,
174
+ });
175
+
176
+ // Handle new connections
177
+ this.wss.on('connection', (socket, request) => {
178
+ this.handleConnection(socket, request);
179
+ });
180
+
181
+ this.wss.on('error', (error) => {
182
+ console.error('[ControlPlane] WebSocket server error:', error);
183
+ this.emitEvent({ action: 'error', timestamp: Date.now(), error: String(error) });
184
+ });
185
+
186
+ this.httpServer.on('error', (error) => {
187
+ console.error('[ControlPlane] HTTP server error:', error);
188
+ reject(error);
189
+ });
190
+
191
+ // Start listening
192
+ this.httpServer.listen(this.config.port, this.config.host, () => {
193
+ console.info(`[ControlPlane] Server listening on ws://${this.config.host}:${this.config.port}`);
194
+ this.emitEvent({ action: 'started', timestamp: Date.now() });
195
+
196
+ // Start heartbeat interval
197
+ this.startHeartbeat();
198
+
199
+ // Start cleanup interval
200
+ this.startCleanup();
201
+
202
+ resolve();
203
+ });
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Start with Tailscale exposure
209
+ */
210
+ async startWithTailscale(): Promise<TailscaleExposureResult | null> {
211
+ const settings = ControlPlaneSettingsManager.loadSettings();
212
+
213
+ // Start the WebSocket server first
214
+ await this.start();
215
+
216
+ // If Tailscale is configured, start exposure
217
+ if (settings.tailscale.mode !== 'off') {
218
+ const result = await startTailscaleExposure({
219
+ mode: settings.tailscale.mode,
220
+ port: this.config.port,
221
+ resetOnExit: settings.tailscale.resetOnExit,
222
+ log: (msg) => console.log(msg),
223
+ warn: (msg) => console.warn(msg),
224
+ });
225
+
226
+ if (result.cleanup) {
227
+ this.tailscaleCleanup = result.cleanup;
228
+ }
229
+
230
+ return result;
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Stop the control plane server
238
+ */
239
+ async stop(): Promise<void> {
240
+ // Stop heartbeat
241
+ if (this.heartbeatInterval) {
242
+ clearInterval(this.heartbeatInterval);
243
+ this.heartbeatInterval = null;
244
+ }
245
+
246
+ // Stop cleanup
247
+ if (this.cleanupInterval) {
248
+ clearInterval(this.cleanupInterval);
249
+ this.cleanupInterval = null;
250
+ }
251
+
252
+ // Cleanup Tailscale
253
+ if (this.tailscaleCleanup) {
254
+ await this.tailscaleCleanup();
255
+ this.tailscaleCleanup = null;
256
+ }
257
+
258
+ // Broadcast shutdown event
259
+ this.clients.broadcast(Events.SHUTDOWN, { reason: 'Server stopping' });
260
+
261
+ // Close all client connections
262
+ this.clients.closeAll(1001, 'Server shutting down');
263
+
264
+ // Close WebSocket server
265
+ if (this.wss) {
266
+ this.wss.close();
267
+ this.wss = null;
268
+ }
269
+
270
+ // Close HTTP server
271
+ if (this.httpServer) {
272
+ return new Promise((resolve) => {
273
+ this.httpServer!.close(() => {
274
+ console.info('[ControlPlane] Server stopped');
275
+ this.emitEvent({ action: 'stopped', timestamp: Date.now() });
276
+ this.httpServer = null;
277
+ resolve();
278
+ });
279
+ });
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Register a method handler
285
+ */
286
+ registerMethod(method: string, handler: MethodHandler): void {
287
+ this.methods.set(method, handler);
288
+ }
289
+
290
+ /**
291
+ * Get server status
292
+ */
293
+ getStatus(): {
294
+ running: boolean;
295
+ address: ReturnType<ControlPlaneServer['getAddress']>;
296
+ clients: ReturnType<ClientRegistry['getStatus']>;
297
+ tailscale: ReturnType<typeof getExposureStatus>;
298
+ } {
299
+ return {
300
+ running: this.isRunning,
301
+ address: this.getAddress(),
302
+ clients: this.clients.getStatus(),
303
+ tailscale: getExposureStatus(),
304
+ };
305
+ }
306
+
307
+ /**
308
+ * Broadcast an event to all authenticated clients
309
+ */
310
+ broadcast(event: string, payload?: unknown): number {
311
+ return this.clients.broadcast(event, payload);
312
+ }
313
+
314
+ // ===== Private Methods =====
315
+
316
+ /**
317
+ * Handle a new WebSocket connection
318
+ */
319
+ private handleConnection(socket: WebSocket, request: http.IncomingMessage): void {
320
+ const remoteAddress =
321
+ (request.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||
322
+ request.socket.remoteAddress ||
323
+ 'unknown';
324
+ const userAgent = request.headers['user-agent'];
325
+ const origin = request.headers['origin'];
326
+
327
+ const client = new ControlPlaneClient(socket, remoteAddress, userAgent, origin);
328
+ this.clients.add(client);
329
+
330
+ console.info(`[ControlPlane] Client connected: ${client.id} from ${remoteAddress}`);
331
+ this.emitEvent({
332
+ action: 'client_connected',
333
+ timestamp: Date.now(),
334
+ clientId: client.id,
335
+ });
336
+
337
+ // Send challenge
338
+ client.sendChallenge();
339
+
340
+ // Set handshake timeout
341
+ const handshakeTimeout = setTimeout(() => {
342
+ if (!client.isAuthenticated) {
343
+ console.warn(`[ControlPlane] Handshake timeout for client ${client.id}`);
344
+ client.close(4008, 'Handshake timeout');
345
+ }
346
+ }, this.config.handshakeTimeoutMs);
347
+
348
+ // Handle messages
349
+ socket.on('message', async (data) => {
350
+ try {
351
+ const message = data.toString();
352
+ await this.handleMessage(client, message);
353
+ } catch (error) {
354
+ console.error(`[ControlPlane] Message handling error:`, error);
355
+ }
356
+ });
357
+
358
+ // Handle close
359
+ socket.on('close', (code, reason) => {
360
+ clearTimeout(handshakeTimeout);
361
+
362
+ // If this was a node, broadcast disconnection event to operators
363
+ if (client.isNode) {
364
+ const nodeInfo = client.getNodeInfo();
365
+ this.clients.broadcastToOperators(Events.NODE_DISCONNECTED, {
366
+ nodeId: client.id,
367
+ node: nodeInfo,
368
+ });
369
+ console.info(`[ControlPlane] Node disconnected: ${client.id} (${nodeInfo?.displayName || 'unnamed'}) (code: ${code})`);
370
+ } else {
371
+ console.info(`[ControlPlane] Client disconnected: ${client.id} (code: ${code})`);
372
+ }
373
+
374
+ this.clients.remove(client.id);
375
+ this.emitEvent({
376
+ action: 'client_disconnected',
377
+ timestamp: Date.now(),
378
+ clientId: client.id,
379
+ details: { code, reason: reason.toString(), wasNode: client.isNode },
380
+ });
381
+ });
382
+
383
+ // Handle error
384
+ socket.on('error', (error) => {
385
+ console.error(`[ControlPlane] Client error (${client.id}):`, error);
386
+ });
387
+ }
388
+
389
+ /**
390
+ * Handle an incoming message from a client
391
+ */
392
+ private async handleMessage(client: ControlPlaneClient, message: string): Promise<void> {
393
+ const frame = parseFrame(message);
394
+
395
+ if (!frame) {
396
+ console.warn(`[ControlPlane] Invalid frame from ${client.id}`);
397
+ return;
398
+ }
399
+
400
+ client.updateActivity();
401
+
402
+ // Only handle request frames
403
+ if (frame.type !== FrameType.Request) {
404
+ return;
405
+ }
406
+
407
+ const request = frame as RequestFrame;
408
+
409
+ // Handle connect method (authentication)
410
+ if (request.method === Methods.CONNECT) {
411
+ await this.handleConnect(client, request);
412
+ return;
413
+ }
414
+
415
+ // All other methods require authentication
416
+ if (!client.isAuthenticated) {
417
+ client.send(createErrorResponse(
418
+ request.id,
419
+ ErrorCodes.UNAUTHORIZED,
420
+ 'Authentication required'
421
+ ));
422
+ return;
423
+ }
424
+
425
+ // Route to method handler
426
+ await this.handleRequest(client, request);
427
+ }
428
+
429
+ /**
430
+ * Handle connect/authentication request
431
+ */
432
+ private async handleConnect(client: ControlPlaneClient, request: RequestFrame): Promise<void> {
433
+ const remoteAddress = client.info.remoteAddress;
434
+
435
+ // Check if IP is banned due to too many failed attempts
436
+ const authRecord = this.authAttempts.get(remoteAddress);
437
+ if (authRecord?.bannedUntil && authRecord.bannedUntil > Date.now()) {
438
+ const remainingMs = authRecord.bannedUntil - Date.now();
439
+ console.warn(`[ControlPlane] Auth blocked for ${remoteAddress}: banned for ${Math.ceil(remainingMs / 1000)}s`);
440
+ client.send(createErrorResponse(
441
+ request.id,
442
+ ErrorCodes.UNAUTHORIZED,
443
+ `Too many failed attempts. Try again in ${Math.ceil(remainingMs / 1000)} seconds.`
444
+ ));
445
+ client.close(4029, 'Rate limited');
446
+ return;
447
+ }
448
+
449
+ const params = request.params as {
450
+ token?: string;
451
+ deviceName?: string;
452
+ nonce?: string;
453
+ // Node-specific params (Mobile Companions)
454
+ role?: 'operator' | 'node';
455
+ client?: {
456
+ id?: string;
457
+ displayName?: string;
458
+ version?: string;
459
+ platform?: 'ios' | 'android' | 'macos';
460
+ mode?: string;
461
+ deviceFamily?: string;
462
+ modelIdentifier?: string;
463
+ };
464
+ capabilities?: string[];
465
+ commands?: string[];
466
+ permissions?: Record<string, boolean>;
467
+ } | undefined;
468
+
469
+ // Verify token
470
+ const providedToken = params?.token || '';
471
+ if (!this.verifyToken(providedToken)) {
472
+ // Track failed attempt
473
+ this.recordFailedAuth(remoteAddress);
474
+
475
+ client.reject();
476
+ client.send(createErrorResponse(
477
+ request.id,
478
+ ErrorCodes.UNAUTHORIZED,
479
+ 'Invalid token'
480
+ ));
481
+ client.close(4001, 'Authentication failed');
482
+ return;
483
+ }
484
+
485
+ // Clear auth attempts on success
486
+ this.authAttempts.delete(remoteAddress);
487
+
488
+ // Check if this is a node (mobile companion) connection
489
+ const isNode = params?.role === 'node';
490
+
491
+ if (isNode) {
492
+ // Authenticate as a node
493
+ const platform = (params?.client?.platform || 'ios') as 'ios' | 'android' | 'macos';
494
+ const capabilities = (params?.capabilities || []) as any[];
495
+ const commands = params?.commands || [];
496
+ const permissions = params?.permissions || {};
497
+
498
+ client.authenticateAsNode({
499
+ deviceName: params?.client?.displayName || params?.deviceName,
500
+ platform,
501
+ version: params?.client?.version || '0.0.0',
502
+ deviceId: params?.client?.id,
503
+ modelIdentifier: params?.client?.modelIdentifier,
504
+ capabilities,
505
+ commands,
506
+ permissions,
507
+ });
508
+
509
+ console.info(`[ControlPlane] Node authenticated: ${client.id} (${params?.client?.displayName || 'unnamed'}) [${platform}]`);
510
+ this.emitEvent({
511
+ action: 'client_authenticated',
512
+ timestamp: Date.now(),
513
+ clientId: client.id,
514
+ details: {
515
+ deviceName: params?.client?.displayName,
516
+ role: 'node',
517
+ platform,
518
+ capabilities,
519
+ },
520
+ });
521
+
522
+ // Broadcast node connected event to operators
523
+ this.clients.broadcastToOperators(Events.NODE_CONNECTED, {
524
+ nodeId: client.id,
525
+ node: client.getNodeInfo(),
526
+ });
527
+
528
+ // Send success response
529
+ client.send(createResponseFrame(request.id, {
530
+ clientId: client.id,
531
+ role: 'node',
532
+ scopes: ['read'],
533
+ }));
534
+ } else {
535
+ // Authenticate as operator with admin scope
536
+ const scopes: ClientScope[] = ['admin'];
537
+ client.authenticate(scopes, params?.deviceName);
538
+
539
+ console.info(`[ControlPlane] Client authenticated: ${client.id} (${params?.deviceName || 'unnamed'})`);
540
+ this.emitEvent({
541
+ action: 'client_authenticated',
542
+ timestamp: Date.now(),
543
+ clientId: client.id,
544
+ details: { deviceName: params?.deviceName, role: 'operator' },
545
+ });
546
+
547
+ // Send success response
548
+ client.send(createResponseFrame(request.id, {
549
+ clientId: client.id,
550
+ role: 'operator',
551
+ scopes,
552
+ }));
553
+ }
554
+
555
+ // Send connect success event
556
+ client.sendEvent(Events.CONNECT_SUCCESS, {
557
+ clientId: client.id,
558
+ serverVersion: '1.0.0',
559
+ });
560
+ }
561
+
562
+ /**
563
+ * Record a failed authentication attempt for rate limiting
564
+ */
565
+ private recordFailedAuth(remoteAddress: string): void {
566
+ const record = this.authAttempts.get(remoteAddress) || { attempts: 0 };
567
+ record.attempts++;
568
+
569
+ if (record.attempts >= this.config.maxAuthAttempts) {
570
+ record.bannedUntil = Date.now() + this.config.authBanDurationMs;
571
+ console.warn(`[ControlPlane] IP ${remoteAddress} banned for ${this.config.authBanDurationMs / 1000}s after ${record.attempts} failed attempts`);
572
+ }
573
+
574
+ this.authAttempts.set(remoteAddress, record);
575
+ }
576
+
577
+ /**
578
+ * Handle an authenticated request
579
+ */
580
+ private async handleRequest(client: ControlPlaneClient, request: RequestFrame): Promise<void> {
581
+ const handler = this.methods.get(request.method);
582
+
583
+ this.emitEvent({
584
+ action: 'request',
585
+ timestamp: Date.now(),
586
+ clientId: client.id,
587
+ method: request.method,
588
+ });
589
+
590
+ if (!handler) {
591
+ client.send(createErrorResponse(
592
+ request.id,
593
+ ErrorCodes.UNKNOWN_METHOD,
594
+ `Unknown method: ${request.method}`
595
+ ));
596
+ return;
597
+ }
598
+
599
+ try {
600
+ const result = await handler(client, request.params);
601
+ client.send(createResponseFrame(request.id, result));
602
+ } catch (error: any) {
603
+ console.error(`[ControlPlane] Method error (${request.method}):`, error);
604
+ client.send(createErrorResponse(
605
+ request.id,
606
+ ErrorCodes.METHOD_FAILED,
607
+ error.message || 'Method execution failed',
608
+ error.details
609
+ ));
610
+ }
611
+ }
612
+
613
+ /**
614
+ * Verify authentication token
615
+ */
616
+ private verifyToken(provided: string): boolean {
617
+ if (!this.config.token || !provided) return false;
618
+
619
+ const expected = Buffer.from(this.config.token);
620
+ const actual = Buffer.from(provided);
621
+ if (expected.length !== actual.length) return false;
622
+
623
+ return crypto.timingSafeEqual(expected, actual);
624
+ }
625
+
626
+ /**
627
+ * Register built-in method handlers
628
+ */
629
+ private registerBuiltinMethods(): void {
630
+ // Ping/health check
631
+ this.registerMethod(Methods.PING, async () => ({
632
+ pong: true,
633
+ timestamp: Date.now(),
634
+ }));
635
+
636
+ this.registerMethod(Methods.HEALTH, async () => ({
637
+ status: 'ok',
638
+ timestamp: Date.now(),
639
+ uptime: process.uptime(),
640
+ }));
641
+
642
+ // Status
643
+ this.registerMethod(Methods.STATUS, async () => this.getStatus());
644
+
645
+ // ===== Node (Mobile Companion) Methods =====
646
+
647
+ // List connected nodes
648
+ this.registerMethod(Methods.NODE_LIST, async () => {
649
+ return {
650
+ nodes: this.clients.getNodeInfoList(),
651
+ };
652
+ });
653
+
654
+ // Describe a specific node
655
+ this.registerMethod(Methods.NODE_DESCRIBE, async (client, params) => {
656
+ const { nodeId } = params as { nodeId?: string };
657
+ if (!nodeId) {
658
+ throw { code: ErrorCodes.INVALID_PARAMS, message: 'nodeId is required' };
659
+ }
660
+ const node = this.clients.getNodeByIdOrName(nodeId);
661
+ if (!node) {
662
+ throw { code: ErrorCodes.NODE_NOT_FOUND, message: `Node not found: ${nodeId}` };
663
+ }
664
+ return {
665
+ node: node.getNodeInfo(),
666
+ };
667
+ });
668
+
669
+ // Invoke a command on a node
670
+ this.registerMethod(Methods.NODE_INVOKE, async (client, params) => {
671
+ const { nodeId, command, params: commandParams, timeoutMs = 30000 } = params as {
672
+ nodeId?: string;
673
+ command?: string;
674
+ params?: Record<string, unknown>;
675
+ timeoutMs?: number;
676
+ };
677
+
678
+ if (!nodeId) {
679
+ throw { code: ErrorCodes.INVALID_PARAMS, message: 'nodeId is required' };
680
+ }
681
+ if (!command) {
682
+ throw { code: ErrorCodes.INVALID_PARAMS, message: 'command is required' };
683
+ }
684
+
685
+ const node = this.clients.getNodeByIdOrName(nodeId);
686
+ if (!node) {
687
+ throw { code: ErrorCodes.NODE_NOT_FOUND, message: `Node not found: ${nodeId}` };
688
+ }
689
+
690
+ // Check if node supports this command
691
+ const nodeInfo = node.getNodeInfo();
692
+ if (!nodeInfo?.commands.includes(command)) {
693
+ throw {
694
+ code: ErrorCodes.NODE_COMMAND_FAILED,
695
+ message: `Node does not support command: ${command}`,
696
+ };
697
+ }
698
+
699
+ // Check if node is in foreground (required for most commands)
700
+ if (!nodeInfo.isForeground && ['camera.snap', 'camera.clip', 'screen.record'].includes(command)) {
701
+ throw {
702
+ code: ErrorCodes.NODE_BACKGROUND_UNAVAILABLE,
703
+ message: 'Node app must be in foreground for this command',
704
+ };
705
+ }
706
+
707
+ // Forward the command to the node
708
+ return await this.invokeNodeCommand(node, command, commandParams, timeoutMs);
709
+ });
710
+
711
+ // Handle node events (from nodes to gateway)
712
+ this.registerMethod(Methods.NODE_EVENT, async (client, params) => {
713
+ if (!client.isNode) {
714
+ throw { code: ErrorCodes.UNAUTHORIZED, message: 'Only nodes can send node events' };
715
+ }
716
+
717
+ const { event, payload } = params as { event?: string; payload?: unknown };
718
+ if (!event) {
719
+ throw { code: ErrorCodes.INVALID_PARAMS, message: 'event is required' };
720
+ }
721
+
722
+ // Handle specific node events
723
+ if (event === 'foreground_changed') {
724
+ const isForeground = (payload as any)?.isForeground ?? true;
725
+ client.setForeground(isForeground);
726
+ this.clients.broadcastToOperators(Events.NODE_EVENT, {
727
+ nodeId: client.id,
728
+ event: 'foreground_changed',
729
+ isForeground,
730
+ });
731
+ } else if (event === 'capabilities_changed') {
732
+ const { capabilities, commands, permissions } = payload as any;
733
+ if (capabilities && commands && permissions) {
734
+ client.updateCapabilities(capabilities, commands, permissions);
735
+ this.clients.broadcastToOperators(Events.NODE_CAPABILITIES_CHANGED, {
736
+ nodeId: client.id,
737
+ node: client.getNodeInfo(),
738
+ });
739
+ }
740
+ }
741
+
742
+ return { ok: true };
743
+ });
744
+ }
745
+
746
+ /**
747
+ * Invoke a command on a node and wait for response
748
+ */
749
+ private async invokeNodeCommand(
750
+ node: ControlPlaneClient,
751
+ command: string,
752
+ params: Record<string, unknown> | undefined,
753
+ timeoutMs: number
754
+ ): Promise<{ ok: boolean; payload?: unknown; error?: { code: string; message: string } }> {
755
+ return new Promise((resolve) => {
756
+ const requestId = crypto.randomUUID();
757
+ let timeoutHandle: NodeJS.Timeout;
758
+
759
+ // Set up one-time response handler
760
+ const handleResponse = (data: Buffer | string) => {
761
+ try {
762
+ const message = data.toString();
763
+ const frame = parseFrame(message);
764
+ if (frame && frame.type === FrameType.Response && (frame as any).id === requestId) {
765
+ clearTimeout(timeoutHandle);
766
+ node.info.socket.removeListener('message', handleResponse);
767
+ const response = frame as any;
768
+ if (response.ok) {
769
+ resolve({ ok: true, payload: response.payload });
770
+ } else {
771
+ resolve({
772
+ ok: false,
773
+ error: response.error || { code: 'UNKNOWN', message: 'Command failed' },
774
+ });
775
+ }
776
+ }
777
+ } catch {
778
+ // Ignore parse errors
779
+ }
780
+ };
781
+
782
+ node.info.socket.on('message', handleResponse);
783
+
784
+ // Set timeout
785
+ timeoutHandle = setTimeout(() => {
786
+ node.info.socket.removeListener('message', handleResponse);
787
+ resolve({
788
+ ok: false,
789
+ error: { code: ErrorCodes.NODE_TIMEOUT, message: `Command timed out after ${timeoutMs}ms` },
790
+ });
791
+ }, timeoutMs);
792
+
793
+ // Send command to node
794
+ const requestFrame = {
795
+ type: FrameType.Request,
796
+ id: requestId,
797
+ method: 'node.invoke',
798
+ params: { command, params },
799
+ };
800
+ node.info.socket.send(JSON.stringify(requestFrame));
801
+ });
802
+ }
803
+
804
+ /**
805
+ * Start heartbeat interval
806
+ */
807
+ private startHeartbeat(): void {
808
+ this.heartbeatInterval = setInterval(() => {
809
+ const event = createEventFrame(Events.HEARTBEAT, {
810
+ timestamp: Date.now(),
811
+ clients: this.clients.count,
812
+ });
813
+
814
+ for (const client of this.clients.getAuthenticated()) {
815
+ client.send(event);
816
+ client.updateHeartbeat();
817
+ }
818
+ }, this.config.heartbeatIntervalMs);
819
+ }
820
+
821
+ /**
822
+ * Start cleanup interval
823
+ */
824
+ private startCleanup(): void {
825
+ this.cleanupInterval = setInterval(() => {
826
+ const removed = this.clients.cleanup();
827
+ if (removed > 0) {
828
+ console.info(`[ControlPlane] Cleaned up ${removed} disconnected clients`);
829
+ }
830
+
831
+ // Also clean up expired auth bans
832
+ const now = Date.now();
833
+ for (const [ip, record] of this.authAttempts) {
834
+ if (record.bannedUntil && record.bannedUntil < now) {
835
+ this.authAttempts.delete(ip);
836
+ }
837
+ }
838
+ }, this.config.cleanupIntervalMs);
839
+ }
840
+
841
+ /**
842
+ * Emit a server event
843
+ */
844
+ private emitEvent(event: ControlPlaneServerEvent): void {
845
+ if (this.config.onEvent) {
846
+ try {
847
+ this.config.onEvent(event);
848
+ } catch (error) {
849
+ console.error('[ControlPlane] Event handler error:', error);
850
+ }
851
+ }
852
+ }
853
+ }