cowork-os 0.3.21 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/README.md +372 -10
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
  48. package/dist/electron/electron/agent/daemon.js +189 -13
  49. package/dist/electron/electron/agent/executor.js +895 -78
  50. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  51. package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
  52. package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
  53. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  54. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  55. package/dist/electron/electron/agent/llm/index.js +13 -1
  56. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  57. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  58. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  59. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  60. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  61. package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
  62. package/dist/electron/electron/agent/llm/types.js +66 -1
  63. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  64. package/dist/electron/electron/agent/search/provider-factory.js +38 -2
  65. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  66. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  67. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  68. package/dist/electron/electron/agent/tools/file-tools.js +66 -3
  69. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  70. package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
  71. package/dist/electron/electron/agent/tools/image-tools.js +11 -1
  72. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  73. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  74. package/dist/electron/electron/agent/tools/registry.js +548 -10
  75. package/dist/electron/electron/agent/tools/search-tools.js +28 -10
  76. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  77. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  78. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  79. package/dist/electron/electron/agents/agent-dispatch.js +63 -0
  80. package/dist/electron/electron/database/repositories.js +19 -5
  81. package/dist/electron/electron/database/schema.js +8 -0
  82. package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
  83. package/dist/electron/electron/gateway/index.js +75 -1
  84. package/dist/electron/electron/gateway/router.js +209 -154
  85. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  86. package/dist/electron/electron/ipc/handlers.js +763 -267
  87. package/dist/electron/electron/main.js +63 -0
  88. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  89. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  90. package/dist/electron/electron/memory/MemoryService.js +2 -1
  91. package/dist/electron/electron/preload.js +78 -1
  92. package/dist/electron/electron/settings/appearance-manager.js +18 -1
  93. package/dist/electron/electron/settings/box-manager.js +54 -0
  94. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  95. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  96. package/dist/electron/electron/settings/notion-manager.js +56 -0
  97. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  98. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  99. package/dist/electron/electron/utils/box-api.js +153 -0
  100. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  101. package/dist/electron/electron/utils/env-migration.js +19 -0
  102. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  103. package/dist/electron/electron/utils/notion-api.js +103 -0
  104. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  105. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  106. package/dist/electron/electron/utils/validation.js +98 -3
  107. package/dist/electron/electron/utils/x-cli.js +1 -1
  108. package/dist/electron/shared/channelMessages.js +284 -3
  109. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  110. package/dist/electron/shared/types.js +90 -1
  111. package/package.json +14 -3
  112. package/resources/skills/nano-banana-pro.json +4 -4
  113. package/resources/skills/openai-image-gen.json +3 -3
  114. package/resources/skills/scripts/gen.py +163 -0
  115. package/resources/skills/scripts/generate_image.py +91 -0
  116. package/src/electron/agent/custom-skill-loader.ts +34 -1
  117. package/src/electron/agent/daemon.ts +210 -14
  118. package/src/electron/agent/executor.ts +1124 -85
  119. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  120. package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
  121. package/src/electron/agent/llm/bedrock-provider.ts +62 -9
  122. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  123. package/src/electron/agent/llm/groq-provider.ts +39 -0
  124. package/src/electron/agent/llm/index.ts +6 -0
  125. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  126. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  127. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  128. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  129. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  130. package/src/electron/agent/llm/provider-factory.ts +459 -6
  131. package/src/electron/agent/llm/types.ts +95 -1
  132. package/src/electron/agent/llm/xai-provider.ts +39 -0
  133. package/src/electron/agent/search/provider-factory.ts +43 -2
  134. package/src/electron/agent/tools/box-tools.ts +239 -0
  135. package/src/electron/agent/tools/builtin-settings.ts +36 -0
  136. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  137. package/src/electron/agent/tools/file-tools.ts +66 -3
  138. package/src/electron/agent/tools/gmail-tools.ts +240 -0
  139. package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
  140. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  141. package/src/electron/agent/tools/grep-tools.ts +97 -12
  142. package/src/electron/agent/tools/image-tools.ts +11 -1
  143. package/src/electron/agent/tools/notion-tools.ts +330 -0
  144. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  145. package/src/electron/agent/tools/registry.ts +794 -10
  146. package/src/electron/agent/tools/search-tools.ts +29 -11
  147. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  148. package/src/electron/agent/tools/shell-tools.ts +11 -3
  149. package/src/electron/agent/tools/x-tools.ts +1 -1
  150. package/src/electron/agents/agent-dispatch.ts +79 -0
  151. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  152. package/src/electron/database/repositories.ts +58 -6
  153. package/src/electron/database/schema.ts +8 -0
  154. package/src/electron/gateway/channels/discord.ts +4 -0
  155. package/src/electron/gateway/channels/google-chat.ts +3 -0
  156. package/src/electron/gateway/channels/line.ts +3 -0
  157. package/src/electron/gateway/channels/matrix-client.ts +15 -0
  158. package/src/electron/gateway/channels/matrix.ts +31 -0
  159. package/src/electron/gateway/channels/mattermost.ts +3 -0
  160. package/src/electron/gateway/channels/signal.ts +3 -0
  161. package/src/electron/gateway/channels/slack.ts +9 -4
  162. package/src/electron/gateway/channels/teams.ts +4 -0
  163. package/src/electron/gateway/channels/telegram.ts +2 -0
  164. package/src/electron/gateway/channels/twitch.ts +2 -0
  165. package/src/electron/gateway/channels/types.ts +8 -0
  166. package/src/electron/gateway/channels/whatsapp.ts +66 -0
  167. package/src/electron/gateway/index.ts +95 -2
  168. package/src/electron/gateway/router.ts +231 -161
  169. package/src/electron/gateway/security.ts +21 -9
  170. package/src/electron/ipc/canvas-handlers.ts +10 -0
  171. package/src/electron/ipc/handlers.ts +848 -292
  172. package/src/electron/main.ts +35 -0
  173. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  174. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  175. package/src/electron/memory/MemoryService.ts +7 -1
  176. package/src/electron/preload.ts +200 -5
  177. package/src/electron/settings/appearance-manager.ts +20 -2
  178. package/src/electron/settings/box-manager.ts +58 -0
  179. package/src/electron/settings/dropbox-manager.ts +58 -0
  180. package/src/electron/settings/google-workspace-manager.ts +59 -0
  181. package/src/electron/settings/notion-manager.ts +60 -0
  182. package/src/electron/settings/onedrive-manager.ts +58 -0
  183. package/src/electron/settings/sharepoint-manager.ts +58 -0
  184. package/src/electron/utils/box-api.ts +184 -0
  185. package/src/electron/utils/dropbox-api.ts +171 -0
  186. package/src/electron/utils/env-migration.ts +22 -0
  187. package/src/electron/utils/gmail-api.ts +121 -0
  188. package/src/electron/utils/google-calendar-api.ts +115 -0
  189. package/src/electron/utils/google-workspace-api.ts +228 -0
  190. package/src/electron/utils/google-workspace-auth.ts +109 -0
  191. package/src/electron/utils/google-workspace-oauth.ts +232 -0
  192. package/src/electron/utils/notion-api.ts +126 -0
  193. package/src/electron/utils/onedrive-api.ts +137 -0
  194. package/src/electron/utils/sharepoint-api.ts +132 -0
  195. package/src/electron/utils/validation.ts +128 -1
  196. package/src/electron/utils/x-cli.ts +1 -1
  197. package/src/renderer/App.tsx +119 -8
  198. package/src/renderer/components/ActivityFeedItem.tsx +34 -17
  199. package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
  200. package/src/renderer/components/AppearanceSettings.tsx +37 -2
  201. package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
  202. package/src/renderer/components/BoxSettings.tsx +203 -0
  203. package/src/renderer/components/BrowserView.tsx +101 -0
  204. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  205. package/src/renderer/components/CanvasPreview.tsx +68 -1
  206. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  207. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  208. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  209. package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
  210. package/src/renderer/components/DiscordSettings.tsx +18 -7
  211. package/src/renderer/components/DropboxSettings.tsx +202 -0
  212. package/src/renderer/components/EmailSettings.tsx +18 -7
  213. package/src/renderer/components/FileViewer.tsx +21 -13
  214. package/src/renderer/components/GoogleChatSettings.tsx +17 -7
  215. package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
  216. package/src/renderer/components/ImessageSettings.tsx +22 -11
  217. package/src/renderer/components/LineIcons.tsx +376 -0
  218. package/src/renderer/components/LineSettings.tsx +18 -7
  219. package/src/renderer/components/MCPSettings.tsx +56 -0
  220. package/src/renderer/components/MainContent.tsx +740 -76
  221. package/src/renderer/components/MatrixSettings.tsx +18 -7
  222. package/src/renderer/components/MattermostSettings.tsx +18 -7
  223. package/src/renderer/components/NodesSettings.tsx +58 -99
  224. package/src/renderer/components/NotificationPanel.tsx +25 -11
  225. package/src/renderer/components/NotionSettings.tsx +231 -0
  226. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  227. package/src/renderer/components/OnboardingModal.tsx +70 -1
  228. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  229. package/src/renderer/components/RightPanel.tsx +141 -28
  230. package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
  231. package/src/renderer/components/SearchSettings.tsx +118 -114
  232. package/src/renderer/components/Settings.tsx +1425 -651
  233. package/src/renderer/components/SharePointSettings.tsx +224 -0
  234. package/src/renderer/components/Sidebar.tsx +94 -19
  235. package/src/renderer/components/SignalSettings.tsx +18 -7
  236. package/src/renderer/components/SkillHubBrowser.tsx +144 -185
  237. package/src/renderer/components/SlackSettings.tsx +18 -7
  238. package/src/renderer/components/TaskQuickActions.tsx +11 -6
  239. package/src/renderer/components/TaskTimeline.tsx +58 -26
  240. package/src/renderer/components/TeamsSettings.tsx +18 -7
  241. package/src/renderer/components/TelegramSettings.tsx +18 -7
  242. package/src/renderer/components/ThemeIcon.tsx +16 -0
  243. package/src/renderer/components/TwitchSettings.tsx +18 -7
  244. package/src/renderer/components/VoiceSettings.tsx +30 -74
  245. package/src/renderer/components/WhatsAppSettings.tsx +48 -37
  246. package/src/renderer/components/WorkingStateHistory.tsx +7 -5
  247. package/src/renderer/components/WorkspaceSelector.tsx +42 -13
  248. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  249. package/src/renderer/styles/index.css +2333 -209
  250. package/src/shared/channelMessages.ts +367 -4
  251. package/src/shared/llm-provider-catalog.ts +217 -0
  252. package/src/shared/types.ts +251 -2
