@littlebearapps/outlook-assistant 3.13.0 → 3.14.1
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/.env.example +30 -3
- package/README.md +67 -27
- package/advanced/index.js +44 -174
- package/auth/auth-errors.js +23 -1
- package/auth/oauth-server.js +7 -1
- package/auth/token-manager.js +7 -3
- package/auth/token-storage.js +28 -30
- package/auth/tools.js +61 -82
- package/calendar/attendees.js +36 -0
- package/calendar/cancel.js +9 -25
- package/calendar/create.js +42 -48
- package/calendar/decline.js +10 -25
- package/calendar/delete.js +10 -25
- package/calendar/index.js +20 -37
- package/calendar/list.js +4 -16
- package/calendar/preview.js +461 -0
- package/calendar/update.js +55 -83
- package/categories/index.js +68 -265
- package/config.js +29 -1
- package/contacts/index.js +72 -128
- package/email/attachments.js +43 -125
- package/email/conversations.js +44 -78
- package/email/delta.js +69 -46
- package/email/draft.js +170 -103
- package/email/export.js +145 -110
- package/email/folder-utils.js +3 -2
- package/email/headers.js +11 -49
- package/email/index.js +86 -110
- package/email/list.js +4 -17
- package/email/mail-tips.js +86 -57
- package/email/mark-as-read.js +13 -49
- package/email/mime.js +39 -51
- package/email/read.js +16 -50
- package/email/search.js +47 -87
- package/email/send.js +82 -48
- package/folder/create.js +6 -25
- package/folder/delete.js +117 -38
- package/folder/index.js +19 -17
- package/folder/list.js +5 -17
- package/folder/move.js +13 -42
- package/folder/resolve.js +11 -6
- package/folder/stats.js +18 -27
- package/index.js +39 -45
- package/llms-install.md +22 -4
- package/llms.txt +20 -11
- package/outlook-auth-server.js +10 -3
- package/package.json +4 -1
- package/request-handler.js +217 -116
- package/rules/create.js +28 -71
- package/rules/index.js +52 -93
- package/rules/list.js +7 -19
- package/rules/rule-builder.js +59 -22
- package/rules/update.js +27 -61
- package/server.js +41 -0
- package/settings/index.js +162 -145
- package/tools.js +30 -0
- package/utils/field-presets.js +4 -2
- package/utils/graph-api.js +65 -22
- package/utils/logger.js +251 -0
- package/utils/mock-data.js +91 -2
- package/utils/read-only.js +59 -0
- package/utils/response-formatter.js +54 -15
- package/utils/risk-classes.js +324 -0
- package/utils/safe-write.js +372 -6
- package/utils/safety.js +247 -42
- package/utils/server-instructions.js +73 -0
- package/utils/tool-error.js +33 -0
package/email/conversations.js
CHANGED
|
@@ -14,14 +14,22 @@ const { ensureAuthenticated } = require('../auth');
|
|
|
14
14
|
const { getEmailFields } = require('../utils/field-presets');
|
|
15
15
|
const { resolveFolderPath } = require('./folder-utils');
|
|
16
16
|
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
writeClaimedFile,
|
|
19
|
+
ensureOutputDir,
|
|
20
|
+
makeClaimedDir,
|
|
21
|
+
confineOutputPath,
|
|
22
|
+
OutputPathError,
|
|
23
|
+
} = require('../utils/safe-write');
|
|
18
24
|
const { escapeODataString } = require('../utils/odata-helpers');
|
|
19
25
|
const {
|
|
20
26
|
formatEmailContent,
|
|
21
27
|
formatEmailsAsCSV,
|
|
22
28
|
stripHtml,
|
|
23
29
|
VERBOSITY,
|
|
30
|
+
DEFAULT_LIMITS,
|
|
24
31
|
} = require('../utils/response-formatter');
|
|
32
|
+
const { toolError, authRequiredError } = require('../utils/tool-error');
|
|
25
33
|
// Note: buildFromFilter/buildToFilter from search.js use OData $filter which causes
|
|
26
34
|
// InefficientFilter on personal accounts with $orderby. Client-side filtering used instead.
|
|
27
35
|
|
|
@@ -235,20 +243,9 @@ async function handleListConversations(args) {
|
|
|
235
243
|
};
|
|
236
244
|
} catch (error) {
|
|
237
245
|
if (error.message === 'Authentication required') {
|
|
238
|
-
return
|
|
239
|
-
content: [
|
|
240
|
-
{
|
|
241
|
-
type: 'text',
|
|
242
|
-
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
243
|
-
},
|
|
244
|
-
],
|
|
245
|
-
};
|
|
246
|
+
return authRequiredError();
|
|
246
247
|
}
|
|
247
|
-
return {
|
|
248
|
-
content: [
|
|
249
|
-
{ type: 'text', text: `Error listing conversations: ${error.message}` },
|
|
250
|
-
],
|
|
251
|
-
};
|
|
248
|
+
return toolError(`Error listing conversations: ${error.message}`);
|
|
252
249
|
}
|
|
253
250
|
}
|
|
254
251
|
|
|
@@ -264,8 +261,8 @@ const EXPORT_CONVERSATION_MESSAGE_LIMIT = 1000;
|
|
|
264
261
|
* personal Microsoft accounts (400 InefficientFilter), so the query carries no
|
|
265
262
|
* `$orderby`: the pages are fetched and the messages are sorted here. Paging
|
|
266
263
|
* stops at the caller's `limit` or if Graph repeats a nextLink; either
|
|
267
|
-
* way the result is marked truncated. (
|
|
268
|
-
*
|
|
264
|
+
* way the result is marked truncated. (callGraphAPIPaginated stops the same
|
|
265
|
+
* way and reports `hasMore` since #279; this loop predates that.)
|
|
269
266
|
* @param {string} accessToken - Access token
|
|
270
267
|
* @param {string} prefix - Mailbox prefix (`me` or `users/{mailbox}`)
|
|
271
268
|
* @param {string} conversationId - Conversation ID
|
|
@@ -364,9 +361,7 @@ async function handleGetConversation(args) {
|
|
|
364
361
|
const prefix = buildMailboxPrefix(sharedMailbox);
|
|
365
362
|
|
|
366
363
|
if (!conversationId) {
|
|
367
|
-
return
|
|
368
|
-
content: [{ type: 'text', text: 'Conversation ID is required.' }],
|
|
369
|
-
};
|
|
364
|
+
return toolError('Conversation ID is required.');
|
|
370
365
|
}
|
|
371
366
|
|
|
372
367
|
try {
|
|
@@ -386,14 +381,9 @@ async function handleGetConversation(args) {
|
|
|
386
381
|
);
|
|
387
382
|
|
|
388
383
|
if (messages.length === 0) {
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
type: 'text',
|
|
393
|
-
text: `No messages found for conversation ID: ${conversationId}`,
|
|
394
|
-
},
|
|
395
|
-
],
|
|
396
|
-
};
|
|
384
|
+
return toolError(
|
|
385
|
+
`No messages found for conversation ID: ${conversationId}`
|
|
386
|
+
);
|
|
397
387
|
}
|
|
398
388
|
|
|
399
389
|
// Format output
|
|
@@ -407,7 +397,13 @@ async function handleGetConversation(args) {
|
|
|
407
397
|
|
|
408
398
|
messages.forEach((msg, index) => {
|
|
409
399
|
output.push(`## Message ${index + 1} of ${messages.length}`);
|
|
410
|
-
output.push(
|
|
400
|
+
output.push(
|
|
401
|
+
formatEmailContent(msg, verbosity, {
|
|
402
|
+
includeHeaders,
|
|
403
|
+
sharedMailbox,
|
|
404
|
+
maxFullBodyChars: DEFAULT_LIMITS.maxFullBodyChars,
|
|
405
|
+
})
|
|
406
|
+
);
|
|
411
407
|
output.push('\n---\n');
|
|
412
408
|
});
|
|
413
409
|
|
|
@@ -427,20 +423,9 @@ async function handleGetConversation(args) {
|
|
|
427
423
|
};
|
|
428
424
|
} catch (error) {
|
|
429
425
|
if (error.message === 'Authentication required') {
|
|
430
|
-
return
|
|
431
|
-
content: [
|
|
432
|
-
{
|
|
433
|
-
type: 'text',
|
|
434
|
-
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
435
|
-
},
|
|
436
|
-
],
|
|
437
|
-
};
|
|
426
|
+
return authRequiredError();
|
|
438
427
|
}
|
|
439
|
-
return {
|
|
440
|
-
content: [
|
|
441
|
-
{ type: 'text', text: `Error getting conversation: ${error.message}` },
|
|
442
|
-
],
|
|
443
|
-
};
|
|
428
|
+
return toolError(`Error getting conversation: ${error.message}`);
|
|
444
429
|
}
|
|
445
430
|
}
|
|
446
431
|
|
|
@@ -467,21 +452,22 @@ async function handleExportConversation(args) {
|
|
|
467
452
|
const prefix = buildMailboxPrefix(sharedMailbox);
|
|
468
453
|
|
|
469
454
|
if (!conversationId) {
|
|
470
|
-
return
|
|
471
|
-
content: [{ type: 'text', text: 'Conversation ID is required.' }],
|
|
472
|
-
};
|
|
455
|
+
return toolError('Conversation ID is required.');
|
|
473
456
|
}
|
|
474
457
|
|
|
475
458
|
const validFormats = ['eml', 'mbox', 'markdown', 'json', 'html', 'csv'];
|
|
476
459
|
if (!validFormats.includes(format)) {
|
|
477
|
-
return {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
460
|
+
return toolError(`Invalid format. Use: ${validFormats.join(', ')}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Resolve and check the directory before fetching anything; write only to
|
|
464
|
+
// the resolved path.
|
|
465
|
+
let resolvedDir;
|
|
466
|
+
try {
|
|
467
|
+
resolvedDir = confineOutputPath(outputDir);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
if (!(error instanceof OutputPathError)) throw error;
|
|
470
|
+
return toolError(error.message, { nextStep: error.nextStep });
|
|
485
471
|
}
|
|
486
472
|
|
|
487
473
|
try {
|
|
@@ -500,20 +486,14 @@ async function handleExportConversation(args) {
|
|
|
500
486
|
);
|
|
501
487
|
|
|
502
488
|
if (messages.length === 0) {
|
|
503
|
-
return
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
type: 'text',
|
|
507
|
-
text: `No messages found for conversation ID: ${conversationId}`,
|
|
508
|
-
},
|
|
509
|
-
],
|
|
510
|
-
};
|
|
489
|
+
return toolError(
|
|
490
|
+
`No messages found for conversation ID: ${conversationId}`
|
|
491
|
+
);
|
|
511
492
|
}
|
|
512
493
|
|
|
513
494
|
// Create output directory
|
|
514
|
-
const resolvedDir = path.resolve(outputDir);
|
|
515
495
|
if (!fs.existsSync(resolvedDir)) {
|
|
516
|
-
|
|
496
|
+
ensureOutputDir(resolvedDir);
|
|
517
497
|
}
|
|
518
498
|
|
|
519
499
|
// Generate filename base
|
|
@@ -788,23 +768,9 @@ async function handleExportConversation(args) {
|
|
|
788
768
|
};
|
|
789
769
|
} catch (error) {
|
|
790
770
|
if (error.message === 'Authentication required') {
|
|
791
|
-
return
|
|
792
|
-
content: [
|
|
793
|
-
{
|
|
794
|
-
type: 'text',
|
|
795
|
-
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
796
|
-
},
|
|
797
|
-
],
|
|
798
|
-
};
|
|
771
|
+
return authRequiredError();
|
|
799
772
|
}
|
|
800
|
-
return {
|
|
801
|
-
content: [
|
|
802
|
-
{
|
|
803
|
-
type: 'text',
|
|
804
|
-
text: `Error exporting conversation: ${error.message}`,
|
|
805
|
-
},
|
|
806
|
-
],
|
|
807
|
-
};
|
|
773
|
+
return toolError(`Error exporting conversation: ${error.message}`);
|
|
808
774
|
}
|
|
809
775
|
}
|
|
810
776
|
|
package/email/delta.js
CHANGED
|
@@ -10,6 +10,7 @@ const { formatEmailList, VERBOSITY } = require('../utils/response-formatter');
|
|
|
10
10
|
const { getEmailFields } = require('../utils/field-presets');
|
|
11
11
|
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
12
12
|
const { resolveFolder, looksLikeFolderId } = require('../folder/resolve');
|
|
13
|
+
const { toolError, authRequiredError } = require('../utils/tool-error');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Extract the mailbox segment (`me` or `users/{address}`) from a delta/
|
|
@@ -82,6 +83,47 @@ function clampPageSize(value) {
|
|
|
82
83
|
return Math.min(Math.max(Math.floor(n), 1), MAX_PAGE_SIZE);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Sync phase of each continuation token this server issued (#262). A
|
|
88
|
+
* `$skiptoken` page belongs to whichever sync produced it, initial or
|
|
89
|
+
* incremental, and its URL doesn't say which, so remember it. Bounded; a
|
|
90
|
+
* continuation token not in here (e.g. after a restart) has phase 'unknown'.
|
|
91
|
+
*/
|
|
92
|
+
const CONTINUATION_PHASES = new Map();
|
|
93
|
+
const MAX_TRACKED_CONTINUATIONS = 200;
|
|
94
|
+
|
|
95
|
+
function rememberPhase(token, phase) {
|
|
96
|
+
CONTINUATION_PHASES.delete(token);
|
|
97
|
+
CONTINUATION_PHASES.set(token, phase);
|
|
98
|
+
if (CONTINUATION_PHASES.size > MAX_TRACKED_CONTINUATIONS) {
|
|
99
|
+
CONTINUATION_PHASES.delete(CONTINUATION_PHASES.keys().next().value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 'initial' (no token, or a continuation of an initial sync), 'incremental'
|
|
105
|
+
* (a delta token, or a continuation of one) or 'unknown' (a continuation
|
|
106
|
+
* token this server didn't issue).
|
|
107
|
+
* @param {string} [deltaToken]
|
|
108
|
+
* @returns {'initial'|'incremental'|'unknown'}
|
|
109
|
+
*/
|
|
110
|
+
function syncPhase(deltaToken) {
|
|
111
|
+
if (!deltaToken) return 'initial';
|
|
112
|
+
if (CONTINUATION_PHASES.has(deltaToken)) {
|
|
113
|
+
return CONTINUATION_PHASES.get(deltaToken);
|
|
114
|
+
}
|
|
115
|
+
// A continuation token this server didn't issue (e.g. from before a
|
|
116
|
+
// restart) can't be placed; any other token is a delta token.
|
|
117
|
+
if (/[?&](\$|%24)skiptoken=/i.test(deltaToken)) return 'unknown';
|
|
118
|
+
return 'incremental';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const PHASE_LABEL = {
|
|
122
|
+
initial: 'Initial',
|
|
123
|
+
incremental: 'Incremental',
|
|
124
|
+
unknown: 'Continuation (initial or incremental unknown)',
|
|
125
|
+
};
|
|
126
|
+
|
|
85
127
|
/**
|
|
86
128
|
* List emails delta handler - incremental sync
|
|
87
129
|
* @param {object} args - Tool arguments
|
|
@@ -116,16 +158,10 @@ async function handleListEmailsDelta(args) {
|
|
|
116
158
|
// different mailbox rather than silently syncing the wrong one.
|
|
117
159
|
const tokenMailbox = mailboxFromToken(deltaToken);
|
|
118
160
|
if (tokenMailbox && mailboxesConflict(tokenMailbox, prefix)) {
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
text:
|
|
124
|
-
`Delta token mailbox mismatch: the token belongs to \`${tokenMailbox}\` but this call targets \`${prefix}\`.\n\n` +
|
|
125
|
-
'A delta token is bound to the mailbox and folder it was issued for. Use the token from that same mailbox/folder, or omit `deltaToken` to start a fresh initial sync here.',
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
};
|
|
161
|
+
return toolError(
|
|
162
|
+
`Delta token mailbox mismatch: the token belongs to \`${tokenMailbox}\` but this call targets \`${prefix}\`.\n\n` +
|
|
163
|
+
'A delta token is bound to the mailbox and folder it was issued for. Use the token from that same mailbox/folder, or omit `deltaToken` to start a fresh initial sync here.'
|
|
164
|
+
);
|
|
129
165
|
}
|
|
130
166
|
endpoint = deltaToken;
|
|
131
167
|
} else {
|
|
@@ -159,6 +195,7 @@ async function handleListEmailsDelta(args) {
|
|
|
159
195
|
);
|
|
160
196
|
|
|
161
197
|
// Process results
|
|
198
|
+
const phase = syncPhase(deltaToken);
|
|
162
199
|
const emails = response.value || [];
|
|
163
200
|
const nextLink = response['@odata.nextLink'];
|
|
164
201
|
const deltaLink = response['@odata.deltaLink'];
|
|
@@ -180,20 +217,20 @@ async function handleListEmailsDelta(args) {
|
|
|
180
217
|
removed: true,
|
|
181
218
|
reason: email['@removed'].reason || 'deleted',
|
|
182
219
|
});
|
|
183
|
-
} else if (
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
changesSummary.updated++;
|
|
220
|
+
} else if (phase === 'initial') {
|
|
221
|
+
// Initial sync (any page) - all items are "created" for our purposes
|
|
222
|
+
changesSummary.created++;
|
|
187
223
|
processedEmails.push(email);
|
|
188
224
|
} else {
|
|
189
|
-
//
|
|
190
|
-
|
|
225
|
+
// Incremental (or unknown) - every non-removed item is a change; Graph
|
|
226
|
+
// doesn't say whether it was created or updated
|
|
227
|
+
changesSummary.updated++;
|
|
191
228
|
processedEmails.push(email);
|
|
192
229
|
}
|
|
193
230
|
}
|
|
194
231
|
|
|
195
232
|
// Build response
|
|
196
|
-
const isInitialSync =
|
|
233
|
+
const isInitialSync = phase === 'initial';
|
|
197
234
|
const hasMoreChanges = Boolean(nextLink);
|
|
198
235
|
const newDeltaToken = deltaLink || nextLink;
|
|
199
236
|
// F-15: nextLink is a continuation token (more pages of the same
|
|
@@ -201,6 +238,7 @@ async function handleListEmailsDelta(args) {
|
|
|
201
238
|
// the initial sync finishes paging. Distinguish them in output so
|
|
202
239
|
// callers know what they're storing.
|
|
203
240
|
const tokenIsContinuation = !deltaLink && Boolean(nextLink);
|
|
241
|
+
if (tokenIsContinuation) rememberPhase(nextLink, phase);
|
|
204
242
|
|
|
205
243
|
// Format output based on verbosity
|
|
206
244
|
let resultText;
|
|
@@ -209,7 +247,7 @@ async function handleListEmailsDelta(args) {
|
|
|
209
247
|
resultText += `| Metric | Value |\n`;
|
|
210
248
|
resultText += `|--------|-------|\n`;
|
|
211
249
|
resultText += `| Items | ${processedEmails.length} |\n`;
|
|
212
|
-
resultText += `| Type | ${
|
|
250
|
+
resultText += `| Type | ${PHASE_LABEL[phase]} |\n`;
|
|
213
251
|
resultText += `| More | ${hasMoreChanges ? 'Yes' : 'No'} |\n`;
|
|
214
252
|
if (newDeltaToken) {
|
|
215
253
|
const label = tokenIsContinuation
|
|
@@ -218,7 +256,7 @@ async function handleListEmailsDelta(args) {
|
|
|
218
256
|
resultText += `\n**${label}**:\n\`\`\`\n${newDeltaToken}\n\`\`\`\n`;
|
|
219
257
|
}
|
|
220
258
|
} else {
|
|
221
|
-
resultText = `## Delta Sync ${
|
|
259
|
+
resultText = `## Delta Sync (${PHASE_LABEL[phase]})\n\n`;
|
|
222
260
|
|
|
223
261
|
// Changes summary
|
|
224
262
|
resultText += `### Changes Summary\n\n`;
|
|
@@ -236,8 +274,11 @@ async function handleListEmailsDelta(args) {
|
|
|
236
274
|
const activeEmails = processedEmails.filter((e) => !e.removed);
|
|
237
275
|
if (activeEmails.length > 0) {
|
|
238
276
|
resultText += `\n### Emails\n\n`;
|
|
277
|
+
// formatEmailList takes (emails, folder, verbosity): the verbosity
|
|
278
|
+
// used to land in the folder slot ("Emails in standard", #306).
|
|
239
279
|
resultText += formatEmailList(
|
|
240
280
|
activeEmails,
|
|
281
|
+
deltaToken ? 'this sync page' : folder,
|
|
241
282
|
verbosity === 'full' ? VERBOSITY.FULL : VERBOSITY.STANDARD
|
|
242
283
|
);
|
|
243
284
|
}
|
|
@@ -280,12 +321,12 @@ async function handleListEmailsDelta(args) {
|
|
|
280
321
|
},
|
|
281
322
|
],
|
|
282
323
|
_meta: {
|
|
283
|
-
syncType:
|
|
324
|
+
syncType: phase,
|
|
284
325
|
mailbox: sharedMailbox || 'me',
|
|
285
326
|
// With a token the folder comes from the token, not the `folder` arg
|
|
286
327
|
// (which is ignored) — don't echo a value we didn't use.
|
|
287
|
-
folder:
|
|
288
|
-
folderSource:
|
|
328
|
+
folder: deltaToken ? null : folder,
|
|
329
|
+
folderSource: deltaToken ? 'deltaToken' : 'argument',
|
|
289
330
|
itemCount: processedEmails.length,
|
|
290
331
|
hasMoreChanges: hasMoreChanges,
|
|
291
332
|
changesSummary: changesSummary,
|
|
@@ -295,14 +336,7 @@ async function handleListEmailsDelta(args) {
|
|
|
295
336
|
};
|
|
296
337
|
} catch (error) {
|
|
297
338
|
if (error.message === 'Authentication required') {
|
|
298
|
-
return
|
|
299
|
-
content: [
|
|
300
|
-
{
|
|
301
|
-
type: 'text',
|
|
302
|
-
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
};
|
|
339
|
+
return authRequiredError();
|
|
306
340
|
}
|
|
307
341
|
|
|
308
342
|
// Handle expired delta token
|
|
@@ -310,25 +344,14 @@ async function handleListEmailsDelta(args) {
|
|
|
310
344
|
error.message.includes('410') ||
|
|
311
345
|
error.message.includes('resyncRequired')
|
|
312
346
|
) {
|
|
313
|
-
return
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
type: 'text',
|
|
317
|
-
text: `## Delta Token Expired\n\nThe provided delta token has expired. Please start a new initial sync by calling without a deltaToken.\n\n**Error:** ${error.message}`,
|
|
318
|
-
},
|
|
319
|
-
],
|
|
320
|
-
};
|
|
347
|
+
return toolError(
|
|
348
|
+
`## Delta Token Expired\n\nThe provided delta token has expired. Please start a new initial sync by calling without a deltaToken.\n\n**Error:** ${error.message}`
|
|
349
|
+
);
|
|
321
350
|
}
|
|
322
351
|
|
|
323
|
-
return {
|
|
324
|
-
content: [
|
|
325
|
-
{
|
|
326
|
-
type: 'text',
|
|
327
|
-
text: `Delta sync failed: ${error.message}`,
|
|
328
|
-
},
|
|
329
|
-
],
|
|
330
|
-
};
|
|
352
|
+
return toolError(`Delta sync failed: ${error.message}`);
|
|
331
353
|
}
|
|
332
354
|
}
|
|
333
355
|
|
|
334
356
|
module.exports = handleListEmailsDelta;
|
|
357
|
+
module.exports.syncPhase = syncPhase;
|