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
@@ -50,6 +50,11 @@ export class MatrixAdapter implements ChannelAdapter {
50
50
  // User cache
51
51
  private userCache: Map<string, MatrixUser> = new Map();
52
52
 
53
+ // Direct (1:1) rooms cache
54
+ private directRooms: Set<string> | null = null;
55
+ private directRoomsLoadedAt = 0;
56
+ private readonly DIRECT_ROOMS_TTL_MS = 5 * 60 * 1000;
57
+
53
58
  // Message deduplication
54
59
  private processedMessages: Map<string, number> = new Map();
55
60
  private readonly DEDUP_CACHE_TTL = 60000; // 1 minute
@@ -479,6 +484,9 @@ export class MatrixAdapter implements ChannelAdapter {
479
484
  // Get reply-to
480
485
  const replyTo = event.content['m.relates_to']?.['m.in_reply_to']?.event_id;
481
486
 
487
+ const directRooms = await this.getDirectRooms();
488
+ const isGroup = directRooms ? !directRooms.has(event.room_id) : undefined;
489
+
482
490
  // Convert to IncomingMessage
483
491
  const message: IncomingMessage = {
484
492
  messageId: event.event_id,
@@ -486,6 +494,7 @@ export class MatrixAdapter implements ChannelAdapter {
486
494
  userId: event.sender,
487
495
  userName,
488
496
  chatId: event.room_id,
497
+ isGroup,
489
498
  text: body,
490
499
  timestamp: new Date(event.origin_server_ts),
491
500
  replyTo,
@@ -552,6 +561,28 @@ export class MatrixAdapter implements ChannelAdapter {
552
561
  ];
553
562
  }
554
563
 
564
+ private async getDirectRooms(): Promise<Set<string> | null> {
565
+ if (!this.client) {
566
+ return this.directRooms;
567
+ }
568
+
569
+ const now = Date.now();
570
+ if (this.directRooms && now - this.directRoomsLoadedAt < this.DIRECT_ROOMS_TTL_MS) {
571
+ return this.directRooms;
572
+ }
573
+
574
+ try {
575
+ const rooms = await this.client.getDirectRooms();
576
+ this.directRooms = new Set(rooms);
577
+ this.directRoomsLoadedAt = now;
578
+ return this.directRooms;
579
+ } catch (error) {
580
+ console.warn('Failed to load Matrix direct rooms:', error);
581
+ this.directRoomsLoadedAt = now;
582
+ return null;
583
+ }
584
+ }
585
+
555
586
  /**
556
587
  * Get user info with caching
557
588
  */
@@ -393,6 +393,8 @@ export class MattermostAdapter implements ChannelAdapter {
393
393
  // Convert attachments
394
394
  const attachments = this.convertAttachments(post);
395
395
 
396
+ const isGroup = channelType !== 'D';
397
+
396
398
  // Convert to IncomingMessage
397
399
  const message: IncomingMessage = {
398
400
  messageId: post.id,
@@ -400,6 +402,7 @@ export class MattermostAdapter implements ChannelAdapter {
400
402
  userId: post.user_id,
401
403
  userName,
402
404
  chatId: post.channel_id,
405
+ isGroup,
403
406
  text: post.message,
404
407
  timestamp: new Date(post.create_at),
405
408
  replyTo: post.root_id || undefined,
@@ -491,6 +491,8 @@ export class SignalAdapter implements ChannelAdapter {
491
491
  return;
492
492
  }
493
493
 
494
+ const isGroup = Boolean(dataMessage.groupInfo?.groupId);
495
+
494
496
  // Convert to IncomingMessage
495
497
  const message: IncomingMessage = {
496
498
  messageId,
@@ -498,6 +500,7 @@ export class SignalAdapter implements ChannelAdapter {
498
500
  userId: envelope.source,
499
501
  userName: envelope.source, // Signal doesn't provide names in messages
500
502
  chatId: dataMessage.groupInfo?.groupId || envelope.source,
503
+ isGroup,
501
504
  text: dataMessage.message,
502
505
  timestamp: new Date(envelope.timestamp),
503
506
  replyTo: dataMessage.quote?.id?.toString(),
@@ -82,11 +82,13 @@ export class SlackAdapter implements ChannelAdapter {
82
82
 
83
83
  // Check if it's a DM or mentions the bot
84
84
  const channelInfo = await client.conversations.info({ channel: message.channel });
85
- const isDM = channelInfo.channel?.is_im || channelInfo.channel?.is_mpim;
85
+ const isDirect = channelInfo.channel?.is_im === true;
86
+ const isMultiPartyDm = channelInfo.channel?.is_mpim === true;
87
+ const isGroup = isDirect ? false : true;
86
88
  const isMentioned = message.text.includes(`<@${this._botId}>`);
87
89
 
88
- if (isDM || isMentioned) {
89
- const incomingMessage = await this.mapMessageToIncoming(message, client);
90
+ if (isDirect || isMultiPartyDm || isMentioned) {
91
+ const incomingMessage = await this.mapMessageToIncoming(message, client, isGroup);
90
92
  console.log(`Processing Slack message from ${incomingMessage.userName}: ${incomingMessage.text.slice(0, 50)}`);
91
93
  await this.handleIncomingMessage(incomingMessage);
92
94
  }
@@ -96,12 +98,14 @@ export class SlackAdapter implements ChannelAdapter {
96
98
  this.app.command(/.*/, async ({ command, ack, respond }) => {
97
99
  await ack();
98
100
 
101
+ const isGroup = command.channel_id ? !command.channel_id.startsWith('D') : undefined;
99
102
  const incomingMessage: IncomingMessage = {
100
103
  messageId: command.trigger_id,
101
104
  channel: 'slack',
102
105
  userId: command.user_id,
103
106
  userName: command.user_name,
104
107
  chatId: command.channel_id,
108
+ isGroup,
105
109
  text: `/${command.command.replace('/', '')} ${command.text}`.trim(),
106
110
  timestamp: new Date(),
107
111
  raw: command,
@@ -363,7 +367,7 @@ export class SlackAdapter implements ChannelAdapter {
363
367
 
364
368
  // Private methods
365
369
 
366
- private async mapMessageToIncoming(message: any, client: any): Promise<IncomingMessage> {
370
+ private async mapMessageToIncoming(message: any, client: any, isGroup?: boolean): Promise<IncomingMessage> {
367
371
  // Remove bot mention from the text if present
368
372
  let text = message.text || '';
369
373
  if (this._botId) {
@@ -390,6 +394,7 @@ export class SlackAdapter implements ChannelAdapter {
390
394
  userId: message.user || '',
391
395
  userName,
392
396
  chatId: message.channel || '',
397
+ isGroup,
393
398
  text: commandText || text,
394
399
  timestamp: new Date(parseFloat(message.ts || '0') * 1000),
395
400
  replyTo: message.thread_ts,
@@ -299,6 +299,9 @@ export class TeamsAdapter implements ChannelAdapter {
299
299
  const userName = activity.from?.name || 'Unknown User';
300
300
  const userId = activity.from?.id || '';
301
301
 
302
+ const conversationType = activity.conversation?.conversationType;
303
+ const isGroup = conversationType ? conversationType !== 'personal' : undefined;
304
+
302
305
  // Map to IncomingMessage format
303
306
  const incomingMessage: IncomingMessage = {
304
307
  messageId: messageId,
@@ -306,6 +309,7 @@ export class TeamsAdapter implements ChannelAdapter {
306
309
  userId: userId,
307
310
  userName: userName,
308
311
  chatId: activity.conversation.id,
312
+ isGroup,
309
313
  text: text.trim(),
310
314
  timestamp: activity.timestamp ? new Date(activity.timestamp) : new Date(),
311
315
  replyTo: activity.replyToId,
@@ -1660,6 +1660,7 @@ export class TelegramAdapter implements ChannelAdapter {
1660
1660
  const msg = ctx.message!;
1661
1661
  const from = msg.from!;
1662
1662
  const chat = msg.chat;
1663
+ const isGroup = chat.type !== 'private';
1663
1664
 
1664
1665
  // Check for forum topic (message_thread_id indicates a forum topic)
1665
1666
  const threadId = msg.message_thread_id?.toString();
@@ -1671,6 +1672,7 @@ export class TelegramAdapter implements ChannelAdapter {
1671
1672
  userId: from.id.toString(),
1672
1673
  userName: from.first_name + (from.last_name ? ` ${from.last_name}` : ''),
1673
1674
  chatId: chat.id.toString(),
1675
+ isGroup,
1674
1676
  text: overrideText ?? msg.text ?? '',
1675
1677
  timestamp: new Date(msg.date * 1000),
1676
1678
  replyTo: msg.reply_to_message?.message_id.toString(),
@@ -357,12 +357,14 @@ export class TwitchAdapter implements ChannelAdapter {
357
357
 
358
358
  // Convert to IncomingMessage
359
359
  // For Twitch, chatId is the channel name (without #)
360
+ const isGroup = !twitchMessage.isWhisper;
360
361
  const message: IncomingMessage = {
361
362
  messageId: twitchMessage.id,
362
363
  channel: 'twitch',
363
364
  userId: twitchMessage.userId,
364
365
  userName: twitchMessage.displayName || twitchMessage.username,
365
366
  chatId: twitchMessage.channel || twitchMessage.username, // Use username for whispers
367
+ isGroup,
366
368
  text: twitchMessage.message,
367
369
  timestamp: twitchMessage.timestamp,
368
370
  replyTo: twitchMessage.replyTo?.messageId,
@@ -29,6 +29,8 @@ export interface IncomingMessage {
29
29
  userName: string;
30
30
  /** Chat/conversation ID (for group chats) */
31
31
  chatId: string;
32
+ /** Whether this message is from a group chat */
33
+ isGroup?: boolean;
32
34
  /** Message content */
33
35
  text: string;
34
36
  /** Timestamp */
@@ -530,6 +532,12 @@ export interface ChannelAdapter {
530
532
  */
531
533
  onMessage(handler: MessageHandler): void;
532
534
 
535
+ /**
536
+ * Update adapter configuration at runtime (if supported).
537
+ * Useful for channels with dynamic settings like self-chat mode.
538
+ */
539
+ updateConfig?(config: ChannelConfig): void;
540
+
533
541
  /**
534
542
  * Register a callback query handler (for inline keyboard buttons)
535
543
  * @param handler Function to call when a button is pressed
@@ -36,6 +36,7 @@ import { app } from 'electron';
36
36
  import {
37
37
  ChannelAdapter,
38
38
  ChannelStatus,
39
+ ChannelConfig,
39
40
  IncomingMessage,
40
41
  OutgoingMessage,
41
42
  MessageHandler,
@@ -110,6 +111,8 @@ export class WhatsAppAdapter implements ChannelAdapter {
110
111
  private backoffAttempt = 0;
111
112
  private backoffTimer?: ReturnType<typeof setTimeout>;
112
113
  private currentQr?: string;
114
+ private shouldReconnect = true;
115
+ private selfChatIgnoreLogAt = 0;
113
116
 
114
117
  private readonly DEFAULT_BACKOFF: BackoffConfig = {
115
118
  initialDelay: 2000,
@@ -136,6 +139,7 @@ export class WhatsAppAdapter implements ChannelAdapter {
136
139
  // In self-chat mode, disable read receipts by default
137
140
  if (this.config.selfChatMode && config.sendReadReceipts === undefined) {
138
141
  this.config.sendReadReceipts = false;
142
+ console.log('[WhatsApp] Self-chat mode enabled; defaulting sendReadReceipts to false.');
139
143
  }
140
144
 
141
145
  // Set auth directory
@@ -187,6 +191,7 @@ export class WhatsAppAdapter implements ChannelAdapter {
187
191
  return;
188
192
  }
189
193
 
194
+ this.shouldReconnect = true;
190
195
  this.setStatus('connecting');
191
196
  this.resetBackoff();
192
197
 
@@ -256,6 +261,17 @@ export class WhatsAppAdapter implements ChannelAdapter {
256
261
  private handleConnectionUpdate(update: Partial<ConnectionState>): void {
257
262
  const { connection, lastDisconnect, qr } = update;
258
263
 
264
+ if (!this.shouldReconnect) {
265
+ if (connection === 'open') {
266
+ // If a manual disconnect happened mid-handshake, close immediately.
267
+ this.sock?.ws?.close();
268
+ this.setStatus('disconnected');
269
+ } else if (connection === 'close') {
270
+ this.setStatus('disconnected');
271
+ }
272
+ return;
273
+ }
274
+
259
275
  // Handle QR code for authentication
260
276
  if (qr) {
261
277
  this.currentQr = qr;
@@ -345,6 +361,14 @@ export class WhatsAppAdapter implements ChannelAdapter {
345
361
  const remoteJidNormalized = normalizeJid(remoteJid);
346
362
 
347
363
  if (remoteJidNormalized !== selfJidNormalized) {
364
+ const now = Date.now();
365
+ if (now - this.selfChatIgnoreLogAt > 30000) {
366
+ console.log(
367
+ `WhatsApp: Ignoring message from ${remoteJidNormalized} because self-chat mode is enabled. ` +
368
+ 'Disable self-chat mode to accept messages from other numbers.'
369
+ );
370
+ this.selfChatIgnoreLogAt = now;
371
+ }
348
372
  // Message is NOT in self-chat, silently ignore it
349
373
  return;
350
374
  }
@@ -430,6 +454,7 @@ export class WhatsAppAdapter implements ChannelAdapter {
430
454
  userId: senderE164 || participantJid || remoteJid,
431
455
  userName: msg.pushName || senderE164 || 'Unknown',
432
456
  chatId: remoteJid,
457
+ isGroup,
433
458
  text: body || (attachments.length === 0 ? this.extractMediaPlaceholder(msg.message) : '') || '',
434
459
  timestamp: messageTimestampMs ? new Date(messageTimestampMs) : new Date(),
435
460
  attachments: attachments.length > 0 ? attachments : undefined,
@@ -444,6 +469,7 @@ export class WhatsAppAdapter implements ChannelAdapter {
444
469
  * Disconnect from WhatsApp
445
470
  */
446
471
  async disconnect(): Promise<void> {
472
+ this.shouldReconnect = false;
447
473
  this.resetBackoff();
448
474
 
449
475
  // Clear timers
@@ -746,6 +772,37 @@ export class WhatsAppAdapter implements ChannelAdapter {
746
772
  this.qrCodeHandlers.push(handler);
747
773
  }
748
774
 
775
+ /**
776
+ * Update adapter configuration at runtime
777
+ */
778
+ updateConfig(config: ChannelConfig): void {
779
+ const next = config as Partial<WhatsAppConfig>;
780
+ const prevDedupEnabled = this.config.deduplicationEnabled !== false;
781
+ const prevSelfChat = this.config.selfChatMode === true;
782
+
783
+ this.config = {
784
+ ...this.config,
785
+ ...next,
786
+ };
787
+
788
+ // If self-chat was just enabled and read receipts weren't explicitly set, default to false.
789
+ if (!prevSelfChat && this.config.selfChatMode && next.sendReadReceipts === undefined) {
790
+ this.config.sendReadReceipts = false;
791
+ console.log('[WhatsApp] Self-chat mode enabled; defaulting sendReadReceipts to false.');
792
+ }
793
+
794
+ const nextDedupEnabled = this.config.deduplicationEnabled !== false;
795
+ if (nextDedupEnabled && !prevDedupEnabled) {
796
+ this.startDedupCleanup();
797
+ } else if (!nextDedupEnabled && prevDedupEnabled) {
798
+ if (this.dedupCleanupTimer) {
799
+ clearInterval(this.dedupCleanupTimer);
800
+ this.dedupCleanupTimer = undefined;
801
+ }
802
+ this.processedMessages.clear();
803
+ }
804
+ }
805
+
749
806
  /**
750
807
  * Get channel info
751
808
  */
@@ -839,6 +896,10 @@ export class WhatsAppAdapter implements ChannelAdapter {
839
896
  * Attempt reconnection with exponential backoff
840
897
  */
841
898
  private async attemptReconnection(): Promise<void> {
899
+ if (!this.shouldReconnect) {
900
+ return;
901
+ }
902
+
842
903
  if (this.isReconnecting) return;
843
904
 
844
905
  const config = this.DEFAULT_BACKOFF;
@@ -857,6 +918,11 @@ export class WhatsAppAdapter implements ChannelAdapter {
857
918
 
858
919
  this.backoffTimer = setTimeout(async () => {
859
920
  try {
921
+ if (!this.shouldReconnect) {
922
+ this.isReconnecting = false;
923
+ return;
924
+ }
925
+
860
926
  this.sock = null;
861
927
  this.isReconnecting = false;
862
928
  this.setStatus('disconnected');
@@ -5,14 +5,17 @@
5
5
  * Manages channel adapters, routing, and sessions.
6
6
  */
7
7
 
8
- import { BrowserWindow } from 'electron';
8
+ import { BrowserWindow, app } from 'electron';
9
9
  import Database from 'better-sqlite3';
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
10
12
  import { MessageRouter, RouterConfig } from './router';
11
13
  import { SecurityManager } from './security';
12
14
  import { SessionManager } from './session';
13
15
  import {
14
16
  ChannelAdapter,
15
17
  ChannelType,
18
+ ChannelConfig,
16
19
  TelegramConfig,
17
20
  DiscordConfig,
18
21
  SlackConfig,
@@ -84,6 +87,7 @@ export class ChannelGateway {
84
87
  private initialized = false;
85
88
  private agentDaemon?: AgentDaemon;
86
89
  private daemonListeners: Array<{ event: string; handler: (...args: any[]) => void }> = [];
90
+ private pendingCleanupInterval: ReturnType<typeof setInterval> | null = null;
87
91
 
88
92
  constructor(db: Database.Database, config: GatewayConfig = {}) {
89
93
  this.db = db;
@@ -116,6 +120,7 @@ export class ChannelGateway {
116
120
  agentName: settings.agentName || 'CoWork',
117
121
  userName: settings.relationship?.userName,
118
122
  personality: settings.activePersonality || 'professional',
123
+ persona: settings.activePersona,
119
124
  emojiUsage: settings.responseStyle?.emojiUsage || 'minimal',
120
125
  quirks: settings.quirks || DEFAULT_QUIRKS,
121
126
  };
@@ -255,6 +260,8 @@ export class ChannelGateway {
255
260
  await this.router.connectAll();
256
261
  }
257
262
 
263
+ this.startPendingCleanup();
264
+
258
265
  this.initialized = true;
259
266
  console.log('Channel Gateway initialized');
260
267
  }
@@ -279,10 +286,48 @@ export class ChannelGateway {
279
286
  }
280
287
 
281
288
  await this.router.disconnectAll();
289
+ this.stopPendingCleanup();
282
290
  this.initialized = false;
283
291
  console.log('Channel Gateway shutdown');
284
292
  }
285
293
 
294
+ private startPendingCleanup(): void {
295
+ if (this.pendingCleanupInterval) return;
296
+ // Run once at startup to clear any stale entries.
297
+ this.cleanupPendingUsers();
298
+ // Then run every 10 minutes.
299
+ this.pendingCleanupInterval = setInterval(() => {
300
+ this.cleanupPendingUsers();
301
+ }, 10 * 60 * 1000);
302
+ }
303
+
304
+ private stopPendingCleanup(): void {
305
+ if (this.pendingCleanupInterval) {
306
+ clearInterval(this.pendingCleanupInterval);
307
+ this.pendingCleanupInterval = null;
308
+ }
309
+ }
310
+
311
+ private cleanupPendingUsers(): void {
312
+ const channels = this.channelRepo.findAll();
313
+ for (const channel of channels) {
314
+ const removed = this.userRepo.deleteExpiredPending(channel.id);
315
+ if (removed > 0) {
316
+ this.emitUsersUpdated(channel);
317
+ }
318
+ }
319
+ }
320
+
321
+ private emitUsersUpdated(channel: Channel): void {
322
+ const mainWindow = this.router.getMainWindow();
323
+ if (mainWindow && !mainWindow.isDestroyed()) {
324
+ mainWindow.webContents.send('gateway:users-updated', {
325
+ channelId: channel.id,
326
+ channelType: channel.type,
327
+ });
328
+ }
329
+ }
330
+
286
331
  // Channel Management
287
332
 
288
333
  /**
@@ -401,6 +446,9 @@ export class ChannelGateway {
401
446
  throw new Error('WhatsApp channel already configured. Update or remove it first.');
402
447
  }
403
448
 
449
+ // Always clear any stale auth so a new QR is required for a new number.
450
+ this.clearWhatsAppAuthDir();
451
+
404
452
  // Create channel record
405
453
  const channel = this.channelRepo.create({
406
454
  type: 'whatsapp',
@@ -771,6 +819,16 @@ export class ChannelGateway {
771
819
  */
772
820
  updateChannel(channelId: string, updates: Partial<Channel>): void {
773
821
  this.channelRepo.update(channelId, updates);
822
+
823
+ if (updates.config === undefined) return;
824
+
825
+ const channel = this.channelRepo.findById(channelId);
826
+ if (!channel) return;
827
+
828
+ const adapter = this.router.getAdapter(channel.type as ChannelType);
829
+ if (adapter?.updateConfig) {
830
+ adapter.updateConfig(channel.config as ChannelConfig);
831
+ }
774
832
  }
775
833
 
776
834
  /**
@@ -896,6 +954,8 @@ export class ChannelGateway {
896
954
  const adapter = this.router.getAdapter('whatsapp') as WhatsAppAdapter | undefined;
897
955
  if (adapter) {
898
956
  await adapter.logout();
957
+ } else {
958
+ this.clearWhatsAppAuthDir();
899
959
  }
900
960
 
901
961
  const channel = this.channelRepo.findByType('whatsapp');
@@ -908,7 +968,21 @@ export class ChannelGateway {
908
968
  * Remove a channel
909
969
  */
910
970
  async removeChannel(channelId: string): Promise<void> {
911
- await this.disableChannel(channelId);
971
+ const channel = this.channelRepo.findById(channelId);
972
+ if (!channel) return;
973
+
974
+ if (channel.type === 'whatsapp') {
975
+ const adapter = this.router.getAdapter('whatsapp') as WhatsAppAdapter | undefined;
976
+ if (adapter) {
977
+ await adapter.logout();
978
+ } else {
979
+ const tempAdapter = this.createAdapterForChannel(channel) as WhatsAppAdapter;
980
+ await tempAdapter.logout();
981
+ }
982
+ this.clearWhatsAppAuthDir(channel);
983
+ } else {
984
+ await this.disableChannel(channelId);
985
+ }
912
986
 
913
987
  // Delete associated data first (to avoid foreign key constraint errors)
914
988
  this.messageRepo.deleteByChannelId(channelId);
@@ -1084,6 +1158,25 @@ export class ChannelGateway {
1084
1158
 
1085
1159
  // Private methods
1086
1160
 
1161
+ private resolveWhatsAppAuthDir(channel?: Channel): string {
1162
+ const configured = (channel?.config as { authDir?: string } | undefined)?.authDir;
1163
+ if (configured && configured.trim()) {
1164
+ return configured;
1165
+ }
1166
+ return path.join(app.getPath('userData'), 'whatsapp-auth');
1167
+ }
1168
+
1169
+ private clearWhatsAppAuthDir(channel?: Channel): void {
1170
+ try {
1171
+ const authDir = this.resolveWhatsAppAuthDir(channel);
1172
+ if (fs.existsSync(authDir)) {
1173
+ fs.rmSync(authDir, { recursive: true, force: true });
1174
+ }
1175
+ } catch (error) {
1176
+ console.error('Failed to clear WhatsApp auth directory:', error);
1177
+ }
1178
+ }
1179
+
1087
1180
  /**
1088
1181
  * Load and register channel adapters
1089
1182
  */