cowork-os 0.3.21 → 0.3.23

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 (170) hide show
  1. package/README.md +293 -6
  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/daemon.js +25 -0
  48. package/dist/electron/electron/agent/executor.js +181 -26
  49. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  50. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  51. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  52. package/dist/electron/electron/agent/llm/index.js +11 -1
  53. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  54. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  55. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  56. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  57. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  58. package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
  59. package/dist/electron/electron/agent/llm/types.js +66 -1
  60. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  61. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  62. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  63. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  64. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  65. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  66. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  67. package/dist/electron/electron/agent/tools/registry.js +541 -0
  68. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  69. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  70. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  71. package/dist/electron/electron/gateway/index.js +1 -0
  72. package/dist/electron/electron/gateway/router.js +123 -143
  73. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  74. package/dist/electron/electron/ipc/handlers.js +627 -158
  75. package/dist/electron/electron/main.js +63 -0
  76. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  77. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  78. package/dist/electron/electron/memory/MemoryService.js +1 -1
  79. package/dist/electron/electron/preload.js +74 -1
  80. package/dist/electron/electron/settings/box-manager.js +54 -0
  81. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  82. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  83. package/dist/electron/electron/settings/notion-manager.js +56 -0
  84. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  85. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  86. package/dist/electron/electron/utils/box-api.js +153 -0
  87. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  88. package/dist/electron/electron/utils/env-migration.js +19 -0
  89. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  90. package/dist/electron/electron/utils/notion-api.js +103 -0
  91. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  92. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  93. package/dist/electron/electron/utils/validation.js +82 -3
  94. package/dist/electron/electron/utils/x-cli.js +1 -1
  95. package/dist/electron/shared/channelMessages.js +284 -3
  96. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  97. package/dist/electron/shared/types.js +88 -1
  98. package/package.json +12 -2
  99. package/src/electron/agent/executor.ts +205 -28
  100. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  101. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  102. package/src/electron/agent/llm/groq-provider.ts +39 -0
  103. package/src/electron/agent/llm/index.ts +5 -0
  104. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  105. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  106. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  107. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  108. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  109. package/src/electron/agent/llm/provider-factory.ts +414 -6
  110. package/src/electron/agent/llm/types.ts +90 -1
  111. package/src/electron/agent/llm/xai-provider.ts +39 -0
  112. package/src/electron/agent/tools/box-tools.ts +239 -0
  113. package/src/electron/agent/tools/builtin-settings.ts +34 -0
  114. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  115. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  116. package/src/electron/agent/tools/notion-tools.ts +330 -0
  117. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  118. package/src/electron/agent/tools/registry.ts +565 -0
  119. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  120. package/src/electron/agent/tools/shell-tools.ts +11 -3
  121. package/src/electron/agent/tools/x-tools.ts +1 -1
  122. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  123. package/src/electron/gateway/index.ts +1 -0
  124. package/src/electron/gateway/router.ts +134 -149
  125. package/src/electron/ipc/canvas-handlers.ts +10 -0
  126. package/src/electron/ipc/handlers.ts +673 -153
  127. package/src/electron/main.ts +35 -0
  128. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  129. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  130. package/src/electron/memory/MemoryService.ts +5 -1
  131. package/src/electron/preload.ts +167 -4
  132. package/src/electron/settings/box-manager.ts +58 -0
  133. package/src/electron/settings/dropbox-manager.ts +58 -0
  134. package/src/electron/settings/google-drive-manager.ts +58 -0
  135. package/src/electron/settings/notion-manager.ts +60 -0
  136. package/src/electron/settings/onedrive-manager.ts +58 -0
  137. package/src/electron/settings/sharepoint-manager.ts +58 -0
  138. package/src/electron/utils/box-api.ts +184 -0
  139. package/src/electron/utils/dropbox-api.ts +171 -0
  140. package/src/electron/utils/env-migration.ts +22 -0
  141. package/src/electron/utils/google-drive-api.ts +183 -0
  142. package/src/electron/utils/notion-api.ts +126 -0
  143. package/src/electron/utils/onedrive-api.ts +137 -0
  144. package/src/electron/utils/sharepoint-api.ts +132 -0
  145. package/src/electron/utils/validation.ts +102 -1
  146. package/src/electron/utils/x-cli.ts +1 -1
  147. package/src/renderer/App.tsx +20 -2
  148. package/src/renderer/components/BoxSettings.tsx +203 -0
  149. package/src/renderer/components/BrowserView.tsx +101 -0
  150. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  151. package/src/renderer/components/CanvasPreview.tsx +68 -1
  152. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  153. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  154. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  155. package/src/renderer/components/DropboxSettings.tsx +202 -0
  156. package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
  157. package/src/renderer/components/MCPSettings.tsx +56 -0
  158. package/src/renderer/components/MainContent.tsx +270 -34
  159. package/src/renderer/components/NotionSettings.tsx +231 -0
  160. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  161. package/src/renderer/components/OnboardingModal.tsx +70 -1
  162. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  163. package/src/renderer/components/Settings.tsx +611 -8
  164. package/src/renderer/components/SharePointSettings.tsx +224 -0
  165. package/src/renderer/components/Sidebar.tsx +25 -9
  166. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  167. package/src/renderer/styles/index.css +438 -25
  168. package/src/shared/channelMessages.ts +367 -4
  169. package/src/shared/llm-provider-catalog.ts +217 -0
  170. package/src/shared/types.ts +226 -1
@@ -44,6 +44,7 @@ const path = __importStar(require("path"));
44
44
  const fs = __importStar(require("fs/promises"));
45
45
  const fsSync = __importStar(require("fs"));
46
46
  const mammoth_1 = __importDefault(require("mammoth"));
47
+ const mime_types_1 = __importDefault(require("mime-types"));
47
48
  // eslint-disable-next-line @typescript-eslint/no-require-imports
48
49
  const pdfParseModule = require('pdf-parse');
49
50
  // Handle both ESM default export and CommonJS module.exports
@@ -57,6 +58,7 @@ const TaskLabelRepository_1 = require("../database/TaskLabelRepository");
57
58
  const WorkingStateRepository_1 = require("../agents/WorkingStateRepository");
58
59
  const context_policy_1 = require("../gateway/context-policy");
59
60
  const types_1 = require("../../shared/types");
61
+ const llm_provider_catalog_1 = require("../../shared/llm-provider-catalog");
60
62
  const os = __importStar(require("os"));
61
63
  const llm_1 = require("../agent/llm");
62
64
  const search_1 = require("../agent/search");
@@ -66,6 +68,19 @@ const validation_1 = require("../utils/validation");
66
68
  const guardrail_manager_1 = require("../guardrails/guardrail-manager");
67
69
  const appearance_manager_1 = require("../settings/appearance-manager");
68
70
  const personality_manager_1 = require("../settings/personality-manager");
