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,1005 @@
1
+ /**
2
+ * TrayManager - macOS Menu Bar App Integration
3
+ *
4
+ * Provides a native menu bar icon with:
5
+ * - Status indicator (connected/disconnected channels)
6
+ * - Quick actions menu (new task, workspaces, settings)
7
+ * - Show/hide main window on click
8
+ * - Gateway status monitoring
9
+ *
10
+ * Settings are stored encrypted in the database using SecureSettingsRepository.
11
+ */
12
+
13
+ import { app, Tray, Menu, nativeImage, BrowserWindow, shell, NativeImage, globalShortcut } from 'electron';
14
+ import * as path from 'path';
15
+ import * as os from 'os';
16
+ import * as fs from 'fs';
17
+ import { ChannelGateway } from '../gateway';
18
+ import { DatabaseManager } from '../database/schema';
19
+ import { TaskRepository, WorkspaceRepository } from '../database/repositories';
20
+ import { AgentDaemon } from '../agent/daemon';
21
+ import { QuickInputWindow } from './QuickInputWindow';
22
+ import { TEMP_WORKSPACE_ID, TEMP_WORKSPACE_NAME, Workspace } from '../../shared/types';
23
+ import { SecureSettingsRepository } from '../database/SecureSettingsRepository';
24
+
25
+ const LEGACY_SETTINGS_FILE = 'tray-settings.json';
26
+
27
+ export interface TrayManagerOptions {
28
+ showDockIcon?: boolean;
29
+ startMinimized?: boolean;
30
+ closeToTray?: boolean;
31
+ }
32
+
33
+ export interface TraySettings {
34
+ enabled: boolean;
35
+ showDockIcon: boolean;
36
+ startMinimized: boolean;
37
+ closeToTray: boolean;
38
+ showNotifications: boolean;
39
+ }
40
+
41
+ const DEFAULT_SETTINGS: TraySettings = {
42
+ enabled: true,
43
+ showDockIcon: true,
44
+ startMinimized: false,
45
+ closeToTray: true,
46
+ showNotifications: true,
47
+ };
48
+
49
+ export class TrayManager {
50
+ private tray: Tray | null = null;
51
+ private mainWindow: BrowserWindow | null = null;
52
+ private gateway: ChannelGateway | null = null;
53
+ private dbManager: DatabaseManager | null = null;
54
+ private agentDaemon: AgentDaemon | null = null;
55
+ private taskRepo: TaskRepository | null = null;
56
+ private workspaceRepo: WorkspaceRepository | null = null;
57
+ private settings: TraySettings = DEFAULT_SETTINGS;
58
+ private connectedChannels: number = 0;
59
+ private activeTaskCount: number = 0;
60
+ private quickInputWindow: QuickInputWindow | null = null;
61
+ private currentQuickTaskId: string | null = null;
62
+ private quickTaskAccumulatedResponse: string = '';
63
+ private currentStepInfo: string = '';
64
+ private legacySettingsPath: string;
65
+
66
+ private static instance: TrayManager | null = null;
67
+ private static migrationCompleted = false;
68
+
69
+ static getInstance(): TrayManager {
70
+ if (!TrayManager.instance) {
71
+ TrayManager.instance = new TrayManager();
72
+ }
73
+ return TrayManager.instance;
74
+ }
75
+
76
+ private constructor() {
77
+ const userDataPath = app.getPath('userData');
78
+ this.legacySettingsPath = path.join(userDataPath, LEGACY_SETTINGS_FILE);
79
+ }
80
+
81
+ /**
82
+ * Initialize the tray manager
83
+ */
84
+ async initialize(
85
+ mainWindow: BrowserWindow,
86
+ gateway: ChannelGateway,
87
+ dbManager: DatabaseManager,
88
+ agentDaemon?: AgentDaemon,
89
+ options: TrayManagerOptions = {}
90
+ ): Promise<void> {
91
+ this.mainWindow = mainWindow;
92
+ this.gateway = gateway;
93
+ this.dbManager = dbManager;
94
+ this.agentDaemon = agentDaemon || null;
95
+
96
+ // Initialize repositories
97
+ const db = dbManager.getDatabase();
98
+ this.taskRepo = new TaskRepository(db);
99
+ this.workspaceRepo = new WorkspaceRepository(db);
100
+
101
+ // Load settings
102
+ this.loadSettings();
103
+
104
+ // Apply options overrides
105
+ if (options.showDockIcon !== undefined) {
106
+ this.settings.showDockIcon = options.showDockIcon;
107
+ }
108
+ if (options.startMinimized !== undefined) {
109
+ this.settings.startMinimized = options.startMinimized;
110
+ }
111
+ if (options.closeToTray !== undefined) {
112
+ this.settings.closeToTray = options.closeToTray;
113
+ }
114
+
115
+ // Create tray if enabled
116
+ if (this.settings.enabled) {
117
+ this.createTray();
118
+ }
119
+
120
+ // Apply dock icon setting (macOS only)
121
+ this.applyDockIconSetting();
122
+
123
+ // Handle start minimized
124
+ if (this.settings.startMinimized && this.mainWindow) {
125
+ this.mainWindow.hide();
126
+ }
127
+
128
+ // Set up window close behavior
129
+ this.setupCloseToTray();
130
+
131
+ // Update status periodically
132
+ this.startStatusUpdates();
133
+
134
+ // Set up task event listening for quick input responses
135
+ this.setupTaskEventListener();
136
+
137
+ // Initialize quick input window
138
+ this.quickInputWindow = new QuickInputWindow();
139
+ this.quickInputWindow.setOnSubmit((task, workspaceId) => {
140
+ this.handleQuickTaskSubmit(task, workspaceId);
141
+ });
142
+ this.quickInputWindow.setOnOpenMain(() => {
143
+ this.showMainWindow();
144
+ this.quickInputWindow?.hide();
145
+ });
146
+
147
+ // Register global shortcut for quick input (Cmd+Shift+Space)
148
+ this.registerGlobalShortcut();
149
+
150
+ console.log('[TrayManager] Initialized');
151
+ }
152
+
153
+ /**
154
+ * Set up listener for task events to stream to quick input
155
+ */
156
+ private setupTaskEventListener(): void {
157
+ if (!this.agentDaemon) return;
158
+
159
+ // Listen for assistant messages (the main text response)
160
+ this.agentDaemon.on('assistant_message', (event: { taskId: string; message?: string }) => {
161
+ if (event.taskId !== this.currentQuickTaskId) return;
162
+ const message = event.message || '';
163
+ if (message) {
164
+ // Append to accumulated response (assistant may send multiple messages)
165
+ if (this.quickTaskAccumulatedResponse) {
166
+ this.quickTaskAccumulatedResponse += '\n\n' + message;
167
+ } else {
168
+ this.quickTaskAccumulatedResponse = message;
169
+ }
170
+ this.quickInputWindow?.updateResponse(
171
+ this.formatResponseWithQuestion(this.quickTaskAccumulatedResponse),
172
+ false
173
+ );
174
+ }
175
+ });
176
+
177
+ // Listen for progress updates
178
+ this.agentDaemon.on('progress_update', (event: { taskId: string; message?: string; progress?: number }) => {
179
+ if (event.taskId !== this.currentQuickTaskId) return;
180
+ // Only show progress if we don't have response content yet
181
+ if (!this.quickTaskAccumulatedResponse && event.message) {
182
+ this.quickInputWindow?.updateResponse(
183
+ `<p style="color: rgba(255,255,255,0.6);">${event.message}</p>`,
184
+ false
185
+ );
186
+ }
187
+ });
188
+
189
+ // Listen for task completion
190
+ this.agentDaemon.on('task_completed', (event: { taskId: string; message?: string; result?: string }) => {
191
+ if (event.taskId !== this.currentQuickTaskId) return;
192
+ // Show the accumulated response as complete (without step prefix)
193
+ const finalContent = this.quickTaskAccumulatedResponse || event.result || event.message || 'Task completed successfully';
194
+ this.quickInputWindow?.updateResponse(
195
+ this.formatResponseWithQuestion(finalContent),
196
+ true
197
+ );
198
+ this.currentQuickTaskId = null;
199
+ this.quickTaskAccumulatedResponse = '';
200
+ this.currentStepInfo = '';
201
+ });
202
+
203
+ // Listen for errors
204
+ this.agentDaemon.on('error', (event: { taskId: string; message?: string }) => {
205
+ if (event.taskId !== this.currentQuickTaskId) return;
206
+ const question = this.quickInputWindow?.getCurrentQuestion() || '';
207
+ const questionHtml = question ? `<div class="user-question"><strong>You:</strong> ${question.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</div>` : '';
208
+ this.quickInputWindow?.updateResponse(
209
+ `${questionHtml}<div class="error-message">Error: ${event.message || 'An error occurred'}</div>`,
210
+ true
211
+ );
212
+ this.currentQuickTaskId = null;
213
+ this.quickTaskAccumulatedResponse = '';
214
+ this.currentStepInfo = '';
215
+ });
216
+
217
+ // Listen for step started (show what step is being executed)
218
+ this.agentDaemon.on('step_started', (event: { taskId: string; step?: { id: number; description: string } }) => {
219
+ if (event.taskId !== this.currentQuickTaskId) return;
220
+ // Show step info above the response
221
+ if (event.step?.description) {
222
+ const stepInfo = `**Step ${event.step.id}:** ${event.step.description}\n\n`;
223
+ // Prepend step info (it will be replaced by next step)
224
+ this.currentStepInfo = stepInfo;
225
+ this.quickInputWindow?.updateResponse(
226
+ this.formatResponseWithQuestion(this.currentStepInfo + this.quickTaskAccumulatedResponse),
227
+ false
228
+ );
229
+ }
230
+ });
231
+
232
+ // Listen for plan created (show what the agent is going to do)
233
+ this.agentDaemon.on('plan_created', (event: { taskId: string; plan?: { steps: Array<{ id: number; description: string }> } }) => {
234
+ if (event.taskId !== this.currentQuickTaskId) return;
235
+ if (event.plan?.steps && event.plan.steps.length > 0) {
236
+ const planSummary = event.plan.steps.map((s, i) => `${i + 1}. ${s.description}`).join('\n');
237
+ this.quickTaskAccumulatedResponse = `**Plan:**\n${planSummary}\n\n`;
238
+ this.quickInputWindow?.updateResponse(
239
+ this.formatResponseWithQuestion(this.quickTaskAccumulatedResponse),
240
+ false
241
+ );
242
+ }
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Format response text for HTML display
248
+ */
249
+ private formatResponseForDisplay(text: string): string {
250
+ // Basic markdown-like formatting
251
+ return text
252
+ // Escape HTML
253
+ .replace(/&/g, '&amp;')
254
+ .replace(/</g, '&lt;')
255
+ .replace(/>/g, '&gt;')
256
+ // Bold
257
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
258
+ // Code blocks
259
+ .replace(/```(\w*)\n?([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
260
+ // Inline code
261
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
262
+ // Line breaks
263
+ .replace(/\n/g, '<br>');
264
+ }
265
+
266
+ /**
267
+ * Format response with user's question prepended
268
+ */
269
+ private formatResponseWithQuestion(text: string): string {
270
+ const question = this.quickInputWindow?.getCurrentQuestion() || '';
271
+ const formattedResponse = this.formatResponseForDisplay(text);
272
+
273
+ if (question) {
274
+ const escapedQuestion = question
275
+ .replace(/&/g, '&amp;')
276
+ .replace(/</g, '&lt;')
277
+ .replace(/>/g, '&gt;');
278
+ return `<div class="user-question"><strong>You:</strong> ${escapedQuestion}</div>${formattedResponse}`;
279
+ }
280
+
281
+ return formattedResponse;
282
+ }
283
+
284
+ /**
285
+ * Get or create the temp workspace
286
+ */
287
+ private async getOrCreateTempWorkspace(): Promise<Workspace> {
288
+ if (!this.dbManager) throw new Error('Database not available');
289
+
290
+ const db = this.dbManager.getDatabase();
291
+
292
+ // Check if temp workspace exists
293
+ const existing = this.workspaceRepo?.findById(TEMP_WORKSPACE_ID);
294
+ if (existing) {
295
+ const updatedPermissions = {
296
+ ...existing.permissions,
297
+ read: true,
298
+ write: true,
299
+ delete: true,
300
+ network: true,
301
+ shell: existing.permissions.shell ?? false,
302
+ unrestrictedFileAccess: true,
303
+ };
304
+
305
+ if (!existing.permissions.unrestrictedFileAccess) {
306
+ this.workspaceRepo?.updatePermissions(existing.id, updatedPermissions);
307
+ }
308
+
309
+ // Verify directory exists
310
+ if (fs.existsSync(existing.path)) {
311
+ return { ...existing, permissions: updatedPermissions, isTemp: true };
312
+ }
313
+ // Directory deleted, remove and recreate
314
+ this.workspaceRepo?.delete(TEMP_WORKSPACE_ID);
315
+ }
316
+
317
+ // Create temp directory
318
+ const tempDir = path.join(os.tmpdir(), 'cowork-os-temp');
319
+ if (!fs.existsSync(tempDir)) {
320
+ fs.mkdirSync(tempDir, { recursive: true });
321
+ }
322
+
323
+ // Create workspace record
324
+ const tempWorkspace: Workspace = {
325
+ id: TEMP_WORKSPACE_ID,
326
+ name: TEMP_WORKSPACE_NAME,
327
+ path: tempDir,
328
+ createdAt: Date.now(),
329
+ permissions: {
330
+ read: true,
331
+ write: true,
332
+ delete: true,
333
+ network: true,
334
+ shell: false,
335
+ unrestrictedFileAccess: true,
336
+ },
337
+ isTemp: true,
338
+ };
339
+
340
+ const stmt = db.prepare(`
341
+ INSERT OR REPLACE INTO workspaces (id, name, path, created_at, permissions)
342
+ VALUES (?, ?, ?, ?, ?)
343
+ `);
344
+ stmt.run(
345
+ tempWorkspace.id,
346
+ tempWorkspace.name,
347
+ tempWorkspace.path,
348
+ tempWorkspace.createdAt,
349
+ JSON.stringify(tempWorkspace.permissions)
350
+ );
351
+
352
+ return tempWorkspace;
353
+ }
354
+
355
+ /**
356
+ * Handle quick task submission - create and run task
357
+ */
358
+ private async handleQuickTaskSubmit(prompt: string, workspaceId?: string): Promise<void> {
359
+ if (!this.taskRepo || !this.workspaceRepo || !this.agentDaemon) {
360
+ // Fall back to sending to main window
361
+ console.log('[TrayManager] Agent daemon not available, falling back to main window');
362
+ this.showMainWindow();
363
+ this.mainWindow?.webContents.send('tray:quick-task', { task: prompt, workspaceId });
364
+ return;
365
+ }
366
+
367
+ // Show loading state and reset accumulated response
368
+ this.quickInputWindow?.showLoading();
369
+ this.quickTaskAccumulatedResponse = '';
370
+ this.currentStepInfo = '';
371
+
372
+ try {
373
+ // Get or select workspace
374
+ let wsId = workspaceId;
375
+ if (!wsId) {
376
+ // Get the first non-temp workspace, or use temp workspace as fallback
377
+ const workspaces = this.workspaceRepo.findAll().filter(w => w.id !== TEMP_WORKSPACE_ID);
378
+ if (workspaces.length > 0) {
379
+ wsId = workspaces[0].id;
380
+ } else {
381
+ // No user workspaces, use temp workspace
382
+ const tempWorkspace = await this.getOrCreateTempWorkspace();
383
+ wsId = tempWorkspace.id;
384
+ }
385
+ }
386
+
387
+ // Create task
388
+ const task = this.taskRepo.create({
389
+ title: prompt.slice(0, 50) + (prompt.length > 50 ? '...' : ''),
390
+ prompt,
391
+ workspaceId: wsId,
392
+ status: 'queued',
393
+ });
394
+
395
+ this.currentQuickTaskId = task.id;
396
+
397
+ // Start task execution
398
+ await this.agentDaemon.startTask(task);
399
+
400
+ // Also notify main window so it updates the task list
401
+ this.mainWindow?.webContents.send('tray:task-created', { taskId: task.id });
402
+
403
+ } catch (error) {
404
+ console.error('[TrayManager] Failed to create quick task:', error);
405
+ const question = this.quickInputWindow?.getCurrentQuestion() || '';
406
+ const questionHtml = question ? `<div class="user-question"><strong>You:</strong> ${question.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</div>` : '';
407
+ this.quickInputWindow?.updateResponse(
408
+ `${questionHtml}<div class="error-message">Failed to create task: ${error instanceof Error ? error.message : 'Unknown error'}</div>`,
409
+ true
410
+ );
411
+ this.currentQuickTaskId = null;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Show the quick input window
417
+ */
418
+ showQuickInput(): void {
419
+ this.quickInputWindow?.show();
420
+ }
421
+
422
+ /**
423
+ * Toggle the quick input window
424
+ */
425
+ toggleQuickInput(): void {
426
+ this.quickInputWindow?.toggle();
427
+ }
428
+
429
+ /**
430
+ * Register global keyboard shortcut for quick input
431
+ */
432
+ private registerGlobalShortcut(): void {
433
+ try {
434
+ // Unregister first in case it's already registered
435
+ globalShortcut.unregister('CommandOrControl+Shift+Space');
436
+
437
+ const registered = globalShortcut.register('CommandOrControl+Shift+Space', () => {
438
+ this.showQuickInput();
439
+ });
440
+
441
+ if (registered) {
442
+ console.log('[TrayManager] Global shortcut registered: Cmd+Shift+Space');
443
+ } else {
444
+ console.warn('[TrayManager] Failed to register global shortcut - may be in use by another app');
445
+ }
446
+ } catch (error) {
447
+ console.error('[TrayManager] Error registering global shortcut:', error);
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Unregister global keyboard shortcut
453
+ */
454
+ private unregisterGlobalShortcut(): void {
455
+ try {
456
+ globalShortcut.unregister('CommandOrControl+Shift+Space');
457
+ console.log('[TrayManager] Global shortcut unregistered');
458
+ } catch (error) {
459
+ console.error('[TrayManager] Error unregistering global shortcut:', error);
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Create the system tray icon
465
+ */
466
+ private createTray(): void {
467
+ if (this.tray) {
468
+ return;
469
+ }
470
+
471
+ try {
472
+ // Create tray icon (use template image for macOS)
473
+ const icon = this.getTrayIcon('idle');
474
+
475
+ this.tray = new Tray(icon);
476
+ this.tray.setToolTip('CoWork OS');
477
+
478
+ // Build and set context menu
479
+ this.updateContextMenu();
480
+
481
+ // Handle click events - always show context menu on click
482
+ this.tray.on('click', () => {
483
+ this.tray?.popUpContextMenu();
484
+ });
485
+ } catch (error) {
486
+ console.error('[TrayManager] Failed to create tray:', error);
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Get or create tray icon
492
+ */
493
+ private getTrayIcon(state: 'idle' | 'active' | 'error'): NativeImage {
494
+ // Try to load from file first
495
+ const iconPath = this.getIconPath(state === 'active' ? 'trayActiveTemplate' : 'trayTemplate');
496
+ const fs = require('fs');
497
+
498
+ if (fs.existsSync(iconPath)) {
499
+ const icon = nativeImage.createFromPath(iconPath);
500
+ if (process.platform === 'darwin') {
501
+ icon.setTemplateImage(true);
502
+ }
503
+ return icon;
504
+ }
505
+
506
+ // Create programmatic icon if file doesn't exist
507
+ return this.createProgrammaticIcon(state);
508
+ }
509
+
510
+ /**
511
+ * Create a programmatic tray icon using raw RGBA bitmap
512
+ * More reliable than SVG data URLs for Electron tray icons
513
+ */
514
+ private createProgrammaticIcon(state: 'idle' | 'active' | 'error'): NativeImage {
515
+ // Standard macOS menu bar icon size (16x16 for 1x, 32x32 for 2x retina)
516
+ const size = 16;
517
+ const scale = 2; // Create at 2x for retina
518
+ const actualSize = size * scale;
519
+
520
+ // Create RGBA buffer (4 bytes per pixel)
521
+ const buffer = Buffer.alloc(actualSize * actualSize * 4);
522
+
523
+ // Get color based on state
524
+ const [r, g, b] = state === 'error' ? [255, 59, 48] : // Red
525
+ state === 'active' ? [0, 122, 255] : // Blue
526
+ [255, 255, 255]; // White
527
+
528
+ // Draw a simple filled circle
529
+ const centerX = actualSize / 2;
530
+ const centerY = actualSize / 2;
531
+ const outerRadius = actualSize / 2 - 2;
532
+ const innerRadius = outerRadius - 4;
533
+
534
+ for (let y = 0; y < actualSize; y++) {
535
+ for (let x = 0; x < actualSize; x++) {
536
+ const dx = x - centerX;
537
+ const dy = y - centerY;
538
+ const distance = Math.sqrt(dx * dx + dy * dy);
539
+
540
+ const idx = (y * actualSize + x) * 4;
541
+
542
+ // Draw ring (between inner and outer radius)
543
+ if (distance <= outerRadius && distance >= innerRadius) {
544
+ // Anti-aliasing at edges
545
+ let alpha = 255;
546
+ if (distance > outerRadius - 1) {
547
+ alpha = Math.round(255 * (outerRadius - distance));
548
+ } else if (distance < innerRadius + 1) {
549
+ alpha = Math.round(255 * (distance - innerRadius));
550
+ }
551
+ alpha = Math.max(0, Math.min(255, alpha));
552
+
553
+ buffer[idx] = r;
554
+ buffer[idx + 1] = g;
555
+ buffer[idx + 2] = b;
556
+ buffer[idx + 3] = alpha;
557
+ } else {
558
+ // Transparent
559
+ buffer[idx] = 0;
560
+ buffer[idx + 1] = 0;
561
+ buffer[idx + 2] = 0;
562
+ buffer[idx + 3] = 0;
563
+ }
564
+ }
565
+ }
566
+
567
+ return nativeImage.createFromBuffer(buffer, {
568
+ width: actualSize,
569
+ height: actualSize,
570
+ scaleFactor: scale,
571
+ });
572
+ }
573
+
574
+ /**
575
+ * Get the path to a tray icon
576
+ */
577
+ private getIconPath(name: string): string {
578
+ const isDev = process.env.NODE_ENV === 'development';
579
+ const basePath = isDev
580
+ ? path.join(__dirname, '../../../assets/tray')
581
+ : path.join(process.resourcesPath, 'assets/tray');
582
+
583
+ // Use PNG for cross-platform compatibility
584
+ const extension = process.platform === 'darwin' ? 'png' : 'png';
585
+ return path.join(basePath, `${name}.${extension}`);
586
+ }
587
+
588
+ /**
589
+ * Update the tray context menu
590
+ */
591
+ private updateContextMenu(): void {
592
+ if (!this.tray) return;
593
+
594
+ const statusText = this.getStatusText();
595
+ const workspaces = this.getWorkspaces();
596
+
597
+ const menuTemplate: Electron.MenuItemConstructorOptions[] = [
598
+ // Status section
599
+ {
600
+ label: statusText,
601
+ enabled: false,
602
+ icon: this.getStatusIcon(),
603
+ },
604
+ { type: 'separator' },
605
+
606
+ // Quick actions
607
+ {
608
+ label: 'Quick Task...',
609
+ accelerator: 'CmdOrCtrl+Shift+Space',
610
+ click: () => {
611
+ this.showQuickInput();
612
+ },
613
+ },
614
+ {
615
+ label: 'New Task...',
616
+ accelerator: 'CmdOrCtrl+N',
617
+ click: () => {
618
+ this.showMainWindow();
619
+ this.mainWindow?.webContents.send('tray:new-task');
620
+ },
621
+ },
622
+ { type: 'separator' },
623
+
624
+ // Workspaces submenu
625
+ {
626
+ label: 'Workspaces',
627
+ submenu: workspaces.length > 0
628
+ ? workspaces.map((ws) => ({
629
+ label: ws.name,
630
+ click: () => {
631
+ this.showMainWindow();
632
+ this.mainWindow?.webContents.send('tray:select-workspace', ws.id);
633
+ },
634
+ }))
635
+ : [{ label: 'No workspaces', enabled: false }],
636
+ },
637
+
638
+ // Channels submenu
639
+ {
640
+ label: 'Channels',
641
+ submenu: this.buildChannelsSubmenu(),
642
+ },
643
+ { type: 'separator' },
644
+
645
+ // Window controls
646
+ {
647
+ label: this.mainWindow?.isVisible() ? 'Hide Window' : 'Show Window',
648
+ accelerator: 'CmdOrCtrl+H',
649
+ click: () => this.toggleMainWindow(),
650
+ },
651
+ {
652
+ label: 'Settings...',
653
+ accelerator: 'CmdOrCtrl+,',
654
+ click: () => {
655
+ this.showMainWindow();
656
+ this.mainWindow?.webContents.send('tray:open-settings');
657
+ },
658
+ },
659
+ { type: 'separator' },
660
+
661
+ // App controls
662
+ {
663
+ label: 'About CoWork OS',
664
+ click: () => {
665
+ this.showMainWindow();
666
+ this.mainWindow?.webContents.send('tray:open-about');
667
+ },
668
+ },
669
+ {
670
+ label: 'Check for Updates...',
671
+ click: () => {
672
+ this.showMainWindow();
673
+ this.mainWindow?.webContents.send('tray:check-updates');
674
+ },
675
+ },
676
+ { type: 'separator' },
677
+ {
678
+ label: 'Quit CoWork OS',
679
+ accelerator: 'CmdOrCtrl+Q',
680
+ click: () => {
681
+ // Force quit (bypass close-to-tray)
682
+ this.settings.closeToTray = false;
683
+ app.quit();
684
+ },
685
+ },
686
+ ];
687
+
688
+ const contextMenu = Menu.buildFromTemplate(menuTemplate);
689
+ this.tray.setContextMenu(contextMenu);
690
+ }
691
+
692
+ /**
693
+ * Build the channels submenu
694
+ */
695
+ private buildChannelsSubmenu(): Electron.MenuItemConstructorOptions[] {
696
+ const channels = this.gateway?.getChannels() || [];
697
+
698
+ if (channels.length === 0) {
699
+ return [{ label: 'No channels configured', enabled: false }];
700
+ }
701
+
702
+ return channels.map((channel) => {
703
+ const statusIcon = channel.status === 'connected' ? '🟢' :
704
+ channel.status === 'connecting' ? '🟡' :
705
+ channel.status === 'error' ? '🔴' : '⚪';
706
+ return {
707
+ label: `${statusIcon} ${channel.name} (${channel.type})`,
708
+ enabled: false,
709
+ };
710
+ });
711
+ }
712
+
713
+ /**
714
+ * Get status text for the menu
715
+ */
716
+ private getStatusText(): string {
717
+ const channels = this.gateway?.getChannels() || [];
718
+ this.connectedChannels = channels.filter((c) => c.status === 'connected').length;
719
+
720
+ if (this.activeTaskCount > 0) {
721
+ return `Working on ${this.activeTaskCount} task${this.activeTaskCount > 1 ? 's' : ''}`;
722
+ }
723
+
724
+ if (this.connectedChannels > 0) {
725
+ return `${this.connectedChannels} channel${this.connectedChannels > 1 ? 's' : ''} connected`;
726
+ }
727
+
728
+ return 'Ready';
729
+ }
730
+
731
+ /**
732
+ * Get status icon for the menu
733
+ */
734
+ private getStatusIcon(): NativeImage | undefined {
735
+ // Return undefined for now - icons in menu items can be complex
736
+ return undefined;
737
+ }
738
+
739
+ /**
740
+ * Get workspaces from database (excluding temp workspace)
741
+ */
742
+ private getWorkspaces(): Array<{ id: string; name: string; path: string }> {
743
+ if (!this.dbManager) return [];
744
+
745
+ try {
746
+ const db = this.dbManager.getDatabase();
747
+ const stmt = db.prepare('SELECT id, name, path FROM workspaces WHERE id != ? ORDER BY name');
748
+ return stmt.all(TEMP_WORKSPACE_ID) as Array<{ id: string; name: string; path: string }>;
749
+ } catch (error) {
750
+ console.error('[TrayManager] Failed to get workspaces:', error);
751
+ return [];
752
+ }
753
+ }
754
+
755
+ /**
756
+ * Toggle main window visibility
757
+ */
758
+ private toggleMainWindow(): void {
759
+ if (!this.mainWindow) return;
760
+
761
+ if (this.mainWindow.isVisible()) {
762
+ this.mainWindow.hide();
763
+ } else {
764
+ this.showMainWindow();
765
+ }
766
+
767
+ // Update menu to reflect new state
768
+ this.updateContextMenu();
769
+ }
770
+
771
+ /**
772
+ * Show and focus the main window
773
+ */
774
+ private showMainWindow(): void {
775
+ if (!this.mainWindow) return;
776
+
777
+ this.mainWindow.show();
778
+ this.mainWindow.focus();
779
+
780
+ // On macOS, also bring app to foreground
781
+ if (process.platform === 'darwin') {
782
+ app.dock?.show();
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Set up close-to-tray behavior
788
+ */
789
+ private setupCloseToTray(): void {
790
+ if (!this.mainWindow) return;
791
+
792
+ this.mainWindow.on('close', (event) => {
793
+ if (this.settings.closeToTray && this.tray) {
794
+ event.preventDefault();
795
+ this.mainWindow?.hide();
796
+
797
+ // On macOS, hide from dock when minimized to tray
798
+ if (process.platform === 'darwin' && !this.settings.showDockIcon) {
799
+ app.dock?.hide();
800
+ }
801
+ }
802
+ });
803
+ }
804
+
805
+ /**
806
+ * Apply dock icon visibility setting (macOS only)
807
+ */
808
+ private applyDockIconSetting(): void {
809
+ if (process.platform !== 'darwin') return;
810
+
811
+ if (this.settings.showDockIcon) {
812
+ app.dock?.show();
813
+ } else {
814
+ app.dock?.hide();
815
+ }
816
+ }
817
+
818
+ /**
819
+ * Start periodic status updates
820
+ */
821
+ private startStatusUpdates(): void {
822
+ // Update every 5 seconds
823
+ setInterval(() => {
824
+ this.updateContextMenu();
825
+ this.updateTrayIcon();
826
+ }, 5000);
827
+ }
828
+
829
+ /**
830
+ * Update tray icon based on status
831
+ */
832
+ private updateTrayIcon(): void {
833
+ if (!this.tray) return;
834
+
835
+ // Determine icon state based on app status
836
+ const state: 'idle' | 'active' | 'error' = this.activeTaskCount > 0 ? 'active' : 'idle';
837
+ const icon = this.getTrayIcon(state);
838
+ this.tray.setImage(icon);
839
+ }
840
+
841
+ /**
842
+ * Update active task count
843
+ */
844
+ setActiveTaskCount(count: number): void {
845
+ this.activeTaskCount = count;
846
+ this.updateContextMenu();
847
+ this.updateTrayIcon();
848
+ }
849
+
850
+ /**
851
+ * Migrate settings from legacy JSON file to encrypted database
852
+ */
853
+ private migrateFromLegacyFile(): void {
854
+ if (TrayManager.migrationCompleted) return;
855
+
856
+ try {
857
+ if (!SecureSettingsRepository.isInitialized()) {
858
+ return;
859
+ }
860
+
861
+ const repository = SecureSettingsRepository.getInstance();
862
+
863
+ // Check if already migrated
864
+ if (repository.exists('tray')) {
865
+ TrayManager.migrationCompleted = true;
866
+ return;
867
+ }
868
+
869
+ // Check if legacy file exists
870
+ if (!fs.existsSync(this.legacySettingsPath)) {
871
+ TrayManager.migrationCompleted = true;
872
+ return;
873
+ }
874
+
875
+ console.log('[TrayManager] Migrating settings from legacy JSON file to encrypted database...');
876
+
877
+ // Create backup before migration
878
+ const backupPath = this.legacySettingsPath + '.migration-backup';
879
+ fs.copyFileSync(this.legacySettingsPath, backupPath);
880
+
881
+ try {
882
+ const data = fs.readFileSync(this.legacySettingsPath, 'utf-8');
883
+ const parsed = JSON.parse(data);
884
+ const merged = { ...DEFAULT_SETTINGS, ...parsed };
885
+
886
+ repository.save('tray', merged);
887
+ console.log('[TrayManager] Settings migrated to encrypted database');
888
+
889
+ // Migration successful - delete backup and original
890
+ fs.unlinkSync(backupPath);
891
+ fs.unlinkSync(this.legacySettingsPath);
892
+ console.log('[TrayManager] Migration complete, cleaned up legacy files');
893
+
894
+ TrayManager.migrationCompleted = true;
895
+ } catch (migrationError) {
896
+ console.error('[TrayManager] Migration failed, backup preserved at:', backupPath);
897
+ throw migrationError;
898
+ }
899
+ } catch (error) {
900
+ console.error('[TrayManager] Migration failed:', error);
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Load settings from encrypted database
906
+ */
907
+ private loadSettings(): void {
908
+ // Migrate from legacy file if needed
909
+ this.migrateFromLegacyFile();
910
+
911
+ try {
912
+ if (SecureSettingsRepository.isInitialized()) {
913
+ const repository = SecureSettingsRepository.getInstance();
914
+ const stored = repository.load<TraySettings>('tray');
915
+ if (stored) {
916
+ this.settings = { ...DEFAULT_SETTINGS, ...stored };
917
+ console.log('[TrayManager] Loaded settings from encrypted database');
918
+ return;
919
+ }
920
+ }
921
+ } catch (error) {
922
+ console.error('[TrayManager] Failed to load settings:', error);
923
+ }
924
+
925
+ // Fall back to defaults
926
+ this.settings = { ...DEFAULT_SETTINGS };
927
+ }
928
+
929
+ /**
930
+ * Save settings to encrypted database
931
+ */
932
+ saveSettings(settings: Partial<TraySettings>): void {
933
+ this.settings = { ...this.settings, ...settings };
934
+
935
+ try {
936
+ if (SecureSettingsRepository.isInitialized()) {
937
+ const repository = SecureSettingsRepository.getInstance();
938
+ repository.save('tray', this.settings);
939
+ console.log('[TrayManager] Settings saved to encrypted database');
940
+ } else {
941
+ console.warn('[TrayManager] SecureSettingsRepository not initialized, settings not persisted');
942
+ }
943
+
944
+ // Apply settings immediately
945
+ this.applyDockIconSetting();
946
+
947
+ // Recreate tray if enabled status changed
948
+ if (settings.enabled !== undefined) {
949
+ if (settings.enabled && !this.tray) {
950
+ this.createTray();
951
+ } else if (!settings.enabled && this.tray) {
952
+ this.destroy();
953
+ }
954
+ }
955
+ } catch (error) {
956
+ console.error('[TrayManager] Failed to save settings:', error);
957
+ }
958
+ }
959
+
960
+ /**
961
+ * Get current settings
962
+ */
963
+ getSettings(): TraySettings {
964
+ return { ...this.settings };
965
+ }
966
+
967
+ /**
968
+ * Show a notification from the tray
969
+ */
970
+ showNotification(title: string, body: string): void {
971
+ if (!this.settings.showNotifications) return;
972
+
973
+ const { Notification } = require('electron');
974
+ if (Notification.isSupported()) {
975
+ const notification = new Notification({
976
+ title,
977
+ body,
978
+ silent: false,
979
+ });
980
+ notification.on('click', () => {
981
+ this.showMainWindow();
982
+ });
983
+ notification.show();
984
+ }
985
+ }
986
+
987
+ /**
988
+ * Destroy the tray
989
+ */
990
+ destroy(): void {
991
+ // Unregister global shortcut
992
+ this.unregisterGlobalShortcut();
993
+
994
+ if (this.quickInputWindow) {
995
+ this.quickInputWindow.destroy();
996
+ this.quickInputWindow = null;
997
+ }
998
+ if (this.tray) {
999
+ this.tray.destroy();
1000
+ this.tray = null;
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ export const trayManager = TrayManager.getInstance();