cowork-os 0.3.25 → 0.3.27
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/dist/electron/electron/agent/daemon.js +24 -2
- package/dist/electron/electron/agent/executor.js +146 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +2 -0
- package/dist/electron/electron/agent/tools/gmail-tools.js +199 -0
- package/dist/electron/electron/agent/tools/google-calendar-tools.js +211 -0
- package/dist/electron/electron/agent/tools/google-drive-tools.js +12 -12
- package/dist/electron/electron/agent/tools/registry.js +214 -0
- package/dist/electron/electron/database/repositories.js +36 -2
- package/dist/electron/electron/gateway/channels/discord.js +4 -0
- package/dist/electron/electron/gateway/channels/google-chat.js +2 -0
- package/dist/electron/electron/gateway/channels/line.js +2 -0
- package/dist/electron/electron/gateway/channels/matrix-client.js +12 -0
- package/dist/electron/electron/gateway/channels/matrix.js +27 -0
- package/dist/electron/electron/gateway/channels/mattermost.js +2 -0
- package/dist/electron/electron/gateway/channels/signal.js +2 -0
- package/dist/electron/electron/gateway/channels/slack.js +9 -4
- package/dist/electron/electron/gateway/channels/teams.js +3 -0
- package/dist/electron/electron/gateway/channels/telegram.js +2 -0
- package/dist/electron/electron/gateway/channels/twitch.js +2 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +3 -0
- package/dist/electron/electron/gateway/index.js +37 -0
- package/dist/electron/electron/gateway/router.js +7 -1
- package/dist/electron/electron/gateway/security.js +22 -9
- package/dist/electron/electron/ipc/handlers.js +24 -19
- package/dist/electron/electron/preload.js +17 -10
- package/dist/electron/electron/settings/{google-drive-manager.js → google-workspace-manager.js} +10 -9
- package/dist/electron/electron/utils/gmail-api.js +99 -0
- package/dist/electron/electron/utils/google-calendar-api.js +92 -0
- package/dist/electron/electron/utils/google-workspace-api.js +196 -0
- package/dist/electron/electron/utils/google-workspace-auth.js +91 -0
- package/dist/electron/electron/utils/google-workspace-oauth.js +184 -0
- package/dist/electron/electron/utils/validation.js +8 -3
- package/dist/electron/shared/types.js +9 -4
- package/dist/renderer/assets/index-CAE-LL8U.js +3401 -0
- package/dist/renderer/assets/index-Cnu5QTSE.css +1 -0
- package/dist/renderer/cowork-os-logo.png +0 -0
- package/dist/renderer/index.html +13 -0
- package/package.json +3 -2
- package/dist/electron/electron/utils/google-drive-api.js +0 -152
|
@@ -182,6 +182,7 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
182
182
|
error: error.message || 'Failed to initialize task executor',
|
|
183
183
|
completedAt: Date.now(),
|
|
184
184
|
});
|
|
185
|
+
this.clearRetryState(task.id);
|
|
185
186
|
this.logEvent(task.id, 'error', { error: error.message });
|
|
186
187
|
// Notify queue manager so it can start next task
|
|
187
188
|
this.queueManager.onTaskFinished(task.id);
|
|
@@ -204,6 +205,7 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
204
205
|
error: error.message,
|
|
205
206
|
completedAt: Date.now(),
|
|
206
207
|
});
|
|
208
|
+
this.clearRetryState(task.id);
|
|
207
209
|
this.logEvent(task.id, 'error', { error: error.message });
|
|
208
210
|
this.activeTasks.delete(task.id);
|
|
209
211
|
// Notify queue manager so it can start next task
|
|
@@ -383,6 +385,7 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
383
385
|
// Check if task is queued (not yet started)
|
|
384
386
|
if (this.queueManager.cancelQueuedTask(taskId)) {
|
|
385
387
|
this.taskRepo.update(taskId, { status: 'cancelled', completedAt: Date.now() });
|
|
388
|
+
this.clearRetryState(taskId);
|
|
386
389
|
this.logEvent(taskId, 'task_cancelled', {
|
|
387
390
|
message: 'Task removed from queue',
|
|
388
391
|
});
|
|
@@ -398,6 +401,7 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
398
401
|
// (handles orphaned tasks that are in runningTaskIds but have no executor)
|
|
399
402
|
this.queueManager.onTaskFinished(taskId);
|
|
400
403
|
// Always emit cancelled event so UI updates
|
|
404
|
+
this.clearRetryState(taskId);
|
|
401
405
|
this.logEvent(taskId, 'task_cancelled', {
|
|
402
406
|
message: 'Task was stopped by user',
|
|
403
407
|
});
|
|
@@ -431,10 +435,15 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
431
435
|
const handle = setTimeout(async () => {
|
|
432
436
|
this.pendingRetries.delete(taskId);
|
|
433
437
|
const task = this.taskRepo.findById(taskId);
|
|
434
|
-
if (!task)
|
|
438
|
+
if (!task) {
|
|
439
|
+
this.retryCounts.delete(taskId);
|
|
435
440
|
return;
|
|
436
|
-
|
|
441
|
+
}
|
|
442
|
+
if (task.status !== 'queued')
|
|
443
|
+
return;
|
|
444
|
+
if (this.activeTasks.has(taskId) || this.queueManager.isRunning(taskId) || this.queueManager.isQueued(taskId)) {
|
|
437
445
|
return;
|
|
446
|
+
}
|
|
438
447
|
await this.startTask(task);
|
|
439
448
|
}, delayMs);
|
|
440
449
|
this.pendingRetries.set(taskId, handle);
|
|
@@ -904,6 +913,9 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
904
913
|
*/
|
|
905
914
|
updateTaskStatus(taskId, status) {
|
|
906
915
|
this.taskRepo.update(taskId, { status });
|
|
916
|
+
if (status === 'completed' || status === 'failed' || status === 'cancelled') {
|
|
917
|
+
this.clearRetryState(taskId);
|
|
918
|
+
}
|
|
907
919
|
}
|
|
908
920
|
/**
|
|
909
921
|
* Get task by ID
|
|
@@ -948,6 +960,14 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
948
960
|
updateTask(taskId, updates) {
|
|
949
961
|
this.taskRepo.update(taskId, updates);
|
|
950
962
|
}
|
|
963
|
+
clearRetryState(taskId) {
|
|
964
|
+
const pending = this.pendingRetries.get(taskId);
|
|
965
|
+
if (pending) {
|
|
966
|
+
clearTimeout(pending);
|
|
967
|
+
this.pendingRetries.delete(taskId);
|
|
968
|
+
}
|
|
969
|
+
this.retryCounts.delete(taskId);
|
|
970
|
+
}
|
|
951
971
|
/**
|
|
952
972
|
* Mark task as completed
|
|
953
973
|
* Note: We keep the executor in memory for follow-up messages (with TTL-based cleanup)
|
|
@@ -957,6 +977,7 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
957
977
|
status: 'completed',
|
|
958
978
|
completedAt: Date.now(),
|
|
959
979
|
});
|
|
980
|
+
this.clearRetryState(taskId);
|
|
960
981
|
// Mark executor as completed for TTL-based cleanup
|
|
961
982
|
const cached = this.activeTasks.get(taskId);
|
|
962
983
|
if (cached) {
|
|
@@ -1076,6 +1097,7 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
1076
1097
|
status: 'failed',
|
|
1077
1098
|
error: 'Task timed out - exceeded maximum allowed execution time',
|
|
1078
1099
|
});
|
|
1100
|
+
this.clearRetryState(taskId);
|
|
1079
1101
|
// Emit timeout event
|
|
1080
1102
|
this.logEvent(taskId, 'step_timeout', {
|
|
1081
1103
|
message: 'Task exceeded maximum execution time and was automatically cancelled',
|
|
@@ -949,6 +949,7 @@ class TaskExecutor {
|
|
|
949
949
|
this.lastNonVerificationOutput = null;
|
|
950
950
|
this.toolResultMemoryLimit = 8;
|
|
951
951
|
this.dispatchedMentionedAgents = false;
|
|
952
|
+
this.lastAssistantText = null;
|
|
952
953
|
// Plan revision tracking to prevent infinite revision loops
|
|
953
954
|
this.planRevisionCount = 0;
|
|
954
955
|
this.maxPlanRevisions = 5;
|
|
@@ -1288,6 +1289,69 @@ class TaskExecutor {
|
|
|
1288
1289
|
* This auto-fills parameters when the LLM fails to provide them but context is available
|
|
1289
1290
|
*/
|
|
1290
1291
|
inferMissingParameters(toolName, input) {
|
|
1292
|
+
if (toolName === 'create_document') {
|
|
1293
|
+
let modified = false;
|
|
1294
|
+
let inference = '';
|
|
1295
|
+
input = input || {};
|
|
1296
|
+
if (!input.filename) {
|
|
1297
|
+
if (input.path) {
|
|
1298
|
+
input.filename = path.basename(String(input.path));
|
|
1299
|
+
modified = true;
|
|
1300
|
+
inference = 'Normalized path -> filename';
|
|
1301
|
+
}
|
|
1302
|
+
else if (input.name) {
|
|
1303
|
+
input.filename = String(input.name);
|
|
1304
|
+
modified = true;
|
|
1305
|
+
inference = 'Normalized name -> filename';
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (!input.format) {
|
|
1309
|
+
const ext = input.filename ? path.extname(String(input.filename)).toLowerCase() : '';
|
|
1310
|
+
if (ext === '.pdf') {
|
|
1311
|
+
input.format = 'pdf';
|
|
1312
|
+
modified = true;
|
|
1313
|
+
inference = `${inference ? `${inference}; ` : ''}Inferred format="pdf" from filename`;
|
|
1314
|
+
}
|
|
1315
|
+
else if (ext === '.docx') {
|
|
1316
|
+
input.format = 'docx';
|
|
1317
|
+
modified = true;
|
|
1318
|
+
inference = `${inference ? `${inference}; ` : ''}Inferred format="docx" from filename`;
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
input.format = 'docx';
|
|
1322
|
+
modified = true;
|
|
1323
|
+
inference = `${inference ? `${inference}; ` : ''}Defaulted format="docx"`;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if (!input.content) {
|
|
1327
|
+
const fallback = this.getContentFallback();
|
|
1328
|
+
if (fallback) {
|
|
1329
|
+
input.content = fallback;
|
|
1330
|
+
modified = true;
|
|
1331
|
+
inference = `${inference ? `${inference}; ` : ''}Inferred content from latest assistant output`;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return { input, modified, inference: modified ? inference : undefined };
|
|
1335
|
+
}
|
|
1336
|
+
if (toolName === 'write_file') {
|
|
1337
|
+
let modified = false;
|
|
1338
|
+
let inference = '';
|
|
1339
|
+
input = input || {};
|
|
1340
|
+
if (!input.path && input.filename) {
|
|
1341
|
+
input.path = String(input.filename);
|
|
1342
|
+
modified = true;
|
|
1343
|
+
inference = 'Normalized filename -> path';
|
|
1344
|
+
}
|
|
1345
|
+
if (!input.content) {
|
|
1346
|
+
const fallback = this.getContentFallback();
|
|
1347
|
+
if (fallback) {
|
|
1348
|
+
input.content = fallback;
|
|
1349
|
+
modified = true;
|
|
1350
|
+
inference = `${inference ? `${inference}; ` : ''}Inferred content from latest assistant output`;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return { input, modified, inference: modified ? inference : undefined };
|
|
1354
|
+
}
|
|
1291
1355
|
// Handle edit_document - infer sourcePath from recently created documents
|
|
1292
1356
|
if (toolName === 'edit_document') {
|
|
1293
1357
|
let modified = false;
|
|
@@ -1388,6 +1452,44 @@ class TaskExecutor {
|
|
|
1388
1452
|
}
|
|
1389
1453
|
return { input, modified: false };
|
|
1390
1454
|
}
|
|
1455
|
+
getContentFallback() {
|
|
1456
|
+
const candidates = [
|
|
1457
|
+
this.lastAssistantText,
|
|
1458
|
+
this.lastNonVerificationOutput,
|
|
1459
|
+
this.lastAssistantOutput,
|
|
1460
|
+
];
|
|
1461
|
+
const placeholders = new Set([
|
|
1462
|
+
'I understand. Let me continue.',
|
|
1463
|
+
]);
|
|
1464
|
+
for (const candidate of candidates) {
|
|
1465
|
+
if (!candidate)
|
|
1466
|
+
continue;
|
|
1467
|
+
const trimmed = candidate.trim();
|
|
1468
|
+
if (trimmed.length < 20)
|
|
1469
|
+
continue;
|
|
1470
|
+
if (placeholders.has(trimmed))
|
|
1471
|
+
continue;
|
|
1472
|
+
return trimmed;
|
|
1473
|
+
}
|
|
1474
|
+
return undefined;
|
|
1475
|
+
}
|
|
1476
|
+
getToolInputValidationError(toolName, input) {
|
|
1477
|
+
if (toolName === 'create_document') {
|
|
1478
|
+
if (!input?.filename)
|
|
1479
|
+
return 'create_document requires a filename';
|
|
1480
|
+
if (!input?.format)
|
|
1481
|
+
return 'create_document requires a format (docx or pdf)';
|
|
1482
|
+
if (!input?.content)
|
|
1483
|
+
return 'create_document requires content';
|
|
1484
|
+
}
|
|
1485
|
+
if (toolName === 'write_file') {
|
|
1486
|
+
if (!input?.path)
|
|
1487
|
+
return 'write_file requires a path';
|
|
1488
|
+
if (!input?.content)
|
|
1489
|
+
return 'write_file requires content';
|
|
1490
|
+
}
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1391
1493
|
async handleCanvasPushFallback(content, assistantText) {
|
|
1392
1494
|
if (content.name !== 'canvas_push') {
|
|
1393
1495
|
return;
|
|
@@ -3241,6 +3343,12 @@ SCHEDULING & REMINDERS:
|
|
|
3241
3343
|
.filter((item) => item.type === 'text' && item.text)
|
|
3242
3344
|
.map((item) => item.text)
|
|
3243
3345
|
.join('\n');
|
|
3346
|
+
if (assistantText && assistantText.trim().length > 0) {
|
|
3347
|
+
this.lastAssistantText = assistantText.trim();
|
|
3348
|
+
}
|
|
3349
|
+
if (assistantText && assistantText.trim().length > 0) {
|
|
3350
|
+
this.lastAssistantText = assistantText.trim();
|
|
3351
|
+
}
|
|
3244
3352
|
if (response.content) {
|
|
3245
3353
|
for (const content of response.content) {
|
|
3246
3354
|
if (content.type === 'text' && content.text) {
|
|
@@ -3350,6 +3458,25 @@ SCHEDULING & REMINDERS:
|
|
|
3350
3458
|
}
|
|
3351
3459
|
// If canvas_push is missing content, try extracting HTML from assistant text or auto-generate
|
|
3352
3460
|
await this.handleCanvasPushFallback(content, assistantText);
|
|
3461
|
+
const validationError = this.getToolInputValidationError(content.name, content.input);
|
|
3462
|
+
if (validationError) {
|
|
3463
|
+
this.daemon.logEvent(this.task.id, 'tool_warning', {
|
|
3464
|
+
tool: content.name,
|
|
3465
|
+
error: validationError,
|
|
3466
|
+
input: content.input,
|
|
3467
|
+
});
|
|
3468
|
+
toolResults.push({
|
|
3469
|
+
type: 'tool_result',
|
|
3470
|
+
tool_use_id: content.id,
|
|
3471
|
+
content: JSON.stringify({
|
|
3472
|
+
error: validationError,
|
|
3473
|
+
suggestion: 'Include all required fields in the tool call (e.g., content for create_document/write_file).',
|
|
3474
|
+
invalid_input: true,
|
|
3475
|
+
}),
|
|
3476
|
+
is_error: true,
|
|
3477
|
+
});
|
|
3478
|
+
continue;
|
|
3479
|
+
}
|
|
3353
3480
|
// Check for duplicate tool calls (prevents stuck loops)
|
|
3354
3481
|
const duplicateCheck = this.toolCallDeduplicator.checkDuplicate(content.name, content.input);
|
|
3355
3482
|
if (duplicateCheck.isDuplicate) {
|
|
@@ -4097,6 +4224,25 @@ SCHEDULING & REMINDERS:
|
|
|
4097
4224
|
}
|
|
4098
4225
|
// If canvas_push is missing content, try extracting HTML from assistant text or auto-generate
|
|
4099
4226
|
await this.handleCanvasPushFallback(content, assistantText);
|
|
4227
|
+
const validationError = this.getToolInputValidationError(content.name, content.input);
|
|
4228
|
+
if (validationError) {
|
|
4229
|
+
this.daemon.logEvent(this.task.id, 'tool_warning', {
|
|
4230
|
+
tool: content.name,
|
|
4231
|
+
error: validationError,
|
|
4232
|
+
input: content.input,
|
|
4233
|
+
});
|
|
4234
|
+
toolResults.push({
|
|
4235
|
+
type: 'tool_result',
|
|
4236
|
+
tool_use_id: content.id,
|
|
4237
|
+
content: JSON.stringify({
|
|
4238
|
+
error: validationError,
|
|
4239
|
+
suggestion: 'Include all required fields in the tool call (e.g., content for create_document/write_file).',
|
|
4240
|
+
invalid_input: true,
|
|
4241
|
+
}),
|
|
4242
|
+
is_error: true,
|
|
4243
|
+
});
|
|
4244
|
+
continue;
|
|
4245
|
+
}
|
|
4100
4246
|
// Check for duplicate tool calls (prevents stuck loops)
|
|
4101
4247
|
const duplicateCheck = this.toolCallDeduplicator.checkDuplicate(content.name, content.input);
|
|
4102
4248
|
if (duplicateCheck.isDuplicate) {
|
|
@@ -113,6 +113,8 @@ const TOOL_CATEGORIES = {
|
|
|
113
113
|
box_action: 'webfetch',
|
|
114
114
|
onedrive_action: 'webfetch',
|
|
115
115
|
google_drive_action: 'webfetch',
|
|
116
|
+
gmail_action: 'webfetch',
|
|
117
|
+
calendar_action: 'webfetch',
|
|
116
118
|
dropbox_action: 'webfetch',
|
|
117
119
|
sharepoint_action: 'webfetch',
|
|
118
120
|
// Browser tools
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GmailTools = void 0;
|
|
4
|
+
const google_workspace_manager_1 = require("../../settings/google-workspace-manager");
|
|
5
|
+
const gmail_api_1 = require("../../utils/gmail-api");
|
|
6
|
+
function encodeMessage(raw) {
|
|
7
|
+
return Buffer.from(raw)
|
|
8
|
+
.toString('base64')
|
|
9
|
+
.replace(/\+/g, '-')
|
|
10
|
+
.replace(/\//g, '_')
|
|
11
|
+
.replace(/=+$/, '');
|
|
12
|
+
}
|
|
13
|
+
function buildRawEmail(input) {
|
|
14
|
+
const headers = [];
|
|
15
|
+
if (input.to)
|
|
16
|
+
headers.push(`To: ${input.to}`);
|
|
17
|
+
if (input.cc)
|
|
18
|
+
headers.push(`Cc: ${input.cc}`);
|
|
19
|
+
if (input.bcc)
|
|
20
|
+
headers.push(`Bcc: ${input.bcc}`);
|
|
21
|
+
if (input.subject)
|
|
22
|
+
headers.push(`Subject: ${input.subject}`);
|
|
23
|
+
headers.push('MIME-Version: 1.0');
|
|
24
|
+
headers.push('Content-Type: text/plain; charset="UTF-8"');
|
|
25
|
+
headers.push('Content-Transfer-Encoding: 7bit');
|
|
26
|
+
const body = input.body ?? '';
|
|
27
|
+
const message = `${headers.join('\r\n')}\r\n\r\n${body}`;
|
|
28
|
+
return encodeMessage(message);
|
|
29
|
+
}
|
|
30
|
+
class GmailTools {
|
|
31
|
+
constructor(workspace, daemon, taskId) {
|
|
32
|
+
this.workspace = workspace;
|
|
33
|
+
this.daemon = daemon;
|
|
34
|
+
this.taskId = taskId;
|
|
35
|
+
}
|
|
36
|
+
setWorkspace(workspace) {
|
|
37
|
+
this.workspace = workspace;
|
|
38
|
+
}
|
|
39
|
+
static isEnabled() {
|
|
40
|
+
return google_workspace_manager_1.GoogleWorkspaceSettingsManager.loadSettings().enabled;
|
|
41
|
+
}
|
|
42
|
+
formatAuthError(error) {
|
|
43
|
+
const message = String(error?.message ?? '');
|
|
44
|
+
const status = error?.status;
|
|
45
|
+
if (status === 401) {
|
|
46
|
+
return 'Google Workspace authorization failed (401). Reconnect in Settings > Integrations > Google Workspace.';
|
|
47
|
+
}
|
|
48
|
+
if (/token refresh failed|refresh token not configured|access token not configured|access token expired/i.test(message)) {
|
|
49
|
+
return `Google Workspace authorization error: ${message}`;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
async requireApproval(summary, details) {
|
|
54
|
+
const approved = await this.daemon.requestApproval(this.taskId, 'external_service', summary, details);
|
|
55
|
+
if (!approved) {
|
|
56
|
+
throw new Error('User denied Gmail action');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async executeAction(input) {
|
|
60
|
+
const settings = google_workspace_manager_1.GoogleWorkspaceSettingsManager.loadSettings();
|
|
61
|
+
if (!settings.enabled) {
|
|
62
|
+
throw new Error('Google Workspace integration is disabled. Enable it in Settings > Integrations > Google Workspace.');
|
|
63
|
+
}
|
|
64
|
+
const action = input.action;
|
|
65
|
+
if (!action) {
|
|
66
|
+
throw new Error('Missing required "action" parameter');
|
|
67
|
+
}
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
switch (action) {
|
|
71
|
+
case 'get_profile': {
|
|
72
|
+
result = await (0, gmail_api_1.gmailRequest)(settings, {
|
|
73
|
+
method: 'GET',
|
|
74
|
+
path: '/users/me/profile',
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'list_messages': {
|
|
79
|
+
result = await (0, gmail_api_1.gmailRequest)(settings, {
|
|
80
|
+
method: 'GET',
|
|
81
|
+
path: '/users/me/messages',
|
|
82
|
+
query: {
|
|
83
|
+
q: input.query,
|
|
84
|
+
maxResults: input.page_size,
|
|
85
|
+
pageToken: input.page_token,
|
|
86
|
+
includeSpamTrash: input.include_spam_trash,
|
|
87
|
+
labelIds: input.label_ids,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'get_message': {
|
|
93
|
+
if (!input.message_id)
|
|
94
|
+
throw new Error('Missing message_id for get_message');
|
|
95
|
+
result = await (0, gmail_api_1.gmailRequest)(settings, {
|
|
96
|
+
method: 'GET',
|
|
97
|
+
path: `/users/me/messages/${input.message_id}`,
|
|
98
|
+
query: {
|
|
99
|
+
format: input.format,
|
|
100
|
+
metadataHeaders: input.metadata_headers ? input.metadata_headers.join(',') : undefined,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'get_thread': {
|
|
106
|
+
if (!input.thread_id)
|
|
107
|
+
throw new Error('Missing thread_id for get_thread');
|
|
108
|
+
result = await (0, gmail_api_1.gmailRequest)(settings, {
|
|
109
|
+
method: 'GET',
|
|
110
|
+
path: `/users/me/threads/${input.thread_id}`,
|
|
111
|
+
query: {
|
|
112
|
+
format: input.format,
|
|
113
|
+
metadataHeaders: input.metadata_headers ? input.metadata_headers.join(',') : undefined,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'list_labels': {
|
|
119
|
+
result = await (0, gmail_api_1.gmailRequest)(settings, {
|
|
120
|
+
method: 'GET',
|
|
121
|
+
path: '/users/me/labels',
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'send_message': {
|
|
126
|
+
if (!input.raw && !input.to) {
|
|
127
|
+
throw new Error('Missing to for send_message');
|
|
128
|
+
}
|
|
129
|
+
if (!input.raw && !input.body && !input.subject) {
|
|
130
|
+
throw new Error('Missing body or subject for send_message');
|
|
131
|
+
}
|
|
132
|
+
await this.requireApproval('Send a Gmail message', {
|
|
133
|
+
action: 'send_message',
|
|
134
|
+
to: input.to,
|
|
135
|
+
subject: input.subject,
|
|
136
|
+
});
|
|
137
|
+
const raw = input.raw || buildRawEmail(input);
|
|
138
|
+
const payload = { raw };
|
|
139
|
+
if (input.thread_id) {
|
|
140
|
+
payload.threadId = input.thread_id;
|
|
141
|
+
}
|
|
142
|
+
result = await (0, gmail_api_1.gmailRequest)(settings, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
path: '/users/me/messages/send',
|
|
145
|
+
body: payload,
|
|
146
|
+
});
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'trash_message': {
|
|
150
|
+
if (!input.message_id)
|
|
151
|
+
throw new Error('Missing message_id for trash_message');
|
|
152
|
+
await this.requireApproval('Trash a Gmail message', {
|
|
153
|
+
action: 'trash_message',
|
|
154
|
+
message_id: input.message_id,
|
|
155
|
+
});
|
|
156
|
+
result = await (0, gmail_api_1.gmailRequest)(settings, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
path: `/users/me/messages/${input.message_id}/trash`,
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
default:
|
|
163
|
+
throw new Error(`Unsupported action: ${action}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
168
|
+
const authMessage = this.formatAuthError(error);
|
|
169
|
+
const finalMessage = authMessage ?? message;
|
|
170
|
+
this.daemon.logEvent(this.taskId, 'tool_error', {
|
|
171
|
+
tool: 'gmail_action',
|
|
172
|
+
action,
|
|
173
|
+
message: finalMessage,
|
|
174
|
+
status: error?.status,
|
|
175
|
+
});
|
|
176
|
+
if (authMessage) {
|
|
177
|
+
throw new Error(authMessage);
|
|
178
|
+
}
|
|
179
|
+
if (error instanceof Error) {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
throw new Error(message);
|
|
183
|
+
}
|
|
184
|
+
this.daemon.logEvent(this.taskId, 'tool_result', {
|
|
185
|
+
tool: 'gmail_action',
|
|
186
|
+
action,
|
|
187
|
+
status: result?.status,
|
|
188
|
+
hasData: result?.data ? true : false,
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
action,
|
|
193
|
+
status: result?.status,
|
|
194
|
+
data: result?.data,
|
|
195
|
+
raw: result?.raw,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.GmailTools = GmailTools;
|