71
+ const notion_manager_1 = require("../settings/notion-manager");
72
+ const notion_api_1 = require("../utils/notion-api");
73
+ const box_manager_1 = require("../settings/box-manager");
74
+ const onedrive_manager_1 = require("../settings/onedrive-manager");
75
+ const google_drive_manager_1 = require("../settings/google-drive-manager");
76
+ const dropbox_manager_1 = require("../settings/dropbox-manager");
77
+ const sharepoint_manager_1 = require("../settings/sharepoint-manager");
78
+ const box_api_1 = require("../utils/box-api");
79
+ const onedrive_api_1 = require("../utils/onedrive-api");
80
+ const google_drive_api_1 = require("../utils/google-drive-api");
81
+ const dropbox_api_1 = require("../utils/dropbox-api");
82
+ const sharepoint_api_1 = require("../utils/sharepoint-api");
83
+ const connector_oauth_1 = require("../mcp/oauth/connector-oauth");
69
84
  const normalizeMentionToken = (value) => value.toLowerCase().replace(/[^a-z0-9]/g, '');
70
85
  const buildAgentMentionIndex = (roles) => {
71
86
  const index = new Map();
@@ -124,7 +139,8 @@ const scoreAgentForTask = (role, text) => {
124
139
  }
125
140
  return score;
126
141
  };
127
- const selectBestAgentsForTask = (text, roles) => {
142
+ const MAX_AUTO_AGENTS = 4;
143
+ const selectBestAgentsForTask = (text, roles, maxAgents = MAX_AUTO_AGENTS) => {
128
144
  if (roles.length === 0)
129
145
  return roles;
130
146
  const scored = roles
@@ -140,24 +156,23 @@ const selectBestAgentsForTask = (text, roles) => {
140
156
  const threshold = Math.max(1, maxScore - 2);
141
157
  const selected = withScore
142
158
  .filter((entry) => entry.score >= threshold)
143
- .slice(0, 4)
159
+ .slice(0, maxAgents)
144
160
  .map((entry) => entry.role);
145
- return selected.length > 0 ? selected : withScore.slice(0, 3).map((entry) => entry.role);
161
+ return selected.length > 0 ? selected : withScore.slice(0, maxAgents).map((entry) => entry.role);
146
162
  }
147
163
  const leads = roles
148
164
  .filter((role) => role.autonomyLevel === 'lead')
149
165
  .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
150
166
  if (leads.length > 0) {
151
- return leads.slice(0, 3);
167
+ return leads.slice(0, maxAgents);
152
168
  }
153
- return roles.slice(0, Math.min(3, roles.length));
169
+ return roles.slice(0, Math.min(maxAgents, roles.length));
154
170
  };
155
171
  const extractMentionedRoles = (text, roles) => {
156
172
  const normalizedText = text.toLowerCase();
157
- const useSmartSelection = /\B@everybody\b/.test(normalizedText);
158
- if (/\B@all\b/.test(normalizedText) || /\B@everyone\b/.test(normalizedText)) {
159
- return roles;
160
- }
173
+ const useSmartSelection = /\B@everybody\b/.test(normalizedText) ||
174
+ /\B@all\b/.test(normalizedText) ||
175
+ /\B@everyone\b/.test(normalizedText);
161
176
  const index = buildAgentMentionIndex(roles);
162
177
  const matches = new Map();
163
178
  const regex = /@([a-zA-Z0-9][a-zA-Z0-9 _-]{0,50})/g;
@@ -172,11 +187,15 @@ const extractMentionedRoles = (text, roles) => {
172
187
  }
173
188
  if (matches.size > 0) {
174
189
  if (useSmartSelection) {
175
- const selected = selectBestAgentsForTask(text, roles);
176
190
  const merged = new Map();
177
- selected.forEach((role) => merged.set(role.id, role));
178
191
  matches.forEach((role) => merged.set(role.id, role));
179
- return Array.from(merged.values());
192
+ const selected = selectBestAgentsForTask(text, roles, MAX_AUTO_AGENTS);
193
+ selected.forEach((role) => {
194
+ if (merged.size < MAX_AUTO_AGENTS) {
195
+ merged.set(role.id, role);
196
+ }
197
+ });
198
+ return Array.from(merged.values()).slice(0, MAX_AUTO_AGENTS);
180
199
  }
181
200
  return Array.from(matches.values());
182
201
  }
@@ -189,7 +208,7 @@ const extractMentionedRoles = (text, roles) => {
189
208
  }
190
209
  });
191
210
  if (useSmartSelection) {
192
- return selectBestAgentsForTask(text, roles);
211
+ return selectBestAgentsForTask(text, roles, MAX_AUTO_AGENTS);
193
212
  }
194
213
  return Array.from(matches.values());
195
214
  };
@@ -263,6 +282,7 @@ const voice_settings_manager_1 = require("../voice/voice-settings-manager");
263
282
  const VoiceService_1 = require("../voice/VoiceService");
264
283
  // Global notification service instance
265
284
  let notificationService = null;
285
+ const resolveCustomProviderId = (providerType) => providerType === 'kimi-coding' ? 'kimi-code' : providerType;
266
286
  /**
267
287
  * Get the notification service instance
268
288
  */
@@ -286,6 +306,9 @@ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_OLLAMA_MODELS,
286
306
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_GEMINI_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
287
307
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
288
308
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_BEDROCK_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
309
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_GROQ_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
310
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_XAI_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
311
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_KIMI_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
289
312
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.SEARCH_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
290
313
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.SEARCH_TEST_PROVIDER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
291
314
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.GATEWAY_ADD_CHANNEL, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
@@ -543,6 +566,76 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
543
566
  return { success: false, error: `Failed to read file: ${error.message}` };
544
567
  }
545
568
  });