@@ -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,64 +208,10 @@ 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
  };
196
- const buildSoulSummary = (soul) => {
197
- if (!soul)
198
- return null;
199
- try {
200
- const parsed = JSON.parse(soul);
201
- const parts = [];
202
- if (typeof parsed.name === 'string')
203
- parts.push(`Name: ${parsed.name}`);
204
- if (typeof parsed.role === 'string')
205
- parts.push(`Role: ${parsed.role}`);
206
- if (typeof parsed.personality === 'string')
207
- parts.push(`Personality: ${parsed.personality}`);
208
- if (typeof parsed.communicationStyle === 'string')
209
- parts.push(`Style: ${parsed.communicationStyle}`);
210
- if (Array.isArray(parsed.focusAreas))
211
- parts.push(`Focus: ${parsed.focusAreas.join(', ')}`);
212
- if (Array.isArray(parsed.strengths))
213
- parts.push(`Strengths: ${parsed.strengths.join(', ')}`);
214
- if (parts.length === 0) {
215
- return null;
216
- }
217
- return parts.join('\n');
218
- }
219
- catch {
220
- return soul;
221
- }
222
- };
223
- const buildAgentDispatchPrompt = (role, parentTask) => {
224
- const lines = [
225
- `You are ${role.displayName}${role.description ? ` — ${role.description}` : ''}.`,
226
- ];
227
- if (role.capabilities && role.capabilities.length > 0) {
228
- lines.push(`Capabilities: ${role.capabilities.join(', ')}`);
229
- }
230
- if (role.systemPrompt) {
231
- lines.push('System guidance:');
232
- lines.push(role.systemPrompt);
233
- }
234
- const soulSummary = buildSoulSummary(role.soul || undefined);
235
- if (soulSummary) {
236
- lines.push('Role notes:');
237
- lines.push(soulSummary);
238
- }
239
- lines.push('');
240
- lines.push(`Parent task: ${parentTask.title}`);
241
- lines.push('Request:');
242
- lines.push(parentTask.prompt);
243
- lines.push('');
244
- lines.push('Deliverables:');
245
- lines.push('- Provide a concise summary of your findings.');
246
- lines.push('- Call out risks or open questions.');
247
- lines.push('- Recommend next steps.');
248
- return lines.join('\n');
249
- };
250
215
  const x_manager_1 = require("../settings/x-manager");
251
216
  const x_cli_1 = require("../utils/x-cli");
252
217
  const custom_skill_loader_1 = require("../agent/custom-skill-loader");
@@ -263,6 +228,7 @@ const voice_settings_manager_1 = require("../voice/voice-settings-manager");
263
228
  const VoiceService_1 = require("../voice/VoiceService");
264
229
  // Global notification service instance
265
230
  let notificationService = null;
231
+ const resolveCustomProviderId = (providerType) => providerType === 'kimi-coding' ? 'kimi-code' : providerType;
266
232
  /**
267
233
  * Get the notification service instance
268
234
  */
@@ -286,6 +252,9 @@ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_OLLAMA_MODELS,
286
252
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_GEMINI_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
287
253
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
288
254
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_BEDROCK_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
255
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_GROQ_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
256
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_XAI_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
257
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_KIMI_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
289
258
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.SEARCH_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
290
259
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.SEARCH_TEST_PROVIDER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
291
260
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.GATEWAY_ADD_CHANNEL, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
@@ -543,6 +512,131 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
543
512
  return { success: false, error: `Failed to read file: ${error.message}` };
544
513
  }
545
514
  });
