cowork-os 0.3.21 → 0.3.25

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 (252) hide show
  1. package/README.md +372 -10
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
  48. package/dist/electron/electron/agent/daemon.js +189 -13
  49. package/dist/electron/electron/agent/executor.js +895 -78
  50. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  51. package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
  52. package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
  53. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  54. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  55. package/dist/electron/electron/agent/llm/index.js +13 -1
  56. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  57. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  58. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  59. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  60. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  61. package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
  62. package/dist/electron/electron/agent/llm/types.js +66 -1
  63. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  64. package/dist/electron/electron/agent/search/provider-factory.js +38 -2
  65. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  66. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  67. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  68. package/dist/electron/electron/agent/tools/file-tools.js +66 -3
  69. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  70. package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
  71. package/dist/electron/electron/agent/tools/image-tools.js +11 -1
  72. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  73. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  74. package/dist/electron/electron/agent/tools/registry.js +548 -10
  75. package/dist/electron/electron/agent/tools/search-tools.js +28 -10
  76. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  77. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  78. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  79. package/dist/electron/electron/agents/agent-dispatch.js +63 -0
  80. package/dist/electron/electron/database/repositories.js +19 -5
  81. package/dist/electron/electron/database/schema.js +8 -0
  82. package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
  83. package/dist/electron/electron/gateway/index.js +75 -1
  84. package/dist/electron/electron/gateway/router.js +209 -154
  85. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  86. package/dist/electron/electron/ipc/handlers.js +763 -267
  87. package/dist/electron/electron/main.js +63 -0
  88. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  89. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  90. package/dist/electron/electron/memory/MemoryService.js +2 -1
  91. package/dist/electron/electron/preload.js +78 -1
  92. package/dist/electron/electron/settings/appearance-manager.js +18 -1
  93. package/dist/electron/electron/settings/box-manager.js +54 -0
  94. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  95. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  96. package/dist/electron/electron/settings/notion-manager.js +56 -0
  97. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  98. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  99. package/dist/electron/electron/utils/box-api.js +153 -0
  100. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  101. package/dist/electron/electron/utils/env-migration.js +19 -0
  102. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  103. package/dist/electron/electron/utils/notion-api.js +103 -0
  104. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  105. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  106. package/dist/electron/electron/utils/validation.js +98 -3
  107. package/dist/electron/electron/utils/x-cli.js +1 -1
  108. package/dist/electron/shared/channelMessages.js +284 -3
  109. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  110. package/dist/electron/shared/types.js +90 -1
  111. package/package.json +14 -3
  112. package/resources/skills/nano-banana-pro.json +4 -4
  113. package/resources/skills/openai-image-gen.json +3 -3
  114. package/resources/skills/scripts/gen.py +163 -0
  115. package/resources/skills/scripts/generate_image.py +91 -0
  116. package/src/electron/agent/custom-skill-loader.ts +34 -1
  117. package/src/electron/agent/daemon.ts +210 -14
  118. package/src/electron/agent/executor.ts +1124 -85
  119. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  120. package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
  121. package/src/electron/agent/llm/bedrock-provider.ts +62 -9
  122. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  123. package/src/electron/agent/llm/groq-provider.ts +39 -0
  124. package/src/electron/agent/llm/index.ts +6 -0
  125. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  126. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  127. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  128. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  129. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  130. package/src/electron/agent/llm/provider-factory.ts +459 -6
  131. package/src/electron/agent/llm/types.ts +95 -1
  132. package/src/electron/agent/llm/xai-provider.ts +39 -0
  133. package/src/electron/agent/search/provider-factory.ts +43 -2
  134. package/src/electron/agent/tools/box-tools.ts +239 -0
  135. package/src/electron/agent/tools/builtin-settings.ts +36 -0
  136. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  137. package/src/electron/agent/tools/file-tools.ts +66 -3
  138. package/src/electron/agent/tools/gmail-tools.ts +240 -0
  139. package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
  140. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  141. package/src/electron/agent/tools/grep-tools.ts +97 -12
  142. package/src/electron/agent/tools/image-tools.ts +11 -1
  143. package/src/electron/agent/tools/notion-tools.ts +330 -0
  144. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  145. package/src/electron/agent/tools/registry.ts +794 -10
  146. package/src/electron/agent/tools/search-tools.ts +29 -11
  147. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  148. package/src/electron/agent/tools/shell-tools.ts +11 -3
  149. package/src/electron/agent/tools/x-tools.ts +1 -1
  150. package/src/electron/agents/agent-dispatch.ts +79 -0
  151. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  152. package/src/electron/database/repositories.ts +58 -6
  153. package/src/electron/database/schema.ts +8 -0
  154. package/src/electron/gateway/channels/discord.ts +4 -0
  155. package/src/electron/gateway/channels/google-chat.ts +3 -0
  156. package/src/electron/gateway/channels/line.ts +3 -0
  157. package/src/electron/gateway/channels/matrix-client.ts +15 -0
  158. package/src/electron/gateway/channels/matrix.ts +31 -0
  159. package/src/electron/gateway/channels/mattermost.ts +3 -0
  160. package/src/electron/gateway/channels/signal.ts +3 -0
  161. package/src/electron/gateway/channels/slack.ts +9 -4
  162. package/src/electron/gateway/channels/teams.ts +4 -0
  163. package/src/electron/gateway/channels/telegram.ts +2 -0
  164. package/src/electron/gateway/channels/twitch.ts +2 -0
  165. package/src/electron/gateway/channels/types.ts +8 -0
  166. package/src/electron/gateway/channels/whatsapp.ts +66 -0
  167. package/src/electron/gateway/index.ts +95 -2
  168. package/src/electron/gateway/router.ts +231 -161
  169. package/src/electron/gateway/security.ts +21 -9
  170. package/src/electron/ipc/canvas-handlers.ts +10 -0
  171. package/src/electron/ipc/handlers.ts +848 -292
  172. package/src/electron/main.ts +35 -0
  173. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  174. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  175. package/src/electron/memory/MemoryService.ts +7 -1
  176. package/src/electron/preload.ts +200 -5
  177. package/src/electron/settings/appearance-manager.ts +20 -2
  178. package/src/electron/settings/box-manager.ts +58 -0
  179. package/src/electron/settings/dropbox-manager.ts +58 -0
  180. package/src/electron/settings/google-workspace-manager.ts +59 -0
  181. package/src/electron/settings/notion-manager.ts +60 -0
  182. package/src/electron/settings/onedrive-manager.ts +58 -0
  183. package/src/electron/settings/sharepoint-manager.ts +58 -0
  184. package/src/electron/utils/box-api.ts +184 -0
  185. package/src/electron/utils/dropbox-api.ts +171 -0
  186. package/src/electron/utils/env-migration.ts +22 -0
  187. package/src/electron/utils/gmail-api.ts +121 -0
  188. package/src/electron/utils/google-calendar-api.ts +115 -0
  189. package/src/electron/utils/google-workspace-api.ts +228 -0
  190. package/src/electron/utils/google-workspace-auth.ts +109 -0
  191. package/src/electron/utils/google-workspace-oauth.ts +232 -0
  192. package/src/electron/utils/notion-api.ts +126 -0
  193. package/src/electron/utils/onedrive-api.ts +137 -0
  194. package/src/electron/utils/sharepoint-api.ts +132 -0
  195. package/src/electron/utils/validation.ts +128 -1
  196. package/src/electron/utils/x-cli.ts +1 -1
  197. package/src/renderer/App.tsx +119 -8
  198. package/src/renderer/components/ActivityFeedItem.tsx +34 -17
  199. package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
  200. package/src/renderer/components/AppearanceSettings.tsx +37 -2
  201. package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
  202. package/src/renderer/components/BoxSettings.tsx +203 -0
  203. package/src/renderer/components/BrowserView.tsx +101 -0
  204. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  205. package/src/renderer/components/CanvasPreview.tsx +68 -1
  206. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  207. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  208. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  209. package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
  210. package/src/renderer/components/DiscordSettings.tsx +18 -7
  211. package/src/renderer/components/DropboxSettings.tsx +202 -0
  212. package/src/renderer/components/EmailSettings.tsx +18 -7
  213. package/src/renderer/components/FileViewer.tsx +21 -13
  214. package/src/renderer/components/GoogleChatSettings.tsx +17 -7
  215. package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
  216. package/src/renderer/components/ImessageSettings.tsx +22 -11
  217. package/src/renderer/components/LineIcons.tsx +376 -0
  218. package/src/renderer/components/LineSettings.tsx +18 -7
  219. package/src/renderer/components/MCPSettings.tsx +56 -0
  220. package/src/renderer/components/MainContent.tsx +740 -76
  221. package/src/renderer/components/MatrixSettings.tsx +18 -7
  222. package/src/renderer/components/MattermostSettings.tsx +18 -7
  223. package/src/renderer/components/NodesSettings.tsx +58 -99
  224. package/src/renderer/components/NotificationPanel.tsx +25 -11
  225. package/src/renderer/components/NotionSettings.tsx +231 -0
  226. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  227. package/src/renderer/components/OnboardingModal.tsx +70 -1
  228. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  229. package/src/renderer/components/RightPanel.tsx +141 -28
  230. package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
  231. package/src/renderer/components/SearchSettings.tsx +118 -114
  232. package/src/renderer/components/Settings.tsx +1425 -651
  233. package/src/renderer/components/SharePointSettings.tsx +224 -0
  234. package/src/renderer/components/Sidebar.tsx +94 -19
  235. package/src/renderer/components/SignalSettings.tsx +18 -7
  236. package/src/renderer/components/SkillHubBrowser.tsx +144 -185
  237. package/src/renderer/components/SlackSettings.tsx +18 -7
  238. package/src/renderer/components/TaskQuickActions.tsx +11 -6
  239. package/src/renderer/components/TaskTimeline.tsx +58 -26
  240. package/src/renderer/components/TeamsSettings.tsx +18 -7
  241. package/src/renderer/components/TelegramSettings.tsx +18 -7
  242. package/src/renderer/components/ThemeIcon.tsx +16 -0
  243. package/src/renderer/components/TwitchSettings.tsx +18 -7
  244. package/src/renderer/components/VoiceSettings.tsx +30 -74
  245. package/src/renderer/components/WhatsAppSettings.tsx +48 -37
  246. package/src/renderer/components/WorkingStateHistory.tsx +7 -5
  247. package/src/renderer/components/WorkspaceSelector.tsx +42 -13
  248. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  249. package/src/renderer/styles/index.css +2333 -209
  250. package/src/shared/channelMessages.ts +367 -4
  251. package/src/shared/llm-provider-catalog.ts +217 -0
  252. package/src/shared/types.ts +251 -2