569
+ // File import handler - copy selected files into the workspace for attachment use
570
+ electron_1.ipcMain.handle('file:importToWorkspace', async (_, data) => {
571
+ const validated = (0, validation_1.validateInput)(validation_1.FileImportSchema, data, 'file import');
572
+ const workspace = workspaceRepo.findById(validated.workspaceId);
573
+ if (!workspace) {
574
+ throw new Error(`Workspace not found: ${validated.workspaceId}`);
575
+ }
576
+ if (!workspace.permissions.write) {
577
+ throw new Error('Write permission not granted for workspace');
578
+ }
579
+ const sanitizeFileName = (fileName) => {
580
+ const sanitized = fileName.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_').trim();
581
+ return sanitized.length > 0 ? sanitized : 'file';
582
+ };
583
+ const ensureUniqueName = (dir, baseName, usedNames) => {
584
+ const ext = path.extname(baseName);
585
+ const stem = path.basename(baseName, ext);
586
+ let candidate = baseName;
587
+ let counter = 1;
588
+ while (usedNames.has(candidate) || fsSync.existsSync(path.join(dir, candidate))) {
589
+ candidate = `${stem}-${counter}${ext}`;
590
+ counter += 1;
591
+ }
592
+ usedNames.add(candidate);
593
+ return candidate;
594
+ };
595
+ let uploadRoot = null;
596
+ const usedNames = new Set();
597
+ const ensureUploadRoot = async () => {
598
+ if (uploadRoot)
599
+ return uploadRoot;
600
+ uploadRoot = path.join(workspace.path, '.cowork', 'uploads', `${Date.now()}`);
601
+ await fs.mkdir(uploadRoot, { recursive: true });
602
+ return uploadRoot;
603
+ };
604
+ const results = [];
605
+ for (const filePath of validated.files) {
606
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
607
+ const stats = await fs.stat(absolutePath);
608
+ if (!stats.isFile()) {
609
+ throw new Error(`Not a file: ${filePath}`);
610
+ }
611
+ const sizeCheck = guardrail_manager_1.GuardrailManager.isFileSizeExceeded(stats.size);
612
+ if (sizeCheck.exceeded) {
613
+ throw new Error(`File "${path.basename(filePath)}" is ${sizeCheck.sizeMB.toFixed(1)}MB and exceeds the ${sizeCheck.limitMB}MB limit.`);
614
+ }
615
+ const mimeType = (mime_types_1.default.lookup(absolutePath) || undefined);
616
+ if (isPathWithinWorkspace(absolutePath, workspace.path)) {
617
+ results.push({
618
+ relativePath: path.relative(workspace.path, absolutePath),
619
+ fileName: path.basename(absolutePath),
620
+ size: stats.size,
621
+ mimeType,
622
+ });
623
+ continue;
624
+ }
625
+ const safeName = sanitizeFileName(path.basename(absolutePath));
626
+ const targetRoot = await ensureUploadRoot();
627
+ const uniqueName = ensureUniqueName(targetRoot, safeName, usedNames);
628
+ const destination = path.join(targetRoot, uniqueName);
629
+ await fs.copyFile(absolutePath, destination);
630
+ results.push({
631
+ relativePath: path.relative(workspace.path, destination),
632
+ fileName: uniqueName,
633
+ size: stats.size,
634
+ mimeType,
635
+ });
636
+ }
637
+ return results;
638
+ });
546
639
  // Workspace handlers
547
640
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_CREATE, async (_, data) => {
548
641
  const validated = (0, validation_1.validateInput)(validation_1.WorkspaceCreateSchema, data, 'workspace');
@@ -612,12 +705,14 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
612
705
  try {
613
706
  const activeRoles = agentRoleRepo.findAll(false).filter((role) => role.isActive);
614
707
  const mentionedRoles = extractMentionedRoles(`${title}\n${prompt}`, activeRoles);
615
- const dispatchRoles = mentionedRoles.length > 0 ? mentionedRoles : activeRoles;
708
+ const dispatchRoles = mentionedRoles;
616
709
  if (dispatchRoles.length > 0) {
617
- taskRepo.update(task.id, {
710
+ const taskUpdate = {
618
711
  mentionedAgentRoleIds: dispatchRoles.map((role) => role.id),
619
- });
620
- for (const role of dispatchRoles) {
712
+ };
713
+ taskRepo.update(task.id, taskUpdate);
714
+ // Parallelize child task creation for better performance
715
+ const dispatchPromises = dispatchRoles.map(async (role) => {
621
716
  const childPrompt = buildAgentDispatchPrompt(role, task);
622
717
  const childTask = await agentDaemon.createChildTask({
623
718
  title: `@${role.displayName}: ${task.title}`,
@@ -631,10 +726,11 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
631
726
  retainMemory: false,
632
727
  },
633
728
  });
634
- taskRepo.update(childTask.id, {
729
+ const childUpdate = {
635
730
  assignedAgentRoleId: role.id,
636
731
  boardColumn: 'todo',
637
- });
732
+ };
733
+ taskRepo.update(childTask.id, childUpdate);
638
734
  const dispatchActivity = activityRepo.create({
639
735
  workspaceId: task.workspaceId,
640
736
  taskId: task.id,
@@ -664,11 +760,24 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
664
760
  metadata: { mentionId: mention.id, mentionType: mention.mentionType },
665
761
  });
666
762
  getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity: mentionActivity });
667
- }
763
+ return { role, childTask };
764
+ });
765
+ await Promise.all(dispatchPromises);
668
766
  }
669
767
  }
670
768
  catch (error) {
769
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
671
770
  console.error('Failed to dispatch to mentioned agents:', error);
771
+ // Notify user of dispatch failure via activity feed
772
+ const errorActivity = activityRepo.create({
773
+ workspaceId: task.workspaceId,
774
+ taskId: task.id,
775
+ actorType: 'system',
776
+ activityType: 'error',
777
+ title: 'Agent dispatch failed',
778
+ description: `Failed to dispatch task to mentioned agents: ${errorMessage}`,
779
+ });
780
+ getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity: errorActivity });
672
781
  }
673
782
  return task;
674
783
  });
@@ -881,12 +990,19 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
881
990
  gemini: validated.gemini,
882
991
  openrouter: validated.openrouter,
883
992
  openai: openaiSettings,
993
+ groq: validated.groq,
994
+ xai: validated.xai,
995
+ kimi: validated.kimi,
996
+ customProviders: validated.customProviders ?? existingSettings.customProviders,
884
997
  // Preserve cached models from existing settings
885
998
  cachedGeminiModels: existingSettings.cachedGeminiModels,
886
999
  cachedOpenRouterModels: existingSettings.cachedOpenRouterModels,
887
1000
  cachedOllamaModels: existingSettings.cachedOllamaModels,
888
1001
  cachedBedrockModels: existingSettings.cachedBedrockModels,
889
1002
  cachedOpenAIModels: existingSettings.cachedOpenAIModels,
1003
+ cachedGroqModels: existingSettings.cachedGroqModels,
1004
+ cachedXaiModels: existingSettings.cachedXaiModels,
1005
+ cachedKimiModels: existingSettings.cachedKimiModels,
890
1006
  });
891
1007
  // Clear cache so next task uses new settings
892
1008
  llm_1.LLMProviderFactory.clearCache();
@@ -902,9 +1018,11 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
902
1018
  openaiAccessToken = settings.openai?.accessToken;
903
1019
  openaiRefreshToken = settings.openai?.refreshToken;
904
1020
  }