515
+ // File import handler - copy selected files into the workspace for attachment use
516
+ electron_1.ipcMain.handle('file:importToWorkspace', async (_, data) => {
517
+ const validated = (0, validation_1.validateInput)(validation_1.FileImportSchema, data, 'file import');
518
+ const workspace = workspaceRepo.findById(validated.workspaceId);
519
+ if (!workspace) {
520
+ throw new Error(`Workspace not found: ${validated.workspaceId}`);
521
+ }
522
+ if (!workspace.permissions.write) {
523
+ throw new Error('Write permission not granted for workspace');
524
+ }
525
+ const sanitizeFileName = (fileName) => {
526
+ const sanitized = fileName.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_').trim();
527
+ return sanitized.length > 0 ? sanitized : 'file';
528
+ };
529
+ const ensureUniqueName = (dir, baseName, usedNames) => {
530
+ const ext = path.extname(baseName);
531
+ const stem = path.basename(baseName, ext);
532
+ let candidate = baseName;
533
+ let counter = 1;
534
+ while (usedNames.has(candidate) || fsSync.existsSync(path.join(dir, candidate))) {
535
+ candidate = `${stem}-${counter}${ext}`;
536
+ counter += 1;
537
+ }
538
+ usedNames.add(candidate);
539
+ return candidate;
540
+ };
541
+ let uploadRoot = null;
542
+ const usedNames = new Set();
543
+ const ensureUploadRoot = async () => {
544
+ if (uploadRoot)
545
+ return uploadRoot;
546
+ uploadRoot = path.join(workspace.path, '.cowork', 'uploads', `${Date.now()}`);
547
+ await fs.mkdir(uploadRoot, { recursive: true });
548
+ return uploadRoot;
549
+ };
550
+ const results = [];
551
+ for (const filePath of validated.files) {
552
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
553
+ const stats = await fs.stat(absolutePath);
554
+ if (!stats.isFile()) {
555
+ throw new Error(`Not a file: ${filePath}`);
556
+ }
557
+ const sizeCheck = guardrail_manager_1.GuardrailManager.isFileSizeExceeded(stats.size);
558
+ if (sizeCheck.exceeded) {
559
+ throw new Error(`File "${path.basename(filePath)}" is ${sizeCheck.sizeMB.toFixed(1)}MB and exceeds the ${sizeCheck.limitMB}MB limit.`);
560
+ }
561
+ const mimeType = (mime_types_1.default.lookup(absolutePath) || undefined);
562
+ if (isPathWithinWorkspace(absolutePath, workspace.path)) {
563
+ results.push({
564
+ relativePath: path.relative(workspace.path, absolutePath),
565
+ fileName: path.basename(absolutePath),
566
+ size: stats.size,
567
+ mimeType,
568
+ });
569
+ continue;
570
+ }
571
+ const safeName = sanitizeFileName(path.basename(absolutePath));
572
+ const targetRoot = await ensureUploadRoot();
573
+ const uniqueName = ensureUniqueName(targetRoot, safeName, usedNames);
574
+ const destination = path.join(targetRoot, uniqueName);
575
+ await fs.copyFile(absolutePath, destination);
576
+ results.push({
577
+ relativePath: path.relative(workspace.path, destination),
578
+ fileName: uniqueName,
579
+ size: stats.size,
580
+ mimeType,
581
+ });
582
+ }
583
+ return results;
584
+ });
585
+ // File import handler - save provided file data into the workspace (clipboard / drag data)
586
+ electron_1.ipcMain.handle('file:importDataToWorkspace', async (_, data) => {
587
+ const validated = (0, validation_1.validateInput)(validation_1.FileImportDataSchema, data, 'file import data');
588
+ const workspace = workspaceRepo.findById(validated.workspaceId);
589
+ if (!workspace) {
590
+ throw new Error(`Workspace not found: ${validated.workspaceId}`);
591
+ }
592
+ if (!workspace.permissions.write) {
593
+ throw new Error('Write permission not granted for workspace');
594
+ }
595
+ const sanitizeFileName = (fileName) => {
596
+ const sanitized = fileName.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_').trim();
597
+ return sanitized.length > 0 ? sanitized : 'file';
598
+ };
599
+ const ensureExtension = (fileName, mimeType) => {
600
+ if (path.extname(fileName) || !mimeType)
601
+ return fileName;
602
+ const ext = mime_types_1.default.extension(mimeType);
603
+ return ext ? `${fileName}.${ext}` : fileName;
604
+ };
605
+ const ensureUniqueName = (dir, baseName, usedNames) => {
606
+ const ext = path.extname(baseName);
607
+ const stem = path.basename(baseName, ext);
608
+ let candidate = baseName;
609
+ let counter = 1;
610
+ while (usedNames.has(candidate) || fsSync.existsSync(path.join(dir, candidate))) {
611
+ candidate = `${stem}-${counter}${ext}`;
612
+ counter += 1;
613
+ }
614
+ usedNames.add(candidate);
615
+ return candidate;
616
+ };
617
+ const uploadRoot = path.join(workspace.path, '.cowork', 'uploads', `${Date.now()}`);
618
+ await fs.mkdir(uploadRoot, { recursive: true });
619
+ const usedNames = new Set();
620
+ const results = [];
621
+ for (const file of validated.files) {
622
+ const rawName = ensureExtension(sanitizeFileName(file.name), file.mimeType);
623
+ const uniqueName = ensureUniqueName(uploadRoot, rawName, usedNames);
624
+ const destination = path.join(uploadRoot, uniqueName);
625
+ const buffer = Buffer.from(file.data, 'base64');
626
+ const sizeCheck = guardrail_manager_1.GuardrailManager.isFileSizeExceeded(buffer.length);
627
+ if (sizeCheck.exceeded) {
628
+ throw new Error(`File "${rawName}" is ${sizeCheck.sizeMB.toFixed(1)}MB and exceeds the ${sizeCheck.limitMB}MB limit.`);
629
+ }
630
+ await fs.writeFile(destination, buffer);
631
+ results.push({
632
+ relativePath: path.relative(workspace.path, destination),
633
+ fileName: uniqueName,
634
+ size: buffer.length,
635
+ mimeType: file.mimeType,
636
+ });
637
+ }
638
+ return results;
639
+ });
546
640
  // Workspace handlers
547
641
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_CREATE, async (_, data) => {
548
642
  const validated = (0, validation_1.validateInput)(validation_1.WorkspaceCreateSchema, data, 'workspace');
@@ -572,7 +666,16 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
572
666
  return getOrCreateTempWorkspace();
573
667
  });
574
668
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_SELECT, async (_, id) => {
575
- return workspaceRepo.findById(id);
669
+ const workspace = workspaceRepo.findById(id);
670
+ if (workspace && workspace.id !== types_1.TEMP_WORKSPACE_ID) {
671
+ try {
672
+ workspaceRepo.updateLastUsedAt(workspace.id);
673
+ }
674
+ catch (error) {
675
+ console.warn('Failed to update workspace last used time:', error);
676
+ }
677
+ }
678
+ return workspace;
576
679
  });
577
680
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_UPDATE_PERMISSIONS, async (_, id, permissions) => {
578
681
  const workspace = workspaceRepo.findById(id);
@@ -583,6 +686,14 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
583
686
  workspaceRepo.updatePermissions(id, updatedPermissions);
584
687
  return workspaceRepo.findById(id);
585
688
  });
689
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_TOUCH, async (_, id) => {
690
+ const workspace = workspaceRepo.findById(id);
691
+ if (!workspace) {
692
+ throw new Error(`Workspace not found: ${id}`);
693
+ }
694
+ workspaceRepo.updateLastUsedAt(id);
695
+ return workspaceRepo.findById(id);
696
+ });
586
697
  // Task handlers
587
698
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_CREATE, async (_, data) => {
588
699
  checkRateLimit(types_1.IPC_CHANNELS.TASK_CREATE);
@@ -596,6 +707,37 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
596
707
  budgetTokens,
597
708
  budgetCost,
598
709
  });
