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,258 @@
|
|
|
1
|
+
import { Workspace } from '../../../shared/types';
|
|
2
|
+
import { AgentDaemon } from '../daemon';
|
|
3
|
+
import { GoogleWorkspaceSettingsManager } from '../../settings/google-workspace-manager';
|
|
4
|
+
import { googleCalendarRequest } from '../../utils/google-calendar-api';
|
|
5
|
+
|
|
6
|
+
type CalendarAction =
|
|
7
|
+
| 'list_calendars'
|
|
8
|
+
| 'list_events'
|
|
9
|
+
| 'get_event'
|
|
10
|
+
| 'create_event'
|
|
11
|
+
| 'update_event'
|
|
12
|
+
| 'delete_event';
|
|
13
|
+
|
|
14
|
+
interface GoogleCalendarActionInput {
|
|
15
|
+
action: CalendarAction;
|
|
16
|
+
calendar_id?: string;
|
|
17
|
+
event_id?: string;
|
|
18
|
+
query?: string;
|
|
19
|
+
time_min?: string;
|
|
20
|
+
time_max?: string;
|
|
21
|
+
max_results?: number;
|
|
22
|
+
page_token?: string;
|
|
23
|
+
single_events?: boolean;
|
|
24
|
+
order_by?: 'startTime' | 'updated';
|
|
25
|
+
summary?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
location?: string;
|
|
28
|
+
start?: string | { dateTime?: string; date?: string; timeZone?: string };
|
|
29
|
+
end?: string | { dateTime?: string; date?: string; timeZone?: string };
|
|
30
|
+
attendees?: Array<string | { email: string }>;
|
|
31
|
+
time_zone?: string;
|
|
32
|
+
payload?: Record<string, any>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildAttendees(attendees?: Array<string | { email: string }>): Array<{ email: string }> | undefined {
|
|
36
|
+
if (!attendees || attendees.length === 0) return undefined;
|
|
37
|
+
return attendees.map((attendee) => {
|
|
38
|
+
if (typeof attendee === 'string') {
|
|
39
|
+
return { email: attendee };
|
|
40
|
+
}
|
|
41
|
+
return { email: attendee.email };
|
|
42
|
+
}).filter((attendee) => attendee.email);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildEventPayload(input: GoogleCalendarActionInput): Record<string, any> {
|
|
46
|
+
if (input.payload) {
|
|
47
|
+
return input.payload;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!input.summary || !input.start || !input.end) {
|
|
51
|
+
throw new Error('Missing summary/start/end for calendar event');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const event: Record<string, any> = {
|
|
55
|
+
summary: input.summary,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (input.description) event.description = input.description;
|
|
59
|
+
if (input.location) event.location = input.location;
|
|
60
|
+
|
|
61
|
+
const timeZone = input.time_zone;
|
|
62
|
+
|
|
63
|
+
const buildDateField = (value: string | { dateTime?: string; date?: string; timeZone?: string }) => {
|
|
64
|
+
if (typeof value === 'string') {
|
|
65
|
+
return {
|
|
66
|
+
dateTime: value,
|
|
67
|
+
timeZone,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
...value,
|
|
72
|
+
timeZone: value.timeZone || timeZone,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
event.start = buildDateField(input.start);
|
|
77
|
+
event.end = buildDateField(input.end);
|
|
78
|
+
|
|
79
|
+
const attendees = buildAttendees(input.attendees);
|
|
80
|
+
if (attendees) {
|
|
81
|
+
event.attendees = attendees;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return event;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class GoogleCalendarTools {
|
|
88
|
+
constructor(
|
|
89
|
+
private workspace: Workspace,
|
|
90
|
+
private daemon: AgentDaemon,
|
|
91
|
+
private taskId: string
|
|
92
|
+
) {}
|
|
93
|
+
|
|
94
|
+
setWorkspace(workspace: Workspace): void {
|
|
95
|
+
this.workspace = workspace;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static isEnabled(): boolean {
|
|
99
|
+
return GoogleWorkspaceSettingsManager.loadSettings().enabled;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private formatAuthError(error: unknown): string | null {
|
|
103
|
+
const message = String((error as any)?.message ?? '');
|
|
104
|
+
const status = (error as any)?.status;
|
|
105
|
+
if (status === 401) {
|
|
106
|
+
return 'Google Workspace authorization failed (401). Reconnect in Settings > Integrations > Google Workspace.';
|
|
107
|
+
}
|
|
108
|
+
if (/token refresh failed|refresh token not configured|access token not configured|access token expired/i.test(message)) {
|
|
109
|
+
return `Google Workspace authorization error: ${message}`;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
|
|
115
|
+
const approved = await this.daemon.requestApproval(
|
|
116
|
+
this.taskId,
|
|
117
|
+
'external_service',
|
|
118
|
+
summary,
|
|
119
|
+
details
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (!approved) {
|
|
123
|
+
throw new Error('User denied Google Calendar action');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async executeAction(input: GoogleCalendarActionInput): Promise<any> {
|
|
128
|
+
const settings = GoogleWorkspaceSettingsManager.loadSettings();
|
|
129
|
+
if (!settings.enabled) {
|
|
130
|
+
throw new Error('Google Workspace integration is disabled. Enable it in Settings > Integrations > Google Workspace.');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const action = input.action;
|
|
134
|
+
if (!action) {
|
|
135
|
+
throw new Error('Missing required "action" parameter');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const calendarId = input.calendar_id || 'primary';
|
|
139
|
+
let result;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
switch (action) {
|
|
143
|
+
case 'list_calendars': {
|
|
144
|
+
result = await googleCalendarRequest(settings, {
|
|
145
|
+
method: 'GET',
|
|
146
|
+
path: '/users/me/calendarList',
|
|
147
|
+
query: {
|
|
148
|
+
maxResults: input.max_results,
|
|
149
|
+
pageToken: input.page_token,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case 'list_events': {
|
|
155
|
+
result = await googleCalendarRequest(settings, {
|
|
156
|
+
method: 'GET',
|
|
157
|
+
path: `/calendars/${encodeURIComponent(calendarId)}/events`,
|
|
158
|
+
query: {
|
|
159
|
+
q: input.query,
|
|
160
|
+
timeMin: input.time_min,
|
|
161
|
+
timeMax: input.time_max,
|
|
162
|
+
maxResults: input.max_results,
|
|
163
|
+
pageToken: input.page_token,
|
|
164
|
+
singleEvents: input.single_events ?? true,
|
|
165
|
+
orderBy: input.order_by,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'get_event': {
|
|
171
|
+
if (!input.event_id) throw new Error('Missing event_id for get_event');
|
|
172
|
+
result = await googleCalendarRequest(settings, {
|
|
173
|
+
method: 'GET',
|
|
174
|
+
path: `/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`,
|
|
175
|
+
});
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case 'create_event': {
|
|
179
|
+
const eventPayload = buildEventPayload(input);
|
|
180
|
+
await this.requireApproval('Create a Google Calendar event', {
|
|
181
|
+
action: 'create_event',
|
|
182
|
+
calendar_id: calendarId,
|
|
183
|
+
summary: eventPayload.summary,
|
|
184
|
+
});
|
|
185
|
+
result = await googleCalendarRequest(settings, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
path: `/calendars/${encodeURIComponent(calendarId)}/events`,
|
|
188
|
+
body: eventPayload,
|
|
189
|
+
});
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case 'update_event': {
|
|
193
|
+
if (!input.event_id) throw new Error('Missing event_id for update_event');
|
|
194
|
+
const updatePayload = buildEventPayload(input);
|
|
195
|
+
await this.requireApproval('Update a Google Calendar event', {
|
|
196
|
+
action: 'update_event',
|
|
197
|
+
calendar_id: calendarId,
|
|
198
|
+
event_id: input.event_id,
|
|
199
|
+
summary: updatePayload.summary,
|
|
200
|
+
});
|
|
201
|
+
result = await googleCalendarRequest(settings, {
|
|
202
|
+
method: 'PATCH',
|
|
203
|
+
path: `/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`,
|
|
204
|
+
body: updatePayload,
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'delete_event': {
|
|
209
|
+
if (!input.event_id) throw new Error('Missing event_id for delete_event');
|
|
210
|
+
await this.requireApproval('Delete a Google Calendar event', {
|
|
211
|
+
action: 'delete_event',
|
|
212
|
+
calendar_id: calendarId,
|
|
213
|
+
event_id: input.event_id,
|
|
214
|
+
});
|
|
215
|
+
result = await googleCalendarRequest(settings, {
|
|
216
|
+
method: 'DELETE',
|
|
217
|
+
path: `/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`,
|
|
218
|
+
});
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
default:
|
|
222
|
+
throw new Error(`Unsupported action: ${action}`);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
226
|
+
const authMessage = this.formatAuthError(error);
|
|
227
|
+
const finalMessage = authMessage ?? message;
|
|
228
|
+
this.daemon.logEvent(this.taskId, 'tool_error', {
|
|
229
|
+
tool: 'calendar_action',
|
|
230
|
+
action,
|
|
231
|
+
message: finalMessage,
|
|
232
|
+
status: (error as any)?.status,
|
|
233
|
+
});
|
|
234
|
+
if (authMessage) {
|
|
235
|
+
throw new Error(authMessage);
|
|
236
|
+
}
|
|
237
|
+
if (error instanceof Error) {
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
throw new Error(message);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.daemon.logEvent(this.taskId, 'tool_result', {
|
|
244
|
+
tool: 'calendar_action',
|
|
245
|
+
action,
|
|
246
|
+
status: result?.status,
|
|
247
|
+
hasData: result?.data ? true : false,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
success: true,
|
|
252
|
+
action,
|
|
253
|
+
status: result?.status,
|
|
254
|
+
data: result?.data,
|
|
255
|
+
raw: result?.raw,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import mime from 'mime-types';
|
|
4
|
+
import { Workspace } from '../../../shared/types';
|
|
5
|
+
import { AgentDaemon } from '../daemon';
|
|
6
|
+
import { GoogleWorkspaceSettingsManager } from '../../settings/google-workspace-manager';
|
|
7
|
+
import { googleDriveRequest, googleDriveUpload } from '../../utils/google-workspace-api';
|
|
8
|
+
|
|
9
|
+
type GoogleDriveAction =
|
|
10
|
+
| 'get_current_user'
|
|
11
|
+
| 'list_files'
|
|
12
|
+
| 'get_file'
|
|
13
|
+
| 'create_folder'
|
|
14
|
+
| 'upload_file'
|
|
15
|
+
| 'delete_file';
|
|
16
|
+
|
|
17
|
+
interface GoogleDriveActionInput {
|
|
18
|
+
action: GoogleDriveAction;
|
|
19
|
+
query?: string;
|
|
20
|
+
page_size?: number;
|
|
21
|
+
page_token?: string;
|
|
22
|
+
fields?: string;
|
|
23
|
+
file_id?: string;
|
|
24
|
+
parent_id?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
file_path?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_LIST_FIELDS =
|
|
30
|
+
'nextPageToken, files(id,name,mimeType,modifiedTime,parents,webViewLink,size)';
|
|
31
|
+
const DEFAULT_FILE_FIELDS =
|
|
32
|
+
'id,name,mimeType,modifiedTime,parents,webViewLink,size';
|
|
33
|
+
|
|
34
|
+
export class GoogleDriveTools {
|
|
35
|
+
constructor(
|
|
36
|
+
private workspace: Workspace,
|
|
37
|
+
private daemon: AgentDaemon,
|
|
38
|
+
private taskId: string
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
setWorkspace(workspace: Workspace): void {
|
|
42
|
+
this.workspace = workspace;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static isEnabled(): boolean {
|
|
46
|
+
return GoogleWorkspaceSettingsManager.loadSettings().enabled;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
|
|
50
|
+
const approved = await this.daemon.requestApproval(
|
|
51
|
+
this.taskId,
|
|
52
|
+
'external_service',
|
|
53
|
+
summary,
|
|
54
|
+
details
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!approved) {
|
|
58
|
+
throw new Error('User denied Google Drive action');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private resolveFilePath(inputPath: string): string {
|
|
63
|
+
if (!this.workspace.permissions.read) {
|
|
64
|
+
throw new Error('Read permission not granted for uploads');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const workspaceRoot = path.resolve(this.workspace.path);
|
|
68
|
+
const allowedPaths = this.workspace.permissions.allowedPaths || [];
|
|
69
|
+
const canReadOutside = this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess;
|
|
70
|
+
|
|
71
|
+
const isPathAllowed = (absolutePath: string): boolean => {
|
|
72
|
+
if (allowedPaths.length === 0) return false;
|
|
73
|
+
const normalizedPath = path.normalize(absolutePath);
|
|
74
|
+
return allowedPaths.some((allowed) => {
|
|
75
|
+
const normalizedAllowed = path.normalize(allowed);
|
|
76
|
+
return normalizedPath === normalizedAllowed || normalizedPath.startsWith(normalizedAllowed + path.sep);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const candidate = path.isAbsolute(inputPath)
|
|
81
|
+
? path.normalize(inputPath)
|
|
82
|
+
: path.resolve(workspaceRoot, inputPath);
|
|
83
|
+
|
|
84
|
+
const relative = path.relative(workspaceRoot, candidate);
|
|
85
|
+
const isInsideWorkspace = !(relative.startsWith('..') || path.isAbsolute(relative));
|
|
86
|
+
if (!isInsideWorkspace && !canReadOutside && !isPathAllowed(candidate)) {
|
|
87
|
+
throw new Error('File path must be inside the workspace or in Allowed Paths');
|
|
88
|
+
}
|
|
89
|
+
if (!fs.existsSync(candidate)) {
|
|
90
|
+
throw new Error(`File not found: ${inputPath}`);
|
|
91
|
+
}
|
|
92
|
+
const stats = fs.statSync(candidate);
|
|
93
|
+
if (!stats.isFile()) {
|
|
94
|
+
throw new Error(`Path is not a file: ${inputPath}`);
|
|
95
|
+
}
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async executeAction(input: GoogleDriveActionInput): Promise<any> {
|
|
100
|
+
const settings = GoogleWorkspaceSettingsManager.loadSettings();
|
|
101
|
+
if (!settings.enabled) {
|
|
102
|
+
throw new Error('Google Workspace integration is disabled. Enable it in Settings > Integrations > Google Workspace.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const action = input.action;
|
|
106
|
+
if (!action) {
|
|
107
|
+
throw new Error('Missing required "action" parameter');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let result;
|
|
111
|
+
|
|
112
|
+
switch (action) {
|
|
113
|
+
case 'get_current_user': {
|
|
114
|
+
result = await googleDriveRequest(settings, {
|
|
115
|
+
method: 'GET',
|
|
116
|
+
path: '/about',
|
|
117
|
+
query: { fields: 'user' },
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'list_files': {
|
|
122
|
+
const query = input.query || 'trashed = false';
|
|
123
|
+
result = await googleDriveRequest(settings, {
|
|
124
|
+
method: 'GET',
|
|
125
|
+
path: '/files',
|
|
126
|
+
query: {
|
|
127
|
+
q: query,
|
|
128
|
+
pageSize: input.page_size,
|
|
129
|
+
pageToken: input.page_token,
|
|
130
|
+
fields: input.fields || DEFAULT_LIST_FIELDS,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'get_file': {
|
|
136
|
+
if (!input.file_id) throw new Error('Missing file_id for get_file');
|
|
137
|
+
result = await googleDriveRequest(settings, {
|
|
138
|
+
method: 'GET',
|
|
139
|
+
path: `/files/${input.file_id}`,
|
|
140
|
+
query: {
|
|
141
|
+
fields: input.fields || DEFAULT_FILE_FIELDS,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'create_folder': {
|
|
147
|
+
if (!input.name) throw new Error('Missing name for create_folder');
|
|
148
|
+
await this.requireApproval('Create a Google Drive folder', {
|
|
149
|
+
action: 'create_folder',
|
|
150
|
+
parent_id: input.parent_id || 'root',
|
|
151
|
+
name: input.name,
|
|
152
|
+
});
|
|
153
|
+
result = await googleDriveRequest(settings, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
path: '/files',
|
|
156
|
+
query: { fields: DEFAULT_FILE_FIELDS },
|
|
157
|
+
body: {
|
|
158
|
+
name: input.name,
|
|
159
|
+
mimeType: 'application/vnd.google-apps.folder',
|
|
160
|
+
parents: input.parent_id ? [input.parent_id] : undefined,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case 'upload_file': {
|
|
166
|
+
if (!input.file_path) throw new Error('Missing file_path for upload_file');
|
|
167
|
+
const resolved = this.resolveFilePath(input.file_path);
|
|
168
|
+
const data = fs.readFileSync(resolved);
|
|
169
|
+
const fileName = input.name || path.basename(resolved);
|
|
170
|
+
const contentType = (mime.lookup(fileName) || 'application/octet-stream') as string;
|
|
171
|
+
await this.requireApproval(`Upload file to Google Drive: ${fileName}`, {
|
|
172
|
+
action: 'upload_file',
|
|
173
|
+
parent_id: input.parent_id || 'root',
|
|
174
|
+
file: fileName,
|
|
175
|
+
});
|
|
176
|
+
const created = await googleDriveRequest(settings, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
path: '/files',
|
|
179
|
+
query: { fields: DEFAULT_FILE_FIELDS },
|
|
180
|
+
body: {
|
|
181
|
+
name: fileName,
|
|
182
|
+
parents: input.parent_id ? [input.parent_id] : undefined,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
const fileId = created.data?.id;
|
|
186
|
+
if (!fileId) {
|
|
187
|
+
throw new Error('Failed to create Google Drive file record');
|
|
188
|
+
}
|
|
189
|
+
const uploaded = await googleDriveUpload(settings, fileId, data, contentType);
|
|
190
|
+
result = {
|
|
191
|
+
status: uploaded.status,
|
|
192
|
+
data: uploaded.data || created.data,
|
|
193
|
+
raw: uploaded.raw,
|
|
194
|
+
};
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'delete_file': {
|
|
198
|
+
if (!input.file_id) throw new Error('Missing file_id for delete_file');
|
|
199
|
+
await this.requireApproval('Delete a Google Drive file', {
|
|
200
|
+
action: 'delete_file',
|
|
201
|
+
file_id: input.file_id,
|
|
202
|
+
});
|
|
203
|
+
result = await googleDriveRequest(settings, {
|
|
204
|
+
method: 'DELETE',
|
|
205
|
+
path: `/files/${input.file_id}`,
|
|
206
|
+
});
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
default:
|
|
210
|
+
throw new Error(`Unsupported action: ${action}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.daemon.logEvent(this.taskId, 'tool_result', {
|
|
214
|
+
tool: 'google_drive_action',
|
|
215
|
+
action,
|
|
216
|
+
status: result?.status,
|
|
217
|
+
hasData: result?.data ? true : false,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
action,
|
|
223
|
+
status: result?.status,
|
|
224
|
+
data: result?.data,
|
|
225
|
+
raw: result?.raw,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
import * as fsPromises from 'fs/promises';
|
|
2
3
|
import * as path from 'path';
|
|
3
4
|
import { Workspace } from '../../../shared/types';
|
|
4
5
|
import { AgentDaemon } from '../daemon';
|
|
@@ -32,6 +33,7 @@ export class GrepTools {
|
|
|
32
33
|
description:
|
|
33
34
|
'Powerful regex-based content search across files. ' +
|
|
34
35
|
'Supports full regex syntax (e.g., "async function.*fetch", "class\\s+\\w+"). ' +
|
|
36
|
+
'Searches text files only; binary formats like PDF/DOCX are skipped. ' +
|
|
35
37
|
'Use this to find code patterns, function definitions, imports, etc. ' +
|
|
36
38
|
'PREFERRED over search_files for content search.',
|
|
37
39
|
input_schema: {
|
|
@@ -99,6 +101,7 @@ export class GrepTools {
|
|
|
99
101
|
filesSearched: number;
|
|
100
102
|
truncated: boolean;
|
|
101
103
|
error?: string;
|
|
104
|
+
warning?: string;
|
|
102
105
|
}> {
|
|
103
106
|
const {
|
|
104
107
|
pattern,
|
|
@@ -115,6 +118,18 @@ export class GrepTools {
|
|
|
115
118
|
});
|
|
116
119
|
|
|
117
120
|
try {
|
|
121
|
+
if ((await this.isDocumentHeavyWorkspace()) && (!globPattern || /\.(pdf|docx)\b/i.test(globPattern))) {
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
pattern,
|
|
125
|
+
matches: [],
|
|
126
|
+
totalMatches: 0,
|
|
127
|
+
filesSearched: 0,
|
|
128
|
+
truncated: false,
|
|
129
|
+
warning: 'Workspace appears document-heavy (PDF/DOCX). The grep tool only searches text files. Use read_file for those documents.',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
118
133
|
// Compile regex
|
|
119
134
|
let regex: RegExp;
|
|
120
135
|
try {
|
|
@@ -327,7 +342,8 @@ export class GrepTools {
|
|
|
327
342
|
|
|
328
343
|
// Apply glob filter if specified
|
|
329
344
|
if (globRegex) {
|
|
330
|
-
|
|
345
|
+
const normalizedRelative = relativePath.split(path.sep).join('/');
|
|
346
|
+
if (!globRegex.test(normalizedRelative) && !globRegex.test(entry.name)) {
|
|
331
347
|
continue;
|
|
332
348
|
}
|
|
333
349
|
}
|
|
@@ -395,18 +411,8 @@ export class GrepTools {
|
|
|
395
411
|
* Convert glob pattern to regex
|
|
396
412
|
*/
|
|
397
413
|
private globToRegex(pattern: string): RegExp {
|
|
398
|
-
// Handle brace expansion
|
|
399
414
|
const expandedPatterns = this.expandBraces(pattern);
|
|
400
|
-
|
|
401
|
-
const regexParts = expandedPatterns.map((p) => {
|
|
402
|
-
let regex = p
|
|
403
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
404
|
-
.replace(/\\\*\\\*/g, '.*')
|
|
405
|
-
.replace(/\\\*/g, '[^/]*')
|
|
406
|
-
.replace(/\\\?/g, '[^/]');
|
|
407
|
-
return regex;
|
|
408
|
-
});
|
|
409
|
-
|
|
415
|
+
const regexParts = expandedPatterns.map((p) => this.globPatternToRegex(p));
|
|
410
416
|
const combined = regexParts.length > 1 ? `(${regexParts.join('|')})` : regexParts[0];
|
|
411
417
|
return new RegExp(`^${combined}$`, 'i');
|
|
412
418
|
}
|
|
@@ -429,4 +435,83 @@ export class GrepTools {
|
|
|
429
435
|
|
|
430
436
|
return results;
|
|
431
437
|
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Heuristic: detect workspaces dominated by PDF/DOCX files
|
|
441
|
+
*/
|
|
442
|
+
private async isDocumentHeavyWorkspace(): Promise<boolean> {
|
|
443
|
+
try {
|
|
444
|
+
const entries = await fsPromises.readdir(this.workspace.path, { withFileTypes: true });
|
|
445
|
+
let fileCount = 0;
|
|
446
|
+
let docCount = 0;
|
|
447
|
+
const maxEntries = 200;
|
|
448
|
+
|
|
449
|
+
for (const entry of entries) {
|
|
450
|
+
if (fileCount >= maxEntries) break;
|
|
451
|
+
if (!entry.isFile()) continue;
|
|
452
|
+
fileCount++;
|
|
453
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
454
|
+
if (ext === '.pdf' || ext === '.docx') {
|
|
455
|
+
docCount++;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (fileCount < 5) return false;
|
|
460
|
+
return docCount / fileCount >= 0.5;
|
|
461
|
+
} catch {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Convert a glob pattern to a regex string (without delimiters)
|
|
468
|
+
*/
|
|
469
|
+
private globPatternToRegex(pattern: string): string {
|
|
470
|
+
let regex = '';
|
|
471
|
+
let i = 0;
|
|
472
|
+
|
|
473
|
+
while (i < pattern.length) {
|
|
474
|
+
const char = pattern[i];
|
|
475
|
+
|
|
476
|
+
if (char === '*') {
|
|
477
|
+
const isDoubleStar = pattern[i + 1] === '*';
|
|
478
|
+
if (isDoubleStar) {
|
|
479
|
+
i += 2;
|
|
480
|
+
if (pattern[i] === '/') {
|
|
481
|
+
regex += '(?:.*/)?';
|
|
482
|
+
i += 1;
|
|
483
|
+
} else {
|
|
484
|
+
regex += '.*';
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
regex += '[^/]*';
|
|
488
|
+
i += 1;
|
|
489
|
+
}
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (char === '?') {
|
|
494
|
+
regex += '[^/]';
|
|
495
|
+
i += 1;
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if ('+^${}()|[]\\.'.includes(char)) {
|
|
500
|
+
regex += `\\${char}`;
|
|
501
|
+
i += 1;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (char === '/') {
|
|
506
|
+
regex += '/';
|
|
507
|
+
i += 1;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
regex += char;
|
|
512
|
+
i += 1;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return regex;
|
|
516
|
+
}
|
|
432
517
|
}
|
|
@@ -64,9 +64,19 @@ export class ImageTools {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
} else {
|
|
67
|
-
|
|
67
|
+
const payload: Record<string, any> = {
|
|
68
68
|
action: 'generate_image',
|
|
69
69
|
error: result.error,
|
|
70
|
+
};
|
|
71
|
+
if (result.error?.includes('Gemini API key not configured')) {
|
|
72
|
+
payload.actionHint = {
|
|
73
|
+
type: 'open_settings',
|
|
74
|
+
label: 'Set up Gemini API key',
|
|
75
|
+
target: 'gemini',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
this.daemon.logEvent(this.taskId, 'error', {
|
|
79
|
+
...payload,
|
|
70
80
|
});
|
|
71
81
|
}
|
|
72
82
|
|