1021
+ const resolvedProviderType = resolveCustomProviderId(config.providerType);
1022
+ const customProviderConfig = config.customProviders?.[resolvedProviderType] || config.customProviders?.[config.providerType];
905
1023
  const providerConfig = {
906
1024
  type: config.providerType,
907
- model: llm_1.LLMProviderFactory.getModelId(config.modelKey, config.providerType, config.ollama?.model, config.gemini?.model, config.openrouter?.model, config.openai?.model),
1025
+ model: llm_1.LLMProviderFactory.getModelId(config.modelKey, config.providerType, config.ollama?.model, config.gemini?.model, config.openrouter?.model, config.openai?.model, config.groq?.model, config.xai?.model, config.kimi?.model, config.customProviders),
908
1026
  anthropicApiKey: config.anthropic?.apiKey,
909
1027
  awsRegion: config.bedrock?.region,
910
1028
  awsAccessKeyId: config.bedrock?.accessKeyId,
@@ -915,9 +1033,18 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
915
1033
  ollamaApiKey: config.ollama?.apiKey,
916
1034
  geminiApiKey: config.gemini?.apiKey,
917
1035
  openrouterApiKey: config.openrouter?.apiKey,
1036
+ openrouterBaseUrl: config.openrouter?.baseUrl,
918
1037
  openaiApiKey: config.openai?.apiKey,
919
1038
  openaiAccessToken: openaiAccessToken,
920
1039
  openaiRefreshToken: openaiRefreshToken,
1040
+ groqApiKey: config.groq?.apiKey,
1041
+ groqBaseUrl: config.groq?.baseUrl,
1042
+ xaiApiKey: config.xai?.apiKey,
1043
+ xaiBaseUrl: config.xai?.baseUrl,
1044
+ kimiApiKey: config.kimi?.apiKey,
1045
+ kimiBaseUrl: config.kimi?.baseUrl,
1046
+ providerApiKey: customProviderConfig?.apiKey,
1047
+ providerBaseUrl: customProviderConfig?.baseUrl,
921
1048
  };
922
1049
  return llm_1.LLMProviderFactory.testProvider(providerConfig);
923
1050
  });
@@ -936,132 +1063,213 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
936
1063
  // Get models based on the current provider type
937
1064
  let models = [];
938
1065
  let currentModel = settings.modelKey;