710
+ if (workspaceId !== types_1.TEMP_WORKSPACE_ID) {
711
+ try {
712
+ workspaceRepo.updateLastUsedAt(workspaceId);
713
+ }
714
+ catch (error) {
715
+ console.warn('Failed to update workspace last used time:', error);
716
+ }
717
+ }
718
+ // Capture mentioned agent roles for deferred dispatch (after main plan is created)
719
+ try {
720
+ const activeRoles = agentRoleRepo.findAll(false).filter((role) => role.isActive);
721
+ const mentionedRoles = extractMentionedRoles(`${title}\n${prompt}`, activeRoles);
722
+ const mentionedAgentRoleIds = mentionedRoles.map((role) => role.id);
723
+ if (mentionedAgentRoleIds.length > 0) {
724
+ taskRepo.update(task.id, { mentionedAgentRoleIds });
725
+ }
726
+ }
727
+ catch (error) {
728
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
729
+ console.error('Failed to record mentioned agents:', error);
730
+ // Notify user of dispatch failure via activity feed
731
+ const errorActivity = activityRepo.create({
732
+ workspaceId: task.workspaceId,
733
+ taskId: task.id,
734
+ actorType: 'system',
735
+ activityType: 'error',
736
+ title: 'Agent mention capture failed',
737
+ description: `Failed to record mentioned agents for deferred dispatch: ${errorMessage}`,
738
+ });
739
+ getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity: errorActivity });
740
+ }
599
741
  // Start task execution in agent daemon
600
742
  try {
601
743
  await agentDaemon.startTask(task);
@@ -608,68 +750,6 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
608
750
  });
609
751
  throw new Error(error.message || 'Failed to start task. Please check your LLM provider settings.');
610
752
  }
611
- // Dispatch to mentioned agents (e.g., "Please review @Vision @Loki")
612
- try {
613
- const activeRoles = agentRoleRepo.findAll(false).filter((role) => role.isActive);
614
- const mentionedRoles = extractMentionedRoles(`${title}\n${prompt}`, activeRoles);
615
- const dispatchRoles = mentionedRoles.length > 0 ? mentionedRoles : activeRoles;
616
- if (dispatchRoles.length > 0) {
617
- taskRepo.update(task.id, {
618
- mentionedAgentRoleIds: dispatchRoles.map((role) => role.id),
619
- });
620
- for (const role of dispatchRoles) {
621
- const childPrompt = buildAgentDispatchPrompt(role, task);
622
- const childTask = await agentDaemon.createChildTask({
623
- title: `@${role.displayName}: ${task.title}`,
624
- prompt: childPrompt,
625
- workspaceId: task.workspaceId,
626
- parentTaskId: task.id,
627
- agentType: 'sub',
628
- agentConfig: {
629
- ...(role.modelKey ? { modelKey: role.modelKey } : {}),
630
- ...(role.personalityId ? { personalityId: role.personalityId } : {}),
631
- retainMemory: false,
632
- },
633
- });
634
- taskRepo.update(childTask.id, {
635
- assignedAgentRoleId: role.id,
636
- boardColumn: 'todo',
637
- });
638
- const dispatchActivity = activityRepo.create({
639
- workspaceId: task.workspaceId,
640
- taskId: task.id,
641
- agentRoleId: role.id,
642
- actorType: 'system',
643
- activityType: 'agent_assigned',
644
- title: `Dispatched to ${role.displayName}`,
645
- description: childTask.title,
646
- });
647
- getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity: dispatchActivity });
648
- const mention = mentionRepo.create({
649
- workspaceId: task.workspaceId,
650
- taskId: task.id,
651
- toAgentRoleId: role.id,
652
- mentionType: 'request',
653
- context: `New task: ${task.title}`,
654
- });
655
- getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'created', mention });
656
- const mentionActivity = activityRepo.create({
657
- workspaceId: task.workspaceId,
658
- taskId: task.id,
659
- agentRoleId: role.id,
660
- actorType: 'user',
661
- activityType: 'mention',
662
- title: `@${role.displayName} mentioned`,
663
- description: mention.context,
664
- metadata: { mentionId: mention.id, mentionType: mention.mentionType },
665
- });
666
- getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity: mentionActivity });
667
- }
668
- }
669
- }
670
- catch (error) {
671
- console.error('Failed to dispatch to mentioned agents:', error);
672
- }
673
753
  return task;
674
754
  });
675
755
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_GET, async (_, id) => {
@@ -872,6 +952,26 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
872
952
  authMethod: existingSettings.openai.authMethod,
873
953
  };
874
954
  }
955
+ const normalizeAzureSettings = (incoming, existing) => {
956
+ if (!incoming && !existing)
957
+ return undefined;
958
+ const mergedDeployments = [
959
+ ...(incoming?.deployments || []),
960
+ ...(existing?.deployments || []),
961
+ ]
962
+ .map((entry) => entry.trim())
963
+ .filter(Boolean);
964
+ const deployment = (incoming?.deployment || existing?.deployment || mergedDeployments[0] || '').trim();
965
+ if (deployment && !mergedDeployments.includes(deployment)) {
966
+ mergedDeployments.unshift(deployment);
967
+ }
968
+ return {
969
+ ...(existing || {}),
970
+ ...(incoming || {}),
971
+ deployment: deployment || undefined,
972
+ deployments: mergedDeployments.length > 0 ? Array.from(new Set(mergedDeployments)) : undefined,
973
+ };
974
+ };
875
975
  llm_1.LLMProviderFactory.saveSettings({
876
976
  providerType: validated.providerType,
877
977
  modelKey: validated.modelKey,
@@ -881,12 +981,20 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
881
981
  gemini: validated.gemini,
882
982
  openrouter: validated.openrouter,
883
983
  openai: openaiSettings,
984
+ azure: normalizeAzureSettings(validated.azure, existingSettings.azure),
985
+ groq: validated.groq,
986
+ xai: validated.xai,
987
+ kimi: validated.kimi,
988
+ customProviders: validated.customProviders ?? existingSettings.customProviders,
884
989
  // Preserve cached models from existing settings
885
990
  cachedGeminiModels: existingSettings.cachedGeminiModels,
886
991
  cachedOpenRouterModels: existingSettings.cachedOpenRouterModels,
887
992
  cachedOllamaModels: existingSettings.cachedOllamaModels,
888
993
  cachedBedrockModels: existingSettings.cachedBedrockModels,
889
994
  cachedOpenAIModels: existingSettings.cachedOpenAIModels,
995
+ cachedGroqModels: existingSettings.cachedGroqModels,
996
+ cachedXaiModels: existingSettings.cachedXaiModels,
997
+ cachedKimiModels: existingSettings.cachedKimiModels,
890
998
  });
891
999
  // Clear cache so next task uses new settings
892
1000
  llm_1.LLMProviderFactory.clearCache();
@@ -902,9 +1010,12 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
902
1010
  openaiAccessToken = settings.openai?.accessToken;
903
1011
  openaiRefreshToken = settings.openai?.refreshToken;
904
1012
  }
1013
+ const resolvedProviderType = resolveCustomProviderId(config.providerType);
1014
+ const customProviderConfig = config.customProviders?.[resolvedProviderType] || config.customProviders?.[config.providerType];
1015
+ const azureDeployment = config.azure?.deployment || config.azure?.deployments?.[0];
905
1016
  const providerConfig = {
906
1017
  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),
1018
+ model: llm_1.LLMProviderFactory.getModelId(config.modelKey, config.providerType, config.ollama?.model, config.gemini?.model, config.openrouter?.model, config.openai?.model, azureDeployment, config.groq?.model, config.xai?.model, config.kimi?.model, config.customProviders),
908
1019
  anthropicApiKey: config.anthropic?.apiKey,
909
1020
  awsRegion: config.bedrock?.region,
910
1021
  awsAccessKeyId: config.bedrock?.accessKeyId,
@@ -915,9 +1026,22 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
915
1026
  ollamaApiKey: config.ollama?.apiKey,
916
1027
  geminiApiKey: config.gemini?.apiKey,
