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.
- package/README.md +372 -10
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
- package/dist/electron/electron/agent/daemon.js +189 -13
- package/dist/electron/electron/agent/executor.js +895 -78
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +13 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/search/provider-factory.js +38 -2
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/file-tools.js +66 -3
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
- package/dist/electron/electron/agent/tools/image-tools.js +11 -1
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +548 -10
- package/dist/electron/electron/agent/tools/search-tools.js +28 -10
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/agents/agent-dispatch.js +63 -0
- package/dist/electron/electron/database/repositories.js +19 -5
- package/dist/electron/electron/database/schema.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
- package/dist/electron/electron/gateway/index.js +75 -1
- package/dist/electron/electron/gateway/router.js +209 -154
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +763 -267
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +2 -1
- package/dist/electron/electron/preload.js +78 -1
- package/dist/electron/electron/settings/appearance-manager.js +18 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +98 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +90 -1
- package/package.json +14 -3
- package/resources/skills/nano-banana-pro.json +4 -4
- package/resources/skills/openai-image-gen.json +3 -3
- package/resources/skills/scripts/gen.py +163 -0
- package/resources/skills/scripts/generate_image.py +91 -0
- package/src/electron/agent/custom-skill-loader.ts +34 -1
- package/src/electron/agent/daemon.ts +210 -14
- package/src/electron/agent/executor.ts +1124 -85
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
- package/src/electron/agent/llm/bedrock-provider.ts +62 -9
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +6 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +459 -6
- package/src/electron/agent/llm/types.ts +95 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/search/provider-factory.ts +43 -2
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +36 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/file-tools.ts +66 -3
- package/src/electron/agent/tools/gmail-tools.ts +240 -0
- package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/grep-tools.ts +97 -12
- package/src/electron/agent/tools/image-tools.ts +11 -1
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +794 -10
- package/src/electron/agent/tools/search-tools.ts +29 -11
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/agents/agent-dispatch.ts +79 -0
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/database/repositories.ts +58 -6
- package/src/electron/database/schema.ts +8 -0
- package/src/electron/gateway/channels/discord.ts +4 -0
- package/src/electron/gateway/channels/google-chat.ts +3 -0
- package/src/electron/gateway/channels/line.ts +3 -0
- package/src/electron/gateway/channels/matrix-client.ts +15 -0
- package/src/electron/gateway/channels/matrix.ts +31 -0
- package/src/electron/gateway/channels/mattermost.ts +3 -0
- package/src/electron/gateway/channels/signal.ts +3 -0
- package/src/electron/gateway/channels/slack.ts +9 -4
- package/src/electron/gateway/channels/teams.ts +4 -0
- package/src/electron/gateway/channels/telegram.ts +2 -0
- package/src/electron/gateway/channels/twitch.ts +2 -0
- package/src/electron/gateway/channels/types.ts +8 -0
- package/src/electron/gateway/channels/whatsapp.ts +66 -0
- package/src/electron/gateway/index.ts +95 -2
- package/src/electron/gateway/router.ts +231 -161
- package/src/electron/gateway/security.ts +21 -9
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +848 -292
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +7 -1
- package/src/electron/preload.ts +200 -5
- package/src/electron/settings/appearance-manager.ts +20 -2
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-workspace-manager.ts +59 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/gmail-api.ts +121 -0
- package/src/electron/utils/google-calendar-api.ts +115 -0
- package/src/electron/utils/google-workspace-api.ts +228 -0
- package/src/electron/utils/google-workspace-auth.ts +109 -0
- package/src/electron/utils/google-workspace-oauth.ts +232 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +128 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +119 -8
- package/src/renderer/components/ActivityFeedItem.tsx +34 -17
- package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
- package/src/renderer/components/AppearanceSettings.tsx +37 -2
- package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
- package/src/renderer/components/DiscordSettings.tsx +18 -7
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/EmailSettings.tsx +18 -7
- package/src/renderer/components/FileViewer.tsx +21 -13
- package/src/renderer/components/GoogleChatSettings.tsx +17 -7
- package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
- package/src/renderer/components/ImessageSettings.tsx +22 -11
- package/src/renderer/components/LineIcons.tsx +376 -0
- package/src/renderer/components/LineSettings.tsx +18 -7
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +740 -76
- package/src/renderer/components/MatrixSettings.tsx +18 -7
- package/src/renderer/components/MattermostSettings.tsx +18 -7
- package/src/renderer/components/NodesSettings.tsx +58 -99
- package/src/renderer/components/NotificationPanel.tsx +25 -11
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/RightPanel.tsx +141 -28
- package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
- package/src/renderer/components/SearchSettings.tsx +118 -114
- package/src/renderer/components/Settings.tsx +1425 -651
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +94 -19
- package/src/renderer/components/SignalSettings.tsx +18 -7
- package/src/renderer/components/SkillHubBrowser.tsx +144 -185
- package/src/renderer/components/SlackSettings.tsx +18 -7
- package/src/renderer/components/TaskQuickActions.tsx +11 -6
- package/src/renderer/components/TaskTimeline.tsx +58 -26
- package/src/renderer/components/TeamsSettings.tsx +18 -7
- package/src/renderer/components/TelegramSettings.tsx +18 -7
- package/src/renderer/components/ThemeIcon.tsx +16 -0
- package/src/renderer/components/TwitchSettings.tsx +18 -7
- package/src/renderer/components/VoiceSettings.tsx +30 -74
- package/src/renderer/components/WhatsAppSettings.tsx +48 -37
- package/src/renderer/components/WorkingStateHistory.tsx +7 -5
- package/src/renderer/components/WorkspaceSelector.tsx +42 -13
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +2333 -209
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- 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
|
|
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 (
|
|
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
|
-
|
|
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
|
*/
|