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,1995 @@
1
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
2
+ import {
3
+ AgentRoleData,
4
+ HeartbeatEvent,
5
+ HeartbeatStatus,
6
+ AgentCapability,
7
+ ActivityData,
8
+ MentionData,
9
+ TaskBoardEvent,
10
+ } from '../../electron/preload';
11
+ import type { Task, Workspace } from '../../shared/types';
12
+ import { AgentRoleEditor } from './AgentRoleEditor';
13
+ import { ActivityFeed } from './ActivityFeed';
14
+ import { MentionInput } from './MentionInput';
15
+ import { MentionList } from './MentionList';
16
+ import { StandupReportViewer } from './StandupReportViewer';
17
+ import { useAgentContext } from '../hooks/useAgentContext';
18
+ import type { UiCopyKey } from '../utils/agentMessages';
19
+
20
+ type AgentRole = AgentRoleData;
21
+ type MissionColumn = {
22
+ id: string;
23
+ label: string;
24
+ color: string;
25
+ boardColumn: NonNullable<Task['boardColumn']>;
26
+ };
27
+
28
+ interface HeartbeatStatusInfo {
29
+ agentRoleId: string;
30
+ agentName: string;
31
+ heartbeatEnabled: boolean;
32
+ heartbeatStatus: HeartbeatStatus;
33
+ lastHeartbeatAt?: number;
34
+ nextHeartbeatAt?: number;
35
+ }
36
+
37
+ const BOARD_COLUMNS: MissionColumn[] = [
38
+ { id: 'inbox', label: 'INBOX', color: '#6b7280', boardColumn: 'backlog' },
39
+ { id: 'assigned', label: 'ASSIGNED', color: '#f59e0b', boardColumn: 'todo' },
40
+ { id: 'in_progress', label: 'IN PROGRESS', color: '#3b82f6', boardColumn: 'in_progress' },
41
+ { id: 'review', label: 'REVIEW', color: '#8b5cf6', boardColumn: 'review' },
42
+ { id: 'done', label: 'DONE', color: '#22c55e', boardColumn: 'done' },
43
+ ];
44
+
45
+ const AUTONOMY_BADGES: Record<string, { label: string; color: string }> = {
46
+ lead: { label: 'LEAD', color: '#f59e0b' },
47
+ specialist: { label: 'SPC', color: '#3b82f6' },
48
+ intern: { label: 'INT', color: '#6b7280' },
49
+ };
50
+
51
+ interface MissionControlPanelProps {
52
+ onClose?: () => void;
53
+ }
54
+
55
+ export function MissionControlPanel({ onClose: _onClose }: MissionControlPanelProps) {
56
+ const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
57
+ const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string | null>(null);
58
+ const [agents, setAgents] = useState<AgentRole[]>([]);
59
+ const [tasks, setTasks] = useState<Task[]>([]);
60
+ const [activities, setActivities] = useState<ActivityData[]>([]);
61
+ const [mentions, setMentions] = useState<MentionData[]>([]);
62
+ const [heartbeatStatuses, setHeartbeatStatuses] = useState<HeartbeatStatusInfo[]>([]);
63
+ const [events, setEvents] = useState<HeartbeatEvent[]>([]);
64
+ const [loading, setLoading] = useState(true);
65
+ const [isRefreshing, setIsRefreshing] = useState(false);
66
+ const [editingAgent, setEditingAgent] = useState<AgentRole | null>(null);
67
+ const [isCreatingAgent, setIsCreatingAgent] = useState(false);
68
+ const [agentError, setAgentError] = useState<string | null>(null);
69
+ const [selectedAgent, setSelectedAgent] = useState<string | null>(null);
70
+ const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
71
+ const [rightTab, setRightTab] = useState<'feed' | 'task'>('feed');
72
+ const [feedFilter, setFeedFilter] = useState<'all' | 'tasks' | 'comments' | 'status'>('all');
73
+ const [currentTime, setCurrentTime] = useState(new Date());
74
+ const [commentText, setCommentText] = useState('');
75
+ const [postingComment, setPostingComment] = useState(false);
76
+ const [standupOpen, setStandupOpen] = useState(false);
77
+ const [dragOverColumn, setDragOverColumn] = useState<string | null>(null);
78
+ const tasksRef = useRef<Task[]>([]);
79
+ const workspaceIdRef = useRef<string | null>(null);
80
+ const agentContext = useAgentContext();
81
+ const filterLabels: Record<typeof feedFilter, UiCopyKey> = {
82
+ all: 'mcFilterAll',
83
+ tasks: 'mcFilterTasks',
84
+ comments: 'mcFilterComments',
85
+ status: 'mcFilterStatus',
86
+ };
87
+
88
+ useEffect(() => {
89
+ tasksRef.current = tasks;
90
+ }, [tasks]);
91
+
92
+ useEffect(() => {
93
+ workspaceIdRef.current = selectedWorkspaceId;
94
+ }, [selectedWorkspaceId]);
95
+
96
+ useEffect(() => {
97
+ setCommentText('');
98
+ }, [selectedTaskId]);
99
+
100
+ // Update clock every second
101
+ useEffect(() => {
102
+ const timer = setInterval(() => setCurrentTime(new Date()), 1000);
103
+ return () => clearInterval(timer);
104
+ }, []);
105
+
106
+ const loadWorkspaces = useCallback(async () => {
107
+ try {
108
+ const loaded = await window.electronAPI.listWorkspaces();
109
+ let tempWorkspace: Workspace | null = null;
110
+ try {
111
+ tempWorkspace = await window.electronAPI.getTempWorkspace();
112
+ } catch {
113
+ tempWorkspace = null;
114
+ }
115
+
116
+ const combined = [
117
+ ...(tempWorkspace ? [tempWorkspace] : []),
118
+ ...loaded.filter((workspace) => workspace.id !== tempWorkspace?.id),
119
+ ];
120
+
121
+ if (combined.length === 0) {
122
+ return;
123
+ }
124
+
125
+ setWorkspaces(combined);
126
+ if (!selectedWorkspaceId || !combined.some((workspace) => workspace.id === selectedWorkspaceId)) {
127
+ setSelectedWorkspaceId(combined[0].id);
128
+ }
129
+ } catch (err) {
130
+ console.error('Failed to load workspaces:', err);
131
+ }
132
+ }, [selectedWorkspaceId]);
133
+
134
+ const loadData = useCallback(async (workspaceId: string) => {
135
+ try {
136
+ setLoading(true);
137
+ const [loadedAgents, statuses, loadedTasks, loadedActivities, loadedMentions] = await Promise.all([
138
+ window.electronAPI.getAgentRoles(true),
139
+ window.electronAPI.getAllHeartbeatStatus(),
140
+ window.electronAPI.listTasks().catch(() => []),
141
+ window.electronAPI.listActivities({ workspaceId, limit: 200 }).catch(() => []),
142
+ window.electronAPI.listMentions({ workspaceId, limit: 200 }).catch(() => []),
143
+ ]);
144
+ setAgents(loadedAgents);
145
+ setHeartbeatStatuses(statuses);
146
+ const workspaceTasks = loadedTasks.filter((task: Task) => task.workspaceId === workspaceId);
147
+ setTasks(workspaceTasks);
148
+ setActivities(loadedActivities);
149
+ setMentions(loadedMentions);
150
+ setSelectedTaskId((prev) =>
151
+ prev && workspaceTasks.some((task) => task.id === prev) ? prev : null
152
+ );
153
+ } catch (err) {
154
+ console.error('Failed to load mission control data:', err);
155
+ } finally {
156
+ setLoading(false);
157
+ }
158
+ }, []);
159
+
160
+ const handleManualRefresh = useCallback(async () => {
161
+ if (!selectedWorkspaceId) return;
162
+ try {
163
+ setIsRefreshing(true);
164
+ const [statuses, loadedTasks, loadedActivities, loadedMentions] = await Promise.all([
165
+ window.electronAPI.getAllHeartbeatStatus().catch(() => []),
166
+ window.electronAPI.listTasks().catch(() => []),
167
+ window.electronAPI.listActivities({ workspaceId: selectedWorkspaceId, limit: 200 }).catch(() => []),
168
+ window.electronAPI.listMentions({ workspaceId: selectedWorkspaceId, limit: 200 }).catch(() => []),
169
+ ]);
170
+ setHeartbeatStatuses(statuses);
171
+ const workspaceTasks = loadedTasks.filter((task: Task) => task.workspaceId === selectedWorkspaceId);
172
+ setTasks(workspaceTasks);
173
+ setActivities(loadedActivities);
174
+ setMentions(loadedMentions);
175
+ } catch (err) {
176
+ console.error('Failed to refresh mission control data:', err);
177
+ } finally {
178
+ setIsRefreshing(false);
179
+ }
180
+ }, [selectedWorkspaceId]);
181
+
182
+ useEffect(() => {
183
+ loadWorkspaces();
184
+ }, [loadWorkspaces]);
185
+
186
+ useEffect(() => {
187
+ if (selectedWorkspaceId) {
188
+ loadData(selectedWorkspaceId);
189
+ }
190
+ }, [selectedWorkspaceId, loadData]);
191
+
192
+ // Set up event subscriptions - these use refs to avoid stale closures
193
+ // and minimize re-subscription when workspace changes
194
+ useEffect(() => {
195
+ // Subscribe to heartbeat events (workspace-independent)
196
+ const unsubscribeHeartbeat = window.electronAPI.onHeartbeatEvent((event: HeartbeatEvent) => {
197
+ setEvents((prev) => [event, ...prev].slice(0, 100));
198
+
199
+ // Update status when event is received
200
+ setHeartbeatStatuses((prev) => prev.map((status) => {
201
+ if (status.agentRoleId === event.agentRoleId) {
202
+ return {
203
+ ...status,
204
+ heartbeatStatus: event.type === 'started' ? 'running' :
205
+ event.type === 'error' ? 'error' : 'sleeping',
206
+ lastHeartbeatAt: ['completed', 'no_work', 'work_found'].includes(event.type)
207
+ ? event.timestamp
208
+ : status.lastHeartbeatAt,
209
+ };
210
+ }
211
+ return status;
212
+ }));
213
+ });
214
+
215
+ // Activity events - filter by current workspace using ref
216
+ const unsubscribeActivities = window.electronAPI.onActivityEvent((event) => {
217
+ const currentWorkspaceId = workspaceIdRef.current;
218
+ switch (event.type) {
219
+ case 'created':
220
+ if (event.activity?.workspaceId === currentWorkspaceId) {
221
+ setActivities((prev) => [event.activity!, ...prev].slice(0, 200));
222
+ }
223
+ break;
224
+ case 'read':
225
+ setActivities((prev) =>
226
+ prev.map((activity) => activity.id === event.id ? { ...activity, isRead: true } : activity)
227
+ );
228
+ break;
229
+ case 'all_read':
230
+ if (event.workspaceId === currentWorkspaceId) {
231
+ setActivities((prev) => prev.map((activity) => ({ ...activity, isRead: true })));
232
+ }
233
+ break;
234
+ case 'pinned':
235
+ if (event.activity) {
236
+ setActivities((prev) =>
237
+ prev.map((activity) => activity.id === event.activity!.id ? event.activity! : activity)
238
+ );
239
+ }
240
+ break;
241
+ case 'deleted':
242
+ setActivities((prev) => prev.filter((activity) => activity.id !== event.id));
243
+ break;
244
+ }
245
+ });
246
+
247
+ // Mention events - filter by current workspace using ref
248
+ const unsubscribeMentions = window.electronAPI.onMentionEvent((event) => {
249
+ const currentWorkspaceId = workspaceIdRef.current;
250
+ if (!event.mention) return;
251
+ if (event.mention.workspaceId !== currentWorkspaceId) return;
252
+ switch (event.type) {
253
+ case 'created':
254
+ setMentions((prev) => [event.mention!, ...prev]);
255
+ break;
256
+ case 'acknowledged':
257
+ case 'completed':
258
+ case 'dismissed':
259
+ setMentions((prev) => prev.map((mention) => mention.id === event.mention!.id ? event.mention! : mention));
260
+ break;
261
+ }
262
+ });
263
+
264
+ // Task events - handle new tasks and status updates
265
+ const unsubscribeTaskEvents = window.electronAPI.onTaskEvent((event: any) => {
266
+ const currentWorkspaceId = workspaceIdRef.current;
267
+ const statusMap: Record<string, Task['status']> = {
268
+ task_created: 'pending',
269
+ task_queued: 'queued',
270
+ task_dequeued: 'planning',
271
+ executing: 'executing',
272
+ step_started: 'executing',
273
+ step_completed: 'executing',
274
+ task_completed: 'completed',
275
+ task_paused: 'paused',
276
+ approval_requested: 'blocked',
277
+ approval_granted: 'executing',
278
+ approval_denied: 'failed',
279
+ error: 'failed',
280
+ task_cancelled: 'cancelled',
281
+ };
282
+
283
+ if (event.type === 'task_created') {
284
+ const isNewTask = !tasksRef.current.some((task) => task.id === event.taskId);
285
+ if (isNewTask && currentWorkspaceId) {
286
+ // Fetch the task and add it if it belongs to current workspace
287
+ window.electronAPI.getTask(event.taskId)
288
+ .then((incoming) => {
289
+ if (!incoming) return;
290
+ if (incoming.workspaceId === currentWorkspaceId) {
291
+ setTasks((prev) => {
292
+ // Avoid duplicates
293
+ if (prev.some((t) => t.id === incoming.id)) return prev;
294
+ return [incoming, ...prev];
295
+ });
296
+ }
297
+ })
298
+ .catch((err) => console.debug('Failed to fetch new task', err));
299
+ }
300
+ return;
301
+ }
302
+
303
+ const newStatus = event.type === 'task_status' ? event.payload?.status : statusMap[event.type];
304
+ if (newStatus) {
305
+ setTasks((prev) =>
306
+ prev.map((task) =>
307
+ task.id === event.taskId ? { ...task, status: newStatus, updatedAt: Date.now() } : task
308
+ )
309
+ );
310
+ }
311
+ });
312
+
313
+ // Task board events - handle column moves, priority changes, etc.
314
+ const unsubscribeBoard = window.electronAPI.onTaskBoardEvent((event: TaskBoardEvent) => {
315
+ setTasks((prev) =>
316
+ prev.map((task) => {
317
+ if (task.id !== event.taskId) return task;
318
+ switch (event.type) {
319
+ case 'moved':
320
+ return { ...task, boardColumn: event.data?.column };
321
+ case 'priorityChanged':
322
+ return { ...task, priority: event.data?.priority };
323
+ case 'labelAdded':
324
+ return {
325
+ ...task,
326
+ labels: [...(task.labels || []), event.data?.labelId!].filter(Boolean),
327
+ };
328
+ case 'labelRemoved':
329
+ return {
330
+ ...task,
331
+ labels: (task.labels || []).filter((label) => label !== event.data?.labelId),
332
+ };
333
+ case 'dueDateChanged':
334
+ return { ...task, dueDate: event.data?.dueDate ?? undefined };
335
+ case 'estimateChanged':
336
+ return { ...task, estimatedMinutes: event.data?.estimatedMinutes ?? undefined };
337
+ default:
338
+ return task;
339
+ }
340
+ })
341
+ );
342
+ });
343
+
344
+ return () => {
345
+ unsubscribeHeartbeat();
346
+ unsubscribeActivities();
347
+ unsubscribeMentions();
348
+ unsubscribeTaskEvents();
349
+ unsubscribeBoard();
350
+ };
351
+ }, []); // Empty deps - subscriptions are stable, use refs for current values
352
+
353
+ const handleCreateAgent = () => {
354
+ setEditingAgent({
355
+ id: '',
356
+ name: '',
357
+ displayName: '',
358
+ description: '',
359
+ icon: '🤖',
360
+ color: '#6366f1',
361
+ capabilities: ['code'] as AgentCapability[],
362
+ isSystem: false,
363
+ isActive: true,
364
+ sortOrder: 100,
365
+ createdAt: Date.now(),
366
+ updatedAt: Date.now(),
367
+ });
368
+ setIsCreatingAgent(true);
369
+ };
370
+
371
+ const handleEditAgent = (agent: AgentRole) => {
372
+ setEditingAgent({ ...agent });
373
+ setIsCreatingAgent(false);
374
+ };
375
+
376
+ const handleSaveAgent = async (agent: AgentRole) => {
377
+ try {
378
+ setAgentError(null);
379
+ if (isCreatingAgent) {
380
+ const created = await window.electronAPI.createAgentRole({
381
+ name: agent.name,
382
+ displayName: agent.displayName,
383
+ description: agent.description,
384
+ icon: agent.icon,
385
+ color: agent.color,
386
+ personalityId: agent.personalityId,
387
+ modelKey: agent.modelKey,
388
+ providerType: agent.providerType,
389
+ systemPrompt: agent.systemPrompt,
390
+ capabilities: agent.capabilities,
391
+ toolRestrictions: agent.toolRestrictions,
392
+ autonomyLevel: agent.autonomyLevel,
393
+ soul: agent.soul,
394
+ heartbeatEnabled: agent.heartbeatEnabled,
395
+ heartbeatIntervalMinutes: agent.heartbeatIntervalMinutes,
396
+ heartbeatStaggerOffset: agent.heartbeatStaggerOffset,
397
+ });
398
+ setAgents((prev) => [...prev, created]);
399
+ } else {
400
+ const updated = await window.electronAPI.updateAgentRole({
401
+ id: agent.id,
402
+ displayName: agent.displayName,
403
+ description: agent.description,
404
+ icon: agent.icon,
405
+ color: agent.color,
406
+ personalityId: agent.personalityId,
407
+ modelKey: agent.modelKey,
408
+ providerType: agent.providerType,
409
+ systemPrompt: agent.systemPrompt,
410
+ capabilities: agent.capabilities,
411
+ toolRestrictions: agent.toolRestrictions,
412
+ isActive: agent.isActive,
413
+ sortOrder: agent.sortOrder,
414
+ autonomyLevel: agent.autonomyLevel,
415
+ soul: agent.soul,
416
+ heartbeatEnabled: agent.heartbeatEnabled,
417
+ heartbeatIntervalMinutes: agent.heartbeatIntervalMinutes,
418
+ heartbeatStaggerOffset: agent.heartbeatStaggerOffset,
419
+ });
420
+ if (updated) {
421
+ setAgents((prev) => prev.map((a) => (a.id === updated.id ? updated : a)));
422
+ }
423
+ }
424
+ setEditingAgent(null);
425
+ setIsCreatingAgent(false);
426
+ // Refresh heartbeat statuses
427
+ const statuses = await window.electronAPI.getAllHeartbeatStatus();
428
+ setHeartbeatStatuses(statuses);
429
+ } catch (err: any) {
430
+ setAgentError(err.message || 'Failed to save agent');
431
+ }
432
+ };
433
+
434
+ const formatRelativeTime = (timestamp?: number) => {
435
+ if (!timestamp) return '';
436
+ const now = Date.now();
437
+ const diff = now - timestamp;
438
+ const abs = Math.abs(diff);
439
+ const format = (value: number, unit: string, suffix: string) =>
440
+ `${value}${unit} ${suffix}`;
441
+ if (abs < 60000) return diff < 0 ? 'in <1m' : 'just now';
442
+ if (abs < 3600000) {
443
+ const minutes = Math.floor(abs / 60000);
444
+ return diff < 0 ? format(minutes, 'm', 'from now') : `${minutes}m ago`;
445
+ }
446
+ if (abs < 86400000) {
447
+ const hours = Math.floor(abs / 3600000);
448
+ return diff < 0 ? format(hours, 'h', 'from now') : `${hours}h ago`;
449
+ }
450
+ const days = Math.floor(abs / 86400000);
451
+ return diff < 0 ? format(days, 'd', 'from now') : `${days}d ago`;
452
+ };
453
+
454
+ const getAgentStatus = (agentId: string): 'working' | 'idle' | 'offline' => {
455
+ const status = heartbeatStatuses.find(s => s.agentRoleId === agentId);
456
+ if (!status?.heartbeatEnabled) return 'offline';
457
+ if (status.heartbeatStatus === 'running') return 'working';
458
+ return 'idle';
459
+ };
460
+
461
+ const getMissionColumnForTask = useCallback((task: Task) => {
462
+ if (task.status === 'completed') return 'done';
463
+ const col = task.boardColumn;
464
+ if (col === 'done') return 'done';
465
+ if (col === 'review') return 'review';
466
+ if (col === 'in_progress') return 'in_progress';
467
+ if (col === 'todo') return 'assigned';
468
+ if (col === 'backlog') return task.assignedAgentRoleId ? 'assigned' : 'inbox';
469
+ if (col === 'assigned' || col === 'inbox') return col;
470
+ return task.assignedAgentRoleId ? 'assigned' : 'inbox';
471
+ }, []);
472
+
473
+ const getBoardColumnForMission = useCallback(
474
+ (missionColumnId: string): NonNullable<Task['boardColumn']> => {
475
+ const column = BOARD_COLUMNS.find((col) => col.id === missionColumnId);
476
+ return column?.boardColumn ?? 'backlog';
477
+ },
478
+ []
479
+ );
480
+
481
+ const activeAgentsCount = useMemo(
482
+ () => agents.filter(a => a.isActive && heartbeatStatuses.some(s => s.agentRoleId === a.id && s.heartbeatEnabled)).length,
483
+ [agents, heartbeatStatuses]
484
+ );
485
+ const totalTasksInQueue = useMemo(
486
+ () => tasks.filter(t => getMissionColumnForTask(t) !== 'done').length,
487
+ [tasks, getMissionColumnForTask]
488
+ );
489
+ const pendingMentionsCount = useMemo(
490
+ () => mentions.filter(m => m.status === 'pending').length,
491
+ [mentions]
492
+ );
493
+ const selectedTask = useMemo(
494
+ () => tasks.find(task => task.id === selectedTaskId) || null,
495
+ [tasks, selectedTaskId]
496
+ );
497
+ const selectedWorkspace = useMemo(
498
+ () => workspaces.find(workspace => workspace.id === selectedWorkspaceId) || null,
499
+ [workspaces, selectedWorkspaceId]
500
+ );
501
+ const tasksByAgent = useMemo(() => {
502
+ const map = new Map<string, Task[]>();
503
+ tasks.forEach((task) => {
504
+ if (!task.assignedAgentRoleId) return;
505
+ const list = map.get(task.assignedAgentRoleId) || [];
506
+ list.push(task);
507
+ map.set(task.assignedAgentRoleId, list);
508
+ });
509
+ map.forEach((list) => list.sort((a, b) => (b.updatedAt || b.createdAt) - (a.updatedAt || a.createdAt)));
510
+ return map;
511
+ }, [tasks]);
512
+
513
+ // Get tasks by column
514
+ const getTasksByColumn = useCallback((columnId: string) => {
515
+ return tasks.filter(t => getMissionColumnForTask(t) === columnId);
516
+ }, [tasks, getMissionColumnForTask]);
517
+
518
+ // Get agent by ID
519
+ const getAgent = useCallback((agentId?: string) => {
520
+ if (!agentId) return null;
521
+ return agents.find(a => a.id === agentId);
522
+ }, [agents]);
523
+
524
+ const handleMoveTask = useCallback(async (taskId: string, missionColumnId: string) => {
525
+ try {
526
+ const boardColumn = getBoardColumnForMission(missionColumnId);
527
+ await window.electronAPI.moveTaskToColumn(taskId, boardColumn);
528
+ setTasks((prev) =>
529
+ prev.map((task) => task.id === taskId ? { ...task, boardColumn, updatedAt: Date.now() } : task)
530
+ );
531
+ } catch (err) {
532
+ console.error('Failed to move task:', err);
533
+ }
534
+ }, [getBoardColumnForMission]);
535
+
536
+ const handleAssignTask = useCallback(async (taskId: string, agentRoleId: string | null) => {
537
+ try {
538
+ await window.electronAPI.assignAgentRoleToTask(taskId, agentRoleId);
539
+ setTasks((prev) =>
540
+ prev.map((task) => task.id === taskId ? { ...task, assignedAgentRoleId: agentRoleId ?? undefined, updatedAt: Date.now() } : task)
541
+ );
542
+ } catch (err) {
543
+ console.error('Failed to assign agent:', err);
544
+ }
545
+ }, []);
546
+
547
+ const handleTriggerHeartbeat = useCallback(async (agentRoleId: string) => {
548
+ try {
549
+ await window.electronAPI.triggerHeartbeat(agentRoleId);
550
+ } catch (err) {
551
+ console.error('Failed to trigger heartbeat:', err);
552
+ }
553
+ }, []);
554
+
555
+ const handlePostComment = useCallback(async () => {
556
+ if (!selectedWorkspaceId || !selectedTask) return;
557
+ const text = commentText.trim();
558
+ if (!text) return;
559
+ try {
560
+ setPostingComment(true);
561
+ await window.electronAPI.createActivity({
562
+ workspaceId: selectedWorkspaceId,
563
+ taskId: selectedTask.id,
564
+ actorType: 'user',
565
+ activityType: 'comment',
566
+ title: 'Comment',
567
+ description: text,
568
+ });
569
+ setCommentText('');
570
+ } catch (err) {
571
+ console.error('Failed to post comment:', err);
572
+ } finally {
573
+ setPostingComment(false);
574
+ }
575
+ }, [commentText, selectedTask, selectedWorkspaceId]);
576
+
577
+ // Build combined feed items with filtering
578
+ const feedItems = useMemo(() => {
579
+ const activityItems = activities.map((activity) => {
580
+ const mappedType =
581
+ activity.activityType === 'comment' || activity.activityType === 'mention'
582
+ ? 'comments'
583
+ : activity.activityType.startsWith('task_') || activity.activityType === 'agent_assigned'
584
+ ? 'tasks'
585
+ : 'status';
586
+ const agentName = activity.actorType === 'user'
587
+ ? agentContext.getUiCopy('activityActorUser')
588
+ : getAgent(activity.agentRoleId)?.displayName || agentContext.getUiCopy('activityActorSystem');
589
+ const content = activity.description ? `${activity.title} — ${activity.description}` : activity.title;
590
+ return {
591
+ id: activity.id,
592
+ type: mappedType as 'comments' | 'tasks' | 'status',
593
+ agentId: activity.agentRoleId,
594
+ agentName,
595
+ content,
596
+ taskId: activity.taskId,
597
+ timestamp: activity.createdAt,
598
+ };
599
+ });
600
+
601
+ const heartbeatItems = events.map((event) => ({
602
+ id: `event-${event.timestamp}`,
603
+ type: 'status' as const,
604
+ agentId: event.agentRoleId,
605
+ agentName: event.agentName,
606
+ content: event.type === 'work_found'
607
+ ? agentContext.getUiCopy('mcHeartbeatFound', {
608
+ mentions: event.result?.pendingMentions || 0,
609
+ tasks: event.result?.assignedTasks || 0,
610
+ })
611
+ : event.type,
612
+ timestamp: event.timestamp,
613
+ taskId: undefined as string | undefined,
614
+ }));
615
+
616
+ return [...heartbeatItems, ...activityItems]
617
+ .filter(item => {
618
+ if (feedFilter !== 'all' && item.type !== feedFilter) return false;
619
+ if (selectedAgent) {
620
+ if (!item.agentId) return false;
621
+ if (item.agentId !== selectedAgent) return false;
622
+ }
623
+ return true;
624
+ })
625
+ .sort((a, b) => b.timestamp - a.timestamp)
626
+ .slice(0, 50);
627
+ }, [activities, events, feedFilter, selectedAgent, getAgent, agentContext]);
628
+
629
+ if (loading) {
630
+ return (
631
+ <div className="mission-control">
632
+ <div className="mc-loading">{agentContext.getUiCopy('mcLoading')}</div>
633
+ <style>{styles}</style>
634
+ </div>
635
+ );
636
+ }
637
+
638
+ // Show agent editor modal if editing
639
+ if (editingAgent) {
640
+ return (
641
+ <div className="mission-control">
642
+ <div className="mc-editor-overlay">
643
+ <div className="mc-editor-modal">
644
+ <AgentRoleEditor
645
+ role={editingAgent}
646
+ isCreating={isCreatingAgent}
647
+ onSave={handleSaveAgent}
648
+ onCancel={() => { setEditingAgent(null); setIsCreatingAgent(false); setAgentError(null); }}
649
+ error={agentError}
650
+ />
651
+ </div>
652
+ </div>
653
+ <style>{styles}</style>
654
+ </div>
655
+ );
656
+ }
657
+
658
+ return (
659
+ <div className="mission-control">
660
+ {/* Header */}
661
+ <header className="mc-header">
662
+ <div className="mc-header-left">
663
+ <h1>{agentContext.getUiCopy('mcTitle')}</h1>
664
+ <div className="mc-workspace-select">
665
+ <span className="mc-workspace-label">{agentContext.getUiCopy('mcWorkspaceLabel')}</span>
666
+ <select
667
+ value={selectedWorkspaceId || ''}
668
+ onChange={(e) => setSelectedWorkspaceId(e.target.value)}
669
+ >
670
+ {workspaces.map((workspace) => (
671
+ <option key={workspace.id} value={workspace.id}>
672
+ {workspace.name}
673
+ </option>
674
+ ))}
675
+ </select>
676
+ </div>
677
+ </div>
678
+ <div className="mc-header-stats">
679
+ <div className="mc-stat">
680
+ <span className="mc-stat-value">{activeAgentsCount}</span>
681
+ <span className="mc-stat-label">{agentContext.getUiCopy('mcAgentsActiveLabel')}</span>
682
+ </div>
683
+ <div className="mc-stat">
684
+ <span className="mc-stat-value">{totalTasksInQueue}</span>
685
+ <span className="mc-stat-label">{agentContext.getUiCopy('mcTasksQueueLabel')}</span>
686
+ </div>
687
+ <div className="mc-stat">
688
+ <span className="mc-stat-value">{pendingMentionsCount}</span>
689
+ <span className="mc-stat-label">{agentContext.getUiCopy('mcMentionsLabel')}</span>
690
+ </div>
691
+ </div>
692
+ <div className="mc-header-right">
693
+ <button
694
+ className="mc-refresh-btn"
695
+ onClick={handleManualRefresh}
696
+ disabled={!selectedWorkspaceId || isRefreshing}
697
+ title="Refresh mission control data"
698
+ >
699
+ {isRefreshing ? 'Refreshing...' : 'Refresh'}
700
+ </button>
701
+ <button
702
+ className="mc-standup-btn"
703
+ onClick={() => setStandupOpen(true)}
704
+ disabled={!selectedWorkspace}
705
+ >
706
+ {agentContext.getUiCopy('mcStandupButton')}
707
+ </button>
708
+ <span className="mc-time">{currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}</span>
709
+ <span className="mc-status-badge online">{agentContext.getUiCopy('mcStatusOnline')}</span>
710
+ </div>
711
+ </header>
712
+
713
+ {/* Main Content */}
714
+ <div className="mc-content">
715
+ {/* Left Panel - Agents */}
716
+ <aside className="mc-agents-panel">
717
+ <div className="mc-panel-header">
718
+ <h2>{agentContext.getUiCopy('mcAgentsTitle')}</h2>
719
+ <span className="mc-count">{agents.filter(a => a.isActive).length}</span>
720
+ </div>
721
+ <div className="mc-agents-list">
722
+ {agents.filter(a => a.isActive).map((agent) => {
723
+ const status = getAgentStatus(agent.id);
724
+ const badge = AUTONOMY_BADGES[agent.autonomyLevel || 'specialist'];
725
+ const statusInfo = heartbeatStatuses.find((s) => s.agentRoleId === agent.id);
726
+ const agentTasks = tasksByAgent.get(agent.id) || [];
727
+ const currentTask = agentTasks[0];
728
+
729
+ return (
730
+ <div
731
+ key={agent.id}
732
+ className={`mc-agent-item ${selectedAgent === agent.id ? 'selected' : ''}`}
733
+ onClick={() => setSelectedAgent(selectedAgent === agent.id ? null : agent.id)}
734
+ onDoubleClick={() => handleEditAgent(agent)}
735
+ role="button"
736
+ tabIndex={0}
737
+ >
738
+ <div className="mc-agent-avatar" style={{ backgroundColor: agent.color }}>
739
+ {agent.icon}
740
+ </div>
741
+ <div className="mc-agent-info">
742
+ <div className="mc-agent-name-row">
743
+ <span className="mc-agent-name">{agent.displayName}</span>
744
+ <span className="mc-autonomy-badge" style={{ backgroundColor: badge.color }}>
745
+ {badge.label}
746
+ </span>
747
+ </div>
748
+ <span className="mc-agent-role">{agent.description?.slice(0, 30) || agent.name}</span>
749
+ <span className="mc-agent-task">
750
+ {currentTask ? currentTask.title : agentContext.getUiCopy('mcNoActiveTask')}
751
+ </span>
752
+ </div>
753
+ <div className={`mc-agent-status ${status}`}>
754
+ <span className="mc-status-dot"></span>
755
+ <span className="mc-status-text">{status.toUpperCase()}</span>
756
+ {statusInfo?.nextHeartbeatAt && (
757
+ <span className="mc-heartbeat-time">
758
+ {agentContext.getUiCopy('mcHeartbeatNext', {
759
+ time: formatRelativeTime(statusInfo.nextHeartbeatAt),
760
+ })}
761
+ </span>
762
+ )}
763
+ </div>
764
+ {statusInfo?.heartbeatEnabled && (
765
+ <span
766
+ className="mc-agent-wake"
767
+ onClick={(e) => {
768
+ e.stopPropagation();
769
+ handleTriggerHeartbeat(agent.id);
770
+ }}
771
+ role="button"
772
+ tabIndex={0}
773
+ >
774
+ {agentContext.getUiCopy('mcWakeAgent')}
775
+ </span>
776
+ )}
777
+ </div>
778
+ );
779
+ })}
780
+ </div>
781
+ <button className="mc-add-agent-btn" onClick={handleCreateAgent}>
782
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
783
+ <line x1="12" y1="5" x2="12" y2="19" />
784
+ <line x1="5" y1="12" x2="19" y2="12" />
785
+ </svg>
786
+ {agentContext.getUiCopy('mcAddAgent')}
787
+ </button>
788
+ </aside>
789
+
790
+ {/* Center - Mission Queue */}
791
+ <main className="mc-queue-panel">
792
+ <div className="mc-panel-header">
793
+ <h2>{agentContext.getUiCopy('mcMissionQueueTitle')}</h2>
794
+ </div>
795
+ <div className="mc-kanban">
796
+ {BOARD_COLUMNS.map((column) => {
797
+ const columnTasks = getTasksByColumn(column.id);
798
+ return (
799
+ <div
800
+ key={column.id}
801
+ className={`mc-kanban-column ${dragOverColumn === column.id ? 'drag-over' : ''}`}
802
+ onDragOver={(e) => {
803
+ e.preventDefault();
804
+ setDragOverColumn(column.id);
805
+ }}
806
+ onDragLeave={() => setDragOverColumn(null)}
807
+ onDrop={(e) => {
808
+ e.preventDefault();
809
+ const taskId = e.dataTransfer.getData('text/plain');
810
+ if (taskId) {
811
+ handleMoveTask(taskId, column.id);
812
+ }
813
+ setDragOverColumn(null);
814
+ }}
815
+ >
816
+ <div className="mc-column-header">
817
+ <span className="mc-column-dot" style={{ backgroundColor: column.color }}></span>
818
+ <span className="mc-column-label">{column.label}</span>
819
+ <span className="mc-column-count">{columnTasks.length}</span>
820
+ </div>
821
+ <div className="mc-column-tasks">
822
+ {columnTasks.map((task) => {
823
+ const assignedAgent = getAgent(task.assignedAgentRoleId);
824
+ return (
825
+ <div
826
+ key={task.id}
827
+ className={`mc-task-card ${selectedTaskId === task.id ? 'selected' : ''}`}
828
+ draggable
829
+ onDragStart={(e) => {
830
+ e.dataTransfer.setData('text/plain', task.id);
831
+ e.dataTransfer.effectAllowed = 'move';
832
+ }}
833
+ onClick={() => {
834
+ setSelectedTaskId(task.id);
835
+ setRightTab('task');
836
+ }}
837
+ >
838
+ <div className="mc-task-title">{task.title}</div>
839
+ {assignedAgent && (
840
+ <div className="mc-task-assignee">
841
+ <span className="mc-task-assignee-avatar" style={{ backgroundColor: assignedAgent.color }}>
842
+ {assignedAgent.icon}
843
+ </span>
844
+ <span className="mc-task-assignee-name">{assignedAgent.displayName}</span>
845
+ </div>
846
+ )}
847
+ <div className="mc-task-meta">
848
+ <span className={`mc-task-status-pill status-${task.status}`}>
849
+ {task.status.replace('_', ' ')}
850
+ </span>
851
+ <span className="mc-task-time">{formatRelativeTime(task.updatedAt)}</span>
852
+ </div>
853
+ </div>
854
+ );
855
+ })}
856
+ {columnTasks.length === 0 && (
857
+ <div className="mc-column-empty">{agentContext.getUiCopy('mcColumnEmpty')}</div>
858
+ )}
859
+ </div>
860
+ </div>
861
+ );
862
+ })}
863
+ </div>
864
+ </main>
865
+
866
+ {/* Right Panel - Live Feed */}
867
+ <aside className="mc-feed-panel">
868
+ <div className="mc-panel-header mc-feed-header">
869
+ <div className="mc-tabs">
870
+ <button
871
+ className={`mc-tab-btn ${rightTab === 'feed' ? 'active' : ''}`}
872
+ onClick={() => setRightTab('feed')}
873
+ >
874
+ {agentContext.getUiCopy('mcLiveFeedTitle')}
875
+ </button>
876
+ <button
877
+ className={`mc-tab-btn ${rightTab === 'task' ? 'active' : ''}`}
878
+ onClick={() => setRightTab('task')}
879
+ >
880
+ {agentContext.getUiCopy('mcTaskTab')}
881
+ </button>
882
+ </div>
883
+ {rightTab === 'task' && selectedTask && (
884
+ <button
885
+ className="mc-clear-task"
886
+ onClick={() => setSelectedTaskId(null)}
887
+ >
888
+ {agentContext.getUiCopy('mcClearTask')}
889
+ </button>
890
+ )}
891
+ </div>
892
+
893
+ {rightTab === 'feed' ? (
894
+ <>
895
+ <div className="mc-feed-filters">
896
+ {(['all', 'tasks', 'comments', 'status'] as const).map((filter) => (
897
+ <button
898
+ key={filter}
899
+ className={`mc-filter-btn ${feedFilter === filter ? 'active' : ''}`}
900
+ onClick={() => setFeedFilter(filter)}
901
+ >
902
+ {agentContext.getUiCopy(filterLabels[filter])}
903
+ </button>
904
+ ))}
905
+ </div>
906
+ <div className="mc-feed-agents">
907
+ <span className="mc-feed-agents-label">{agentContext.getUiCopy('mcAllAgentsLabel')}</span>
908
+ <div className="mc-feed-agent-chips">
909
+ {agents.filter(a => a.isActive).map((agent) => (
910
+ <button
911
+ key={agent.id}
912
+ className={`mc-agent-chip ${selectedAgent === agent.id ? 'active' : ''}`}
913
+ style={{ borderColor: agent.color }}
914
+ onClick={() => setSelectedAgent(selectedAgent === agent.id ? null : agent.id)}
915
+ >
916
+ {agent.icon} {agent.displayName.split(' ')[0]}
917
+ </button>
918
+ ))}
919
+ </div>
920
+ </div>
921
+ <div className="mc-feed-list">
922
+ {feedItems.length === 0 ? (
923
+ <div className="mc-feed-empty">{agentContext.getUiCopy('mcFeedEmpty')}</div>
924
+ ) : (
925
+ feedItems.map((item) => {
926
+ const agent = getAgent(item.agentId);
927
+ return (
928
+ <div key={item.id} className="mc-feed-item">
929
+ <div className="mc-feed-item-header">
930
+ {agent && (
931
+ <span className="mc-feed-agent" style={{ color: agent.color }}>
932
+ {agent.icon} {agent.displayName}
933
+ </span>
934
+ )}
935
+ {!agent && item.agentName && (
936
+ <span className="mc-feed-agent system">{item.agentName}</span>
937
+ )}
938
+ <span className="mc-feed-time">{formatRelativeTime(item.timestamp)}</span>
939
+ </div>
940
+ <div className="mc-feed-content">{item.content}</div>
941
+ </div>
942
+ );
943
+ })
944
+ )}
945
+ </div>
946
+ </>
947
+ ) : (
948
+ <div className="mc-task-detail">
949
+ {selectedTask ? (
950
+ <>
951
+ <div className="mc-task-detail-header">
952
+ <div className="mc-task-detail-title">
953
+ <h3>{selectedTask.title}</h3>
954
+ <span className={`mc-task-detail-status status-${selectedTask.status}`}>
955
+ {selectedTask.status.replace('_', ' ')}
956
+ </span>
957
+ </div>
958
+ <div className="mc-task-detail-updated">
959
+ {agentContext.getUiCopy('mcTaskUpdatedAt', {
960
+ time: formatRelativeTime(selectedTask.updatedAt),
961
+ })}
962
+ </div>
963
+ </div>
964
+
965
+ <div className="mc-task-detail-meta">
966
+ <label>
967
+ {agentContext.getUiCopy('mcTaskAssigneeLabel')}
968
+ <select
969
+ value={selectedTask.assignedAgentRoleId || ''}
970
+ onChange={(e) => handleAssignTask(selectedTask.id, e.target.value || null)}
971
+ >
972
+ <option value="">{agentContext.getUiCopy('mcTaskUnassigned')}</option>
973
+ {agents.filter(a => a.isActive).map((agent) => (
974
+ <option key={agent.id} value={agent.id}>
975
+ {agent.displayName}
976
+ </option>
977
+ ))}
978
+ </select>
979
+ </label>
980
+ <label>
981
+ {agentContext.getUiCopy('mcTaskStageLabel')}
982
+ <select
983
+ value={getMissionColumnForTask(selectedTask)}
984
+ onChange={(e) => handleMoveTask(selectedTask.id, e.target.value)}
985
+ >
986
+ {BOARD_COLUMNS.map((column) => (
987
+ <option key={column.id} value={column.id}>
988
+ {column.label}
989
+ </option>
990
+ ))}
991
+ </select>
992
+ </label>
993
+ </div>
994
+
995
+ <div className="mc-task-detail-section">
996
+ <h4>{agentContext.getUiCopy('mcTaskBriefTitle')}</h4>
997
+ <p className="mc-task-detail-brief">{selectedTask.prompt}</p>
998
+ </div>
999
+
1000
+ <div className="mc-task-detail-section">
1001
+ <h4>{agentContext.getUiCopy('mcTaskUpdatesTitle')}</h4>
1002
+ {selectedWorkspaceId && (
1003
+ <ActivityFeed
1004
+ workspaceId={selectedWorkspaceId}
1005
+ taskId={selectedTask.id}
1006
+ compact
1007
+ maxItems={20}
1008
+ showFilters={false}
1009
+ />
1010
+ )}
1011
+ <div className="mc-comment-box">
1012
+ <textarea
1013
+ placeholder={agentContext.getUiCopy('mcTaskUpdatePlaceholder')}
1014
+ value={commentText}
1015
+ onChange={(e) => setCommentText(e.target.value)}
1016
+ rows={3}
1017
+ />
1018
+ <button
1019
+ className="mc-comment-submit"
1020
+ onClick={handlePostComment}
1021
+ disabled={postingComment || commentText.trim().length === 0}
1022
+ >
1023
+ {postingComment
1024
+ ? agentContext.getUiCopy('mcTaskPosting')
1025
+ : agentContext.getUiCopy('mcTaskPostUpdate')}
1026
+ </button>
1027
+ </div>
1028
+ </div>
1029
+
1030
+ <div className="mc-task-detail-section">
1031
+ <h4>{agentContext.getUiCopy('mcTaskMentionsTitle')}</h4>
1032
+ {selectedWorkspaceId && (
1033
+ <>
1034
+ <MentionInput
1035
+ workspaceId={selectedWorkspaceId}
1036
+ taskId={selectedTask.id}
1037
+ placeholder={agentContext.getUiCopy('mcTaskMentionPlaceholder')}
1038
+ />
1039
+ <MentionList
1040
+ workspaceId={selectedWorkspaceId}
1041
+ taskId={selectedTask.id}
1042
+ />
1043
+ </>
1044
+ )}
1045
+ </div>
1046
+ </>
1047
+ ) : (
1048
+ <div className="mc-task-empty">
1049
+ {agentContext.getUiCopy('mcTaskEmpty')}
1050
+ </div>
1051
+ )}
1052
+ </div>
1053
+ )}
1054
+ </aside>
1055
+ </div>
1056
+
1057
+ {standupOpen && selectedWorkspace && (
1058
+ <div className="mc-editor-overlay">
1059
+ <div className="mc-editor-modal mc-standup-modal">
1060
+ <StandupReportViewer
1061
+ workspaceId={selectedWorkspace.id}
1062
+ onClose={() => setStandupOpen(false)}
1063
+ />
1064
+ </div>
1065
+ </div>
1066
+ )}
1067
+
1068
+ <style>{styles}</style>
1069
+ </div>
1070
+ );
1071
+ }
1072
+
1073
+ const styles = `
1074
+ .mission-control {
1075
+ position: absolute;
1076
+ inset: 0;
1077
+ display: flex;
1078
+ flex-direction: column;
1079
+ background: var(--color-bg-primary);
1080
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1081
+ }
1082
+
1083
+ .mc-loading {
1084
+ display: flex;
1085
+ align-items: center;
1086
+ justify-content: center;
1087
+ height: 100%;
1088
+ color: var(--color-text-secondary);
1089
+ }
1090
+
1091
+ /* Header */
1092
+ .mc-header {
1093
+ display: flex;
1094
+ align-items: center;
1095
+ justify-content: space-between;
1096
+ padding: 12px 20px;
1097
+ background: var(--color-bg-secondary);
1098
+ border-bottom: 1px solid var(--color-border);
1099
+ }
1100
+
1101
+ .mc-header-left {
1102
+ display: flex;
1103
+ align-items: center;
1104
+ gap: 16px;
1105
+ }
1106
+
1107
+ .mc-header h1 {
1108
+ font-size: 14px;
1109
+ font-weight: 600;
1110
+ letter-spacing: 1px;
1111
+ color: var(--color-text-primary);
1112
+ margin: 0;
1113
+ }
1114
+
1115
+ .mc-workspace-select {
1116
+ display: flex;
1117
+ align-items: center;
1118
+ gap: 6px;
1119
+ padding: 4px 8px;
1120
+ background: var(--color-bg-tertiary);
1121
+ border: 1px solid var(--color-border);
1122
+ border-radius: 6px;
1123
+ }
1124
+
1125
+ .mc-workspace-label {
1126
+ font-size: 10px;
1127
+ color: var(--color-text-muted);
1128
+ letter-spacing: 0.4px;
1129
+ }
1130
+
1131
+ .mc-workspace-select select {
1132
+ border: none;
1133
+ background: transparent;
1134
+ color: var(--color-text-primary);
1135
+ font-size: 12px;
1136
+ outline: none;
1137
+ }
1138
+
1139
+ .mc-header-stats {
1140
+ display: flex;
1141
+ gap: 40px;
1142
+ }
1143
+
1144
+ .mc-stat {
1145
+ display: flex;
1146
+ flex-direction: column;
1147
+ align-items: center;
1148
+ }
1149
+
1150
+ .mc-stat-value {
1151
+ font-size: 24px;
1152
+ font-weight: 600;
1153
+ color: var(--color-text-primary);
1154
+ }
1155
+
1156
+ .mc-stat-label {
1157
+ font-size: 10px;
1158
+ color: var(--color-text-secondary);
1159
+ letter-spacing: 0.5px;
1160
+ }
1161
+
1162
+ .mc-header-right {
1163
+ display: flex;
1164
+ align-items: center;
1165
+ gap: 16px;
1166
+ }
1167
+
1168
+ .mc-refresh-btn {
1169
+ padding: 6px 10px;
1170
+ border-radius: 6px;
1171
+ border: 1px solid var(--color-border);
1172
+ background: var(--color-bg-tertiary);
1173
+ color: var(--color-text-secondary);
1174
+ font-size: 11px;
1175
+ font-weight: 600;
1176
+ cursor: pointer;
1177
+ transition: all 0.15s;
1178
+ }
1179
+
1180
+ .mc-refresh-btn:hover:not(:disabled) {
1181
+ background: var(--color-bg-hover);
1182
+ color: var(--color-text-primary);
1183
+ }
1184
+
1185
+ .mc-refresh-btn:disabled {
1186
+ opacity: 0.5;
1187
+ cursor: not-allowed;
1188
+ }
1189
+
1190
+ .mc-standup-btn {
1191
+ padding: 6px 10px;
1192
+ border-radius: 6px;
1193
+ border: 1px solid var(--color-border);
1194
+ background: var(--color-bg-tertiary);
1195
+ color: var(--color-text-secondary);
1196
+ font-size: 11px;
1197
+ font-weight: 600;
1198
+ cursor: pointer;
1199
+ transition: all 0.15s;
1200
+ }
1201
+
1202
+ .mc-standup-btn:hover:not(:disabled) {
1203
+ background: var(--color-bg-hover);
1204
+ color: var(--color-text-primary);
1205
+ }
1206
+
1207
+ .mc-standup-btn:disabled {
1208
+ opacity: 0.5;
1209
+ cursor: not-allowed;
1210
+ }
1211
+
1212
+ .mc-time {
1213
+ font-size: 14px;
1214
+ font-weight: 500;
1215
+ color: var(--color-text-primary);
1216
+ font-family: 'SF Mono', Monaco, monospace;
1217
+ }
1218
+
1219
+ .mc-status-badge {
1220
+ padding: 4px 12px;
1221
+ border-radius: 4px;
1222
+ font-size: 11px;
1223
+ font-weight: 600;
1224
+ letter-spacing: 0.5px;
1225
+ }
1226
+
1227
+ .mc-status-badge.online {
1228
+ background: var(--color-success-subtle);
1229
+ color: var(--color-success);
1230
+ }
1231
+
1232
+ /* Main Content Layout */
1233
+ .mc-content {
1234
+ display: flex;
1235
+ flex: 1;
1236
+ overflow: hidden;
1237
+ }
1238
+
1239
+ .mc-panel-header {
1240
+ display: flex;
1241
+ align-items: center;
1242
+ gap: 8px;
1243
+ padding: 12px 16px;
1244
+ border-bottom: 1px solid var(--color-border);
1245
+ }
1246
+
1247
+ .mc-panel-header h2 {
1248
+ font-size: 11px;
1249
+ font-weight: 600;
1250
+ letter-spacing: 0.5px;
1251
+ color: var(--color-text-secondary);
1252
+ margin: 0;
1253
+ }
1254
+
1255
+ .mc-count {
1256
+ font-size: 11px;
1257
+ color: var(--color-text-muted);
1258
+ }
1259
+
1260
+ /* Agents Panel */
1261
+ .mc-agents-panel {
1262
+ width: 280px;
1263
+ min-width: 280px;
1264
+ background: var(--color-bg-secondary);
1265
+ border-right: 1px solid var(--color-border);
1266
+ display: flex;
1267
+ flex-direction: column;
1268
+ flex-shrink: 0;
1269
+ }
1270
+
1271
+ .mc-agents-list {
1272
+ flex: 1;
1273
+ overflow-y: auto;
1274
+ padding: 8px;
1275
+ }
1276
+
1277
+ .mc-agent-item {
1278
+ display: flex;
1279
+ align-items: center;
1280
+ gap: 10px;
1281
+ width: 100%;
1282
+ padding: 10px;
1283
+ background: transparent;
1284
+ border: none;
1285
+ border-radius: 8px;
1286
+ cursor: pointer;
1287
+ text-align: left;
1288
+ transition: background 0.15s;
1289
+ }
1290
+
1291
+ .mc-agent-item:hover {
1292
+ background: var(--color-bg-tertiary);
1293
+ }
1294
+
1295
+ .mc-agent-item.selected {
1296
+ background: var(--color-accent-subtle);
1297
+ }
1298
+
1299
+ .mc-agent-avatar {
1300
+ width: 36px;
1301
+ height: 36px;
1302
+ border-radius: 50%;
1303
+ display: flex;
1304
+ align-items: center;
1305
+ justify-content: center;
1306
+ font-size: 16px;
1307
+ flex-shrink: 0;
1308
+ }
1309
+
1310
+ .mc-agent-info {
1311
+ flex: 1;
1312
+ min-width: 0;
1313
+ display: flex;
1314
+ flex-direction: column;
1315
+ gap: 2px;
1316
+ }
1317
+
1318
+ .mc-agent-name-row {
1319
+ display: flex;
1320
+ align-items: center;
1321
+ gap: 6px;
1322
+ }
1323
+
1324
+ .mc-agent-name {
1325
+ font-size: 13px;
1326
+ font-weight: 600;
1327
+ color: var(--color-text-primary);
1328
+ white-space: nowrap;
1329
+ overflow: hidden;
1330
+ text-overflow: ellipsis;
1331
+ }
1332
+
1333
+ .mc-autonomy-badge {
1334
+ padding: 1px 5px;
1335
+ border-radius: 3px;
1336
+ font-size: 9px;
1337
+ font-weight: 600;
1338
+ color: white;
1339
+ letter-spacing: 0.3px;
1340
+ }
1341
+
1342
+ .mc-agent-role {
1343
+ font-size: 11px;
1344
+ color: var(--color-text-secondary);
1345
+ white-space: nowrap;
1346
+ overflow: hidden;
1347
+ text-overflow: ellipsis;
1348
+ }
1349
+
1350
+ .mc-agent-task {
1351
+ font-size: 10px;
1352
+ color: var(--color-text-muted);
1353
+ white-space: nowrap;
1354
+ overflow: hidden;
1355
+ text-overflow: ellipsis;
1356
+ }
1357
+
1358
+ .mc-agent-status {
1359
+ display: flex;
1360
+ align-items: center;
1361
+ gap: 4px;
1362
+ flex-shrink: 0;
1363
+ }
1364
+
1365
+ .mc-status-dot {
1366
+ width: 6px;
1367
+ height: 6px;
1368
+ border-radius: 50%;
1369
+ }
1370
+
1371
+ .mc-agent-status.working .mc-status-dot {
1372
+ background: var(--color-success);
1373
+ }
1374
+
1375
+ .mc-agent-status.idle .mc-status-dot {
1376
+ background: var(--color-text-muted);
1377
+ }
1378
+
1379
+ .mc-agent-status.offline .mc-status-dot {
1380
+ background: var(--color-border);
1381
+ }
1382
+
1383
+ .mc-status-text {
1384
+ font-size: 9px;
1385
+ font-weight: 500;
1386
+ color: var(--color-text-secondary);
1387
+ }
1388
+
1389
+ .mc-heartbeat-time {
1390
+ font-size: 9px;
1391
+ color: var(--color-text-muted);
1392
+ margin-left: 6px;
1393
+ }
1394
+
1395
+ .mc-agent-wake {
1396
+ margin-left: 10px;
1397
+ padding: 4px 8px;
1398
+ border-radius: 6px;
1399
+ background: var(--color-accent-subtle);
1400
+ color: var(--color-accent);
1401
+ font-size: 10px;
1402
+ font-weight: 600;
1403
+ cursor: pointer;
1404
+ }
1405
+
1406
+ .mc-agent-wake:hover {
1407
+ filter: brightness(0.95);
1408
+ }
1409
+
1410
+ .mc-add-agent-btn {
1411
+ display: flex;
1412
+ align-items: center;
1413
+ justify-content: center;
1414
+ gap: 6px;
1415
+ margin: 8px;
1416
+ padding: 10px;
1417
+ background: var(--color-bg-tertiary);
1418
+ border: 1px dashed var(--color-border);
1419
+ border-radius: 8px;
1420
+ font-size: 12px;
1421
+ color: var(--color-text-secondary);
1422
+ cursor: pointer;
1423
+ transition: all 0.15s;
1424
+ }
1425
+
1426
+ .mc-add-agent-btn:hover {
1427
+ background: var(--color-bg-hover);
1428
+ border-color: var(--color-text-muted);
1429
+ }
1430
+
1431
+ /* Queue Panel (Kanban) */
1432
+ .mc-queue-panel {
1433
+ flex: 1;
1434
+ background: var(--color-bg-primary);
1435
+ display: flex;
1436
+ flex-direction: column;
1437
+ min-width: 0;
1438
+ }
1439
+
1440
+ .mc-kanban {
1441
+ display: flex;
1442
+ flex-wrap: nowrap;
1443
+ gap: 16px;
1444
+ padding: 16px;
1445
+ flex: 1;
1446
+ overflow: auto;
1447
+ align-content: flex-start;
1448
+ }
1449
+
1450
+ .mc-kanban-column {
1451
+ flex: 1 1 200px;
1452
+ min-width: 180px;
1453
+ max-width: 300px;
1454
+ display: flex;
1455
+ flex-direction: column;
1456
+ }
1457
+
1458
+ .mc-kanban-column.drag-over .mc-column-header {
1459
+ background: var(--color-bg-tertiary);
1460
+ border-radius: 6px;
1461
+ padding-left: 8px;
1462
+ padding-right: 8px;
1463
+ }
1464
+
1465
+ .mc-column-header {
1466
+ display: flex;
1467
+ align-items: center;
1468
+ gap: 8px;
1469
+ padding: 8px 0;
1470
+ margin-bottom: 8px;
1471
+ }
1472
+
1473
+ .mc-column-dot {
1474
+ width: 8px;
1475
+ height: 8px;
1476
+ border-radius: 50%;
1477
+ }
1478
+
1479
+ .mc-column-label {
1480
+ font-size: 11px;
1481
+ font-weight: 600;
1482
+ color: var(--color-text-secondary);
1483
+ letter-spacing: 0.5px;
1484
+ }
1485
+
1486
+ .mc-column-count {
1487
+ font-size: 11px;
1488
+ color: var(--color-text-muted);
1489
+ margin-left: auto;
1490
+ }
1491
+
1492
+ .mc-column-tasks {
1493
+ display: flex;
1494
+ flex-direction: column;
1495
+ gap: 8px;
1496
+ }
1497
+
1498
+ .mc-task-card {
1499
+ background: var(--color-bg-secondary);
1500
+ border: 1px solid var(--color-border);
1501
+ border-radius: 8px;
1502
+ padding: 12px;
1503
+ cursor: pointer;
1504
+ transition: all 0.15s;
1505
+ }
1506
+
1507
+ .mc-task-card:hover {
1508
+ box-shadow: var(--shadow-sm);
1509
+ transform: translateY(-1px);
1510
+ }
1511
+
1512
+ .mc-task-card.selected {
1513
+ border-color: var(--color-accent);
1514
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 20%, transparent);
1515
+ }
1516
+
1517
+ .mc-task-title {
1518
+ font-size: 13px;
1519
+ font-weight: 500;
1520
+ color: var(--color-text-primary);
1521
+ margin-bottom: 8px;
1522
+ line-height: 1.4;
1523
+ }
1524
+
1525
+ .mc-task-assignee {
1526
+ display: flex;
1527
+ align-items: center;
1528
+ gap: 6px;
1529
+ margin-bottom: 6px;
1530
+ }
1531
+
1532
+ .mc-task-assignee-avatar {
1533
+ width: 20px;
1534
+ height: 20px;
1535
+ border-radius: 50%;
1536
+ display: flex;
1537
+ align-items: center;
1538
+ justify-content: center;
1539
+ font-size: 10px;
1540
+ }
1541
+
1542
+ .mc-task-assignee-name {
1543
+ font-size: 11px;
1544
+ color: var(--color-text-secondary);
1545
+ }
1546
+
1547
+ .mc-task-meta {
1548
+ display: flex;
1549
+ align-items: center;
1550
+ gap: 8px;
1551
+ }
1552
+
1553
+ /* Shared status pill styles */
1554
+ .mc-task-status-pill,
1555
+ .mc-task-detail-status {
1556
+ padding: 2px 6px;
1557
+ border-radius: 10px;
1558
+ font-size: 9px;
1559
+ font-weight: 600;
1560
+ text-transform: uppercase;
1561
+ letter-spacing: 0.3px;
1562
+ background: var(--color-bg-tertiary);
1563
+ color: var(--color-text-secondary);
1564
+ }
1565
+
1566
+ .mc-task-status-pill.status-completed,
1567
+ .mc-task-detail-status.status-completed {
1568
+ background: var(--color-success-subtle);
1569
+ color: var(--color-success);
1570
+ }
1571
+
1572
+ .mc-task-status-pill.status-executing,
1573
+ .mc-task-status-pill.status-planning,
1574
+ .mc-task-detail-status.status-executing,
1575
+ .mc-task-detail-status.status-planning {
1576
+ background: color-mix(in srgb, var(--color-accent) 15%, var(--color-bg-tertiary));
1577
+ color: var(--color-accent);
1578
+ }
1579
+
1580
+ .mc-task-status-pill.status-queued,
1581
+ .mc-task-status-pill.status-pending,
1582
+ .mc-task-detail-status.status-queued,
1583
+ .mc-task-detail-status.status-pending {
1584
+ background: color-mix(in srgb, var(--color-text-muted) 15%, var(--color-bg-tertiary));
1585
+ color: var(--color-text-secondary);
1586
+ }
1587
+
1588
+ .mc-task-status-pill.status-paused,
1589
+ .mc-task-status-pill.status-blocked,
1590
+ .mc-task-detail-status.status-paused,
1591
+ .mc-task-detail-status.status-blocked {
1592
+ background: color-mix(in srgb, #f59e0b 20%, var(--color-bg-tertiary));
1593
+ color: #f59e0b;
1594
+ }
1595
+
1596
+ .mc-task-status-pill.status-failed,
1597
+ .mc-task-status-pill.status-cancelled,
1598
+ .mc-task-detail-status.status-failed,
1599
+ .mc-task-detail-status.status-cancelled {
1600
+ background: color-mix(in srgb, #ef4444 20%, var(--color-bg-tertiary));
1601
+ color: #ef4444;
1602
+ }
1603
+
1604
+ .mc-task-time {
1605
+ font-size: 10px;
1606
+ color: var(--color-text-muted);
1607
+ }
1608
+
1609
+ .mc-column-more {
1610
+ font-size: 11px;
1611
+ color: var(--color-text-secondary);
1612
+ text-align: center;
1613
+ padding: 8px;
1614
+ }
1615
+
1616
+ .mc-column-empty {
1617
+ font-size: 11px;
1618
+ color: var(--color-text-muted);
1619
+ text-align: center;
1620
+ padding: 20px 8px;
1621
+ background: var(--color-bg-secondary);
1622
+ border: 1px dashed var(--color-border);
1623
+ border-radius: 8px;
1624
+ }
1625
+
1626
+ /* Feed Panel */
1627
+ .mc-feed-panel {
1628
+ width: 300px;
1629
+ background: var(--color-bg-secondary);
1630
+ border-left: 1px solid var(--color-border);
1631
+ display: flex;
1632
+ flex-direction: column;
1633
+ flex-shrink: 0;
1634
+ }
1635
+
1636
+ .mc-feed-header {
1637
+ justify-content: space-between;
1638
+ }
1639
+
1640
+ .mc-tabs {
1641
+ display: flex;
1642
+ gap: 6px;
1643
+ }
1644
+
1645
+ .mc-tab-btn {
1646
+ padding: 4px 10px;
1647
+ border-radius: 12px;
1648
+ border: 1px solid var(--color-border);
1649
+ background: transparent;
1650
+ font-size: 11px;
1651
+ color: var(--color-text-secondary);
1652
+ cursor: pointer;
1653
+ transition: all 0.15s;
1654
+ }
1655
+
1656
+ .mc-tab-btn.active {
1657
+ background: var(--color-accent);
1658
+ border-color: var(--color-accent);
1659
+ color: white;
1660
+ }
1661
+
1662
+ .mc-clear-task {
1663
+ padding: 4px 8px;
1664
+ border-radius: 6px;
1665
+ border: 1px solid var(--color-border);
1666
+ background: var(--color-bg-tertiary);
1667
+ font-size: 10px;
1668
+ color: var(--color-text-secondary);
1669
+ cursor: pointer;
1670
+ }
1671
+
1672
+ .mc-feed-filters {
1673
+ display: flex;
1674
+ gap: 4px;
1675
+ padding: 8px 12px;
1676
+ border-bottom: 1px solid var(--color-border-subtle);
1677
+ }
1678
+
1679
+ .mc-filter-btn {
1680
+ padding: 4px 10px;
1681
+ background: transparent;
1682
+ border: 1px solid var(--color-border);
1683
+ border-radius: 12px;
1684
+ font-size: 11px;
1685
+ color: var(--color-text-secondary);
1686
+ cursor: pointer;
1687
+ transition: all 0.15s;
1688
+ }
1689
+
1690
+ .mc-filter-btn:hover {
1691
+ background: var(--color-bg-tertiary);
1692
+ }
1693
+
1694
+ .mc-filter-btn.active {
1695
+ background: var(--color-accent);
1696
+ border-color: var(--color-accent);
1697
+ color: white;
1698
+ }
1699
+
1700
+ .mc-feed-agents {
1701
+ padding: 12px;
1702
+ border-bottom: 1px solid var(--color-border-subtle);
1703
+ }
1704
+
1705
+ .mc-feed-agents-label {
1706
+ font-size: 11px;
1707
+ font-weight: 500;
1708
+ color: var(--color-text-secondary);
1709
+ display: block;
1710
+ margin-bottom: 8px;
1711
+ }
1712
+
1713
+ .mc-feed-agent-chips {
1714
+ display: flex;
1715
+ flex-wrap: wrap;
1716
+ gap: 6px;
1717
+ }
1718
+
1719
+ .mc-agent-chip {
1720
+ padding: 3px 8px;
1721
+ background: var(--color-bg-primary);
1722
+ border: 1px solid var(--color-border);
1723
+ border-radius: 12px;
1724
+ font-size: 10px;
1725
+ color: var(--color-text-secondary);
1726
+ cursor: pointer;
1727
+ }
1728
+
1729
+ .mc-agent-chip.active {
1730
+ background: var(--color-accent-subtle);
1731
+ border-color: var(--color-accent);
1732
+ color: var(--color-accent);
1733
+ }
1734
+
1735
+ .mc-feed-list {
1736
+ flex: 1;
1737
+ overflow-y: auto;
1738
+ padding: 8px;
1739
+ }
1740
+
1741
+ .mc-feed-item {
1742
+ padding: 10px;
1743
+ border-radius: 6px;
1744
+ transition: background 0.15s;
1745
+ }
1746
+
1747
+ .mc-feed-item:hover {
1748
+ background: var(--color-bg-tertiary);
1749
+ }
1750
+
1751
+ .mc-feed-item-header {
1752
+ display: flex;
1753
+ align-items: center;
1754
+ justify-content: space-between;
1755
+ margin-bottom: 4px;
1756
+ }
1757
+
1758
+ .mc-feed-agent {
1759
+ font-size: 12px;
1760
+ font-weight: 600;
1761
+ }
1762
+
1763
+ .mc-feed-agent.system {
1764
+ color: var(--color-text-secondary);
1765
+ }
1766
+
1767
+ .mc-feed-time {
1768
+ font-size: 10px;
1769
+ color: var(--color-text-muted);
1770
+ }
1771
+
1772
+ .mc-feed-content {
1773
+ font-size: 12px;
1774
+ color: var(--color-text-secondary);
1775
+ line-height: 1.4;
1776
+ }
1777
+
1778
+ .mc-feed-empty {
1779
+ padding: 40px 16px;
1780
+ text-align: center;
1781
+ color: var(--color-text-muted);
1782
+ font-size: 12px;
1783
+ }
1784
+
1785
+ .mc-task-detail {
1786
+ flex: 1;
1787
+ overflow-y: auto;
1788
+ padding: 12px;
1789
+ display: flex;
1790
+ flex-direction: column;
1791
+ gap: 16px;
1792
+ }
1793
+
1794
+ .mc-task-detail-header {
1795
+ display: flex;
1796
+ flex-direction: column;
1797
+ gap: 6px;
1798
+ }
1799
+
1800
+ .mc-task-detail-title {
1801
+ display: flex;
1802
+ align-items: center;
1803
+ gap: 8px;
1804
+ }
1805
+
1806
+ .mc-task-detail-title h3 {
1807
+ margin: 0;
1808
+ font-size: 14px;
1809
+ color: var(--color-text-primary);
1810
+ }
1811
+
1812
+ /* Note: .mc-task-detail-status styles are shared with .mc-task-status-pill above */
1813
+
1814
+ .mc-task-detail-updated {
1815
+ font-size: 11px;
1816
+ color: var(--color-text-muted);
1817
+ }
1818
+
1819
+ .mc-task-detail-meta {
1820
+ display: grid;
1821
+ grid-template-columns: 1fr 1fr;
1822
+ gap: 8px;
1823
+ }
1824
+
1825
+ .mc-task-detail-meta label {
1826
+ display: flex;
1827
+ flex-direction: column;
1828
+ gap: 6px;
1829
+ font-size: 11px;
1830
+ color: var(--color-text-secondary);
1831
+ }
1832
+
1833
+ .mc-task-detail-meta select {
1834
+ border: 1px solid var(--color-border);
1835
+ border-radius: 6px;
1836
+ padding: 6px 8px;
1837
+ background: var(--color-bg-primary);
1838
+ color: var(--color-text-primary);
1839
+ font-size: 12px;
1840
+ }
1841
+
1842
+ .mc-task-detail-section {
1843
+ display: flex;
1844
+ flex-direction: column;
1845
+ gap: 8px;
1846
+ }
1847
+
1848
+ .mc-task-detail-section h4 {
1849
+ margin: 0;
1850
+ font-size: 12px;
1851
+ color: var(--color-text-secondary);
1852
+ letter-spacing: 0.4px;
1853
+ text-transform: uppercase;
1854
+ }
1855
+
1856
+ .mc-task-detail-brief {
1857
+ margin: 0;
1858
+ font-size: 12px;
1859
+ color: var(--color-text-primary);
1860
+ line-height: 1.4;
1861
+ white-space: pre-wrap;
1862
+ }
1863
+
1864
+ .mc-comment-box {
1865
+ display: flex;
1866
+ flex-direction: column;
1867
+ gap: 8px;
1868
+ }
1869
+
1870
+ .mc-comment-box textarea {
1871
+ border: 1px solid var(--color-border);
1872
+ border-radius: 8px;
1873
+ padding: 8px;
1874
+ background: var(--color-bg-primary);
1875
+ color: var(--color-text-primary);
1876
+ font-size: 12px;
1877
+ resize: vertical;
1878
+ }
1879
+
1880
+ .mc-comment-submit {
1881
+ align-self: flex-start;
1882
+ padding: 6px 10px;
1883
+ border-radius: 6px;
1884
+ border: 1px solid var(--color-accent);
1885
+ background: var(--color-accent);
1886
+ color: white;
1887
+ font-size: 11px;
1888
+ font-weight: 600;
1889
+ cursor: pointer;
1890
+ }
1891
+
1892
+ .mc-comment-submit:disabled {
1893
+ opacity: 0.6;
1894
+ cursor: not-allowed;
1895
+ }
1896
+
1897
+ .mc-task-empty {
1898
+ padding: 40px 16px;
1899
+ text-align: center;
1900
+ color: var(--color-text-muted);
1901
+ font-size: 12px;
1902
+ }
1903
+
1904
+ /* Editor Modal */
1905
+ .mc-editor-overlay {
1906
+ position: fixed;
1907
+ inset: 0;
1908
+ background: rgba(0, 0, 0, 0.5);
1909
+ display: flex;
1910
+ align-items: center;
1911
+ justify-content: center;
1912
+ z-index: 1000;
1913
+ }
1914
+
1915
+ .mc-editor-modal {
1916
+ background: var(--color-bg-elevated);
1917
+ border-radius: 12px;
1918
+ width: 90%;
1919
+ max-width: 600px;
1920
+ max-height: 90%;
1921
+ overflow: auto;
1922
+ box-shadow: var(--shadow-lg);
1923
+ }
1924
+
1925
+ .mc-standup-modal {
1926
+ max-width: 900px;
1927
+ }
1928
+
1929
+ /* Responsive breakpoints */
1930
+ @media (max-width: 1200px) {
1931
+ .mc-feed-panel {
1932
+ width: 240px;
1933
+ }
1934
+ }
1935
+
1936
+ @media (max-width: 1000px) {
1937
+ .mc-content {
1938
+ flex-direction: column;
1939
+ }
1940
+
1941
+ .mc-agents-panel {
1942
+ width: 100%;
1943
+ max-height: 200px;
1944
+ border-right: none;
1945
+ border-bottom: 1px solid var(--color-border);
1946
+ }
1947
+
1948
+ .mc-agents-list {
1949
+ display: flex;
1950
+ flex-wrap: wrap;
1951
+ gap: 8px;
1952
+ padding: 8px;
1953
+ }
1954
+
1955
+ .mc-agent-item {
1956
+ flex: 0 0 auto;
1957
+ width: auto;
1958
+ padding: 8px 12px;
1959
+ }
1960
+
1961
+ .mc-add-agent-btn {
1962
+ flex: 0 0 auto;
1963
+ margin: 0;
1964
+ padding: 8px 12px;
1965
+ }
1966
+
1967
+ .mc-feed-panel {
1968
+ width: 100%;
1969
+ max-height: 250px;
1970
+ border-left: none;
1971
+ border-top: 1px solid var(--color-border);
1972
+ }
1973
+ }
1974
+
1975
+ @media (max-width: 700px) {
1976
+ .mc-header {
1977
+ flex-wrap: wrap;
1978
+ gap: 12px;
1979
+ padding: 12px 16px;
1980
+ }
1981
+
1982
+ .mc-header-stats {
1983
+ gap: 24px;
1984
+ }
1985
+
1986
+ .mc-stat-value {
1987
+ font-size: 18px;
1988
+ }
1989
+
1990
+ .mc-kanban-column {
1991
+ flex: 1 1 100%;
1992
+ max-width: none;
1993
+ }
1994
+ }
1995
+ `;