917
1028
  openrouterApiKey: config.openrouter?.apiKey,
1029
+ openrouterBaseUrl: config.openrouter?.baseUrl,
918
1030
  openaiApiKey: config.openai?.apiKey,
919
1031
  openaiAccessToken: openaiAccessToken,
920
1032
  openaiRefreshToken: openaiRefreshToken,
1033
+ azureApiKey: config.azure?.apiKey,
1034
+ azureEndpoint: config.azure?.endpoint,
1035
+ azureDeployment: azureDeployment,
1036
+ azureApiVersion: config.azure?.apiVersion,
1037
+ groqApiKey: config.groq?.apiKey,
1038
+ groqBaseUrl: config.groq?.baseUrl,
1039
+ xaiApiKey: config.xai?.apiKey,
1040
+ xaiBaseUrl: config.xai?.baseUrl,
1041
+ kimiApiKey: config.kimi?.apiKey,
1042
+ kimiBaseUrl: config.kimi?.baseUrl,
1043
+ providerApiKey: customProviderConfig?.apiKey,
1044
+ providerBaseUrl: customProviderConfig?.baseUrl,
921
1045
  };
922
1046
  return llm_1.LLMProviderFactory.testProvider(providerConfig);
923
1047
  });
@@ -936,132 +1060,230 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
936
1060
  // Get models based on the current provider type
937
1061
  let models = [];
938
1062
  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,
1063
+ const resolvedProviderType = resolveCustomProviderId(settings.providerType);
1064
+ const customEntry = llm_provider_catalog_1.CUSTOM_PROVIDER_MAP.get(resolvedProviderType);
1065
+ if (customEntry) {
1066
+ const customConfig = settings.customProviders?.[resolvedProviderType] || settings.customProviders?.[settings.providerType];
1067
+ currentModel = customConfig?.model || customEntry.defaultModel;
1068
+ models = [
1069
+ {
1070
+ key: currentModel,
1071
+ displayName: currentModel,
1072
+ description: customEntry.description || `${customEntry.name} model`,
1073
+ },
1074
+ ];
1075
+ }
1076
+ else {
1077
+ switch (settings.providerType) {
1078
+ case 'anthropic':
1079
+ case 'bedrock':
1080
+ // Use Anthropic/Bedrock models from MODELS
1081
+ models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
1082
+ key,
963
1083
  displayName: value.displayName,
964
- description: value.description,
1084
+ description: key.includes('opus') ? 'Most capable for complex work' :
1085
+ key.includes('sonnet') ? 'Balanced performance and speed' :
1086
+ 'Fast and efficient',
965
1087
  }));
1088
+ break;
1089
+ case 'gemini': {
1090
+ // For Gemini, use the specific model from settings (full model ID)
1091
+ currentModel = settings.gemini?.model || 'gemini-2.0-flash';
1092
+ // Use cached models if available, otherwise fall back to static list
1093
+ const cachedGemini = llm_1.LLMProviderFactory.getCachedModels('gemini');
1094
+ if (cachedGemini && cachedGemini.length > 0) {
1095
+ models = cachedGemini;
1096
+ }
1097
+ else {
1098
+ // Fall back to static models
1099
+ models = Object.values(llm_1.GEMINI_MODELS).map((value) => ({
1100
+ key: value.id,
1101
+ displayName: value.displayName,
1102
+ description: value.description,
1103
+ }));
1104
+ }
1105
+ // Ensure the currently selected model is in the list
1106
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1107
+ models.unshift({
1108
+ key: currentModel,
1109
+ displayName: currentModel,
1110
+ description: 'Selected model',
1111
+ });
1112
+ }
1113
+ break;
966
1114
  }
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
- });
1115
+ case 'openrouter': {
1116
+ // For OpenRouter, use the specific model from settings (full model ID)
1117
+ currentModel = settings.openrouter?.model || 'anthropic/claude-3.5-sonnet';
1118
+ // Use cached models if available, otherwise fall back to static list
1119
+ const cachedOpenRouter = llm_1.LLMProviderFactory.getCachedModels('openrouter');
1120
+ if (cachedOpenRouter && cachedOpenRouter.length > 0) {
1121
+ models = cachedOpenRouter;
1122
+ }
1123
+ else {
1124
+ // Fall back to static models
1125
+ models = Object.values(llm_1.OPENROUTER_MODELS).map((value) => ({
1126
+ key: value.id,
1127
+ displayName: value.displayName,
1128
+ description: value.description,
1129
+ }));
1130
+ }
1131
+ // Ensure the currently selected model is in the list
1132
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1133
+ models.unshift({
1134
+ key: currentModel,
1135
+ displayName: currentModel,
1136
+ description: 'Selected model',
1137
+ });
1138
+ }
1139
+ break;
974
1140
  }
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;
1141
+ case 'ollama': {
1142
+ // For Ollama, use the specific model from settings
1143
+ currentModel = settings.ollama?.model || 'llama3.2';
1144
+ // Use cached models if available, otherwise fall back to static list
1145
+ const cachedOllama = llm_1.LLMProviderFactory.getCachedModels('ollama');
1146
+ if (cachedOllama && cachedOllama.length > 0) {
1147
+ models = cachedOllama;
1148
+ }
1149
+ else {
1150
+ // Fall back to static models
1151
+ models = Object.entries(llm_1.OLLAMA_MODELS).map(([key, value]) => ({
1152
+ key,
1153
+ displayName: value.displayName,
1154
+ description: `${value.size} parameter model`,
1155
+ }));
1156
+ }
1157
+ // Ensure the currently selected model is in the list
1158
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1159
+ models.unshift({
1160
+ key: currentModel,
1161
+ displayName: currentModel,
1162
+ description: 'Selected model',
1163
+ });
1164
+ }
1165
+ break;
984
1166
  }
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,
1167
+ case 'openai': {
1168
+ // For OpenAI, use the specific model from settings
1169
+ currentModel = settings.openai?.model || 'gpt-4o-mini';
1170
+ // Use cached models if available, otherwise fall back to static list
1171
+ const cachedOpenAI = llm_1.LLMProviderFactory.getCachedModels('openai');
1172
+ if (cachedOpenAI && cachedOpenAI.length > 0) {
1173
+ models = cachedOpenAI;
1174
+ }
1175
+ else {
1176
+ // Fall back to static models
1177
+ models = [
1178
+ { key: 'gpt-4o', displayName: 'GPT-4o', description: 'Most capable model for complex tasks' },
1179
+ { key: 'gpt-4o-mini', displayName: 'GPT-4o Mini', description: 'Fast and affordable for most tasks' },
1180
+ { key: 'gpt-4-turbo', displayName: 'GPT-4 Turbo', description: 'Previous generation flagship' },
1181
+ { key: 'gpt-3.5-turbo', displayName: 'GPT-3.5 Turbo', description: 'Fast and cost-effective' },
1182
+ { key: 'o1', displayName: 'o1', description: 'Advanced reasoning model' },
1183
+ { key: 'o1-mini', displayName: 'o1 Mini', description: 'Fast reasoning model' },
1184
+ ];
1185
+ }
1186
+ // Ensure the currently selected model is in the list
1187
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1188
+ models.unshift({
1189
+ key: currentModel,
1190
+ displayName: currentModel,
1191
+ description: 'Selected model',
1192
+ });
1193
+ }
1194
+ break;
1195
+ }
1196
+ case 'azure': {
1197
+ const deployments = (settings.azure?.deployments || []).filter(Boolean);
1198
+ currentModel = settings.azure?.deployment || deployments[0] || 'deployment-name';
1199
+ models = deployments.map((deployment) => ({
1200
+ key: deployment,
1201
+ displayName: deployment,
1202
+ description: 'Azure OpenAI deployment',
991
1203
  }));
1204
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1205
+ models.unshift({
1206
+ key: currentModel,
1207
+ displayName: currentModel,
1208
+ description: 'Selected model',
1209
+ });
1210
+ }
1211
+ break;
992
1212
  }
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
- });
1213
+ case 'groq': {
1214
+ currentModel = settings.groq?.model || 'llama-3.1-8b-instant';
1215
+ const cachedGroq = llm_1.LLMProviderFactory.getCachedModels('groq');
1216
+ if (cachedGroq && cachedGroq.length > 0) {
1217
+ models = cachedGroq;
1218
+ }
1219
+ else {
1220
+ models = Object.values(llm_1.GROQ_MODELS).map((value) => ({
1221
+ key: value.id,
1222
+ displayName: value.displayName,
1223
+ description: value.description,
1224
+ }));
1225
+ }
1226
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1227
+ models.unshift({
1228
+ key: currentModel,
1229
+ displayName: currentModel,
1230
+ description: 'Selected model',
1231
+ });
1232
+ }
1233
+ break;
1000
1234
  }
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;
1235
+ case 'xai': {
1236
+ currentModel = settings.xai?.model || 'grok-4-fast-non-reasoning';
1237
+ const cachedXai = llm_1.LLMProviderFactory.getCachedModels('xai');
1238
+ if (cachedXai && cachedXai.length > 0) {
1239
+ models = cachedXai;
1240
+ }
1241
+ else {
1242
+ models = Object.values(llm_1.XAI_MODELS).map((value) => ({
1243
+ key: value.id,
1244
+ displayName: value.displayName,
1245
+ description: value.description,
1246
+ }));
1247
+ }
1248
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1249
+ models.unshift({
1250
+ key: currentModel,
1251
+ displayName: currentModel,
1252
+ description: 'Selected model',
1253
+ });
1254
+ }
1255
+ break;
1256
+ }
1257
+ case 'kimi': {
1258
+ currentModel = settings.kimi?.model || 'kimi-k2.5';
1259
+ const cachedKimi = llm_1.LLMProviderFactory.getCachedModels('kimi');
1260
+ if (cachedKimi && cachedKimi.length > 0) {
1261
+ models = cachedKimi;
1262
+ }
1263
+ else {
1264
+ models = Object.values(llm_1.KIMI_MODELS).map((value) => ({
1265
+ key: value.id,
1266
+ displayName: value.displayName,
1267
+ description: value.description,
1268
+ }));
1269
+ }
1270
+ if (currentModel && !models.some(m => m.key === currentModel)) {
1271
+ models.unshift({
1272
+ key: currentModel,
1273
+ displayName: currentModel,
1274
+ description: 'Selected model',
1275
+ });
1276
+ }
1277
+ break;
1010
1278
  }