939
- switch (settings.providerType) {
940
- case 'anthropic':
941
- case 'bedrock':
942
- // Use Anthropic/Bedrock models from MODELS
943
- models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
944
- key,
945
- displayName: value.displayName,
946
- description: key.includes('opus') ? 'Most capable for complex work' :
947
- key.includes('sonnet') ? 'Balanced performance and speed' :
948
- 'Fast and efficient',
949
- }));
950
- break;
951
- case 'gemini': {
952
- // For Gemini, use the specific model from settings (full model ID)
953
- currentModel = settings.gemini?.model || 'gemini-2.0-flash';
954
- // Use cached models if available, otherwise fall back to static list
955
- const cachedGemini = llm_1.LLMProviderFactory.getCachedModels('gemini');
956
- if (cachedGemini && cachedGemini.length > 0) {
957
- models = cachedGemini;
958
- }
959
- else {
960
- // Fall back to static models
961
- models = Object.values(llm_1.GEMINI_MODELS).map((value) => ({
962
- key: value.id,
1066
+ const resolvedProviderType = resolveCustomProviderId(settings.providerType);
1067
+ const customEntry = llm_provider_catalog_1.CUSTOM_PROVIDER_MAP.get(resolvedProviderType);
1068
+ if (customEntry) {
1069
+ const customConfig = settings.customProviders?.[resolvedProviderType] || settings.customProviders?.[settings.providerType];
1070
+ currentModel = customConfig?.model || customEntry.defaultModel;
1071
+ models = [
1072
+ {
1073
+ key: currentModel,
1074
+ displayName: currentModel,
1075
+ description: customEntry.description || `${customEntry.name} model`,
1076
+ },
1077
+ ];
1078
+ }
1079
+ else {
1080
+ switch (settings.providerType) {
1081
+ case 'anthropic':
1082
+ case 'bedrock':
1083
+ // Use Anthropic/Bedrock models from MODELS
1084
+ models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
1085
+ key,
963
1086
  displayName: value.displayName,
964
- description: value.description,
1087
+ description: key.includes('opus') ? 'Most capable for complex work' :
1088
+ key.includes('sonnet') ? 'Balanced performance and speed' :
1089
+ 'Fast and efficient',
965
1090
  }));
1091
+ break;
1092
+ case 'gemini': {
1093
+ // For Gemini, use the specific model from settings (full model ID)
1094
+ currentModel = settings.gemini?.model || 'gemini-2.0-flash';
1095
+ // Use cached models if available, otherwise fall back to static list
1096
+ const cachedGemini = llm_1.LLMProviderFactory.getCachedModels('gemini');
1097
+ if (cachedGemini && cachedGemini.length > 0) {
1098
+ models = cachedGemini;
1099
+ }
1100
+ else {
1101
+ // Fall back to static models
1102
+ models = Object.values(llm_1.GEMINI_MODELS).map((value) => ({
1103
+ key: value.id,
1104
+ displayName: value.displayName,
1105
+ description: value.description,
1106
+ }));
1107
+ }
1108
+ // Ensure the currently selected model is in the list
1109
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1110
+ models.unshift({
1111
+ key: currentModel,
1112
+ displayName: currentModel,
1113
+ description: 'Selected model',
1114
+ });
1115
+ }
1116
+ break;
966
1117
  }
967
- // Ensure the currently selected model is in the list
968
- if (currentModel && !models.some(m => m.key === currentModel)) {
969
- models.unshift({
970
- key: currentModel,
971
- displayName: currentModel,
972
- description: 'Selected model',
973
- });
1118
+ case 'openrouter': {
1119
+ // For OpenRouter, use the specific model from settings (full model ID)
1120
+ currentModel = settings.openrouter?.model || 'anthropic/claude-3.5-sonnet';
1121
+ // Use cached models if available, otherwise fall back to static list
1122
+ const cachedOpenRouter = llm_1.LLMProviderFactory.getCachedModels('openrouter');
1123
+ if (cachedOpenRouter && cachedOpenRouter.length > 0) {
1124
+ models = cachedOpenRouter;
1125
+ }
1126
+ else {
1127
+ // Fall back to static models
1128
+ models = Object.values(llm_1.OPENROUTER_MODELS).map((value) => ({
1129
+ key: value.id,
1130
+ displayName: value.displayName,
1131
+ description: value.description,
1132
+ }));
1133
+ }
1134
+ // Ensure the currently selected model is in the list
1135
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1136
+ models.unshift({
1137
+ key: currentModel,
1138
+ displayName: currentModel,
1139
+ description: 'Selected model',
1140
+ });
1141
+ }
1142
+ break;
974
1143
  }
975
- break;
976
- }
977
- case 'openrouter': {
978
- // For OpenRouter, use the specific model from settings (full model ID)
979
- currentModel = settings.openrouter?.model || 'anthropic/claude-3.5-sonnet';
980
- // Use cached models if available, otherwise fall back to static list
981
- const cachedOpenRouter = llm_1.LLMProviderFactory.getCachedModels('openrouter');
982
- if (cachedOpenRouter && cachedOpenRouter.length > 0) {
983
- models = cachedOpenRouter;
1144
+ case 'ollama': {
1145
+ // For Ollama, use the specific model from settings
1146
+ currentModel = settings.ollama?.model || 'llama3.2';
1147
+ // Use cached models if available, otherwise fall back to static list
1148
+ const cachedOllama = llm_1.LLMProviderFactory.getCachedModels('ollama');
1149
+ if (cachedOllama && cachedOllama.length > 0) {
1150
+ models = cachedOllama;
1151
+ }
1152
+ else {
1153
+ // Fall back to static models
1154
+ models = Object.entries(llm_1.OLLAMA_MODELS).map(([key, value]) => ({
1155
+ key,
1156
+ displayName: value.displayName,
1157
+ description: `${value.size} parameter model`,
1158
+ }));
1159
+ }
1160
+ // Ensure the currently selected model is in the list
1161
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1162
+ models.unshift({
1163
+ key: currentModel,
1164
+ displayName: currentModel,
1165
+ description: 'Selected model',
1166
+ });
1167
+ }
1168
+ break;
984
1169
  }
985
- else {
986
- // Fall back to static models
987
- models = Object.values(llm_1.OPENROUTER_MODELS).map((value) => ({
988
- key: value.id,
989
- displayName: value.displayName,
990
- description: value.description,
991
- }));
1170
+ case 'openai': {
1171
+ // For OpenAI, use the specific model from settings
1172
+ currentModel = settings.openai?.model || 'gpt-4o-mini';
1173
+ // Use cached models if available, otherwise fall back to static list
1174
+ const cachedOpenAI = llm_1.LLMProviderFactory.getCachedModels('openai');
1175
+ if (cachedOpenAI && cachedOpenAI.length > 0) {
1176
+ models = cachedOpenAI;
1177
+ }
1178
+ else {
1179
+ // Fall back to static models
1180
+ models = [
1181
+ { key: 'gpt-4o', displayName: 'GPT-4o', description: 'Most capable model for complex tasks' },
1182
+ { key: 'gpt-4o-mini', displayName: 'GPT-4o Mini', description: 'Fast and affordable for most tasks' },
1183
+ { key: 'gpt-4-turbo', displayName: 'GPT-4 Turbo', description: 'Previous generation flagship' },
1184
+ { key: 'gpt-3.5-turbo', displayName: 'GPT-3.5 Turbo', description: 'Fast and cost-effective' },
1185
+ { key: 'o1', displayName: 'o1', description: 'Advanced reasoning model' },
1186
+ { key: 'o1-mini', displayName: 'o1 Mini', description: 'Fast reasoning model' },
1187
+ ];
1188
+ }
1189
+ // Ensure the currently selected model is in the list
1190
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1191
+ models.unshift({
1192
+ key: currentModel,
1193
+ displayName: currentModel,
1194
+ description: 'Selected model',
1195
+ });
1196
+ }
1197
+ break;
992
1198
  }
993
- // Ensure the currently selected model is in the list
994
- if (currentModel && !models.some(m => m.key === currentModel)) {
995
- models.unshift({
996
- key: currentModel,
997
- displayName: currentModel,
998
- description: 'Selected model',
999
- });
1199
+ case 'groq': {
1200
+ currentModel = settings.groq?.model || 'llama-3.1-8b-instant';
1201
+ const cachedGroq = llm_1.LLMProviderFactory.getCachedModels('groq');
1202
+ if (cachedGroq && cachedGroq.length > 0) {
1203
+ models = cachedGroq;
1204
+ }
1205
+ else {
1206
+ models = Object.values(llm_1.GROQ_MODELS).map((value) => ({
1207
+ key: value.id,
1208
+ displayName: value.displayName,
1209
+ description: value.description,
1210
+ }));
1211
+ }
1212
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1213
+ models.unshift({
1214
+ key: currentModel,
1215
+ displayName: currentModel,
1216
+ description: 'Selected model',
1217
+ });
1218
+ }
1219
+ break;
1000
1220
  }
1001
- break;
1002
- }
1003
- case 'ollama': {
1004
- // For Ollama, use the specific model from settings
1005
- currentModel = settings.ollama?.model || 'llama3.2';
1006
- // Use cached models if available, otherwise fall back to static list
1007
- const cachedOllama = llm_1.LLMProviderFactory.getCachedModels('ollama');
1008
- if (cachedOllama && cachedOllama.length > 0) {
1009
- models = cachedOllama;
1221
+ case 'xai': {
1222
+ currentModel = settings.xai?.model || 'grok-4-fast-non-reasoning';
1223
+ const cachedXai = llm_1.LLMProviderFactory.getCachedModels('xai');
1224
+ if (cachedXai && cachedXai.length > 0) {
1225
+ models = cachedXai;
1226
+ }
1227
+ else {
1228
+ models = Object.values(llm_1.XAI_MODELS).map((value) => ({
1229
+ key: value.id,
1230
+ displayName: value.displayName,
1231
+ description: value.description,
1232
+ }));
1233
+ }
1234
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1235
+ models.unshift({
1236
+ key: currentModel,
1237
+ displayName: currentModel,
1238
+ description: 'Selected model',
1239
+ });
1240
+ }
1241
+ break;
1010
1242
  }
1011
- else {
1012
- // Fall back to static models
1013
- models = Object.entries(llm_1.OLLAMA_MODELS).map(([key, value]) => ({
1243
+ case 'kimi': {
1244
+ currentModel = settings.kimi?.model || 'kimi-k2.5';
1245
+ const cachedKimi = llm_1.LLMProviderFactory.getCachedModels('kimi');
1246
+ if (cachedKimi && cachedKimi.length > 0) {
1247
+ models = cachedKimi;
1248
+ }
1249
+ else {
1250
+ models = Object.values(llm_1.KIMI_MODELS).map((value) => ({
1251
+ key: value.id,
1252
+ displayName: value.displayName,
1253
+ description: value.description,
1254
+ }));
1255
+ }
1256
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1257
+ models.unshift({
1258
+ key: currentModel,
1259
+ displayName: currentModel,
1260
+ description: 'Selected model',
1261
+ });
1262
+ }
1263
+ break;
1264
+ }
1265
+ default:
1266
+ // Fallback to Anthropic models
1267
+ models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
1014
1268
  key,
1015
1269
  displayName: value.displayName,
1016
- description: `${value.size} parameter model`,
1270
+ description: 'Claude model',
1017
1271
  }));
1018
- }
1019
- // Ensure the currently selected model is in the list
1020
- if (currentModel && !models.some(m => m.key === currentModel)) {
1021
- models.unshift({
1022
- key: currentModel,
1023
- displayName: currentModel,
1024
- description: 'Selected model',
1025
- });
1026
- }
1027
- break;
1028
1272
  }
1029
- case 'openai': {
1030
- // For OpenAI, use the specific model from settings
1031
- currentModel = settings.openai?.model || 'gpt-4o-mini';
1032
- // Use cached models if available, otherwise fall back to static list
1033
- const cachedOpenAI = llm_1.LLMProviderFactory.getCachedModels('openai');
1034
- if (cachedOpenAI && cachedOpenAI.length > 0) {
1035
- models = cachedOpenAI;
1036
- }
1037
- else {
1038
- // Fall back to static models
1039
- models = [
1040
- { key: 'gpt-4o', displayName: 'GPT-4o', description: 'Most capable model for complex tasks' },
1041
- { key: 'gpt-4o-mini', displayName: 'GPT-4o Mini', description: 'Fast and affordable for most tasks' },
1042
- { key: 'gpt-4-turbo', displayName: 'GPT-4 Turbo', description: 'Previous generation flagship' },
1043
- { key: 'gpt-3.5-turbo', displayName: 'GPT-3.5 Turbo', description: 'Fast and cost-effective' },
1044
- { key: 'o1', displayName: 'o1', description: 'Advanced reasoning model' },
1045
- { key: 'o1-mini', displayName: 'o1 Mini', description: 'Fast reasoning model' },
1046
- ];
1047
- }
1048
- // Ensure the currently selected model is in the list
1049
- if (currentModel && !models.some(m => m.key === currentModel)) {
1050
- models.unshift({
1051
- key: currentModel,
1052
- displayName: currentModel,
1053
- description: 'Selected model',
1054
- });
1055
- }
1056
- break;
1057
- }
1058
- default:
1059
- // Fallback to Anthropic models
1060
- models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
1061
- key,
1062
- displayName: value.displayName,
1063
- description: 'Claude model',
1064
- }));
1065
1273
  }