@@ -5,11 +5,12 @@ import { RightPanel } from './components/RightPanel';
5
5
  import { Settings } from './components/Settings';
6
6
  import { DisclaimerModal } from './components/DisclaimerModal';
7
7
  import { Onboarding } from './components/Onboarding';
8
+ import { BrowserView } from './components/BrowserView';
8
9
  // TaskQueuePanel moved to RightPanel
9
10
  import { ToastContainer } from './components/Toast';
10
11
  import { QuickTaskFAB } from './components/QuickTaskFAB';
11
12
  import { NotificationPanel } from './components/NotificationPanel';
12
- import { Task, Workspace, TaskEvent, LLMModelInfo, LLMProviderInfo, SuccessCriteria, UpdateInfo, ThemeMode, AccentColor, QueueStatus, ToastNotification } from '../shared/types';
13
+ import { Task, Workspace, TaskEvent, LLMModelInfo, LLMProviderInfo, SuccessCriteria, UpdateInfo, ThemeMode, VisualTheme, AccentColor, QueueStatus, ToastNotification, TEMP_WORKSPACE_ID } from '../shared/types';
13
14
 
14
15
 
15
16
  // Helper to get effective theme based on system preference
@@ -20,14 +21,15 @@ function getEffectiveTheme(themeMode: ThemeMode): 'light' | 'dark' {
20
21
  return themeMode;
21
22
  }