1011
- else {
1012
- // Fall back to static models
1013
- models = Object.entries(llm_1.OLLAMA_MODELS).map(([key, value]) => ({
1279
+ default:
1280
+ // Fallback to Anthropic models
1281
+ models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
1014
1282
  key,
1015
1283
  displayName: value.displayName,
1016
- description: `${value.size} parameter model`,
1284
+ description: 'Claude model',
1017
1285
  }));
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
- }
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
1286
  }
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
1287
  }
1066
1288
  return {
1067
1289
  currentProvider: settings.providerType,
@@ -1073,26 +1295,61 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1073
1295
  // Set the current model (persists selection across sessions)
1074
1296
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_SET_MODEL, async (_, modelKey) => {
1075
1297
  const settings = llm_1.LLMProviderFactory.loadSettings();
1298
+ const resolvedProviderType = resolveCustomProviderId(settings.providerType);
1076
1299
  // 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;
1300
+ if (llm_provider_catalog_1.CUSTOM_PROVIDER_IDS.has(resolvedProviderType)) {
1301
+ const existing = settings.customProviders?.[resolvedProviderType] || {};
1302
+ settings.customProviders = {
1303
+ ...(settings.customProviders || {}),
1304
+ [resolvedProviderType]: {
1305
+ ...existing,
1306
+ model: modelKey,
1307
+ },
1308
+ };
1309
+ }
1310
+ else {
1311
+ switch (settings.providerType) {
1312
+ case 'gemini':
1313
+ settings.gemini = { ...settings.gemini, model: modelKey };
1314
+ break;
1315
+ case 'openrouter':
1316
+ settings.openrouter = { ...settings.openrouter, model: modelKey };
1317
+ break;
1318
+ case 'ollama':
1319
+ settings.ollama = { ...settings.ollama, model: modelKey };
1320
+ break;
1321
+ case 'openai':
1322
+ settings.openai = { ...settings.openai, model: modelKey };
1323
+ break;
1324
+ case 'azure':
1325
+ {
1326
+ const existingDeployments = (settings.azure?.deployments || []).filter(Boolean);
1327
+ const nextDeployments = existingDeployments.includes(modelKey)
1328
+ ? existingDeployments
1329
+ : [modelKey, ...existingDeployments];
1330
+ settings.azure = {
1331
+ ...settings.azure,
1332
+ deployment: modelKey,
1333
+ deployments: nextDeployments.length > 0 ? nextDeployments : undefined,
1334
+ };
1335
+ }
1336
+ break;
1337
+ case 'groq':
1338
+ settings.groq = { ...settings.groq, model: modelKey };
1339
+ break;
1340
+ case 'xai':
1341
+ settings.xai = { ...settings.xai, model: modelKey };
1342
+ break;
1343
+ case 'kimi':
1344
+ settings.kimi = { ...settings.kimi, model: modelKey };
1345
+ break;
1346
+ case 'anthropic':
1347
+ case 'bedrock':
1348
+ default:
1349
+ // For Anthropic/Bedrock, use the modelKey field
1350
+ settings.modelKey = modelKey;
1351
+ break;
1352
+ }
1096
1353
  }
1097
1354
  llm_1.LLMProviderFactory.saveSettings(settings);
1098
1355
  return { success: true };
@@ -1123,9 +1380,9 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1123
1380
  llm_1.LLMProviderFactory.saveCachedModels('gemini', cachedModels);
1124
1381
  return models;
1125
1382
  });
1126
- electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, async (_, apiKey) => {
1383
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, async (_, apiKey, baseUrl) => {
1127
1384
  checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS);
1128
- const models = await llm_1.LLMProviderFactory.getOpenRouterModels(apiKey);
1385
+ const models = await llm_1.LLMProviderFactory.getOpenRouterModels(apiKey, baseUrl);
1129
1386
  // Cache the models for use in config status
1130
1387
  const cachedModels = models.map(m => ({
1131
1388
  key: m.id,
@@ -1148,6 +1405,39 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1148
1405
  llm_1.LLMProviderFactory.saveCachedModels('openai', cachedModels);
1149
1406
  return models;
1150
1407
  });
1408
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_GROQ_MODELS, async (_, apiKey, baseUrl) => {
1409
+ checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_GROQ_MODELS);
1410
+ const models = await llm_1.LLMProviderFactory.getGroqModels(apiKey, baseUrl);
1411
+ const cachedModels = models.map(m => ({
1412
+ key: m.id,
1413
+ displayName: m.name,
1414
+ description: 'Groq model',
1415
+ }));
1416
+ llm_1.LLMProviderFactory.saveCachedModels('groq', cachedModels);
1417
+ return models;
1418
+ });
1419
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_XAI_MODELS, async (_, apiKey, baseUrl) => {
1420
+ checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_XAI_MODELS);
1421
+ const models = await llm_1.LLMProviderFactory.getXAIModels(apiKey, baseUrl);
1422
+ const cachedModels = models.map(m => ({
1423
+ key: m.id,
1424
+ displayName: m.name,
1425
+ description: 'xAI model',
1426
+ }));
1427
+ llm_1.LLMProviderFactory.saveCachedModels('xai', cachedModels);
1428
+ return models;
1429
+ });
1430
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_KIMI_MODELS, async (_, apiKey, baseUrl) => {
1431
+ checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_KIMI_MODELS);
1432
+ const models = await llm_1.LLMProviderFactory.getKimiModels(apiKey, baseUrl);
1433
+ const cachedModels = models.map(m => ({
1434
+ key: m.id,
1435
+ displayName: m.name,
1436
+ description: 'Kimi model',
1437
+ }));
1438
+ llm_1.LLMProviderFactory.saveCachedModels('kimi', cachedModels);
1439
+ return models;
1440
+ });
1151
1441
  // OpenAI OAuth handlers
