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
@@ -3,7 +3,9 @@
3
3
  * Allows switching between Anthropic API and AWS Bedrock
4
4
  */
5
5
 
6
- export type LLMProviderType = 'anthropic' | 'bedrock' | 'ollama' | 'gemini' | 'openrouter' | 'openai';
6
+ import type { LLMProviderType } from '../../../shared/types';
7
+
8
+ export type { LLMProviderType };
7
9
 
8
10
  export interface LLMProviderConfig {
9
11
  type: LLMProviderType;
@@ -24,11 +26,24 @@ export interface LLMProviderConfig {
24
26
  geminiApiKey?: string;
25
27
  // OpenRouter-specific
26
28
  openrouterApiKey?: string;
29
+ openrouterBaseUrl?: string;
27
30
  // OpenAI-specific
28
31
  openaiApiKey?: string;
29
32
  openaiAccessToken?: string; // OAuth access token
30
33
  openaiRefreshToken?: string; // OAuth refresh token
31
34
  openaiTokenExpiresAt?: number; // OAuth token expiry timestamp
35
+ // Groq-specific
36
+ groqApiKey?: string;
37
+ groqBaseUrl?: string;
38
+ // xAI-specific
39
+ xaiApiKey?: string;
40
+ xaiBaseUrl?: string;
41
+ // Kimi-specific
42
+ kimiApiKey?: string;
43
+ kimiBaseUrl?: string;
44
+ // Generic provider support
45
+ providerApiKey?: string;
46
+ providerBaseUrl?: string;
32
47
  }
33
48
 
34
49
  export interface LLMTool {
@@ -267,6 +282,80 @@ export const OPENAI_MODELS = {
267
282
 
268
283
  export type OpenAIModelKey = keyof typeof OPENAI_MODELS;
269
284
 
285
+ /**
286
+ * Popular Groq models
287
+ */
288
+ export const GROQ_MODELS = {
289
+ 'llama-3.1-8b-instant': {
290
+ id: 'llama-3.1-8b-instant',
291
+ displayName: 'Llama 3.1 8B Instant',
292
+ description: 'Fast, cost-efficient Groq model',
293
+ },
294
+ 'llama-3.3-70b-versatile': {
295
+ id: 'llama-3.3-70b-versatile',
296
+ displayName: 'Llama 3.3 70B Versatile',
297
+ description: 'Higher capability Groq model',
298
+ },
299
+ } as const;
300
+
301
+ export type GroqModelKey = keyof typeof GROQ_MODELS;
302
+
303
+ /**
304
+ * Popular xAI (Grok) models
305
+ */
306
+ export const XAI_MODELS = {
307
+ 'grok-4': {
308
+ id: 'grok-4',
309
+ displayName: 'Grok 4',
310
+ description: 'Flagship model',
311
+ },
312
+ 'grok-4-fast-non-reasoning': {
313
+ id: 'grok-4-fast-non-reasoning',
314
+ displayName: 'Grok 4 Fast (Non-Reasoning)',
315
+ description: 'Fast responses without explicit reasoning',
316
+ },
317
+ 'grok-4-fast-reasoning': {
318
+ id: 'grok-4-fast-reasoning',
319
+ displayName: 'Grok 4 Fast (Reasoning)',
320
+ description: 'Faster model with reasoning support',
321
+ },
322
+ } as const;
323
+
324
+ export type XAIModelKey = keyof typeof XAI_MODELS;
325
+
326
+ /**
327
+ * Kimi (Moonshot) models
328
+ */
329
+ export const KIMI_MODELS = {
330
+ 'kimi-k2.5': {
331
+ id: 'kimi-k2.5',
332
+ displayName: 'Kimi K2.5',
333
+ description: 'Latest Kimi K2.5 model',
334
+ },
335
+ 'kimi-k2-0905-preview': {
336
+ id: 'kimi-k2-0905-preview',
337
+ displayName: 'Kimi K2.5 Preview',
338
+ description: 'Preview K2.5 model',
339
+ },
340
+ 'kimi-k2-turbo-preview': {
341
+ id: 'kimi-k2-turbo-preview',
342
+ displayName: 'Kimi K2 Turbo (Preview)',
343
+ description: 'Faster K2 preview model',
344
+ },
345
+ 'kimi-k2-thinking': {
346
+ id: 'kimi-k2-thinking',
347
+ displayName: 'Kimi K2 Thinking',
348
+ description: 'Reasoning-focused K2 model',
349
+ },
350
+ 'kimi-k2-thinking-turbo': {
351
+ id: 'kimi-k2-thinking-turbo',
352
+ displayName: 'Kimi K2 Thinking Turbo',
353
+ description: 'Faster reasoning K2 model',
354
+ },
355
+ } as const;
356
+
357
+ export type KimiModelKey = keyof typeof KIMI_MODELS;
358
+
270
359
  /**
271
360
  * Popular Ollama models with their details
272
361
  * Users can use any model available on their Ollama server
@@ -0,0 +1,39 @@
1
+ import { LLMProvider, LLMProviderConfig, LLMRequest, LLMResponse } from './types';
2
+ import { OpenAICompatibleProvider } from './openai-compatible-provider';
3
+
4
+ const XAI_BASE_URL = 'https://api.x.ai/v1';
5
+ const DEFAULT_XAI_MODEL = 'grok-4-fast-non-reasoning';
6
+
7
+ export class XAIProvider implements LLMProvider {
8
+ readonly type = 'xai' as const;
9
+ private client: OpenAICompatibleProvider;
10
+
11
+ constructor(config: LLMProviderConfig) {
12
+ const apiKey = config.xaiApiKey;
13
+ if (!apiKey) {
14
+ throw new Error('xAI API key is required. Configure it in Settings.');
15
+ }
16
+
17
+ const baseUrl = config.xaiBaseUrl || XAI_BASE_URL;
18
+
19
+ this.client = new OpenAICompatibleProvider({
20
+ type: 'xai',
21
+ providerName: 'xAI',
22
+ apiKey,
23
+ baseUrl,
24
+ defaultModel: config.model || DEFAULT_XAI_MODEL,
25
+ });
26
+ }
27
+
28
+ createMessage(request: LLMRequest): Promise<LLMResponse> {
29
+ return this.client.createMessage(request);
30
+ }
31
+
32
+ testConnection() {
33
+ return this.client.testConnection();
34
+ }
35
+
36
+ getAvailableModels() {
37
+ return this.client.getAvailableModels();
38
+ }
39
+ }
@@ -0,0 +1,239 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Workspace } from '../../../shared/types';
4
+ import { AgentDaemon } from '../daemon';
5
+ import { BoxSettingsManager } from '../../settings/box-manager';
6
+ import { boxRequest, boxUploadFile } from '../../utils/box-api';
7
+
8
+ type BoxAction =
9
+ | 'get_current_user'
10
+ | 'search'
11
+ | 'get_file'
12
+ | 'get_folder'
13
+ | 'list_folder_items'
14
+ | 'create_folder'
15
+ | 'delete_file'
16
+ | 'delete_folder'
17
+ | 'upload_file';
18
+
19
+ interface BoxActionInput {
20
+ action: BoxAction;
21
+ query?: string;
22
+ limit?: number;
23
+ offset?: number;
24
+ fields?: string;
25
+ type?: 'file' | 'folder' | 'web_link';
26
+ ancestor_folder_ids?: string;
27
+ file_extensions?: string;
28
+ content_types?: string;
29
+ scope?: string;
30
+ folder_id?: string;
31
+ file_id?: string;
32
+ parent_id?: string;
33
+ name?: string;
34
+ file_path?: string;
35
+ }
36
+
37
+ const DEFAULT_FOLDER_ID = '0';
38
+
39
+ export class BoxTools {
40
+ constructor(
41
+ private workspace: Workspace,
42
+ private daemon: AgentDaemon,
43
+ private taskId: string
44
+ ) {}
45
+
46
+ setWorkspace(workspace: Workspace): void {
47
+ this.workspace = workspace;
48
+ }
49
+
50
+ static isEnabled(): boolean {
51
+ return BoxSettingsManager.loadSettings().enabled;
52
+ }
53
+
54
+ private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
55
+ const approved = await this.daemon.requestApproval(
56
+ this.taskId,
57
+ 'external_service',
58
+ summary,
59
+ details
60
+ );
61
+
62
+ if (!approved) {
63
+ throw new Error('User denied Box action');
64
+ }
65
+ }
66
+
67
+ private resolveFilePath(inputPath: string): string {
68
+ if (!this.workspace.permissions.read) {
69
+ throw new Error('Read permission not granted for uploads');
70
+ }
71
+
72
+ const workspaceRoot = path.resolve(this.workspace.path);
73
+ const allowedPaths = this.workspace.permissions.allowedPaths || [];
74
+ const canReadOutside = this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess;
75
+
76
+ const isPathAllowed = (absolutePath: string): boolean => {
77
+ if (allowedPaths.length === 0) return false;
78
+ const normalizedPath = path.normalize(absolutePath);
79
+ return allowedPaths.some((allowed) => {
80
+ const normalizedAllowed = path.normalize(allowed);
81
+ return normalizedPath === normalizedAllowed || normalizedPath.startsWith(normalizedAllowed + path.sep);
82
+ });
83
+ };
84
+
85
+ const candidate = path.isAbsolute(inputPath)
86
+ ? path.normalize(inputPath)
87
+ : path.resolve(workspaceRoot, inputPath);
88
+
89
+ const relative = path.relative(workspaceRoot, candidate);
90
+ const isInsideWorkspace = !(relative.startsWith('..') || path.isAbsolute(relative));
91
+ if (!isInsideWorkspace && !canReadOutside && !isPathAllowed(candidate)) {
92
+ throw new Error('File path must be inside the workspace or in Allowed Paths');
93
+ }
94
+ if (!fs.existsSync(candidate)) {
95
+ throw new Error(`File not found: ${inputPath}`);
96
+ }
97
+ const stats = fs.statSync(candidate);
98
+ if (!stats.isFile()) {
99
+ throw new Error(`Path is not a file: ${inputPath}`);
100
+ }
101
+ return candidate;
102
+ }
103
+
104
+ async executeAction(input: BoxActionInput): Promise<any> {
105
+ const settings = BoxSettingsManager.loadSettings();
106
+ if (!settings.enabled) {
107
+ throw new Error('Box integration is disabled. Enable it in Settings > Integrations > Box.');
108
+ }
109
+
110
+ const action = input.action;
111
+ if (!action) {
112
+ throw new Error('Missing required "action" parameter');
113
+ }
114
+
115
+ let result;
116
+
117
+ switch (action) {
118
+ case 'get_current_user': {
119
+ result = await boxRequest(settings, { method: 'GET', path: '/users/me' });
120
+ break;
121
+ }
122
+ case 'search': {
123
+ if (!input.query) throw new Error('Missing query for search');
124
+ result = await boxRequest(settings, {
125
+ method: 'GET',
126
+ path: '/search',
127
+ query: {
128
+ query: input.query,
129
+ limit: input.limit,
130
+ offset: input.offset,
131
+ fields: input.fields,
132
+ type: input.type,
133
+ ancestor_folder_ids: input.ancestor_folder_ids,
134
+ file_extensions: input.file_extensions,
135
+ content_types: input.content_types,
136
+ scope: input.scope,
137
+ },
138
+ });
139
+ break;
140
+ }
141
+ case 'get_file': {
142
+ if (!input.file_id) throw new Error('Missing file_id for get_file');
143
+ result = await boxRequest(settings, {
144
+ method: 'GET',
145
+ path: `/files/${input.file_id}`,
146
+ query: input.fields ? { fields: input.fields } : undefined,
147
+ });
148
+ break;
149
+ }
150
+ case 'get_folder': {
151
+ if (!input.folder_id) throw new Error('Missing folder_id for get_folder');
152
+ result = await boxRequest(settings, {
153
+ method: 'GET',
154
+ path: `/folders/${input.folder_id}`,
155
+ query: input.fields ? { fields: input.fields } : undefined,
156
+ });
157
+ break;
158
+ }
159
+ case 'list_folder_items': {
160
+ const folderId = input.folder_id || DEFAULT_FOLDER_ID;
161
+ result = await boxRequest(settings, {
162
+ method: 'GET',
163
+ path: `/folders/${folderId}/items`,
164
+ query: {
165
+ limit: input.limit,
166
+ offset: input.offset,
167
+ fields: input.fields,
168
+ },
169
+ });
170
+ break;
171
+ }
172
+ case 'create_folder': {
173
+ if (!input.name) throw new Error('Missing name for create_folder');
174
+ const parentId = input.parent_id || DEFAULT_FOLDER_ID;
175
+ await this.requireApproval('Create a Box folder', {
176
+ action: 'create_folder',
177
+ parent_id: parentId,
178
+ name: input.name,
179
+ });
180
+ result = await boxRequest(settings, {
181
+ method: 'POST',
182
+ path: '/folders',
183
+ body: {
184
+ name: input.name,
185
+ parent: { id: parentId },
186
+ },
187
+ });
188
+ break;
189
+ }
190
+ case 'delete_file': {
191
+ if (!input.file_id) throw new Error('Missing file_id for delete_file');
192
+ await this.requireApproval('Delete a Box file', { action: 'delete_file', file_id: input.file_id });
193
+ result = await boxRequest(settings, { method: 'DELETE', path: `/files/${input.file_id}` });
194
+ break;
195
+ }
196
+ case 'delete_folder': {
197
+ if (!input.folder_id) throw new Error('Missing folder_id for delete_folder');
198
+ await this.requireApproval('Delete a Box folder', { action: 'delete_folder', folder_id: input.folder_id });
199
+ result = await boxRequest(settings, { method: 'DELETE', path: `/folders/${input.folder_id}` });
200
+ break;
201
+ }
202
+ case 'upload_file': {
203
+ if (!input.file_path) throw new Error('Missing file_path for upload_file');
204
+ const parentId = input.parent_id || DEFAULT_FOLDER_ID;
205
+ const resolved = this.resolveFilePath(input.file_path);
206
+ const data = fs.readFileSync(resolved);
207
+ const fileName = input.name || path.basename(resolved);
208
+ await this.requireApproval(`Upload file to Box: ${fileName}`, {
209
+ action: 'upload_file',
210
+ parent_id: parentId,
211
+ file: fileName,
212
+ });
213
+ result = await boxUploadFile(settings, {
214
+ fileName,
215
+ parentId,
216
+ data,
217
+ });
218
+ break;
219
+ }
220
+ default:
221
+ throw new Error(`Unsupported action: ${action}`);
222
+ }
223
+
224
+ this.daemon.logEvent(this.taskId, 'tool_result', {
225
+ tool: 'box_action',
226
+ action,
227
+ status: result?.status,
228
+ hasData: result?.data ? true : false,
229
+ });
230
+
231
+ return {
232
+ success: true,
233
+ action,
234
+ status: result?.status,
235
+ data: result?.data,
236
+ raw: result?.raw,
237
+ };
238
+ }
239
+ }
@@ -44,6 +44,10 @@ export interface BuiltinToolsSettings {
44
44
  };
45
45
  // Individual tool overrides (tool name -> override)
46
46
  toolOverrides: Record<string, ToolOverride>;
47
+ // Per-tool timeout overrides in milliseconds (tool name -> timeout)
48
+ toolTimeouts: Record<string, number>;
49
+ // Per-tool auto-approval overrides (tool name -> enabled)
50
+ toolAutoApprove: Record<string, boolean>;
47
51
  // Version for migrations
48
52
  version: string;
49
53
  }
@@ -100,6 +104,8 @@ const DEFAULT_SETTINGS: BuiltinToolsSettings = {
100
104
  },
101
105
  },
102
106
  toolOverrides: {},
107
+ toolTimeouts: {},
108
+ toolAutoApprove: {},
103
109
  version: '1.0.0',
104
110
  };
105
111
 
@@ -113,6 +119,12 @@ const TOOL_CATEGORIES: Record<string, keyof BuiltinToolsSettings['categories']>
113
119
  edit_file: 'code',
114
120
  // Web fetch tools (high priority)
115
121
  web_fetch: 'webfetch',
122
+ notion_action: 'webfetch',
123
+ box_action: 'webfetch',
124
+ onedrive_action: 'webfetch',
125
+ google_drive_action: 'webfetch',
126
+ dropbox_action: 'webfetch',
127
+ sharepoint_action: 'webfetch',
116
128
  // Browser tools
117
129
  browser_navigate: 'browser',
118
130
  browser_screenshot: 'browser',
@@ -301,6 +313,8 @@ export class BuiltinToolsSettingsManager {
301
313
  ...settings.categories,
302
314
  },
303
315
  toolOverrides: settings.toolOverrides || {},
316
+ toolTimeouts: settings.toolTimeouts || {},
317
+ toolAutoApprove: settings.toolAutoApprove || {},
304
318
  version: settings.version || defaults.version,
305
319
  };
306
320
  }
@@ -353,6 +367,26 @@ export class BuiltinToolsSettingsManager {
353
367
  return TOOL_CATEGORIES[toolName] || null;
354
368
  }
355
369
 
370
+ /**
371
+ * Get per-tool timeout override (ms), if configured
372
+ */
373
+ static getToolTimeoutMs(toolName: string): number | null {
374
+ const settings = this.loadSettings();
375
+ const timeout = settings.toolTimeouts?.[toolName];
376
+ if (typeof timeout !== 'number' || !Number.isFinite(timeout) || timeout <= 0) {
377
+ return null;
378
+ }
379
+ return Math.round(timeout);
380
+ }
381
+
382
+ /**
383
+ * Check if a tool should be auto-approved
384
+ */
385
+ static getToolAutoApprove(toolName: string): boolean {
386
+ const settings = this.loadSettings();
387
+ return Boolean(settings.toolAutoApprove?.[toolName]);
388
+ }
389
+
356
390
  /**
357
391
  * Get all tool categories with their tools
358
392
  */
@@ -0,0 +1,237 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Workspace } from '../../../shared/types';
4
+ import { AgentDaemon } from '../daemon';
5
+ import { DropboxSettingsManager } from '../../settings/dropbox-manager';
6
+ import { dropboxRequest, dropboxContentUpload } from '../../utils/dropbox-api';
7
+
8
+ type DropboxAction =
9
+ | 'get_current_user'
10
+ | 'list_folder'
11
+ | 'list_folder_continue'
12
+ | 'search'
13
+ | 'get_metadata'
14
+ | 'create_folder'
15
+ | 'delete_item'
16
+ | 'upload_file';
17
+
18
+ interface DropboxActionInput {
19
+ action: DropboxAction;
20
+ path?: string;
21
+ query?: string;
22
+ limit?: number;
23
+ cursor?: string;
24
+ name?: string;
25
+ parent_path?: string;
26
+ file_path?: string;
27
+ }
28
+
29
+ export class DropboxTools {
30
+ constructor(
31
+ private workspace: Workspace,
32
+ private daemon: AgentDaemon,
33
+ private taskId: string
34
+ ) {}
35
+
36
+ setWorkspace(workspace: Workspace): void {
37
+ this.workspace = workspace;
38
+ }
39
+
40
+ static isEnabled(): boolean {
41
+ return DropboxSettingsManager.loadSettings().enabled;
42
+ }
43
+
44
+ private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
45
+ const approved = await this.daemon.requestApproval(
46
+ this.taskId,
47
+ 'external_service',
48
+ summary,
49
+ details
50
+ );
51
+
52
+ if (!approved) {
53
+ throw new Error('User denied Dropbox action');
54
+ }
55
+ }
56
+
57
+ private resolveFilePath(inputPath: string): string {
58
+ if (!this.workspace.permissions.read) {
59
+ throw new Error('Read permission not granted for uploads');
60
+ }
61
+
62
+ const workspaceRoot = path.resolve(this.workspace.path);
63
+ const allowedPaths = this.workspace.permissions.allowedPaths || [];
64
+ const canReadOutside = this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess;
65
+
66
+ const isPathAllowed = (absolutePath: string): boolean => {
67
+ if (allowedPaths.length === 0) return false;
68
+ const normalizedPath = path.normalize(absolutePath);
69
+ return allowedPaths.some((allowed) => {
70
+ const normalizedAllowed = path.normalize(allowed);
71
+ return normalizedPath === normalizedAllowed || normalizedPath.startsWith(normalizedAllowed + path.sep);
72
+ });
73
+ };
74
+
75
+ const candidate = path.isAbsolute(inputPath)
76
+ ? path.normalize(inputPath)
77
+ : path.resolve(workspaceRoot, inputPath);
78
+
79
+ const relative = path.relative(workspaceRoot, candidate);
80
+ const isInsideWorkspace = !(relative.startsWith('..') || path.isAbsolute(relative));
81
+ if (!isInsideWorkspace && !canReadOutside && !isPathAllowed(candidate)) {
82
+ throw new Error('File path must be inside the workspace or in Allowed Paths');
83
+ }
84
+ if (!fs.existsSync(candidate)) {
85
+ throw new Error(`File not found: ${inputPath}`);
86
+ }
87
+ const stats = fs.statSync(candidate);
88
+ if (!stats.isFile()) {
89
+ throw new Error(`Path is not a file: ${inputPath}`);
90
+ }
91
+ return candidate;
92
+ }
93
+
94
+ private normalizeDropboxPath(pathValue: string): string {
95
+ const trimmed = pathValue.trim();
96
+ if (!trimmed) return '';
97
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
98
+ }
99
+
100
+ async executeAction(input: DropboxActionInput): Promise<any> {
101
+ const settings = DropboxSettingsManager.loadSettings();
102
+ if (!settings.enabled) {
103
+ throw new Error('Dropbox integration is disabled. Enable it in Settings > Integrations > Dropbox.');
104
+ }
105
+
106
+ const action = input.action;
107
+ if (!action) {
108
+ throw new Error('Missing required "action" parameter');
109
+ }
110
+
111
+ let result;
112
+
113
+ switch (action) {
114
+ case 'get_current_user': {
115
+ result = await dropboxRequest(settings, { method: 'POST', path: '/users/get_current_account' });
116
+ break;
117
+ }
118
+ case 'list_folder': {
119
+ const pathValue = input.path ? this.normalizeDropboxPath(input.path) : '';
120
+ result = await dropboxRequest(settings, {
121
+ method: 'POST',
122
+ path: '/files/list_folder',
123
+ body: {
124
+ path: pathValue,
125
+ recursive: false,
126
+ include_deleted: false,
127
+ include_has_explicit_shared_members: false,
128
+ include_mounted_folders: true,
129
+ limit: input.limit,
130
+ },
131
+ });
132
+ break;
133
+ }
134
+ case 'list_folder_continue': {
135
+ if (!input.cursor) throw new Error('Missing cursor for list_folder_continue');
136
+ result = await dropboxRequest(settings, {
137
+ method: 'POST',
138
+ path: '/files/list_folder/continue',
139
+ body: { cursor: input.cursor },
140
+ });
141
+ break;
142
+ }
143
+ case 'search': {
144
+ if (!input.query) throw new Error('Missing query for search');
145
+ result = await dropboxRequest(settings, {
146
+ method: 'POST',
147
+ path: '/files/search_v2',
148
+ body: {
149
+ query: input.query,
150
+ options: {
151
+ path: input.path ? this.normalizeDropboxPath(input.path) : undefined,
152
+ max_results: input.limit,
153
+ },
154
+ },
155
+ });
156
+ break;
157
+ }
158
+ case 'get_metadata': {
159
+ if (!input.path) throw new Error('Missing path for get_metadata');
160
+ result = await dropboxRequest(settings, {
161
+ method: 'POST',
162
+ path: '/files/get_metadata',
163
+ body: {
164
+ path: this.normalizeDropboxPath(input.path),
165
+ include_media_info: false,
166
+ include_deleted: false,
167
+ include_has_explicit_shared_members: false,
168
+ },
169
+ });
170
+ break;
171
+ }
172
+ case 'create_folder': {
173
+ if (!input.path) throw new Error('Missing path for create_folder');
174
+ const folderPath = this.normalizeDropboxPath(input.path);
175
+ await this.requireApproval('Create a Dropbox folder', {
176
+ action: 'create_folder',
177
+ path: folderPath,
178
+ });
179
+ result = await dropboxRequest(settings, {
180
+ method: 'POST',
181
+ path: '/files/create_folder_v2',
182
+ body: {
183
+ path: folderPath,
184
+ autorename: true,
185
+ },
186
+ });
187
+ break;
188
+ }
189
+ case 'delete_item': {
190
+ if (!input.path) throw new Error('Missing path for delete_item');
191
+ const deletePath = this.normalizeDropboxPath(input.path);
192
+ await this.requireApproval('Delete a Dropbox item', {
193
+ action: 'delete_item',
194
+ path: deletePath,
195
+ });
196
+ result = await dropboxRequest(settings, {
197
+ method: 'POST',
198
+ path: '/files/delete_v2',
199
+ body: { path: deletePath },
200
+ });
201
+ break;
202
+ }
203
+ case 'upload_file': {
204
+ if (!input.file_path) throw new Error('Missing file_path for upload_file');
205
+ const resolved = this.resolveFilePath(input.file_path);
206
+ const data = fs.readFileSync(resolved);
207
+ const fileName = input.name || path.basename(resolved);
208
+ const targetPath = input.path
209
+ ? this.normalizeDropboxPath(input.path)
210
+ : this.normalizeDropboxPath(`${input.parent_path || ''}/${fileName}`);
211
+ await this.requireApproval(`Upload file to Dropbox: ${fileName}`, {
212
+ action: 'upload_file',
213
+ path: targetPath,
214
+ });
215
+ result = await dropboxContentUpload(settings, { path: targetPath, data });
216
+ break;
217
+ }
218
+ default:
219
+ throw new Error(`Unsupported action: ${action}`);
220
+ }
221
+
222
+ this.daemon.logEvent(this.taskId, 'tool_result', {
223
+ tool: 'dropbox_action',
224
+ action,
225
+ status: result?.status,
226
+ hasData: result?.data ? true : false,
227
+ });
228
+
229
+ return {
230
+ success: true,
231
+ action,
232
+ status: result?.status,
233
+ data: result?.data,
234
+ raw: result?.raw,
235
+ };
236
+ }
237
+ }