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,818 @@
1
+ /**
2
+ * Canvas Manager
3
+ *
4
+ * Manages Live Canvas sessions - agent-driven visual workspaces that render
5
+ * HTML/CSS/JS content in dedicated Electron BrowserWindows.
6
+ *
7
+ * Features:
8
+ * - Session lifecycle management (create, show, hide, close)
9
+ * - Content pushing with auto-reload via file watching
10
+ * - JavaScript execution in canvas context
11
+ * - Screenshot capture
12
+ * - A2UI (Agent-to-UI) action handling
13
+ */
14
+
15
+ import { app, BrowserWindow, screen, shell } from 'electron';
16
+ import * as path from 'path';
17
+ import * as fs from 'fs/promises';
18
+ import { existsSync, readdirSync } from 'fs';
19
+ import { randomUUID } from 'crypto';
20
+ import chokidar, { type FSWatcher } from 'chokidar';
21
+ import type {
22
+ CanvasSession,
23
+ CanvasSessionMode,
24
+ CanvasEvent,
25
+ CanvasA2UIAction,
26
+ CanvasSnapshot,
27
+ } from '../../shared/types';
28
+ import { loadCanvasStore, saveCanvasStore } from './canvas-store';
29
+
30
+ // Default HTML scaffold for new canvas sessions
31
+ const DEFAULT_HTML = `<!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ <meta charset="UTF-8">
35
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
36
+ <title>Live Canvas</title>
37
+ <style>
38
+ body {
39
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
40
+ margin: 0;
41
+ padding: 20px;
42
+ background: #0f1220;
43
+ color: #e7e9f2;
44
+ min-height: 100vh;
45
+ display: grid;
46
+ place-items: center;
47
+ }
48
+ .loading {
49
+ display: flex;
50
+ flex-direction: column;
51
+ align-items: center;
52
+ justify-content: center;
53
+ gap: 16px;
54
+ text-align: center;
55
+ }
56
+ .spinner {
57
+ width: 44px;
58
+ height: 44px;
59
+ border-radius: 50%;
60
+ border: 4px solid rgba(255, 255, 255, 0.15);
61
+ border-top-color: #52d1dc;
62
+ animation: spin 0.9s linear infinite;
63
+ box-shadow: 0 0 18px rgba(82, 209, 220, 0.35);
64
+ }
65
+ .message {
66
+ font-size: 1.05em;
67
+ color: #a3acc4;
68
+ letter-spacing: 0.2px;
69
+ }
70
+ @keyframes spin {
71
+ to { transform: rotate(360deg); }
72
+ }
73
+ </style>
74
+ </head>
75
+ <body>
76
+ <div class="loading">
77
+ <div class="spinner"></div>
78
+ <div class="message">Waiting for content...</div>
79
+ </div>
80
+ </body>
81
+ </html>`;
82
+
83
+ /**
84
+ * Canvas Manager Singleton
85
+ */
86
+ export class CanvasManager {
87
+ private static instance: CanvasManager;
88
+
89
+ private sessions: Map<string, CanvasSession> = new Map();
90
+ private windows: Map<string, BrowserWindow> = new Map();
91
+ private watchers: Map<string, FSWatcher> = new Map();
92
+ private windowToSession: Map<number, string> = new Map();
93
+ private mainWindow: BrowserWindow | null = null;
94
+ private eventCallback: ((event: CanvasEvent) => void) | null = null;
95
+ private a2uiCallback: ((action: CanvasA2UIAction) => void) | null = null;
96
+
97
+ private constructor() {}
98
+
99
+ private getSessionMode(session: CanvasSession): CanvasSessionMode {
100
+ return session.mode || 'html';
101
+ }
102
+
103
+ private getCanvasUrl(sessionId: string): string {
104
+ return `canvas://${sessionId}/index.html`;
105
+ }
106
+
107
+ private normalizeUrl(rawUrl: string): string {
108
+ const trimmed = rawUrl.trim();
109
+ if (!trimmed) {
110
+ throw new Error('URL cannot be empty');
111
+ }
112
+
113
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
114
+ const parsed = new URL(withScheme);
115
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
116
+ throw new Error('Only http and https URLs are supported for canvas browsing');
117
+ }
118
+ return parsed.toString();
119
+ }
120
+
121
+ /**
122
+ * Get the singleton instance
123
+ */
124
+ static getInstance(): CanvasManager {
125
+ if (!CanvasManager.instance) {
126
+ CanvasManager.instance = new CanvasManager();
127
+ }
128
+ return CanvasManager.instance;
129
+ }
130
+
131
+ /**
132
+ * Set the main window reference for event broadcasting
133
+ */
134
+ setMainWindow(window: BrowserWindow): void {
135
+ this.mainWindow = window;
136
+ }
137
+
138
+ /**
139
+ * Set callback for canvas events (used for IPC broadcasting)
140
+ */
141
+ setEventCallback(callback: (event: CanvasEvent) => void): void {
142
+ this.eventCallback = callback;
143
+ }
144
+
145
+ /**
146
+ * Set callback for A2UI actions
147
+ */
148
+ setA2UICallback(callback: (action: CanvasA2UIAction) => void): void {
149
+ this.a2uiCallback = callback;
150
+ }
151
+
152
+ /**
153
+ * Restore sessions from disk storage
154
+ * Called on app startup to reload persisted sessions
155
+ */
156
+ async restoreSessions(): Promise<void> {
157
+ try {
158
+ const store = await loadCanvasStore();
159
+
160
+ for (const session of store.sessions) {
161
+ // Only restore active sessions with valid directories
162
+ if (session.status === 'active' && existsSync(session.sessionDir)) {
163
+ this.sessions.set(session.id, session);
164
+ console.log(`[CanvasManager] Restored session ${session.id} for task ${session.taskId}`);
165
+ }
166
+ }
167
+
168
+ console.log(`[CanvasManager] Restored ${this.sessions.size} sessions from disk`);
169
+ } catch (error) {
170
+ console.error('[CanvasManager] Failed to restore sessions:', error);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Persist all sessions to disk
176
+ */
177
+ async persistSessions(): Promise<void> {
178
+ try {
179
+ const sessions = Array.from(this.sessions.values());
180
+ await saveCanvasStore({ version: 1, sessions });
181
+ console.log(`[CanvasManager] Persisted ${sessions.length} sessions to disk`);
182
+ } catch (error) {
183
+ console.error('[CanvasManager] Failed to persist sessions:', error);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Create a new canvas session
189
+ */
190
+ async createSession(
191
+ taskId: string,
192
+ workspaceId: string,
193
+ title?: string,
194
+ options?: { mode?: CanvasSessionMode; url?: string }
195
+ ): Promise<CanvasSession> {
196
+ const sessionId = randomUUID();
197
+ const sessionDir = path.join(
198
+ app.getPath('userData'),
199
+ 'canvas',
200
+ sessionId
201
+ );
202
+
203
+ // Create session directory
204
+ await fs.mkdir(sessionDir, { recursive: true });
205
+
206
+ // Write default HTML scaffold
207
+ await fs.writeFile(
208
+ path.join(sessionDir, 'index.html'),
209
+ DEFAULT_HTML,
210
+ 'utf-8'
211
+ );
212
+
213
+ const normalizedUrl = options?.url ? this.normalizeUrl(options.url) : undefined;
214
+ const normalizedMode = options?.mode === 'browser' && normalizedUrl ? 'browser' : 'html';
215
+ const session: CanvasSession = {
216
+ id: sessionId,
217
+ taskId,
218
+ workspaceId,
219
+ sessionDir,
220
+ mode: normalizedMode,
221
+ url: normalizedMode === 'browser' ? normalizedUrl : undefined,
222
+ status: 'active',
223
+ title: title || `Canvas ${new Date().toLocaleTimeString()}`,
224
+ createdAt: Date.now(),
225
+ lastUpdatedAt: Date.now(),
226
+ };
227
+
228
+ this.sessions.set(sessionId, session);
229
+
230
+ // Persist sessions to disk
231
+ await this.persistSessions();
232
+
233
+ // Emit event
234
+ this.emitEvent({
235
+ type: 'session_created',
236
+ sessionId,
237
+ taskId,
238
+ timestamp: Date.now(),
239
+ session,
240
+ });
241
+
242
+ console.log(`[CanvasManager] Created session ${sessionId} for task ${taskId}`);
243
+ return session;
244
+ }
245
+
246
+ /**
247
+ * Get a session by ID
248
+ */
249
+ getSession(sessionId: string): CanvasSession | undefined {
250
+ return this.sessions.get(sessionId);
251
+ }
252
+
253
+ /**
254
+ * Get session ID from a BrowserWindow
255
+ */
256
+ getSessionFromWindow(window: BrowserWindow): string | undefined {
257
+ return this.windowToSession.get(window.id);
258
+ }
259
+
260
+ /**
261
+ * List all sessions for a task
262
+ */
263
+ listSessionsForTask(taskId: string): CanvasSession[] {
264
+ return Array.from(this.sessions.values()).filter(
265
+ (s) => s.taskId === taskId
266
+ );
267
+ }
268
+
269
+ /**
270
+ * List all active sessions
271
+ */
272
+ listAllSessions(): CanvasSession[] {
273
+ return Array.from(this.sessions.values());
274
+ }
275
+
276
+ /**
277
+ * Push content to a canvas session
278
+ */
279
+ async pushContent(
280
+ sessionId: string,
281
+ content: string,
282
+ filename: string = 'index.html'
283
+ ): Promise<void> {
284
+ const session = this.sessions.get(sessionId);
285
+ if (!session) {
286
+ const existingSessions = Array.from(this.sessions.keys());
287
+ console.error(`[CanvasManager] Session not found: "${sessionId}"`);
288
+ console.error(`[CanvasManager] Existing sessions: ${existingSessions.length > 0 ? existingSessions.join(', ') : 'none'}`);
289
+ throw new Error(`Canvas session not found: "${sessionId}". Available sessions: ${existingSessions.join(', ') || 'none'}`);
290
+ }
291
+
292
+ const wasBrowser = this.getSessionMode(session) === 'browser';
293
+
294
+ // Sanitize filename to prevent path traversal
295
+ const safeFilename = path.basename(filename);
296
+ const filePath = path.join(session.sessionDir, safeFilename);
297
+
298
+ await fs.writeFile(filePath, content, 'utf-8');
299
+
300
+ // Switch back to HTML mode when content is pushed
301
+ session.mode = 'html';
302
+ session.url = undefined;
303
+
304
+ // Update session timestamp
305
+ session.lastUpdatedAt = Date.now();
306
+
307
+ // Persist sessions to disk (in background, don't await to avoid blocking)
308
+ this.persistSessions().catch(err => console.error('[CanvasManager] Failed to persist after push:', err));
309
+
310
+ // Ensure a hidden window exists for snapshots (NOT shown to user)
311
+ // The window will only be shown when user explicitly requests via showCanvas()
312
+ const window = await this.ensureWindowForSnapshots(sessionId);
313
+
314
+ // If the session previously loaded a remote URL, navigate back to canvas content
315
+ if (wasBrowser && window && !window.isDestroyed()) {
316
+ await window.loadURL(this.getCanvasUrl(sessionId));
317
+ }
318
+
319
+ // Ensure watcher is running for HTML mode
320
+ this.startWatcher(sessionId, session.sessionDir, window);
321
+
322
+ // Emit event
323
+ this.emitEvent({
324
+ type: 'content_pushed',
325
+ sessionId,
326
+ taskId: session.taskId,
327
+ timestamp: Date.now(),
328
+ });
329
+
330
+ this.emitEvent({
331
+ type: 'session_updated',
332
+ sessionId,
333
+ taskId: session.taskId,
334
+ timestamp: Date.now(),
335
+ session,
336
+ });
337
+
338
+ console.log(`[CanvasManager] Pushed ${safeFilename} to session ${sessionId}`);
339
+ }
340
+
341
+ /**
342
+ * Open a remote URL inside the canvas window (browser mode)
343
+ */
344
+ async openUrl(
345
+ sessionId: string,
346
+ rawUrl: string,
347
+ options?: { show?: boolean }
348
+ ): Promise<string> {
349
+ const session = this.sessions.get(sessionId);
350
+ if (!session) {
351
+ const existingSessions = Array.from(this.sessions.keys());
352
+ console.error(`[CanvasManager] Session not found: "${sessionId}"`);
353
+ console.error(`[CanvasManager] Existing sessions: ${existingSessions.length > 0 ? existingSessions.join(', ') : 'none'}`);
354
+ throw new Error(`Canvas session not found: "${sessionId}". Available sessions: ${existingSessions.join(', ') || 'none'}`);
355
+ }
356
+
357
+ const normalizedUrl = this.normalizeUrl(rawUrl);
358
+
359
+ session.mode = 'browser';
360
+ session.url = normalizedUrl;
361
+ session.lastUpdatedAt = Date.now();
362
+
363
+ this.persistSessions().catch(err => console.error('[CanvasManager] Failed to persist after openUrl:', err));
364
+
365
+ const window = await this.ensureWindowForSnapshots(sessionId);
366
+ if (window && !window.isDestroyed()) {
367
+ this.stopWatcher(sessionId);
368
+ if (window.webContents.getURL() !== normalizedUrl) {
369
+ await window.loadURL(normalizedUrl);
370
+ }
371
+ }
372
+
373
+ this.emitEvent({
374
+ type: 'session_updated',
375
+ sessionId,
376
+ taskId: session.taskId,
377
+ timestamp: Date.now(),
378
+ session,
379
+ });
380
+
381
+ if (options?.show) {
382
+ await this.showCanvas(sessionId);
383
+ }
384
+
385
+ console.log(`[CanvasManager] Opened URL in session ${sessionId}: ${normalizedUrl}`);
386
+ return normalizedUrl;
387
+ }
388
+
389
+ /**
390
+ * Ensure a window exists for taking snapshots (hidden by default)
391
+ * This creates a hidden window that can be used for previews without
392
+ * showing a separate window to the user
393
+ */
394
+ private async ensureWindowForSnapshots(sessionId: string): Promise<BrowserWindow> {
395
+ const session = this.sessions.get(sessionId);
396
+ if (!session) {
397
+ throw new Error('Canvas session not found');
398
+ }
399
+
400
+ let window = this.windows.get(sessionId);
401
+
402
+ if (!window || window.isDestroyed()) {
403
+ // Calculate initial position - to the right of main window or right side of screen
404
+ let initialX: number | undefined;
405
+ let initialY: number | undefined;
406
+ let initialHeight = 700;
407
+
408
+ if (this.mainWindow && !this.mainWindow.isDestroyed()) {
409
+ const mainBounds = this.mainWindow.getBounds();
410
+ initialX = mainBounds.x + mainBounds.width + 20;
411
+ initialY = mainBounds.y;
412
+ initialHeight = mainBounds.height;
413
+ } else {
414
+ // Fallback: position on right side of primary display
415
+ const primaryDisplay = screen.getPrimaryDisplay();
416
+ const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize;
417
+ initialX = screenWidth - 920; // 900 width + 20 margin
418
+ initialY = 50;
419
+ initialHeight = screenHeight - 100;
420
+ }
421
+
422
+ // Create new HIDDEN window for snapshots
423
+ // NOT a child window - will be positioned to the side when shown
424
+ window = new BrowserWindow({
425
+ x: initialX,
426
+ y: initialY,
427
+ width: 900,
428
+ height: initialHeight,
429
+ title: session.title || 'Live Canvas',
430
+ show: false, // Start hidden - only show when user explicitly requests
431
+ // No parent - independent window that won't overlap main app
432
+ webPreferences: {
433
+ preload: path.join(__dirname, 'canvas-preload.js'),
434
+ contextIsolation: true,
435
+ nodeIntegration: false,
436
+ sandbox: false,
437
+ },
438
+ backgroundColor: '#1a1a2e',
439
+ });
440
+
441
+ this.windows.set(sessionId, window);
442
+ this.windowToSession.set(window.id, sessionId);
443
+
444
+ // Handle window close
445
+ window.on('closed', () => {
446
+ this.windows.delete(sessionId);
447
+ this.windowToSession.delete(window!.id);
448
+ this.stopWatcher(sessionId);
449
+ });
450
+
451
+ const mode = this.getSessionMode(session);
452
+ let targetUrl = this.getCanvasUrl(sessionId);
453
+ if (mode === 'browser' && session.url) {
454
+ try {
455
+ targetUrl = this.normalizeUrl(session.url);
456
+ } catch (error) {
457
+ console.warn('[CanvasManager] Invalid stored URL, falling back to canvas content:', error);
458
+ session.mode = 'html';
459
+ session.url = undefined;
460
+ }
461
+ }
462
+
463
+ // Load the canvas URL (or remote URL for browser mode)
464
+ await window.loadURL(targetUrl);
465
+
466
+ // Start file watcher for auto-reload only in HTML mode
467
+ if (mode === 'html') {
468
+ this.startWatcher(sessionId, session.sessionDir, window);
469
+ }
470
+ }
471
+
472
+ // Ensure watcher state is correct for existing windows
473
+ if (window && !window.isDestroyed()) {
474
+ const mode = this.getSessionMode(session);
475
+ if (mode === 'html') {
476
+ this.startWatcher(sessionId, session.sessionDir, window);
477
+ } else {
478
+ this.stopWatcher(sessionId);
479
+ }
480
+ }
481
+
482
+ return window;
483
+ }
484
+
485
+ /**
486
+ * Show the canvas window (opens it visibly to the user)
487
+ */
488
+ async showCanvas(sessionId: string): Promise<void> {
489
+ // Ensure window exists (may be hidden)
490
+ const window = await this.ensureWindowForSnapshots(sessionId);
491
+
492
+ let bounds: { x: number; y: number; width: number; height: number };
493
+
494
+ if (this.mainWindow && !this.mainWindow.isDestroyed()) {
495
+ const mainBounds = this.mainWindow.getBounds();
496
+ console.log(`[CanvasManager] Main window bounds:`, mainBounds);
497
+
498
+ // Position canvas window completely to the RIGHT of the main window
499
+ // This ensures it never overlaps with the main app
500
+ bounds = {
501
+ x: mainBounds.x + mainBounds.width + 20, // 20px gap to the right
502
+ y: mainBounds.y,
503
+ width: 900,
504
+ height: mainBounds.height,
505
+ };
506
+ } else {
507
+ console.log(`[CanvasManager] WARNING: mainWindow not available, using fallback position`);
508
+ // Fallback: position on right side of primary display
509
+ const primaryDisplay = screen.getPrimaryDisplay();
510
+ const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize;
511
+ bounds = {
512
+ x: screenWidth - 920, // 900 width + 20 margin
513
+ y: 50,
514
+ width: 900,
515
+ height: screenHeight - 100,
516
+ };
517
+ }
518
+
519
+ console.log(`[CanvasManager] Setting canvas window bounds:`, bounds);
520
+
521
+ // Always set position first to ensure correct placement
522
+ window.setPosition(bounds.x, bounds.y);
523
+ window.setSize(bounds.width, bounds.height);
524
+
525
+ // Show and focus the window so keyboard input works for interactive browsing
526
+ if (!window.isVisible()) {
527
+ window.show();
528
+ }
529
+ window.focus();
530
+
531
+ // Ensure bounds are applied after show (some systems need this)
532
+ window.setBounds(bounds);
533
+
534
+ this.emitEvent({
535
+ type: 'window_opened',
536
+ sessionId,
537
+ taskId: this.sessions.get(sessionId)!.taskId,
538
+ timestamp: Date.now(),
539
+ });
540
+ }
541
+
542
+ /**
543
+ * Hide the canvas window
544
+ */
545
+ hideCanvas(sessionId: string): void {
546
+ const window = this.windows.get(sessionId);
547
+ if (window && !window.isDestroyed()) {
548
+ window.hide();
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Close a canvas session
554
+ */
555
+ async closeSession(sessionId: string): Promise<void> {
556
+ const session = this.sessions.get(sessionId);
557
+ if (!session) {
558
+ return;
559
+ }
560
+
561
+ // Close window if open
562
+ const window = this.windows.get(sessionId);
563
+ if (window && !window.isDestroyed()) {
564
+ window.close();
565
+ }
566
+
567
+ // Stop watcher
568
+ this.stopWatcher(sessionId);
569
+
570
+ // Update session status
571
+ session.status = 'closed';
572
+
573
+ // Persist sessions to disk (removes closed sessions)
574
+ await this.persistSessions();
575
+
576
+ // Emit event
577
+ this.emitEvent({
578
+ type: 'session_closed',
579
+ sessionId,
580
+ taskId: session.taskId,
581
+ timestamp: Date.now(),
582
+ });
583
+
584
+ console.log(`[CanvasManager] Closed session ${sessionId}`);
585
+ }
586
+
587
+ /**
588
+ * Execute JavaScript in the canvas context
589
+ */
590
+ async evalScript(sessionId: string, script: string): Promise<unknown> {
591
+ // Ensure window exists (create hidden one if needed)
592
+ const window = await this.ensureWindowForSnapshots(sessionId);
593
+ if (!window || window.isDestroyed()) {
594
+ throw new Error('Canvas window could not be created');
595
+ }
596
+
597
+ return window.webContents.executeJavaScript(script);
598
+ }
599
+
600
+ /**
601
+ * Take a screenshot of the canvas
602
+ */
603
+ async takeSnapshot(sessionId: string): Promise<CanvasSnapshot> {
604
+ // Ensure window exists (create hidden one if needed)
605
+ const window = await this.ensureWindowForSnapshots(sessionId);
606
+ if (!window || window.isDestroyed()) {
607
+ throw new Error('Canvas window could not be created');
608
+ }
609
+
610
+ const image = await window.webContents.capturePage();
611
+ const size = image.getSize();
612
+
613
+ return {
614
+ sessionId,
615
+ imageBase64: image.toPNG().toString('base64'),
616
+ width: size.width,
617
+ height: size.height,
618
+ };
619
+ }
620
+
621
+ /**
622
+ * Export canvas content as a standalone HTML file
623
+ * Returns the HTML content with all assets inlined if possible
624
+ */
625
+ async exportAsHTML(sessionId: string): Promise<{ content: string; filename: string }> {
626
+ const session = this.sessions.get(sessionId);
627
+ if (!session) {
628
+ throw new Error(`Canvas session not found: ${sessionId}`);
629
+ }
630
+
631
+ const htmlPath = path.join(session.sessionDir, 'index.html');
632
+ if (!existsSync(htmlPath)) {
633
+ throw new Error('Canvas index.html not found');
634
+ }
635
+
636
+ const content = await fs.readFile(htmlPath, 'utf-8');
637
+ const filename = `canvas-${session.title?.replace(/[^a-z0-9]/gi, '-') || sessionId.slice(0, 8)}.html`;
638
+
639
+ console.log(`[CanvasManager] Exported HTML for session ${sessionId}`);
640
+ return { content, filename };
641
+ }
642
+
643
+ /**
644
+ * Export all canvas files to a target directory
645
+ */
646
+ async exportToFolder(sessionId: string, targetDir: string): Promise<{ files: string[]; targetDir: string }> {
647
+ const session = this.sessions.get(sessionId);
648
+ if (!session) {
649
+ throw new Error(`Canvas session not found: ${sessionId}`);
650
+ }
651
+
652
+ if (!existsSync(session.sessionDir)) {
653
+ throw new Error('Canvas session directory not found');
654
+ }
655
+
656
+ // Create target directory if it doesn't exist
657
+ await fs.mkdir(targetDir, { recursive: true });
658
+
659
+ // Get all files in the session directory
660
+ const files = readdirSync(session.sessionDir);
661
+ const copiedFiles: string[] = [];
662
+
663
+ for (const file of files) {
664
+ const srcPath = path.join(session.sessionDir, file);
665
+ const destPath = path.join(targetDir, file);
666
+ await fs.copyFile(srcPath, destPath);
667
+ copiedFiles.push(file);
668
+ }
669
+
670
+ console.log(`[CanvasManager] Exported ${copiedFiles.length} files for session ${sessionId} to ${targetDir}`);
671
+ return { files: copiedFiles, targetDir };
672
+ }
673
+
674
+ /**
675
+ * Open canvas content in the default system browser
676
+ */
677
+ async openInBrowser(sessionId: string): Promise<{ success: boolean; path: string }> {
678
+ const session = this.sessions.get(sessionId);
679
+ if (!session) {
680
+ throw new Error(`Canvas session not found: ${sessionId}`);
681
+ }
682
+
683
+ if (this.getSessionMode(session) === 'browser' && session.url) {
684
+ await shell.openExternal(session.url);
685
+ console.log(`[CanvasManager] Opened session ${sessionId} in browser: ${session.url}`);
686
+ return { success: true, path: session.url };
687
+ }
688
+
689
+ const htmlPath = path.join(session.sessionDir, 'index.html');
690
+ if (!existsSync(htmlPath)) {
691
+ throw new Error('Canvas index.html not found');
692
+ }
693
+
694
+ // Open in default browser
695
+ await shell.openPath(htmlPath);
696
+
697
+ console.log(`[CanvasManager] Opened session ${sessionId} in browser: ${htmlPath}`);
698
+ return { success: true, path: htmlPath };
699
+ }
700
+
701
+ /**
702
+ * Get the session directory path for external access
703
+ */
704
+ getSessionDir(sessionId: string): string | null {
705
+ const session = this.sessions.get(sessionId);
706
+ return session?.sessionDir || null;
707
+ }
708
+
709
+ /**
710
+ * Handle A2UI action from canvas window
711
+ */
712
+ handleA2UIAction(
713
+ windowId: number,
714
+ action: { actionName: string; componentId?: string; context?: Record<string, unknown> }
715
+ ): void {
716
+ const sessionId = this.windowToSession.get(windowId);
717
+ if (!sessionId) return;
718
+
719
+ const session = this.sessions.get(sessionId);
720
+ if (!session) return;
721
+
722
+ const a2uiAction: CanvasA2UIAction = {
723
+ actionName: action.actionName,
724
+ sessionId,
725
+ componentId: action.componentId,
726
+ context: action.context,
727
+ timestamp: Date.now(),
728
+ };
729
+
730
+ // Emit event for UI
731
+ this.emitEvent({
732
+ type: 'a2ui_action',
733
+ sessionId,
734
+ taskId: session.taskId,
735
+ timestamp: Date.now(),
736
+ action: a2uiAction,
737
+ });
738
+
739
+ // Call A2UI callback if set
740
+ if (this.a2uiCallback) {
741
+ this.a2uiCallback(a2uiAction);
742
+ }
743
+ }
744
+
745
+ /**
746
+ * Start file watcher for a session
747
+ */
748
+ private startWatcher(
749
+ sessionId: string,
750
+ sessionDir: string,
751
+ window: BrowserWindow
752
+ ): void {
753
+ if (this.watchers.has(sessionId)) {
754
+ return;
755
+ }
756
+
757
+ const watcher = chokidar.watch(sessionDir, {
758
+ ignoreInitial: true,
759
+ awaitWriteFinish: {
760
+ stabilityThreshold: 100,
761
+ pollInterval: 50,
762
+ },
763
+ });
764
+
765
+ watcher.on('change', () => {
766
+ if (!window.isDestroyed()) {
767
+ window.webContents.reload();
768
+ }
769
+ });
770
+
771
+ this.watchers.set(sessionId, watcher);
772
+ }
773
+
774
+ /**
775
+ * Stop file watcher for a session
776
+ */
777
+ private stopWatcher(sessionId: string): void {
778
+ const watcher = this.watchers.get(sessionId);
779
+ if (watcher) {
780
+ watcher.close();
781
+ this.watchers.delete(sessionId);
782
+ }
783
+ }
784
+
785
+ /**
786
+ * Emit a canvas event
787
+ */
788
+ private emitEvent(event: CanvasEvent): void {
789
+ // Call event callback
790
+ if (this.eventCallback) {
791
+ this.eventCallback(event);
792
+ }
793
+
794
+ // Broadcast to main window
795
+ if (this.mainWindow && !this.mainWindow.isDestroyed()) {
796
+ this.mainWindow.webContents.send('canvas:event', event);
797
+ }
798
+ }
799
+
800
+ /**
801
+ * Cleanup all sessions and resources
802
+ */
803
+ async cleanup(): Promise<void> {
804
+ // Close all windows
805
+ for (const [sessionId, window] of this.windows) {
806
+ if (!window.isDestroyed()) {
807
+ window.close();
808
+ }
809
+ this.stopWatcher(sessionId);
810
+ }
811
+
812
+ this.sessions.clear();
813
+ this.windows.clear();
814
+ this.windowToSession.clear();
815
+
816
+ console.log('[CanvasManager] Cleanup complete');
817
+ }
818
+ }