22
23
 
23
- type AppView = 'main' | 'settings';
24
+ type AppView = 'main' | 'settings' | 'browser';
24
25
 
25
26
  export function App() {
26
27
  const [currentWorkspace, setCurrentWorkspace] = useState<Workspace | null>(null);
27
28
  const [tasks, setTasks] = useState<Task[]>([]);
28
29
  const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
29
30
  const [currentView, setCurrentView] = useState<AppView>('main');
30
- const [settingsTab, setSettingsTab] = useState<'appearance' | 'llm' | 'search' | 'telegram' | 'slack' | 'whatsapp' | 'teams' | 'morechannels' | 'updates' | 'guardrails' | 'queue' | 'skills' | 'scheduled' | 'voice'>('appearance');
31
+ const [browserUrl, setBrowserUrl] = useState<string>('');
32
+ const [settingsTab, setSettingsTab] = useState<'appearance' | 'llm' | 'search' | 'telegram' | 'slack' | 'whatsapp' | 'teams' | 'x' | 'morechannels' | 'integrations' | 'updates' | 'guardrails' | 'queue' | 'skills' | 'scheduled' | 'voice' | 'missioncontrol'>('appearance');
31
33
  const [events, setEvents] = useState<TaskEvent[]>([]);
32
34
 
33
35
  // Model selection state
@@ -41,6 +43,7 @@ export function App() {
41
43
 
42
44
  // Theme state (loaded from main process on mount)
43
45
  const [themeMode, setThemeMode] = useState<ThemeMode>('dark');
46
+ const [visualTheme, setVisualTheme] = useState<VisualTheme>('terminal');
44
47
  const [accentColor, setAccentColor] = useState<AccentColor>('cyan');
45
48
 
46
49
  // Queue state
@@ -82,6 +85,11 @@ export function App() {
82
85
  loadLLMConfig();
83
86
  };
84
87
 
88
+ const handleOpenBrowserView = (url?: string) => {
89
+ setBrowserUrl(url || '');
90
+ setCurrentView('browser');
91
+ };
92
+
85
93
  const handleShowOnboarding = () => {
86
94
  // Reset onboarding state to show the wizard again
87
95
  setOnboardingCompleted(false);
@@ -106,12 +114,22 @@ export function App() {
106
114
  loadLLMConfig();
107
115
  }, []);
108
116
 
117
+ useEffect(() => {
118
+ const handler = () => {
119
+ setSettingsTab('llm');
120
+ setCurrentView('settings');
121
+ };
122
+ window.addEventListener('open-settings', handler as EventListener);
123
+ return () => window.removeEventListener('open-settings', handler as EventListener);
124
+ }, []);
125
+
109
126
  // Load appearance settings on mount
110
127
  useEffect(() => {
111
128
  const loadAppearanceSettings = async () => {
112
129
  try {
113
130
  const settings = await window.electronAPI.getAppearanceSettings();
114
131
  setThemeMode(settings.themeMode);
132
+ setVisualTheme(settings.visualTheme || 'terminal');
115
133
  setAccentColor(settings.accentColor);
116
134
  setDisclaimerAccepted(settings.disclaimerAccepted ?? false);
117
135
  setOnboardingCompleted(settings.onboardingCompleted ?? false);
@@ -218,17 +236,23 @@ export function App() {
218
236
  // Remove existing theme classes
219
237
  root.classList.remove('theme-light', 'theme-dark');
220
238
 
221
- // Apply theme class (only light needs explicit class, dark is default)
239
+ // Apply theme mode class
222
240
  if (effectiveTheme === 'light') {
223
241
  root.classList.add('theme-light');
224
242
  }
243
+ // dark is default, no class needed unless specified otherwise by visual styles
244
+
245
+ // Remove existing visual theme classes
246
+ root.classList.remove('visual-terminal', 'visual-warm', 'visual-oblivion');
247
+ const resolvedVisualTheme = visualTheme === 'warm' ? 'oblivion' : visualTheme;
248
+ root.classList.add(`visual-${resolvedVisualTheme}`);
225
249
 
226
250
  // Remove existing accent classes
227
- root.classList.remove('accent-cyan', 'accent-blue', 'accent-purple', 'accent-pink', 'accent-rose', 'accent-orange', 'accent-green', 'accent-teal');
251
+ root.classList.remove('accent-cyan', 'accent-blue', 'accent-purple', 'accent-pink', 'accent-rose', 'accent-orange', 'accent-green', 'accent-teal', 'accent-coral');
228
252
 
229
253
  // Apply accent class
230
254
  root.classList.add(`accent-${accentColor}`);
231
- }, [themeMode, accentColor]);
255
+ }, [themeMode, visualTheme, accentColor]);
232
256
 
233
257
  // Listen for system theme changes when in 'system' mode
234
258
  useEffect(() => {
@@ -277,6 +301,42 @@ export function App() {
277
301
  }
278
302
  }, [currentWorkspace]);
279
303
 
304
+ // Sync current workspace to the selected task's workspace
305
+ useEffect(() => {
306
+ if (!selectedTaskId) return;
307
+ const task = tasks.find(t => t.id === selectedTaskId);
308
+ if (!task) return;
309
+ if (currentWorkspace?.id === task.workspaceId) return;
310
+
311
+ let cancelled = false;
312
+
313
+ const loadTaskWorkspace = async () => {
314
+ try {
315
+ const resolved = task.workspaceId === TEMP_WORKSPACE_ID
316
+ ? await window.electronAPI.getTempWorkspace()
317
+ : await window.electronAPI.selectWorkspace(task.workspaceId);
318
+ if (!cancelled && resolved) {
319
+ setCurrentWorkspace(resolved);
320
+ }
321
+ } catch (error) {
322
+ console.error('Failed to load task workspace:', error);
323
+ }
324
+ };
325
+
326
+ void loadTaskWorkspace();
327
+ return () => {
328
+ cancelled = true;
329
+ };
330
+ }, [selectedTaskId, tasks, currentWorkspace?.id]);
331
+
332
+ // Track recency when the active workspace changes
333
+ useEffect(() => {
334
+ if (!currentWorkspace || currentWorkspace.id === TEMP_WORKSPACE_ID) return;
335
+ window.electronAPI.touchWorkspace(currentWorkspace.id).catch((error: unknown) => {
336
+ console.error('Failed to update workspace recency:', error);
337
+ });
338
+ }, [currentWorkspace?.id]);
339
+
280
340
  // Toast helper functions
281
341
  const addToast = (toast: Omit<ToastNotification, 'id'>) => {
282
342
  const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
@@ -334,6 +394,38 @@ export function App() {
334
394
  void window.electronAPI.resumeTask(event.taskId);
335
395
  }
336
396
 
397
+ if (event.type === 'task_paused' || event.type === 'approval_requested') {
398
+ const isApproval = event.type === 'approval_requested';
399
+ const task = tasksRef.current.find(t => t.id === event.taskId);
400
+ const baseTitle = isApproval ? 'Approval needed' : 'Input needed';
401
+ const title = task?.title ? `${baseTitle} · ${task.title}` : baseTitle;
402
+ const message =
403
+ (isApproval
404
+ ? event.payload?.approval?.description
405
+ : event.payload?.message) || 'Awaiting your input.';
406
+
407
+ void (async () => {
408
+ try {
409
+ const existing = await window.electronAPI.listNotifications();
410
+ const hasExisting = existing.some((n) =>
411
+ n.type === 'input_required' &&
412
+ n.taskId === event.taskId &&
413
+ !n.read
414
+ );
415
+ if (hasExisting) return;
416
+ await window.electronAPI.addNotification({
417
+ type: 'input_required',
418
+ title,
419
+ message,
420
+ taskId: event.taskId,
421
+ workspaceId: task?.workspaceId,
422
+ });
423
+ } catch (error) {
424
+ console.error('Failed to add input-required notification:', error);
425
+ }
426
+ })();
427
+ }
428
+
337
429
  // Show toast notifications for task completion/failure
338
430
  if (event.type === 'task_completed') {
339
431
  const task = tasksRef.current.find(t => t.id === event.taskId);
@@ -515,13 +607,19 @@ export function App() {
515
607
  const handleThemeChange = (theme: ThemeMode) => {
516
608
  setThemeMode(theme);
517
609
  // Persist to main process
518
- window.electronAPI.saveAppearanceSettings({ themeMode: theme, accentColor });
610
+ window.electronAPI.saveAppearanceSettings({ themeMode: theme, visualTheme, accentColor });
611
+ };
612
+
613
+ const handleVisualThemeChange = (visual: VisualTheme) => {
614
+ setVisualTheme(visual);
615
+ // Persist to main process
616
+ window.electronAPI.saveAppearanceSettings({ themeMode, visualTheme: visual, accentColor });
519
617
  };
520
618
 
521
619
  const handleAccentChange = (accent: AccentColor) => {
522
620
  setAccentColor(accent);
523
621
  // Persist to main process
524
- window.electronAPI.saveAppearanceSettings({ themeMode, accentColor: accent });
622
+ window.electronAPI.saveAppearanceSettings({ themeMode, visualTheme, accentColor: accent });
525
623
  };
526
624
 
527
625
  // Show loading state while checking disclaimer/onboarding status
@@ -710,6 +808,10 @@ export function App() {
710
808
  selectedTaskId={selectedTaskId}
711
809
  onSelectTask={setSelectedTaskId}
712
810
  onOpenSettings={() => setCurrentView('settings')}
811
+ onOpenMissionControl={() => {
812
+ setSettingsTab('missioncontrol');
813
+ setCurrentView('settings');
814
+ }}
713
815
  onTasksChanged={loadTasks}
714
816
  />
715
817
  )}
@@ -727,6 +829,7 @@ export function App() {
727
829
  setCurrentView('settings');
728
830
  }}
729
831
  onStopTask={handleCancelTask}
832
+ onOpenBrowserView={handleOpenBrowserView}
730
833
  selectedModel={selectedModel}
731
834
  availableModels={availableModels}
732
835
  onModelChange={handleModelChange}
@@ -762,14 +865,22 @@ export function App() {
762
865
  onBack={() => setCurrentView('main')}
763
866
  onSettingsChanged={loadLLMConfig}
764
867
  themeMode={themeMode}
868
+ visualTheme={visualTheme}
765
869
  accentColor={accentColor}
766
870
  onThemeChange={handleThemeChange}
871
+ onVisualThemeChange={handleVisualThemeChange}
767
872
  onAccentChange={handleAccentChange}
768
873
  initialTab={settingsTab}
769
874
  onShowOnboarding={handleShowOnboarding}
770
875
  onboardingCompletedAt={onboardingCompletedAt}
771
876
  />
772
877
  )}
878
+ {currentView === 'browser' && (
879
+ <BrowserView
880
+ initialUrl={browserUrl}
881
+ onBack={() => setCurrentView('main')}
882
+ />
883
+ )}
773
884
  </div>
774
885
  );
775
886
  }
@@ -1,4 +1,21 @@
1
1
  import { ActivityData, ActivityType, ActivityActorType } from '../../electron/preload';
2
+ import { ThemeIcon } from './ThemeIcon';
3
+ import {
4
+ AlertTriangleIcon,
5
+ AtIcon,
6
+ BotIcon,
7
+ CheckIcon,
8
+ ClipboardIcon,
9
+ CodeIcon,
10
+ FileIcon,
11
+ InfoIcon,
12
+ MessageIcon,
13
+ PauseIcon,
14
+ PlayIcon,
15
+ SlidersIcon,
16
+ TrashIcon,
17
+ XIcon,
18
+ } from './LineIcons';
2
19
 
3
20
  interface ActivityFeedItemProps {
4
21
  activity: ActivityData;
@@ -8,23 +25,23 @@ interface ActivityFeedItemProps {
8
25
  compact?: boolean;
9
26
  }
10
27
 
11
- const ACTIVITY_ICONS: Record<ActivityType, string> = {
12
- task_created: '📋',
13
- task_started: '▶️',
14
- task_completed: '',
15
- task_failed: '',
16
- task_paused: '⏸️',
17
- task_resumed: '▶️',
18
- comment: '💬',
19
- file_created: '📄',
20
- file_modified: '✏️',
21
- file_deleted: '🗑️',
22
- command_executed: '💻',
23
- tool_used: '🔧',
24
- mention: '@',
25
- agent_assigned: '🤖',
26
- error: '⚠️',
27
- info: 'ℹ️',
28
+ const ACTIVITY_ICONS: Record<ActivityType, React.ReactNode> = {
29
+ task_created: <ThemeIcon emoji="📋" icon={<ClipboardIcon size={16} />} />,
30
+ task_started: <ThemeIcon emoji="▶️" icon={<PlayIcon size={16} />} />,
31
+ task_completed: <ThemeIcon emoji="" icon={<CheckIcon size={16} />} />,
32
+ task_failed: <ThemeIcon emoji="" icon={<XIcon size={16} />} />,
33
+ task_paused: <ThemeIcon emoji="⏸️" icon={<PauseIcon size={16} />} />,
34
+ task_resumed: <ThemeIcon emoji="▶️" icon={<PlayIcon size={16} />} />,
35
+ comment: <ThemeIcon emoji="💬" icon={<MessageIcon size={16} />} />,
36
+ file_created: <ThemeIcon emoji="📄" icon={<FileIcon size={16} />} />,
37
+ file_modified: <ThemeIcon emoji="✏️" icon={<FileIcon size={16} />} />,
38
+ file_deleted: <ThemeIcon emoji="🗑️" icon={<TrashIcon size={16} />} />,
39
+ command_executed: <ThemeIcon emoji="💻" icon={<CodeIcon size={16} />} />,
40
+ tool_used: <ThemeIcon emoji="🔧" icon={<SlidersIcon size={16} />} />,
41
+ mention: <ThemeIcon emoji="@" icon={<AtIcon size={16} />} />,
42
+ agent_assigned: <ThemeIcon emoji="🤖" icon={<BotIcon size={16} />} />,
43
+ error: <ThemeIcon emoji="⚠️" icon={<AlertTriangleIcon size={16} />} />,
44
+ info: <ThemeIcon emoji="ℹ️" icon={<InfoIcon size={16} />} />,
28
45
  };
29
46
 
30
47
  const ACTIVITY_COLORS: Record<ActivityType, string> = {
@@ -5,6 +5,8 @@ import {
5
5
  AgentRoleData,
6
6
  } from '../../electron/preload';
7
7
  import { useAgentContext } from '../hooks/useAgentContext';
8
+ import { ThemeIcon } from './ThemeIcon';
9
+ import { ChartIcon, ClipboardIcon, EditIcon, TargetIcon } from './LineIcons';
8
10
 
9
11
  interface AgentWorkingStatePanelProps {
10
12
  agentRoleId: string;
@@ -13,25 +15,25 @@ interface AgentWorkingStatePanelProps {
13
15
  onEdit?: (state: AgentWorkingStateData) => void;
14
16
  }
15
17
 
16
- const STATE_TYPE_LABELS: Record<WorkingStateType, { label: string; icon: string; description: string }> = {
18
+ const STATE_TYPE_LABELS: Record<WorkingStateType, { label: string; icon: React.ReactNode; description: string }> = {
17
19
  context: {
18
20
  label: 'Context',
19
- icon: '📋',
21
+ icon: <ThemeIcon emoji="📋" icon={<ClipboardIcon size={16} />} />,
20
22
  description: 'Background information and current understanding',
21
23
  },
22
24
  progress: {
23
25
  label: 'Progress',
24
- icon: '📊',
26
+ icon: <ThemeIcon emoji="📊" icon={<ChartIcon size={16} />} />,
25
27
  description: 'Current work progress and status',
26
28
  },
27
29
  notes: {
28
30
  label: 'Notes',
29
- icon: '📝',
31
+ icon: <ThemeIcon emoji="📝" icon={<EditIcon size={16} />} />,
30
32
  description: 'Important observations and reminders',
31
33
  },
32
34
  plan: {
33
35
  label: 'Plan',
34
- icon: '🎯',
36
+ icon: <ThemeIcon emoji="🎯" icon={<TargetIcon size={16} />} />,
35
37
  description: 'Action plan and next steps',
36
38
  },
37
39
  };
@@ -1,9 +1,11 @@
1
- import { ThemeMode, AccentColor, ACCENT_COLORS } from '../../shared/types';
1
+ import { ThemeMode, AccentColor, VisualTheme, ACCENT_COLORS } from '../../shared/types';
2
2
 
3
3
  interface AppearanceSettingsProps {
4
4
  themeMode: ThemeMode;
5
+ visualTheme: VisualTheme;
5
6
  accentColor: AccentColor;
6
7
  onThemeChange: (theme: ThemeMode) => void;
8
+ onVisualThemeChange: (theme: VisualTheme) => void;
7
9
  onAccentChange: (accent: AccentColor) => void;
8
10
  onShowOnboarding?: () => void;
9
11
  onboardingCompletedAt?: string;
@@ -11,12 +13,15 @@ interface AppearanceSettingsProps {
11
13
 
12
14
  export function AppearanceSettings({
13
15
  themeMode,
16
+ visualTheme,
14
17
  accentColor,
15
18
  onThemeChange,
19
+ onVisualThemeChange,
16
20
  onAccentChange,
17
21
  onShowOnboarding,
18
22
  onboardingCompletedAt,
19
23
  }: AppearanceSettingsProps) {
24
+ const isWarmVisualTheme = visualTheme === 'warm' || visualTheme === 'oblivion';
20
25
  const formatCompletedDate = (isoString?: string) => {
21
26
  if (!isoString) return null;
22
27
  try {
@@ -62,9 +67,39 @@ export function AppearanceSettings({
62
67
  </p>
63
68
  </div>
64
69
 
70
+ {/* Visual Style */}
71
+ <div className="appearance-section">
72
+ <h4>Visual Style</h4>
73
+ <div className="theme-switcher">
74
+ <button
75
+ className={`theme-option ${visualTheme === 'terminal' ? 'selected' : ''}`}
76
+ onClick={() => onVisualThemeChange('terminal')}
77
+ >
78
+ <div className="theme-option-preview terminal">
79
+ <div className="theme-option-preview-line code-line" />
80
+ <div className="theme-option-preview-line code-line" />
81
+ <div className="theme-option-preview-line code-line" />
82
+ </div>
83
+ <span className="theme-option-label">Terminal</span>
84
+ </button>
85
+
86
+ <button
87
+ className={`theme-option ${isWarmVisualTheme ? 'selected' : ''}`}
88
+ onClick={() => onVisualThemeChange('warm')}
89
+ >
90
+ <div className="theme-option-preview warm">
91
+ <div className="theme-option-preview-line ui-line" />
92
+ <div className="theme-option-preview-line ui-line" />
93
+ <div className="theme-option-preview-line ui-line" />
94
+ </div>
95
+ <span className="theme-option-label">Studio</span>
96
+ </button>
97
+ </div>
98
+ </div>
99
+
65
100
  {/* Theme Mode */}
66
101
  <div className="appearance-section">
67
- <h4>Theme</h4>
102
+ <h4>Color Mode</h4>
68
103
  <div className="theme-switcher">
69
104
  <button
70
105
  className={`theme-option ${themeMode === 'light' ? 'selected' : ''}`}
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, useCallback } from 'react';
2
2
  import { ChannelData, ChannelUserData, SecurityMode, ContextType, ContextPolicy } from '../../shared/types';
3
3
  import { PairingCodeDisplay } from './PairingCodeDisplay';
4
4
  import { ContextPolicySettings } from './ContextPolicySettings';
@@ -32,11 +32,7 @@ export function BlueBubblesSettings({ onStatusChange }: BlueBubblesSettingsProps
32
32
  const [contextPolicies, setContextPolicies] = useState<Record<ContextType, ContextPolicy>>({} as Record<ContextType, ContextPolicy>);
33
33
  const [savingPolicy, setSavingPolicy] = useState(false);
34
34
 
35
- useEffect(() => {
36
- loadChannel();
37
- }, []);
38
-
39
- const loadChannel = async () => {
35
+ const loadChannel = useCallback(async () => {
40
36
  try {
41
37
  setLoading(true);
42
38
  const channels = await window.electronAPI.getGatewayChannels();
@@ -74,7 +70,22 @@ export function BlueBubblesSettings({ onStatusChange }: BlueBubblesSettingsProps
74
70
  } finally {
75
71
  setLoading(false);
76
72
  }
77
- };
73
+ }, [onStatusChange]);
74
+
75
+ useEffect(() => {
76
+ loadChannel();
77
+ }, [loadChannel]);
78
+
79
+ useEffect(() => {
80
+ const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
81
+ if (data?.channelType !== 'bluebubbles') return;
82
+ if (channel && data?.channelId && data.channelId !== channel.id) return;
83
+ loadChannel();
84
+ });
85
+ return () => {
86
+ if (unsubscribe) unsubscribe();
87
+ };
88
+ }, [channel?.id, loadChannel]);
78
89
 
79
90
  const handleAddChannel = async () => {
80
91
  if (!serverUrl.trim() || !password.trim()) {
@@ -0,0 +1,203 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { BoxSettingsData } from '../../shared/types';
3
+
4
+ export function BoxSettings() {
5
+ const [settings, setSettings] = useState<BoxSettingsData | null>(null);
6
+ const [saving, setSaving] = useState(false);
7
+ const [testing, setTesting] = useState(false);
8
+ const [testResult, setTestResult] = useState<{ success: boolean; error?: string; name?: string; userId?: string } | null>(null);
9
+ const [status, setStatus] = useState<{ configured: boolean; connected: boolean; name?: string; error?: string } | null>(null);
10
+ const [statusLoading, setStatusLoading] = useState(false);
11
+
12
+ useEffect(() => {
13
+ loadSettings();
14
+ refreshStatus();
15
+ }, []);
16
+
17
+ const loadSettings = async () => {
18
+ try {
19
+ const loaded = await window.electronAPI.getBoxSettings();
20
+ setSettings(loaded);
21
+ } catch (error) {
22
+ console.error('Failed to load Box settings:', error);
23
+ }
24
+ };
25
+
26
+ const updateSettings = (updates: Partial<BoxSettingsData>) => {
27
+ if (!settings) return;
28
+ setSettings({ ...settings, ...updates });
29
+ };
30
+
31
+ const handleSave = async () => {
32
+ if (!settings) return;
33
+ setSaving(true);
34
+ setTestResult(null);
35
+ try {
36
+ const payload: BoxSettingsData = { ...settings };
37
+ await window.electronAPI.saveBoxSettings(payload);
38
+ setSettings(payload);
39
+ await refreshStatus();
40
+ } catch (error) {
41
+ console.error('Failed to save Box settings:', error);
42
+ } finally {
43
+ setSaving(false);
44
+ }
45
+ };
46
+
47
+ const refreshStatus = async () => {
48
+ try {
49
+ setStatusLoading(true);
50
+ const result = await window.electronAPI.getBoxStatus();
51
+ setStatus(result);
52
+ } catch (error) {
53
+ console.error('Failed to load Box status:', error);
54
+ } finally {
55
+ setStatusLoading(false);
56
+ }
57
+ };
58
+
59
+ const handleTestConnection = async () => {
60
+ setTesting(true);
61
+ setTestResult(null);
62
+ try {
63
+ const result = await window.electronAPI.testBoxConnection();
64
+ setTestResult(result);
65
+ await refreshStatus();
66
+ } catch (error: any) {
67
+ setTestResult({ success: false, error: error.message || 'Failed to test connection' });
68
+ } finally {
69
+ setTesting(false);
70
+ }
71
+ };
72
+
73
+ if (!settings) {
74
+ return <div className="settings-loading">Loading Box settings...</div>;
75
+ }
76
+
77
+ const statusLabel = !status?.configured
78
+ ? 'Missing Token'
79
+ : status.connected
80
+ ? 'Connected'
81
+ : 'Configured';
82
+
83
+ const statusClass = !status?.configured
84
+ ? 'missing'
85
+ : status.connected
86
+ ? 'connected'
87
+ : 'configured';
88
+
89
+ return (
90
+ <div className="box-settings">
91
+ <div className="settings-section">
92
+ <div className="settings-section-header">
93
+ <div className="settings-title-with-badge">
94
+ <h3>Connect Box</h3>
95
+ {status && (
96
+ <span
97
+ className={`box-status-badge ${statusClass}`}
98
+ title={!status.configured ? 'Access token not configured' : status.connected ? 'Connected to Box' : 'Configured'}
99
+ >
100
+ {statusLabel}
101
+ </span>
102
+ )}
103
+ {statusLoading && !status && (
104
+ <span className="box-status-badge configured">Checking…</span>
105
+ )}
106
+ </div>
107
+ <button className="btn-secondary btn-sm" onClick={refreshStatus} disabled={statusLoading}>
108
+ {statusLoading ? 'Checking...' : 'Refresh Status'}
109
+ </button>
110
+ </div>
111
+ <p className="settings-description">
112
+ Connect the agent to Box using a developer token or OAuth access token, then use the built-in `box_action`
113
+ tool to search and manage files.
114
+ </p>
115
+ {status?.error && (
116
+ <p className="settings-hint">Status check: {status.error}</p>
117
+ )}
118
+ <div className="settings-actions">
119
+ <button
120
+ className="btn-secondary btn-sm"
121
+ onClick={() => window.electronAPI.openExternal('https://app.box.com/developers/console')}
122
+ >
123
+ Open Box Console
124
+ </button>
125
+ </div>
126
+ </div>
127
+
128
+ <div className="settings-section">
129
+ <div className="settings-field">
130
+ <label>Enable Integration</label>
131
+ <label className="settings-toggle">
132
+ <input
133
+ type="checkbox"
134
+ checked={settings.enabled}
135
+ onChange={(e) => updateSettings({ enabled: e.target.checked })}
136
+ />
137
+ <span className="toggle-slider" />
138
+ </label>
139
+ </div>
140
+
141
+ <div className="settings-field">
142
+ <label>Access Token</label>
143
+ <input
144
+ type="password"
145
+ className="settings-input"
146
+ placeholder="Box access token"
147
+ value={settings.accessToken || ''}
148
+ onChange={(e) => updateSettings({ accessToken: e.target.value || undefined })}
149
+ />
150
+ <p className="settings-hint">Use a developer token or OAuth access token with required scopes.</p>
151
+ </div>
152
+
153
+ <div className="settings-field">
154
+ <label>Timeout (ms)</label>
155
+ <input
156
+ type="number"
157
+ className="settings-input"
158
+ min={1000}
159
+ max={120000}
160
+ value={settings.timeoutMs ?? 20000}
161
+ onChange={(e) => updateSettings({ timeoutMs: Number(e.target.value) })}
162
+ />
163
+ </div>
164
+
165
+ <div className="settings-actions">
166
+ <button className="btn-secondary btn-sm" onClick={handleTestConnection} disabled={testing}>
167
+ {testing ? 'Testing...' : 'Test Connection'}
168
+ </button>
169
+ <button className="btn-primary btn-sm" onClick={handleSave} disabled={saving}>
170
+ {saving ? 'Saving...' : 'Save Settings'}
171
+ </button>
172
+ </div>
173
+
174
+ {testResult && (
175
+ <div className={`test-result ${testResult.success ? 'success' : 'error'}`}>
176
+ {testResult.success ? (
177
+ <span>Connected{testResult.name ? ` as ${testResult.name}` : ''}</span>
178
+ ) : (
179
+ <span>Connection failed: {testResult.error}</span>
180
+ )}
181
+ </div>
182
+ )}
183
+ </div>
184
+
185
+ <div className="settings-section">
186
+ <h4>Quick Usage</h4>
187
+ <pre className="settings-info-box">{`// List root folder items
188
+ box_action({
189
+ action: "list_folder_items",
190
+ folder_id: "0",
191
+ limit: 25
192
+ });
193
+
194
+ // Upload a file to root
195
+ box_action({
196
+ action: "upload_file",
197
+ file_path: "reports/summary.pdf",
198
+ parent_id: "0"
199
+ });`}</pre>
200
+ </div>
201
+ </div>
202
+ );
203
+ }