1066
1274
  return {
1067
1275
  currentProvider: settings.providerType,
@@ -1073,26 +1281,48 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1073
1281
  // Set the current model (persists selection across sessions)
1074
1282
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_SET_MODEL, async (_, modelKey) => {
1075
1283
  const settings = llm_1.LLMProviderFactory.loadSettings();
1284
+ const resolvedProviderType = resolveCustomProviderId(settings.providerType);
1076
1285
  // Update the model key based on the current provider
1077
- switch (settings.providerType) {
1078
- case 'gemini':
1079
- settings.gemini = { ...settings.gemini, model: modelKey };
1080
- break;
1081
- case 'openrouter':
1082
- settings.openrouter = { ...settings.openrouter, model: modelKey };
1083
- break;
1084
- case 'ollama':
1085
- settings.ollama = { ...settings.ollama, model: modelKey };
1086
- break;
1087
- case 'openai':
1088
- settings.openai = { ...settings.openai, model: modelKey };
1089
- break;
1090
- case 'anthropic':
1091
- case 'bedrock':
1092
- default:
1093
- // For Anthropic/Bedrock, use the modelKey field
1094
- settings.modelKey = modelKey;
1095
- break;
1286
+ if (llm_provider_catalog_1.CUSTOM_PROVIDER_IDS.has(resolvedProviderType)) {
1287
+ const existing = settings.customProviders?.[resolvedProviderType] || {};
1288
+ settings.customProviders = {
1289
+ ...(settings.customProviders || {}),
1290
+ [resolvedProviderType]: {
1291
+ ...existing,
1292
+ model: modelKey,
1293
+ },
1294
+ };
1295
+ }
1296
+ else {
1297
+ switch (settings.providerType) {
1298
+ case 'gemini':
1299
+ settings.gemini = { ...settings.gemini, model: modelKey };
1300
+ break;
1301
+ case 'openrouter':
1302
+ settings.openrouter = { ...settings.openrouter, model: modelKey };
1303
+ break;
1304
+ case 'ollama':
1305
+ settings.ollama = { ...settings.ollama, model: modelKey };
1306
+ break;
1307
+ case 'openai':
1308
+ settings.openai = { ...settings.openai, model: modelKey };
1309
+ break;
1310
+ case 'groq':
1311
+ settings.groq = { ...settings.groq, model: modelKey };
1312
+ break;
1313
+ case 'xai':
1314
+ settings.xai = { ...settings.xai, model: modelKey };
1315
+ break;
1316
+ case 'kimi':
1317
+ settings.kimi = { ...settings.kimi, model: modelKey };
1318
+ break;
1319
+ case 'anthropic':
1320
+ case 'bedrock':
1321
+ default:
1322
+ // For Anthropic/Bedrock, use the modelKey field
1323
+ settings.modelKey = modelKey;
1324
+ break;
1325
+ }
1096
1326
  }
1097
1327
  llm_1.LLMProviderFactory.saveSettings(settings);
1098
1328
  return { success: true };
@@ -1123,9 +1353,9 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1123
1353
  llm_1.LLMProviderFactory.saveCachedModels('gemini', cachedModels);
1124
1354
  return models;
1125
1355
  });
1126
- electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, async (_, apiKey) => {
1356
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, async (_, apiKey, baseUrl) => {
1127
1357
  checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS);
1128
- const models = await llm_1.LLMProviderFactory.getOpenRouterModels(apiKey);
1358
+ const models = await llm_1.LLMProviderFactory.getOpenRouterModels(apiKey, baseUrl);
1129
1359
  // Cache the models for use in config status
1130
1360
  const cachedModels = models.map(m => ({
1131
1361
  key: m.id,
@@ -1148,6 +1378,39 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1148
1378
  llm_1.LLMProviderFactory.saveCachedModels('openai', cachedModels);
1149
1379
  return models;
1150
1380
  });
1381
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_GROQ_MODELS, async (_, apiKey, baseUrl) => {
1382
+ checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_GROQ_MODELS);
1383
+ const models = await llm_1.LLMProviderFactory.getGroqModels(apiKey, baseUrl);
1384
+ const cachedModels = models.map(m => ({
1385
+ key: m.id,
1386
+ displayName: m.name,
1387
+ description: 'Groq model',
1388
+ }));
1389
+ llm_1.LLMProviderFactory.saveCachedModels('groq', cachedModels);
1390
+ return models;
1391
+ });
1392
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_XAI_MODELS, async (_, apiKey, baseUrl) => {
1393
+ checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_XAI_MODELS);
1394
+ const models = await llm_1.LLMProviderFactory.getXAIModels(apiKey, baseUrl);
1395
+ const cachedModels = models.map(m => ({
1396
+ key: m.id,
1397
+ displayName: m.name,
1398
+ description: 'xAI model',
1399
+ }));
1400
+ llm_1.LLMProviderFactory.saveCachedModels('xai', cachedModels);
1401
+ return models;
1402
+ });
1403
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_KIMI_MODELS, async (_, apiKey, baseUrl) => {
1404
+ checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_KIMI_MODELS);
1405
+ const models = await llm_1.LLMProviderFactory.getKimiModels(apiKey, baseUrl);
1406
+ const cachedModels = models.map(m => ({
1407
+ key: m.id,
1408
+ displayName: m.name,
1409
+ description: 'Kimi model',
1410
+ }));
1411
+ llm_1.LLMProviderFactory.saveCachedModels('kimi', cachedModels);
1412
+ return models;
1413
+ });
1151
1414
  // OpenAI OAuth handlers
