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.
- package/README.md +293 -6
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/daemon.js +25 -0
- package/dist/electron/electron/agent/executor.js +181 -26
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +11 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +541 -0
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/gateway/index.js +1 -0
- package/dist/electron/electron/gateway/router.js +123 -143
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +627 -158
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +1 -1
- package/dist/electron/electron/preload.js +74 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +82 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +88 -1
- package/package.json +12 -2
- package/src/electron/agent/executor.ts +205 -28
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +5 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +414 -6
- package/src/electron/agent/llm/types.ts +90 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +34 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +565 -0
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/gateway/index.ts +1 -0
- package/src/electron/gateway/router.ts +134 -149
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +673 -153
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +5 -1
- package/src/electron/preload.ts +167 -4
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-drive-manager.ts +58 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/google-drive-api.ts +183 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +102 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +20 -2
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +270 -34
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/Settings.tsx +611 -8
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +25 -9
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +438 -25
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- 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
|
-
|
|
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
|
+
}
|