cowork-os 0.3.21 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +372 -10
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
- package/dist/electron/electron/agent/daemon.js +189 -13
- package/dist/electron/electron/agent/executor.js +895 -78
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +13 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/search/provider-factory.js +38 -2
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/file-tools.js +66 -3
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
- package/dist/electron/electron/agent/tools/image-tools.js +11 -1
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +548 -10
- package/dist/electron/electron/agent/tools/search-tools.js +28 -10
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/agents/agent-dispatch.js +63 -0
- package/dist/electron/electron/database/repositories.js +19 -5
- package/dist/electron/electron/database/schema.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
- package/dist/electron/electron/gateway/index.js +75 -1
- package/dist/electron/electron/gateway/router.js +209 -154
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +763 -267
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +2 -1
- package/dist/electron/electron/preload.js +78 -1
- package/dist/electron/electron/settings/appearance-manager.js +18 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +98 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +90 -1
- package/package.json +14 -3
- package/resources/skills/nano-banana-pro.json +4 -4
- package/resources/skills/openai-image-gen.json +3 -3
- package/resources/skills/scripts/gen.py +163 -0
- package/resources/skills/scripts/generate_image.py +91 -0
- package/src/electron/agent/custom-skill-loader.ts +34 -1
- package/src/electron/agent/daemon.ts +210 -14
- package/src/electron/agent/executor.ts +1124 -85
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
- package/src/electron/agent/llm/bedrock-provider.ts +62 -9
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +6 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +459 -6
- package/src/electron/agent/llm/types.ts +95 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/search/provider-factory.ts +43 -2
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +36 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/file-tools.ts +66 -3
- package/src/electron/agent/tools/gmail-tools.ts +240 -0
- package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/grep-tools.ts +97 -12
- package/src/electron/agent/tools/image-tools.ts +11 -1
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +794 -10
- package/src/electron/agent/tools/search-tools.ts +29 -11
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/agents/agent-dispatch.ts +79 -0
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/database/repositories.ts +58 -6
- package/src/electron/database/schema.ts +8 -0
- package/src/electron/gateway/channels/discord.ts +4 -0
- package/src/electron/gateway/channels/google-chat.ts +3 -0
- package/src/electron/gateway/channels/line.ts +3 -0
- package/src/electron/gateway/channels/matrix-client.ts +15 -0
- package/src/electron/gateway/channels/matrix.ts +31 -0
- package/src/electron/gateway/channels/mattermost.ts +3 -0
- package/src/electron/gateway/channels/signal.ts +3 -0
- package/src/electron/gateway/channels/slack.ts +9 -4
- package/src/electron/gateway/channels/teams.ts +4 -0
- package/src/electron/gateway/channels/telegram.ts +2 -0
- package/src/electron/gateway/channels/twitch.ts +2 -0
- package/src/electron/gateway/channels/types.ts +8 -0
- package/src/electron/gateway/channels/whatsapp.ts +66 -0
- package/src/electron/gateway/index.ts +95 -2
- package/src/electron/gateway/router.ts +231 -161
- package/src/electron/gateway/security.ts +21 -9
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +848 -292
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +7 -1
- package/src/electron/preload.ts +200 -5
- package/src/electron/settings/appearance-manager.ts +20 -2
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-workspace-manager.ts +59 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/gmail-api.ts +121 -0
- package/src/electron/utils/google-calendar-api.ts +115 -0
- package/src/electron/utils/google-workspace-api.ts +228 -0
- package/src/electron/utils/google-workspace-auth.ts +109 -0
- package/src/electron/utils/google-workspace-oauth.ts +232 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +128 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +119 -8
- package/src/renderer/components/ActivityFeedItem.tsx +34 -17
- package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
- package/src/renderer/components/AppearanceSettings.tsx +37 -2
- package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
- package/src/renderer/components/DiscordSettings.tsx +18 -7
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/EmailSettings.tsx +18 -7
- package/src/renderer/components/FileViewer.tsx +21 -13
- package/src/renderer/components/GoogleChatSettings.tsx +17 -7
- package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
- package/src/renderer/components/ImessageSettings.tsx +22 -11
- package/src/renderer/components/LineIcons.tsx +376 -0
- package/src/renderer/components/LineSettings.tsx +18 -7
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +740 -76
- package/src/renderer/components/MatrixSettings.tsx +18 -7
- package/src/renderer/components/MattermostSettings.tsx +18 -7
- package/src/renderer/components/NodesSettings.tsx +58 -99
- package/src/renderer/components/NotificationPanel.tsx +25 -11
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/RightPanel.tsx +141 -28
- package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
- package/src/renderer/components/SearchSettings.tsx +118 -114
- package/src/renderer/components/Settings.tsx +1425 -651
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +94 -19
- package/src/renderer/components/SignalSettings.tsx +18 -7
- package/src/renderer/components/SkillHubBrowser.tsx +144 -185
- package/src/renderer/components/SlackSettings.tsx +18 -7
- package/src/renderer/components/TaskQuickActions.tsx +11 -6
- package/src/renderer/components/TaskTimeline.tsx +58 -26
- package/src/renderer/components/TeamsSettings.tsx +18 -7
- package/src/renderer/components/TelegramSettings.tsx +18 -7
- package/src/renderer/components/ThemeIcon.tsx +16 -0
- package/src/renderer/components/TwitchSettings.tsx +18 -7
- package/src/renderer/components/VoiceSettings.tsx +30 -74
- package/src/renderer/components/WhatsAppSettings.tsx +48 -37
- package/src/renderer/components/WorkingStateHistory.tsx +7 -5
- package/src/renderer/components/WorkspaceSelector.tsx +42 -13
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +2333 -209
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- package/src/shared/types.ts +251 -2
|
@@ -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
|
+
}
|
|
@@ -178,11 +178,27 @@ export class FileTools {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
this.checkPermission('read');
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
let fullPath = this.resolvePath(relativePath, 'read');
|
|
182
|
+
let ext = path.extname(fullPath).toLowerCase();
|
|
183
183
|
|
|
184
184
|
try {
|
|
185
|
-
|
|
185
|
+
let stats: { size: number };
|
|
186
|
+
try {
|
|
187
|
+
stats = await fs.stat(fullPath);
|
|
188
|
+
} catch (error: any) {
|
|
189
|
+
if (this.isNotFoundError(error) && !path.isAbsolute(relativePath)) {
|
|
190
|
+
const fallbackPath = await this.resolveCaseInsensitivePath(relativePath);
|
|
191
|
+
if (fallbackPath && fallbackPath !== fullPath) {
|
|
192
|
+
fullPath = fallbackPath;
|
|
193
|
+
ext = path.extname(fullPath).toLowerCase();
|
|
194
|
+
stats = await fs.stat(fullPath);
|
|
195
|
+
} else {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
186
202
|
|
|
187
203
|
// Handle DOCX files
|
|
188
204
|
if (ext === '.docx') {
|
|
@@ -224,6 +240,53 @@ export class FileTools {
|
|
|
224
240
|
}
|
|
225
241
|
}
|
|
226
242
|
|
|
243
|
+
private isNotFoundError(error: any): boolean {
|
|
244
|
+
const code = error?.code;
|
|
245
|
+
if (code === 'ENOENT' || code === 'ENOTDIR') return true;
|
|
246
|
+
const message = String(error?.message || '');
|
|
247
|
+
return /no such file/i.test(message) || /not found/i.test(message);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Try resolving a path case-insensitively within the workspace.
|
|
252
|
+
* Only applies to workspace-relative paths without traversal.
|
|
253
|
+
*/
|
|
254
|
+
private async resolveCaseInsensitivePath(relativePath: string): Promise<string | null> {
|
|
255
|
+
const normalized = path.normalize(relativePath);
|
|
256
|
+
if (path.isAbsolute(normalized)) return null;
|
|
257
|
+
|
|
258
|
+
const parts = normalized.split(path.sep).filter(Boolean);
|
|
259
|
+
if (parts.some(part => part === '..')) return null;
|
|
260
|
+
|
|
261
|
+
let current = this.workspace.path;
|
|
262
|
+
for (let i = 0; i < parts.length; i++) {
|
|
263
|
+
const segment = parts[i];
|
|
264
|
+
const lower = segment.toLowerCase();
|
|
265
|
+
let entries: Array<{ name: string; isDirectory: () => boolean }>;
|
|
266
|
+
try {
|
|
267
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
268
|
+
} catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (entries.length > MAX_DIR_ENTRIES * 5) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const match = entries.find(entry => entry.name.toLowerCase() === lower);
|
|
277
|
+
if (!match) return null;
|
|
278
|
+
|
|
279
|
+
const nextPath = path.join(current, match.name);
|
|
280
|
+
const isLast = i === parts.length - 1;
|
|
281
|
+
if (!isLast && !match.isDirectory()) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
current = nextPath;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return current;
|
|
288
|
+
}
|
|
289
|
+
|
|
227
290
|
/**
|
|
228
291
|
* Read DOCX file and extract text content
|
|
229
292
|
*/
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { Workspace } from '../../../shared/types';
|
|
2
|
+
import { AgentDaemon } from '../daemon';
|
|
3
|
+
import { GoogleWorkspaceSettingsManager } from '../../settings/google-workspace-manager';
|
|
4
|
+
import { gmailRequest } from '../../utils/gmail-api';
|
|
5
|
+
|
|
6
|
+
type GmailAction =
|
|
7
|
+
| 'get_profile'
|
|
8
|
+
| 'list_messages'
|
|
9
|
+
| 'get_message'
|
|
10
|
+
| 'get_thread'
|
|
11
|
+
| 'list_labels'
|
|
12
|
+
| 'send_message'
|
|
13
|
+
| 'trash_message';
|
|
14
|
+
|
|
15
|
+
interface GmailActionInput {
|
|
16
|
+
action: GmailAction;
|
|
17
|
+
query?: string;
|
|
18
|
+
page_size?: number;
|
|
19
|
+
page_token?: string;
|
|
20
|
+
label_ids?: string[];
|
|
21
|
+
include_spam_trash?: boolean;
|
|
22
|
+
message_id?: string;
|
|
23
|
+
thread_id?: string;
|
|
24
|
+
format?: 'full' | 'metadata' | 'minimal' | 'raw';
|
|
25
|
+
metadata_headers?: string[];
|
|
26
|
+
to?: string;
|
|
27
|
+
cc?: string;
|
|
28
|
+
bcc?: string;
|
|
29
|
+
subject?: string;
|
|
30
|
+
body?: string;
|
|
31
|
+
raw?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function encodeMessage(raw: string): string {
|
|
35
|
+
return Buffer.from(raw)
|
|
36
|
+
.toString('base64')
|
|
37
|
+
.replace(/\+/g, '-')
|
|
38
|
+
.replace(/\//g, '_')
|
|
39
|
+
.replace(/=+$/, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildRawEmail(input: GmailActionInput): string {
|
|
43
|
+
const headers: string[] = [];
|
|
44
|
+
if (input.to) headers.push(`To: ${input.to}`);
|
|
45
|
+
if (input.cc) headers.push(`Cc: ${input.cc}`);
|
|
46
|
+
if (input.bcc) headers.push(`Bcc: ${input.bcc}`);
|
|
47
|
+
if (input.subject) headers.push(`Subject: ${input.subject}`);
|
|
48
|
+
headers.push('MIME-Version: 1.0');
|
|
49
|
+
headers.push('Content-Type: text/plain; charset="UTF-8"');
|
|
50
|
+
headers.push('Content-Transfer-Encoding: 7bit');
|
|
51
|
+
|
|
52
|
+
const body = input.body ?? '';
|
|
53
|
+
const message = `${headers.join('\r\n')}\r\n\r\n${body}`;
|
|
54
|
+
return encodeMessage(message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class GmailTools {
|
|
58
|
+
constructor(
|
|
59
|
+
private workspace: Workspace,
|
|
60
|
+
private daemon: AgentDaemon,
|
|
61
|
+
private taskId: string
|
|
62
|
+
) {}
|
|
63
|
+
|
|
64
|
+
setWorkspace(workspace: Workspace): void {
|
|
65
|
+
this.workspace = workspace;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static isEnabled(): boolean {
|
|
69
|
+
return GoogleWorkspaceSettingsManager.loadSettings().enabled;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private formatAuthError(error: unknown): string | null {
|
|
73
|
+
const message = String((error as any)?.message ?? '');
|
|
74
|
+
const status = (error as any)?.status;
|
|
75
|
+
if (status === 401) {
|
|
76
|
+
return 'Google Workspace authorization failed (401). Reconnect in Settings > Integrations > Google Workspace.';
|
|
77
|
+
}
|
|
78
|
+
if (/token refresh failed|refresh token not configured|access token not configured|access token expired/i.test(message)) {
|
|
79
|
+
return `Google Workspace authorization error: ${message}`;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
|
|
85
|
+
const approved = await this.daemon.requestApproval(
|
|
86
|
+
this.taskId,
|
|
87
|
+
'external_service',
|
|
88
|
+
summary,
|
|
89
|
+
details
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!approved) {
|
|
93
|
+
throw new Error('User denied Gmail action');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async executeAction(input: GmailActionInput): Promise<any> {
|
|
98
|
+
const settings = GoogleWorkspaceSettingsManager.loadSettings();
|
|
99
|
+
if (!settings.enabled) {
|
|
100
|
+
throw new Error('Google Workspace integration is disabled. Enable it in Settings > Integrations > Google Workspace.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const action = input.action;
|
|
104
|
+
if (!action) {
|
|
105
|
+
throw new Error('Missing required "action" parameter');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let result;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
switch (action) {
|
|
112
|
+
case 'get_profile': {
|
|
113
|
+
result = await gmailRequest(settings, {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
path: '/users/me/profile',
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'list_messages': {
|
|
120
|
+
result = await gmailRequest(settings, {
|
|
121
|
+
method: 'GET',
|
|
122
|
+
path: '/users/me/messages',
|
|
123
|
+
query: {
|
|
124
|
+
q: input.query,
|
|
125
|
+
maxResults: input.page_size,
|
|
126
|
+
pageToken: input.page_token,
|
|
127
|
+
includeSpamTrash: input.include_spam_trash,
|
|
128
|
+
labelIds: input.label_ids,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'get_message': {
|
|
134
|
+
if (!input.message_id) throw new Error('Missing message_id for get_message');
|
|
135
|
+
result = await gmailRequest(settings, {
|
|
136
|
+
method: 'GET',
|
|
137
|
+
path: `/users/me/messages/${input.message_id}`,
|
|
138
|
+
query: {
|
|
139
|
+
format: input.format,
|
|
140
|
+
metadataHeaders: input.metadata_headers ? input.metadata_headers.join(',') : undefined,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'get_thread': {
|
|
146
|
+
if (!input.thread_id) throw new Error('Missing thread_id for get_thread');
|
|
147
|
+
result = await gmailRequest(settings, {
|
|
148
|
+
method: 'GET',
|
|
149
|
+
path: `/users/me/threads/${input.thread_id}`,
|
|
150
|
+
query: {
|
|
151
|
+
format: input.format,
|
|
152
|
+
metadataHeaders: input.metadata_headers ? input.metadata_headers.join(',') : undefined,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'list_labels': {
|
|
158
|
+
result = await gmailRequest(settings, {
|
|
159
|
+
method: 'GET',
|
|
160
|
+
path: '/users/me/labels',
|
|
161
|
+
});
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case 'send_message': {
|
|
165
|
+
if (!input.raw && !input.to) {
|
|
166
|
+
throw new Error('Missing to for send_message');
|
|
167
|
+
}
|
|
168
|
+
if (!input.raw && !input.body && !input.subject) {
|
|
169
|
+
throw new Error('Missing body or subject for send_message');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await this.requireApproval('Send a Gmail message', {
|
|
173
|
+
action: 'send_message',
|
|
174
|
+
to: input.to,
|
|
175
|
+
subject: input.subject,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const raw = input.raw || buildRawEmail(input);
|
|
179
|
+
const payload: Record<string, any> = { raw };
|
|
180
|
+
if (input.thread_id) {
|
|
181
|
+
payload.threadId = input.thread_id;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
result = await gmailRequest(settings, {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
path: '/users/me/messages/send',
|
|
187
|
+
body: payload,
|
|
188
|
+
});
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'trash_message': {
|
|
192
|
+
if (!input.message_id) throw new Error('Missing message_id for trash_message');
|
|
193
|
+
await this.requireApproval('Trash a Gmail message', {
|
|
194
|
+
action: 'trash_message',
|
|
195
|
+
message_id: input.message_id,
|
|
196
|
+
});
|
|
197
|
+
result = await gmailRequest(settings, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
path: `/users/me/messages/${input.message_id}/trash`,
|
|
200
|
+
});
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
default:
|
|
204
|
+
throw new Error(`Unsupported action: ${action}`);
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
208
|
+
const authMessage = this.formatAuthError(error);
|
|
209
|
+
const finalMessage = authMessage ?? message;
|
|
210
|
+
this.daemon.logEvent(this.taskId, 'tool_error', {
|
|
211
|
+
tool: 'gmail_action',
|
|
212
|
+
action,
|
|
213
|
+
message: finalMessage,
|
|
214
|
+
status: (error as any)?.status,
|
|
215
|
+
});
|
|
216
|
+
if (authMessage) {
|
|
217
|
+
throw new Error(authMessage);
|
|
218
|
+
}
|
|
219
|
+
if (error instanceof Error) {
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
throw new Error(message);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.daemon.logEvent(this.taskId, 'tool_result', {
|
|
226
|
+
tool: 'gmail_action',
|
|
227
|
+
action,
|
|
228
|
+
status: result?.status,
|
|
229
|
+
hasData: result?.data ? true : false,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
action,
|
|
235
|
+
status: result?.status,
|
|
236
|
+
data: result?.data,
|
|
237
|
+
raw: result?.raw,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|