1152
1442
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_START, async () => {
1153
1443
  checkRateLimit(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_START);
@@ -1260,6 +1550,204 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1260
1550
  error: result.success ? undefined : result.error,
1261
1551
  };
1262
1552
  });
1553
+ // Notion Settings handlers
1554
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_GET_SETTINGS, async () => {
1555
+ return notion_manager_1.NotionSettingsManager.loadSettings();
1556
+ });
1557
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_SAVE_SETTINGS, async (_, settings) => {
1558
+ checkRateLimit(types_1.IPC_CHANNELS.NOTION_SAVE_SETTINGS);
1559
+ const validated = (0, validation_1.validateInput)(validation_1.NotionSettingsSchema, settings, 'notion settings');
1560
+ notion_manager_1.NotionSettingsManager.saveSettings(validated);
1561
+ notion_manager_1.NotionSettingsManager.clearCache();
1562
+ return { success: true };
1563
+ });
1564
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_TEST_CONNECTION, async () => {
1565
+ checkRateLimit(types_1.IPC_CHANNELS.NOTION_TEST_CONNECTION);
1566
+ const settings = notion_manager_1.NotionSettingsManager.loadSettings();
1567
+ return (0, notion_api_1.testNotionConnection)(settings);
1568
+ });
1569
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTION_GET_STATUS, async () => {
1570
+ checkRateLimit(types_1.IPC_CHANNELS.NOTION_GET_STATUS);
1571
+ const settings = notion_manager_1.NotionSettingsManager.loadSettings();
1572
+ if (!settings.apiKey) {
1573
+ return { configured: false, connected: false };
1574
+ }
1575
+ if (!settings.enabled) {
1576
+ return { configured: true, connected: false };
1577
+ }
1578
+ const result = await (0, notion_api_1.testNotionConnection)(settings);
1579
+ return {
1580
+ configured: true,
1581
+ connected: result.success,
1582
+ name: result.name,
1583
+ error: result.success ? undefined : result.error,
1584
+ };
1585
+ });
1586
+ // Box Settings handlers
1587
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_GET_SETTINGS, async () => {
1588
+ return box_manager_1.BoxSettingsManager.loadSettings();
1589
+ });
1590
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_SAVE_SETTINGS, async (_, settings) => {
1591
+ checkRateLimit(types_1.IPC_CHANNELS.BOX_SAVE_SETTINGS);
1592
+ const validated = (0, validation_1.validateInput)(validation_1.BoxSettingsSchema, settings, 'box settings');
1593
+ box_manager_1.BoxSettingsManager.saveSettings(validated);
1594
+ box_manager_1.BoxSettingsManager.clearCache();
1595
+ return { success: true };
1596
+ });
1597
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_TEST_CONNECTION, async () => {
1598
+ checkRateLimit(types_1.IPC_CHANNELS.BOX_TEST_CONNECTION);
1599
+ const settings = box_manager_1.BoxSettingsManager.loadSettings();
1600
+ return (0, box_api_1.testBoxConnection)(settings);
1601
+ });
1602
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BOX_GET_STATUS, async () => {
1603
+ checkRateLimit(types_1.IPC_CHANNELS.BOX_GET_STATUS);
1604
+ const settings = box_manager_1.BoxSettingsManager.loadSettings();
1605
+ if (!settings.accessToken) {
1606
+ return { configured: false, connected: false };
1607
+ }
1608
+ if (!settings.enabled) {
1609
+ return { configured: true, connected: false };
1610
+ }
1611
+ const result = await (0, box_api_1.testBoxConnection)(settings);
1612
+ return {
1613
+ configured: true,
1614
+ connected: result.success,
1615
+ name: result.name,
1616
+ error: result.success ? undefined : result.error,
1617
+ };
1618
+ });
1619
+ // OneDrive Settings handlers
1620
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_GET_SETTINGS, async () => {
1621
+ return onedrive_manager_1.OneDriveSettingsManager.loadSettings();
1622
+ });
1623
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_SAVE_SETTINGS, async (_, settings) => {
1624
+ checkRateLimit(types_1.IPC_CHANNELS.ONEDRIVE_SAVE_SETTINGS);
1625
+ const validated = (0, validation_1.validateInput)(validation_1.OneDriveSettingsSchema, settings, 'onedrive settings');
1626
+ onedrive_manager_1.OneDriveSettingsManager.saveSettings(validated);
1627
+ onedrive_manager_1.OneDriveSettingsManager.clearCache();
1628
+ return { success: true };
1629
+ });
1630
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_TEST_CONNECTION, async () => {
1631
+ checkRateLimit(types_1.IPC_CHANNELS.ONEDRIVE_TEST_CONNECTION);
1632
+ const settings = onedrive_manager_1.OneDriveSettingsManager.loadSettings();
1633
+ return (0, onedrive_api_1.testOneDriveConnection)(settings);
1634
+ });
1635
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ONEDRIVE_GET_STATUS, async () => {
1636
+ checkRateLimit(types_1.IPC_CHANNELS.ONEDRIVE_GET_STATUS);
1637
+ const settings = onedrive_manager_1.OneDriveSettingsManager.loadSettings();
1638
+ if (!settings.accessToken) {
1639
+ return { configured: false, connected: false };
1640
+ }
1641
+ if (!settings.enabled) {
1642
+ return { configured: true, connected: false };
1643
+ }
1644
+ const result = await (0, onedrive_api_1.testOneDriveConnection)(settings);
1645
+ return {
1646
+ configured: true,
1647
+ connected: result.success,
1648
+ name: result.name,
1649
+ error: result.success ? undefined : result.error,
1650
+ };
1651
+ });
1652
+ // Google Drive Settings handlers
1653
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_GET_SETTINGS, async () => {
1654
+ return google_drive_manager_1.GoogleDriveSettingsManager.loadSettings();
1655
+ });
1656
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_SAVE_SETTINGS, async (_, settings) => {
1657
+ checkRateLimit(types_1.IPC_CHANNELS.GOOGLE_DRIVE_SAVE_SETTINGS);
1658
+ const validated = (0, validation_1.validateInput)(validation_1.GoogleDriveSettingsSchema, settings, 'google drive settings');
1659
+ google_drive_manager_1.GoogleDriveSettingsManager.saveSettings(validated);
1660
+ google_drive_manager_1.GoogleDriveSettingsManager.clearCache();
1661
+ return { success: true };
1662
+ });
1663
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_TEST_CONNECTION, async () => {
1664
+ checkRateLimit(types_1.IPC_CHANNELS.GOOGLE_DRIVE_TEST_CONNECTION);
1665
+ const settings = google_drive_manager_1.GoogleDriveSettingsManager.loadSettings();
1666
+ return (0, google_drive_api_1.testGoogleDriveConnection)(settings);
1667
+ });
1668
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GOOGLE_DRIVE_GET_STATUS, async () => {
1669
+ checkRateLimit(types_1.IPC_CHANNELS.GOOGLE_DRIVE_GET_STATUS);
1670
+ const settings = google_drive_manager_1.GoogleDriveSettingsManager.loadSettings();
1671
+ if (!settings.accessToken) {
1672
+ return { configured: false, connected: false };
1673
+ }
1674
+ if (!settings.enabled) {
1675
+ return { configured: true, connected: false };
1676
+ }
1677
+ const result = await (0, google_drive_api_1.testGoogleDriveConnection)(settings);
1678
+ return {
1679
+ configured: true,
1680
+ connected: result.success,
1681
+ name: result.name,
1682
+ error: result.success ? undefined : result.error,
1683
+ };
1684
+ });
1685
+ // Dropbox Settings handlers
1686
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_GET_SETTINGS, async () => {
1687
+ return dropbox_manager_1.DropboxSettingsManager.loadSettings();
1688
+ });
1689
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_SAVE_SETTINGS, async (_, settings) => {
1690
+ checkRateLimit(types_1.IPC_CHANNELS.DROPBOX_SAVE_SETTINGS);
1691
+ const validated = (0, validation_1.validateInput)(validation_1.DropboxSettingsSchema, settings, 'dropbox settings');
1692
+ dropbox_manager_1.DropboxSettingsManager.saveSettings(validated);
1693
+ dropbox_manager_1.DropboxSettingsManager.clearCache();
1694
+ return { success: true };
1695
+ });
1696
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_TEST_CONNECTION, async () => {
1697
+ checkRateLimit(types_1.IPC_CHANNELS.DROPBOX_TEST_CONNECTION);
1698
+ const settings = dropbox_manager_1.DropboxSettingsManager.loadSettings();
1699
+ return (0, dropbox_api_1.testDropboxConnection)(settings);
1700
+ });
1701
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.DROPBOX_GET_STATUS, async () => {
1702
+ checkRateLimit(types_1.IPC_CHANNELS.DROPBOX_GET_STATUS);
1703
+ const settings = dropbox_manager_1.DropboxSettingsManager.loadSettings();
1704
+ if (!settings.accessToken) {
1705
+ return { configured: false, connected: false };
1706
+ }
1707
+ if (!settings.enabled) {
1708
+ return { configured: true, connected: false };
1709
+ }
1710
+ const result = await (0, dropbox_api_1.testDropboxConnection)(settings);
1711
+ return {
1712
+ configured: true,
1713
+ connected: result.success,
1714
+ name: result.name,
1715
+ error: result.success ? undefined : result.error,
1716
+ };
1717
+ });
1718
+ // SharePoint Settings handlers
1719
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_GET_SETTINGS, async () => {
1720
+ return sharepoint_manager_1.SharePointSettingsManager.loadSettings();
1721
+ });
1722
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_SAVE_SETTINGS, async (_, settings) => {
1723
+ checkRateLimit(types_1.IPC_CHANNELS.SHAREPOINT_SAVE_SETTINGS);
1724
+ const validated = (0, validation_1.validateInput)(validation_1.SharePointSettingsSchema, settings, 'sharepoint settings');
1725
+ sharepoint_manager_1.SharePointSettingsManager.saveSettings(validated);
1726
+ sharepoint_manager_1.SharePointSettingsManager.clearCache();
1727
+ return { success: true };
1728
+ });
1729
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_TEST_CONNECTION, async () => {
1730
+ checkRateLimit(types_1.IPC_CHANNELS.SHAREPOINT_TEST_CONNECTION);
1731
+ const settings = sharepoint_manager_1.SharePointSettingsManager.loadSettings();
1732
+ return (0, sharepoint_api_1.testSharePointConnection)(settings);
1733
+ });
1734
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SHAREPOINT_GET_STATUS, async () => {
1735
+ checkRateLimit(types_1.IPC_CHANNELS.SHAREPOINT_GET_STATUS);
1736
+ const settings = sharepoint_manager_1.SharePointSettingsManager.loadSettings();
1737
+ if (!settings.accessToken) {
1738
+ return { configured: false, connected: false };
1739
+ }
1740
+ if (!settings.enabled) {
1741
+ return { configured: true, connected: false };
1742
+ }
1743
+ const result = await (0, sharepoint_api_1.testSharePointConnection)(settings);
1744
+ return {
1745
+ configured: true,
1746
+ connected: result.success,
1747
+ name: result.name,
1748
+ error: result.success ? undefined : result.error,
1749
+ };
1750
+ });
1263
1751
  // Gateway / Channel handlers
