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
@@ -1,4 +1,23 @@
1
1
  import { TaskEvent, DEFAULT_QUIRKS } from '../../shared/types';
2
+ import { ThemeIcon } from './ThemeIcon';
3
+ import {
4
+ AlertTriangleIcon,
5
+ BanIcon,
6
+ CheckIcon,
7
+ ClipboardIcon,
8
+ DotIcon,
9
+ FileIcon,
10
+ PackageIcon,
11
+ PauseIcon,
12
+ PlayIcon,
13
+ ShieldIcon,
14
+ SlidersIcon,
15
+ StopIcon,
16
+ TargetIcon,
17
+ TrashIcon,
18
+ XIcon,
19
+ ZapIcon,
20
+ } from './LineIcons';
2
21
  import type { AgentContext } from '../hooks/useAgentContext';
3
22
  import { getUiCopy, type UiCopyKey } from '../utils/agentMessages';
4
23
 
@@ -39,57 +58,57 @@ export function TaskTimeline({ events, agentContext }: TaskTimelineProps) {
39
58
  const getEventIcon = (type: TaskEvent['type']) => {
40
59
  switch (type) {
41
60
  case 'task_created':
42
- return '🎯';
61
+ return <ThemeIcon emoji="🎯" icon={<TargetIcon size={16} />} />;
43
62
  case 'plan_created':
44
- return '📋';
63
+ return <ThemeIcon emoji="📋" icon={<ClipboardIcon size={16} />} />;
45
64
  case 'step_started':
46
- return '▶️';
65
+ return <ThemeIcon emoji="▶️" icon={<PlayIcon size={16} />} />;
47
66
  case 'step_completed':
48
- return '';
67
+ return <ThemeIcon emoji="" icon={<CheckIcon size={16} />} />;
49
68
  case 'tool_call':
50
- return '🔧';
69
+ return <ThemeIcon emoji="🔧" icon={<SlidersIcon size={16} />} />;
51
70
  case 'tool_result':
52
- return '📦';
71
+ return <ThemeIcon emoji="📦" icon={<PackageIcon size={16} />} />;
53
72
  case 'file_created':
54
73
  case 'file_modified':
55
- return '📄';
74
+ return <ThemeIcon emoji="📄" icon={<FileIcon size={16} />} />;
56
75
  case 'file_deleted':
57
- return '🗑️';
76
+ return <ThemeIcon emoji="🗑️" icon={<TrashIcon size={16} />} />;
58
77
  case 'error':
59
- return '';
78
+ return <ThemeIcon emoji="" icon={<XIcon size={16} />} />;
60
79
  case 'task_cancelled':
61
- return '🛑';
80
+ return <ThemeIcon emoji="🛑" icon={<StopIcon size={16} />} />;
62
81
  case 'approval_requested':
63
- return '⚠️';
82
+ return <ThemeIcon emoji="⚠️" icon={<AlertTriangleIcon size={16} />} />;
64
83
  case 'approval_granted':
65
- return '';
84
+ return <ThemeIcon emoji="" icon={<CheckIcon size={16} />} />;
66
85
  case 'approval_denied':
67
- return '';
86
+ return <ThemeIcon emoji="" icon={<BanIcon size={16} />} />;
68
87
  case 'task_paused':
69
- return '⏸️';
88
+ return <ThemeIcon emoji="⏸️" icon={<PauseIcon size={16} />} />;
70
89
  case 'task_resumed':
71
- return '▶️';
90
+ return <ThemeIcon emoji="▶️" icon={<PlayIcon size={16} />} />;
72
91
  case 'executing':
73
- return '';
92
+ return <ThemeIcon emoji="" icon={<ZapIcon size={16} />} />;
74
93
  case 'task_completed':
75
- return '';
94
+ return <ThemeIcon emoji="" icon={<CheckIcon size={16} />} />;
76
95
  case 'follow_up_completed':
77
- return '';
96
+ return <ThemeIcon emoji="" icon={<CheckIcon size={16} />} />;
78
97
  default:
79
- return '';
98
+ return <ThemeIcon emoji="" icon={<DotIcon size={8} />} />;
80
99
  }
81
100
  };
82
101
 
83
102
  const getEventTitle = (event: TaskEvent) => {
84
103
  switch (event.type) {
85
104
  case 'task_created':
86
- return isCompanion ? "Session started - I'm here." : '🚀 Session Started';
105
+ return isCompanion ? "Session started - I'm here." : 'Session started';
87
106
  case 'plan_created':
88
107
  return isCompanion ? "Here's the path I'm taking" : "Here's our approach";
89
108
  case 'step_started':
90
109
  return `Working on: ${event.payload.step?.description || 'Getting started'}`;
91
110
  case 'step_completed':
92
- return `✓ ${event.payload.step?.description || event.payload.message || 'Done'}`;
111
+ return event.payload.step?.description || event.payload.message || 'Done';
93
112
  case 'tool_call':
94
113
  return `Using: ${event.payload.tool}`;
95
114
  case 'tool_result':
@@ -119,9 +138,9 @@ export function TaskTimeline({ events, agentContext }: TaskTimelineProps) {
119
138
  case 'executing':
120
139
  return event.payload.message || 'Working on it';
121
140
  case 'task_completed':
122
- return isCompanion ? 'All done.' : 'All done!';
141
+ return isCompanion ? 'All done.' : 'All done!';
123
142
  case 'follow_up_completed':
124
- return 'All done!';
143
+ return 'All done!';
125
144
  case 'log':
126
145
  return event.payload.message;
127
146
  default:
@@ -156,12 +175,23 @@ export function TaskTimeline({ events, agentContext }: TaskTimelineProps) {
156
175
  <pre>{JSON.stringify(event.payload.result, null, 2)}</pre>
157
176
  </div>
158
177
  );
159
- case 'error':
178
+ case 'error': {
179
+ const errorMessage = event.payload.error || event.payload.message;
180
+ const actionHint = event.payload.actionHint;
160
181
  return (
161
182
  <div className="event-details error">
162
- {event.payload.error || event.payload.message}
183
+ <div>{errorMessage}</div>
184
+ {actionHint?.type === 'open_settings' && (
185
+ <button
186
+ className="button-primary button-small"
187
+ onClick={() => window.dispatchEvent(new CustomEvent('open-settings'))}
188
+ >
189
+ {actionHint.label || 'Open Settings'}
190
+ </button>
191
+ )}
163
192
  </div>
164
193
  );
194
+ }
165
195
  case 'task_cancelled':
166
196
  return (
167
197
  <div className="event-details cancelled">
@@ -200,7 +230,9 @@ export function TaskTimeline({ events, agentContext }: TaskTimelineProps) {
200
230
  {/* Show summary of blocked events if any - collapsed for cleaner UI */}
201
231
  {blockedEvents.length > 0 && (
202
232
  <div className="timeline-event timeline-event-muted">
203
- <div className="event-icon">🛡️</div>
233
+ <div className="event-icon">
234
+ <ThemeIcon emoji="🛡️" icon={<ShieldIcon size={16} />} />
235
+ </div>
204
236
  <div className="event-content">
205
237
  <div className="event-header">
206
238
  <div className="event-title">
@@ -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 TeamsSettings({ onStatusChange }: TeamsSettingsProps) {
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();
@@ -65,7 +61,22 @@ export function TeamsSettings({ onStatusChange }: TeamsSettingsProps) {
65
61
  } finally {
66
62
  setLoading(false);
67
63
  }
68
- };
64
+ }, [onStatusChange]);
65
+
66
+ useEffect(() => {
67
+ loadChannel();
68
+ }, [loadChannel]);
69
+
70
+ useEffect(() => {
71
+ const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
72
+ if (data?.channelType !== 'teams') return;
73
+ if (channel && data?.channelId && data.channelId !== channel.id) return;
74
+ loadChannel();
75
+ });
76
+ return () => {
77
+ if (unsubscribe) unsubscribe();
78
+ };
79
+ }, [channel?.id, loadChannel]);
69
80
 
70
81
  const handleAddChannel = async () => {
71
82
  if (!appId.trim() || !appPassword.trim()) return;
@@ -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';
@@ -29,11 +29,7 @@ export function TelegramSettings({ onStatusChange }: TelegramSettingsProps) {
29
29
  const [contextPolicies, setContextPolicies] = useState<Record<ContextType, ContextPolicy>>({} as Record<ContextType, ContextPolicy>);
30
30
  const [savingPolicy, setSavingPolicy] = useState(false);
31
31
 
32
- useEffect(() => {
33
- loadChannel();
34
- }, []);
35
-
36
- const loadChannel = async () => {
32
+ const loadChannel = useCallback(async () => {
37
33
  try {
38
34
  setLoading(true);
39
35
  const channels = await window.electronAPI.getGatewayChannels();
@@ -62,7 +58,22 @@ export function TelegramSettings({ onStatusChange }: TelegramSettingsProps) {
62
58
  } finally {
63
59
  setLoading(false);
64
60
  }
65
- };
61
+ }, [onStatusChange]);
62
+
63
+ useEffect(() => {
64
+ loadChannel();
65
+ }, [loadChannel]);
66
+
67
+ useEffect(() => {
68
+ const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
69
+ if (data?.channelType !== 'telegram') return;
70
+ if (channel && data?.channelId && data.channelId !== channel.id) return;
71
+ loadChannel();
72
+ });
73
+ return () => {
74
+ if (unsubscribe) unsubscribe();
75
+ };
76
+ }, [channel?.id, loadChannel]);
66
77
 
67
78
  const handleAddChannel = async () => {
68
79
  if (!botToken.trim()) return;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+
3
+ interface ThemeIconProps {
4
+ emoji: string;
5
+ icon: React.ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ export function ThemeIcon({ emoji, icon, className }: ThemeIconProps) {
10
+ return (
11
+ <span className={className} aria-hidden="true">
12
+ <span className="terminal-only">{emoji}</span>
13
+ <span className="modern-only">{icon}</span>
14
+ </span>
15
+ );
16
+ }
@@ -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 TwitchSettings({ onStatusChange }: TwitchSettingsProps) {
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 TwitchSettings({ onStatusChange }: TwitchSettingsProps) {
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 !== 'twitch') 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 (!username.trim() || !oauthToken.trim() || !twitchChannels.trim()) {
@@ -391,63 +391,38 @@ export function VoiceSettings({ onStateChange }: VoiceSettingsProps) {
391
391
  <div className="settings-section">
392
392
  <h4>Text-to-Speech Provider</h4>
393
393
  <p className="settings-description">Choose the voice synthesis provider.</p>
394
- <div className="provider-options">
394
+ <div className="llm-provider-tabs">
395
395
  <button
396
- className={`provider-option ${settings.ttsProvider === 'elevenlabs' ? 'selected' : ''} ${settings.elevenLabsApiKey ? 'configured' : ''}`}
396
+ className={`llm-provider-tab ${settings.ttsProvider === 'elevenlabs' ? 'active' : ''}`}
397
397
  onClick={() => handleTTSProviderChange('elevenlabs')}
398
398
  disabled={saving}
399
399
  >
400
- <div className="provider-option-header">
401
- <span className="provider-name">ElevenLabs</span>
402
- {settings.elevenLabsApiKey && (
403
- <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
404
- <path d="M20 6L9 17l-5-5" />
405
- </svg>
406
- )}
407
- </div>
408
- <span className="provider-badge">Premium</span>
400
+ <span className="llm-provider-tab-label">ElevenLabs</span>
401
+ {settings.elevenLabsApiKey && <span className="llm-provider-tab-status" />}
409
402
  </button>
410
403
  <button
411
- className={`provider-option ${settings.ttsProvider === 'openai' ? 'selected' : ''} ${settings.openaiApiKey ? 'configured' : ''}`}
404
+ className={`llm-provider-tab ${settings.ttsProvider === 'openai' ? 'active' : ''}`}
412
405
  onClick={() => handleTTSProviderChange('openai')}
413
406
  disabled={saving}
414
407
  >
415
- <div className="provider-option-header">
416
- <span className="provider-name">OpenAI</span>
417
- {settings.openaiApiKey && (
418
- <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
419
- <path d="M20 6L9 17l-5-5" />
420
- </svg>
421
- )}
422
- </div>
408
+ <span className="llm-provider-tab-label">OpenAI</span>
409
+ {settings.openaiApiKey && <span className="llm-provider-tab-status" />}
423
410
  </button>
424
411
  <button
425
- className={`provider-option ${settings.ttsProvider === 'azure' ? 'selected' : ''} ${settings.azureApiKey && settings.azureEndpoint ? 'configured' : ''}`}
412
+ className={`llm-provider-tab ${settings.ttsProvider === 'azure' ? 'active' : ''}`}
426
413
  onClick={() => handleTTSProviderChange('azure')}
427
414
  disabled={saving}
428
415
  >
429
- <div className="provider-option-header">
430
- <span className="provider-name">Azure OpenAI</span>
431
- {settings.azureApiKey && settings.azureEndpoint && (
432
- <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
433
- <path d="M20 6L9 17l-5-5" />
434
- </svg>
435
- )}
436
- </div>
437
- <span className="provider-badge">Enterprise</span>
416
+ <span className="llm-provider-tab-label">Azure OpenAI</span>
417
+ {settings.azureApiKey && settings.azureEndpoint && <span className="llm-provider-tab-status" />}
438
418
  </button>
439
419
  <button
440
- className={`provider-option ${settings.ttsProvider === 'local' ? 'selected' : ''} configured`}
420
+ className={`llm-provider-tab ${settings.ttsProvider === 'local' ? 'active' : ''}`}
441
421
  onClick={() => handleTTSProviderChange('local')}
442
422
  disabled={saving}
443
423
  >
444
- <div className="provider-option-header">
445
- <span className="provider-name">System</span>
446
- <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
447
- <path d="M20 6L9 17l-5-5" />
448
- </svg>
449
- </div>
450
- <span className="provider-badge">Free</span>
424
+ <span className="llm-provider-tab-label">System</span>
425
+ <span className="llm-provider-tab-status" />
451
426
  </button>
452
427
  </div>
453
428
  </div>
@@ -698,49 +673,30 @@ export function VoiceSettings({ onStateChange }: VoiceSettingsProps) {
698
673
  <div className="settings-section">
699
674
  <h4>Speech-to-Text Provider</h4>
700
675
  <p className="settings-description">Choose the speech recognition provider.</p>
701
- <div className="provider-options">
676
+ <div className="llm-provider-tabs">
702
677
  <button
703
- className={`provider-option ${settings.sttProvider === 'openai' ? 'selected' : ''} ${settings.openaiApiKey ? 'configured' : ''}`}
678
+ className={`llm-provider-tab ${settings.sttProvider === 'openai' ? 'active' : ''}`}
704
679
  onClick={() => handleSTTProviderChange('openai')}
705
680
  disabled={saving}
706
681
  >
707
- <div className="provider-option-header">
708
- <span className="provider-name">OpenAI Whisper</span>
709
- {settings.openaiApiKey && (
710
- <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
711
- <path d="M20 6L9 17l-5-5" />
712
- </svg>
713
- )}
714
- </div>
715
- <span className="provider-badge">Recommended</span>
682
+ <span className="llm-provider-tab-label">OpenAI Whisper</span>
683
+ {settings.openaiApiKey && <span className="llm-provider-tab-status" />}
716
684
  </button>
717
685
  <button
718
- className={`provider-option ${settings.sttProvider === 'azure' ? 'selected' : ''} ${settings.azureApiKey && settings.azureEndpoint ? 'configured' : ''}`}
686
+ className={`llm-provider-tab ${settings.sttProvider === 'azure' ? 'active' : ''}`}
719
687
  onClick={() => handleSTTProviderChange('azure')}
720
688
  disabled={saving}
721
689
  >
722
- <div className="provider-option-header">
723
- <span className="provider-name">Azure Whisper</span>
724
- {settings.azureApiKey && settings.azureEndpoint && (
725
- <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
726
- <path d="M20 6L9 17l-5-5" />
727
- </svg>
728
- )}
729
- </div>
730
- <span className="provider-badge">Enterprise</span>
690
+ <span className="llm-provider-tab-label">Azure Whisper</span>
691
+ {settings.azureApiKey && settings.azureEndpoint && <span className="llm-provider-tab-status" />}
731
692
  </button>
732
693
  <button
733
- className={`provider-option ${settings.sttProvider === 'local' ? 'selected' : ''} configured`}
694
+ className={`llm-provider-tab ${settings.sttProvider === 'local' ? 'active' : ''}`}
734
695
  onClick={() => handleSTTProviderChange('local')}
735
696
  disabled={saving}
736
697
  >
737
- <div className="provider-option-header">
738
- <span className="provider-name">System</span>
739
- <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
740
- <path d="M20 6L9 17l-5-5" />
741
- </svg>
742
- </div>
743
- <span className="provider-badge">Free</span>
698
+ <span className="llm-provider-tab-label">System</span>
699
+ <span className="llm-provider-tab-status" />
744
700
  </button>
745
701
  </div>
746
702
  </div>
@@ -748,27 +704,27 @@ export function VoiceSettings({ onStateChange }: VoiceSettingsProps) {
748
704
  {/* Voice Input Mode */}
749
705
  <div className="settings-section">
750
706
  <h4>Voice Input Mode</h4>
751
- <div className="provider-options">
707
+ <div className="llm-provider-tabs">
752
708
  <button
753
- className={`provider-option ${settings.inputMode === 'push_to_talk' ? 'selected' : ''}`}
709
+ className={`llm-provider-tab ${settings.inputMode === 'push_to_talk' ? 'active' : ''}`}
754
710
  onClick={() => handleInputModeChange('push_to_talk')}
755
711
  disabled={saving}
756
712
  >
757
- <span className="provider-name">Push to Talk</span>
713
+ <span className="llm-provider-tab-label">Push to Talk</span>
758
714
  </button>
759
715
  <button
760
- className={`provider-option ${settings.inputMode === 'voice_activity' ? 'selected' : ''}`}
716
+ className={`llm-provider-tab ${settings.inputMode === 'voice_activity' ? 'active' : ''}`}
761
717
  onClick={() => handleInputModeChange('voice_activity')}
762
718
  disabled={saving}
763
719
  >
764
- <span className="provider-name">Voice Activity</span>
720
+ <span className="llm-provider-tab-label">Voice Activity</span>
765
721
  </button>
766
722
  <button
767
- className={`provider-option ${settings.inputMode === 'disabled' ? 'selected' : ''}`}
723
+ className={`llm-provider-tab ${settings.inputMode === 'disabled' ? 'active' : ''}`}
768
724
  onClick={() => handleInputModeChange('disabled')}
769
725
  disabled={saving}
770
726
  >
771
- <span className="provider-name">Disabled</span>
727
+ <span className="llm-provider-tab-label">Disabled</span>
772
728
  </button>
773
729
  </div>
774
730
  <p className="settings-hint">
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, useCallback } from 'react';
2
2
  import { ChannelData, ChannelUserData, SecurityMode } from '../../shared/types';
3
3
  import QRCode from 'qrcode';
4
4
 
@@ -29,6 +29,41 @@ export function WhatsAppSettings({ onStatusChange }: WhatsAppSettingsProps) {
29
29
  // Pairing code state
30
30
  const [pairingCode, setPairingCode] = useState<string | null>(null);
31
31
 
32
+ const loadChannel = useCallback(async () => {
33
+ try {
34
+ setLoading(true);
35
+ const channels = await window.electronAPI.getGatewayChannels();
36
+ const whatsappChannel = channels.find((c: ChannelData) => c.type === 'whatsapp');
37
+
38
+ if (whatsappChannel) {
39
+ setChannel(whatsappChannel);
40
+ setChannelName(whatsappChannel.name);
41
+ setSecurityMode(whatsappChannel.securityMode);
42
+ onStatusChange?.(whatsappChannel.status === 'connected');
43
+
44
+ // Load self-chat mode settings from config
45
+ if (whatsappChannel.config) {
46
+ setSelfChatMode(whatsappChannel.config.selfChatMode ?? true);
47
+ setResponsePrefix(whatsappChannel.config.responsePrefix ?? '🤖');
48
+ }
49
+
50
+ // Load users for this channel
51
+ const channelUsers = await window.electronAPI.getGatewayUsers(whatsappChannel.id);
52
+ setUsers(channelUsers);
53
+
54
+ // Check for QR code
55
+ const info = await window.electronAPI.getWhatsAppInfo?.();
56
+ if (info?.qrCode) {
57
+ setQrCode(info.qrCode);
58
+ }
59
+ }
60
+ } catch (error) {
61
+ console.error('Failed to load WhatsApp channel:', error);
62
+ } finally {
63
+ setLoading(false);
64
+ }
65
+ }, [onStatusChange]);
66
+
32
67
  useEffect(() => {
33
68
  loadChannel();
34
69
 
@@ -50,7 +85,18 @@ export function WhatsAppSettings({ onStatusChange }: WhatsAppSettingsProps) {
50
85
  return () => {
51
86
  // Cleanup listeners if needed
52
87
  };
53
- }, []);
88
+ }, [loadChannel]);
89
+
90
+ useEffect(() => {
91
+ const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
92
+ if (data?.channelType !== 'whatsapp') return;
93
+ if (channel && data?.channelId && data.channelId !== channel.id) return;
94
+ loadChannel();
95
+ });
96
+ return () => {
97
+ if (unsubscribe) unsubscribe();
98
+ };
99
+ }, [channel?.id, loadChannel]);
54
100
 
55
101
  // Render QR code when it changes
56
102
  useEffect(() => {
@@ -75,41 +121,6 @@ export function WhatsAppSettings({ onStatusChange }: WhatsAppSettingsProps) {
75
121
  }
76
122
  };
77
123
 
78
- const loadChannel = async () => {
79
- try {
80
- setLoading(true);
81
- const channels = await window.electronAPI.getGatewayChannels();
82
- const whatsappChannel = channels.find((c: ChannelData) => c.type === 'whatsapp');
83
-
84
- if (whatsappChannel) {
85
- setChannel(whatsappChannel);
86
- setChannelName(whatsappChannel.name);
87
- setSecurityMode(whatsappChannel.securityMode);
88
- onStatusChange?.(whatsappChannel.status === 'connected');
89
-
90
- // Load self-chat mode settings from config
91
- if (whatsappChannel.config) {
92
- setSelfChatMode(whatsappChannel.config.selfChatMode ?? true);
93
- setResponsePrefix(whatsappChannel.config.responsePrefix ?? '🤖');
94
- }
95
-
96
- // Load users for this channel
97
- const channelUsers = await window.electronAPI.getGatewayUsers(whatsappChannel.id);
98
- setUsers(channelUsers);
99
-
100
- // Check for QR code
101
- const info = await window.electronAPI.getWhatsAppInfo?.();
102
- if (info?.qrCode) {
103
- setQrCode(info.qrCode);
104
- }
105
- }
106
- } catch (error) {
107
- console.error('Failed to load WhatsApp channel:', error);
108
- } finally {
109
- setLoading(false);
110
- }
111
- };
112
-
113
124
  const handleAddChannel = async () => {
114
125
  try {
115
126
  setSaving(true);
@@ -4,6 +4,8 @@ import {
4
4
  WorkingStateType,
5
5
  } from '../../electron/preload';
6
6
  import { useAgentContext } from '../hooks/useAgentContext';
7
+ import { ThemeIcon } from './ThemeIcon';
8
+ import { ChartIcon, ClipboardIcon, EditIcon, TargetIcon } from './LineIcons';
7
9
 
8
10
  interface WorkingStateHistoryProps {
9
11
  agentRoleId: string;
@@ -12,11 +14,11 @@ interface WorkingStateHistoryProps {
12
14
  onClose: () => void;
13
15
  }
14
16
 
15
- const STATE_TYPE_LABELS: Record<WorkingStateType, { label: string; icon: string }> = {
16
- context: { label: 'Context', icon: '📋' },
17
- progress: { label: 'Progress', icon: '📊' },
18
- notes: { label: 'Notes', icon: '📝' },
19
- plan: { label: 'Plan', icon: '🎯' },
17
+ const STATE_TYPE_LABELS: Record<WorkingStateType, { label: string; icon: React.ReactNode }> = {
18
+ context: { label: 'Context', icon: <ThemeIcon emoji="📋" icon={<ClipboardIcon size={14} />} /> },
19
+ progress: { label: 'Progress', icon: <ThemeIcon emoji="📊" icon={<ChartIcon size={14} />} /> },
20
+ notes: { label: 'Notes', icon: <ThemeIcon emoji="📝" icon={<EditIcon size={14} />} /> },
21
+ plan: { label: 'Plan', icon: <ThemeIcon emoji="🎯" icon={<TargetIcon size={14} />} /> },
20
22
  };
21
23
 
22
24
  function formatDate(timestamp: number): string {