1152
1415
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_START, async () => {
1153
1416
  checkRateLimit(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_START);
@@ -1260,6 +1523,204 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1260
1523
  error: result.success ? undefined : result.error,
1261
1524
  };
1262
1525
  });
1526
+ // Notion Settings handlers
1527
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_GET_SETTINGS, async () => {
1528
+ return notion_manager_1.NotionSettingsManager.loadSettings();
1529
+ });
1530
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_SAVE_SETTINGS, async (_, settings) => {
1531
+ checkRateLimit(types_1.IPC_CHANNELS.NOTION_SAVE_SETTINGS);
1532
+ const validated = (0, validation_1.validateInput)(validation_1.NotionSettingsSchema, settings, 'notion settings');
1533
+ notion_manager_1.NotionSettingsManager.saveSettings(validated);
1534
+ notion_manager_1.NotionSettingsManager.clearCache();
1535
+ return { success: true };
1536
+ });
1537
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_TEST_CONNECTION, async () => {
1538
+ checkRateLimit(types_1.IPC_CHANNELS.NOTION_TEST_CONNECTION);
1539
+ const settings = notion_manager_1.NotionSettingsManager.loadSettings();
1540
+ return (0, notion_api_1.testNotionConnection)(settings);
1541
+ });
1542
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_GET_STATUS, async () => {
1543
+ checkRateLimit(types_1.IPC_CHANNELS.NOTION_GET_STATUS);
1544
+ const settings = notion_manager_1.NotionSettingsManager.loadSettings();
1545
+ if (!settings.apiKey) {
1546
+ return { configured: false, connected: false };
1547
+ }
1548
+ if (!settings.enabled) {
1549
+ return { configured: true, connected: false };
1550
+ }
1551
+ const result = await (0, notion_api_1.testNotionConnection)(settings);
1552
+ return {
1553
+ configured: true,
1554
+ connected: result.success,
1555
+ name: result.name,
1556
+ error: result.success ? undefined : result.error,
1557
+ };
1558
+ });
1559
+ // Box Settings handlers
1560
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_GET_SETTINGS, async () => {
1561
+ return box_manager_1.BoxSettingsManager.loadSettings();
1562
+ });
1563
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_SAVE_SETTINGS, async (_, settings) => {
1564
+ checkRateLimit(types_1.IPC_CHANNELS.BOX_SAVE_SETTINGS);
1565
+ const validated = (0, validation_1.validateInput)(validation_1.BoxSettingsSchema, settings, 'box settings');
1566
+ box_manager_1.BoxSettingsManager.saveSettings(validated);
1567
+ box_manager_1.BoxSettingsManager.clearCache();
1568
+ return { success: true };
1569
+ });
1570
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_TEST_CONNECTION, async () => {
1571
+ checkRateLimit(types_1.IPC_CHANNELS.BOX_TEST_CONNECTION);
1572
+ const settings = box_manager_1.BoxSettingsManager.loadSettings();
1573
+ return (0, box_api_1.testBoxConnection)(settings);
1574
+ });
1575
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_GET_STATUS, async () => {
1576
+ checkRateLimit(types_1.IPC_CHANNELS.BOX_GET_STATUS);
1577
+ const settings = box_manager_1.BoxSettingsManager.loadSettings();
1578
+ if (!settings.accessToken) {
1579
+ return { configured: false, connected: false };
1580
+ }
1581
+ if (!settings.enabled) {
1582
+ return { configured: true, connected: false };
1583
+ }
1584
+ const result = await (0, box_api_1.testBoxConnection)(settings);
1585
+ return {
1586
+ configured: true,
1587
+ connected: result.success,
1588
+ name: result.name,
1589
+ error: result.success ? undefined : result.error,
1590
+ };
1591
+ });
1592
+ // OneDrive Settings handlers
1593
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_GET_SETTINGS, async () => {
1594
+ return onedrive_manager_1.OneDriveSettingsManager.loadSettings();
1595
+ });
1596
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_SAVE_SETTINGS, async (_, settings) => {
1597
+ checkRateLimit(types_1.IPC_CHANNELS.ONEDRIVE_SAVE_SETTINGS);
1598
+ const validated = (0, validation_1.validateInput)(validation_1.OneDriveSettingsSchema, settings, 'onedrive settings');
1599
+ onedrive_manager_1.OneDriveSettingsManager.saveSettings(validated);
1600
+ onedrive_manager_1.OneDriveSettingsManager.clearCache();
1601
+ return { success: true };
1602
+ });
1603
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_TEST_CONNECTION, async () => {
1604
+ checkRateLimit(types_1.IPC_CHANNELS.ONEDRIVE_TEST_CONNECTION);
1605
+ const settings = onedrive_manager_1.OneDriveSettingsManager.loadSettings();
1606
+ return (0, onedrive_api_1.testOneDriveConnection)(settings);
1607
+ });
1608
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_GET_STATUS, async () => {
1609
+ checkRateLimit(types_1.IPC_CHANNELS.ONEDRIVE_GET_STATUS);
1610
+ const settings = onedrive_manager_1.OneDriveSettingsManager.loadSettings();
1611
+ if (!settings.accessToken) {
1612
+ return { configured: false, connected: false };
1613
+ }
1614
+ if (!settings.enabled) {
1615
+ return { configured: true, connected: false };
1616
+ }
1617
+ const result = await (0, onedrive_api_1.testOneDriveConnection)(settings);
1618
+ return {
1619
+ configured: true,
1620
+ connected: result.success,
1621
+ name: result.name,
1622
+ error: result.success ? undefined : result.error,
1623
+ };
1624
+ });
1625
+ // Google Drive Settings handlers
1626
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_GET_SETTINGS, async () => {
1627
+ return google_drive_manager_1.GoogleDriveSettingsManager.loadSettings();
1628
+ });
1629
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_SAVE_SETTINGS, async (_, settings) => {
1630
+ checkRateLimit(types_1.IPC_CHANNELS.GOOGLE_DRIVE_SAVE_SETTINGS);
1631
+ const validated = (0, validation_1.validateInput)(validation_1.GoogleDriveSettingsSchema, settings, 'google drive settings');
1632
+ google_drive_manager_1.GoogleDriveSettingsManager.saveSettings(validated);
1633
+ google_drive_manager_1.GoogleDriveSettingsManager.clearCache();
1634
+ return { success: true };
1635
+ });
1636
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_TEST_CONNECTION, async () => {
1637
+ checkRateLimit(types_1.IPC_CHANNELS.GOOGLE_DRIVE_TEST_CONNECTION);
1638
+ const settings = google_drive_manager_1.GoogleDriveSettingsManager.loadSettings();
1639
+ return (0, google_drive_api_1.testGoogleDriveConnection)(settings);
1640
+ });
1641
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_GET_STATUS, async () => {
1642
+ checkRateLimit(types_1.IPC_CHANNELS.GOOGLE_DRIVE_GET_STATUS);
1643
+ const settings = google_drive_manager_1.GoogleDriveSettingsManager.loadSettings();
1644
+ if (!settings.accessToken) {
1645
+ return { configured: false, connected: false };
1646
+ }
1647
+ if (!settings.enabled) {
1648
+ return { configured: true, connected: false };
1649
+ }
1650
+ const result = await (0, google_drive_api_1.testGoogleDriveConnection)(settings);
1651
+ return {
1652
+ configured: true,
1653
+ connected: result.success,
1654
+ name: result.name,
1655
+ error: result.success ? undefined : result.error,
1656
+ };
1657
+ });
1658
+ // Dropbox Settings handlers
1659
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_GET_SETTINGS, async () => {
1660
+ return dropbox_manager_1.DropboxSettingsManager.loadSettings();
1661
+ });
1662
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_SAVE_SETTINGS, async (_, settings) => {
1663
+ checkRateLimit(types_1.IPC_CHANNELS.DROPBOX_SAVE_SETTINGS);
1664
+ const validated = (0, validation_1.validateInput)(validation_1.DropboxSettingsSchema, settings, 'dropbox settings');
1665
+ dropbox_manager_1.DropboxSettingsManager.saveSettings(validated);
1666
+ dropbox_manager_1.DropboxSettingsManager.clearCache();
1667
+ return { success: true };
1668
+ });
1669
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_TEST_CONNECTION, async () => {
1670
+ checkRateLimit(types_1.IPC_CHANNELS.DROPBOX_TEST_CONNECTION);
1671
+ const settings = dropbox_manager_1.DropboxSettingsManager.loadSettings();
1672
+ return (0, dropbox_api_1.testDropboxConnection)(settings);
1673
+ });
1674
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_GET_STATUS, async () => {
1675
+ checkRateLimit(types_1.IPC_CHANNELS.DROPBOX_GET_STATUS);
1676
+ const settings = dropbox_manager_1.DropboxSettingsManager.loadSettings();
1677
+ if (!settings.accessToken) {
1678
+ return { configured: false, connected: false };
1679
+ }
1680
+ if (!settings.enabled) {
1681
+ return { configured: true, connected: false };
1682
+ }
1683
+ const result = await (0, dropbox_api_1.testDropboxConnection)(settings);
1684
+ return {
1685
+ configured: true,
1686
+ connected: result.success,
1687
+ name: result.name,
1688
+ error: result.success ? undefined : result.error,
1689
+ };
1690
+ });
1691
+ // SharePoint Settings handlers
1692
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_GET_SETTINGS, async () => {
1693
+ return sharepoint_manager_1.SharePointSettingsManager.loadSettings();
1694
+ });
1695
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_SAVE_SETTINGS, async (_, settings) => {
1696
+ checkRateLimit(types_1.IPC_CHANNELS.SHAREPOINT_SAVE_SETTINGS);
1697
+ const validated = (0, validation_1.validateInput)(validation_1.SharePointSettingsSchema, settings, 'sharepoint settings');
1698
+ sharepoint_manager_1.SharePointSettingsManager.saveSettings(validated);
1699
+ sharepoint_manager_1.SharePointSettingsManager.clearCache();
1700
+ return { success: true };
1701
+ });
1702
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_TEST_CONNECTION, async () => {
1703
+ checkRateLimit(types_1.IPC_CHANNELS.SHAREPOINT_TEST_CONNECTION);
1704
+ const settings = sharepoint_manager_1.SharePointSettingsManager.loadSettings();
1705
+ return (0, sharepoint_api_1.testSharePointConnection)(settings);
1706
+ });
1707
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_GET_STATUS, async () => {
1708
+ checkRateLimit(types_1.IPC_CHANNELS.SHAREPOINT_GET_STATUS);
1709
+ const settings = sharepoint_manager_1.SharePointSettingsManager.loadSettings();
1710
+ if (!settings.accessToken) {
1711
+ return { configured: false, connected: false };
1712
+ }
1713
+ if (!settings.enabled) {
1714
+ return { configured: true, connected: false };
1715
+ }
1716
+ const result = await (0, sharepoint_api_1.testSharePointConnection)(settings);
1717
+ return {
1718
+ configured: true,
1719
+ connected: result.success,
1720
+ name: result.name,
1721
+ error: result.success ? undefined : result.error,
1722
+ };
1723
+ });
1263
1724
  // Gateway / Channel handlers