1264
1752
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_GET_CHANNELS, async () => {
1265
1753
  if (!gateway)
@@ -1679,7 +2167,8 @@ async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
1679
2167
  throw new Error('Agent role not found');
1680
2168
  }
1681
2169
  }
1682
- taskRepo.update(validatedTaskId, { assignedAgentRoleId: agentRoleId ?? undefined });
2170
+ const taskUpdate = { assignedAgentRoleId: agentRoleId ?? undefined };
2171
+ taskRepo.update(validatedTaskId, taskUpdate);
1683
2172
  const task = taskRepo.findById(validatedTaskId);
1684
2173
  if (task) {
1685
2174
  if (agentRoleId) {
@@ -2030,6 +2519,7 @@ function setupMCPHandlers() {
2030
2519
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_CONNECT_SERVER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2031
2520
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_TEST_SERVER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2032
2521
  rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_REGISTRY_INSTALL, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2522
+ rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_CONNECTOR_OAUTH_START, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
2033
2523
  // Initialize MCP settings manager
2034
2524
  settings_1.MCPSettingsManager.initialize();
2035
2525
  // Get settings
@@ -2053,7 +2543,7 @@ function setupMCPHandlers() {
2053
2543
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_ADD_SERVER, async (_, serverConfig) => {
2054
2544
  checkRateLimit(types_1.IPC_CHANNELS.MCP_ADD_SERVER);
2055
2545
  const validated = (0, validation_1.validateInput)(validation_2.MCPServerConfigSchema, serverConfig, 'MCP server config');
2056
- const { id, ...configWithoutId } = validated;
2546
+ const { id: _id, ...configWithoutId } = validated;
2057
2547
  return settings_1.MCPSettingsManager.addServer(configWithoutId);
2058
2548
  });
2059
2549
  // Update a server
@@ -2136,6 +2626,12 @@ function setupMCPHandlers() {
2136
2626
  const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
2137
2627
  return MCPRegistryManager_1.MCPRegistryManager.updateServer(validatedId);
2138
2628
  });
2629
+ // MCP Connector OAuth (Salesforce/Jira)
2630
+ electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_CONNECTOR_OAUTH_START, async (_, payload) => {
2631
+ checkRateLimit(types_1.IPC_CHANNELS.MCP_CONNECTOR_OAUTH_START);
2632
+ const validated = (0, validation_1.validateInput)(validation_1.MCPConnectorOAuthSchema, payload, 'connector oauth');
2633
+ return (0, connector_oauth_1.startConnectorOAuth)(validated);
2634
+ });
2139
2635
  // MCP Host handlers
2140
2636
  electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_HOST_START, async () => {
2141
2637
  const hostServer = MCPHostServer_1.MCPHostServer.getInstance();