1264
1725
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_GET_CHANNELS, async () => {
1265
1726
  if (!gateway)
@@ -1679,7 +2140,8 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1679
2140
  throw new Error('Agent role not found');
1680
2141
  }
1681
2142
  }
1682
- taskRepo.update(validatedTaskId, { assignedAgentRoleId: agentRoleId ?? undefined });
2143
+ const taskUpdate = { assignedAgentRoleId: agentRoleId ?? undefined };
2144
+ taskRepo.update(validatedTaskId, taskUpdate);
1683
2145
  const task = taskRepo.findById(validatedTaskId);
1684
2146
  if (task) {
1685
2147
  if (agentRoleId) {
@@ -2030,6 +2492,7 @@ function setupMCPHandlers() {
2030
2492
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_CONNECT_SERVER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2031
2493
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_TEST_SERVER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2032
2494
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_REGISTRY_INSTALL, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2495
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_CONNECTOR_OAUTH_START, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2033
2496
  // Initialize MCP settings manager
2034
2497
  settings_1.MCPSettingsManager.initialize();
2035
2498
  // Get settings
@@ -2053,7 +2516,7 @@ function setupMCPHandlers() {
2053
2516
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_ADD_SERVER, async (_, serverConfig) => {
2054
2517
  checkRateLimit(types_1.IPC_CHANNELS.MCP_ADD_SERVER);
2055
2518
  const validated = (0, validation_1.validateInput)(validation_2.MCPServerConfigSchema, serverConfig, 'MCP server config');
2056
- const { id, ...configWithoutId } = validated;
2519
+ const { id: _id, ...configWithoutId } = validated;
2057
2520
  return settings_1.MCPSettingsManager.addServer(configWithoutId);
2058
2521
  });
2059
2522
  // Update a server
@@ -2136,6 +2599,12 @@ function setupMCPHandlers() {
2136
2599
  const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
2137
2600
  return MCPRegistryManager_1.MCPRegistryManager.updateServer(validatedId);
2138
2601
  });
2602
+ // MCP Connector OAuth (Salesforce/Jira)
2603
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_CONNECTOR_OAUTH_START, async (_, payload) => {
2604
+ checkRateLimit(types_1.IPC_CHANNELS.MCP_CONNECTOR_OAUTH_START);
2605
+ const validated = (0, validation_1.validateInput)(validation_1.MCPConnectorOAuthSchema, payload, 'connector oauth');
2606
+ return (0, connector_oauth_1.startConnectorOAuth)(validated);
2607
+ });
2139
2608
  // MCP Host handlers
2140
2609
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_HOST_START, async () => {
2141
2610
  const hostServer = MCPHostServer_1.MCPHostServer.getInstance();