@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/categories/index.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
7
7
|
const { ensureAuthenticated } = require('../auth');
|
|
8
8
|
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
9
|
+
const { toolMetadata } = require('../utils/risk-classes');
|
|
10
|
+
const { toolError, authRequiredError } = require('../utils/tool-error');
|
|
9
11
|
|
|
10
12
|
// Category color presets (Outlook uses these names)
|
|
11
13
|
const CATEGORY_COLORS = [
|
|
@@ -137,23 +139,9 @@ async function handleListCategories(args) {
|
|
|
137
139
|
};
|
|
138
140
|
} catch (error) {
|
|
139
141
|
if (error.message === 'Authentication required') {
|
|
140
|
-
return
|
|
141
|
-
content: [
|
|
142
|
-
{
|
|
143
|
-
type: 'text',
|
|
144
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
145
|
-
},
|
|
146
|
-
],
|
|
147
|
-
};
|
|
142
|
+
return authRequiredError();
|
|
148
143
|
}
|
|
149
|
-
return {
|
|
150
|
-
content: [
|
|
151
|
-
{
|
|
152
|
-
type: 'text',
|
|
153
|
-
text: `Error listing categories: ${error.message}`,
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
};
|
|
144
|
+
return toolError(`Error listing categories: ${error.message}`);
|
|
157
145
|
}
|
|
158
146
|
}
|
|
159
147
|
|
|
@@ -164,30 +152,18 @@ async function handleCreateCategory(args) {
|
|
|
164
152
|
const { displayName, color } = args;
|
|
165
153
|
|
|
166
154
|
if (!displayName) {
|
|
167
|
-
return
|
|
168
|
-
content: [
|
|
169
|
-
{
|
|
170
|
-
type: 'text',
|
|
171
|
-
text: 'Category name (displayName) is required.',
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
};
|
|
155
|
+
return toolError('Category name (displayName) is required.');
|
|
175
156
|
}
|
|
176
157
|
|
|
177
158
|
// Validate color if provided
|
|
178
159
|
if (color && !CATEGORY_COLORS.includes(color)) {
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
187
|
-
.join(', ')}`,
|
|
188
|
-
},
|
|
189
|
-
],
|
|
190
|
-
};
|
|
160
|
+
return toolError(
|
|
161
|
+
`Invalid color. Valid options: ${CATEGORY_COLORS.join(', ')}\n\nColor names: ${Object.entries(
|
|
162
|
+
COLOR_NAMES
|
|
163
|
+
)
|
|
164
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
165
|
+
.join(', ')}`
|
|
166
|
+
);
|
|
191
167
|
}
|
|
192
168
|
|
|
193
169
|
try {
|
|
@@ -220,35 +196,14 @@ async function handleCreateCategory(args) {
|
|
|
220
196
|
};
|
|
221
197
|
} catch (error) {
|
|
222
198
|
if (error.message === 'Authentication required') {
|
|
223
|
-
return
|
|
224
|
-
content: [
|
|
225
|
-
{
|
|
226
|
-
type: 'text',
|
|
227
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
};
|
|
199
|
+
return authRequiredError();
|
|
231
200
|
}
|
|
232
201
|
|
|
233
202
|
if (error.message.includes('already exists')) {
|
|
234
|
-
return {
|
|
235
|
-
content: [
|
|
236
|
-
{
|
|
237
|
-
type: 'text',
|
|
238
|
-
text: `A category named "${displayName}" already exists.`,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
};
|
|
203
|
+
return toolError(`A category named "${displayName}" already exists.`);
|
|
242
204
|
}
|
|
243
205
|
|
|
244
|
-
return {
|
|
245
|
-
content: [
|
|
246
|
-
{
|
|
247
|
-
type: 'text',
|
|
248
|
-
text: `Error creating category: ${error.message}`,
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
};
|
|
206
|
+
return toolError(`Error creating category: ${error.message}`);
|
|
252
207
|
}
|
|
253
208
|
}
|
|
254
209
|
|
|
@@ -261,37 +216,20 @@ async function handleUpdateCategory(args) {
|
|
|
261
216
|
const { displayName, color } = args;
|
|
262
217
|
|
|
263
218
|
if (!id) {
|
|
264
|
-
return
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
type: 'text',
|
|
268
|
-
text: 'Category ID is required. Use manage-category with action=list to find category IDs.',
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
};
|
|
219
|
+
return toolError(
|
|
220
|
+
'Category ID is required. Use manage-category with action=list to find category IDs.'
|
|
221
|
+
);
|
|
272
222
|
}
|
|
273
223
|
|
|
274
224
|
if (!displayName && !color) {
|
|
275
|
-
return
|
|
276
|
-
content: [
|
|
277
|
-
{
|
|
278
|
-
type: 'text',
|
|
279
|
-
text: 'At least one of displayName or color must be provided.',
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
};
|
|
225
|
+
return toolError('At least one of displayName or color must be provided.');
|
|
283
226
|
}
|
|
284
227
|
|
|
285
228
|
// Validate color if provided
|
|
286
229
|
if (color && !CATEGORY_COLORS.includes(color)) {
|
|
287
|
-
return
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
type: 'text',
|
|
291
|
-
text: `Invalid color. Valid options: ${CATEGORY_COLORS.join(', ')}`,
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
};
|
|
230
|
+
return toolError(
|
|
231
|
+
`Invalid color. Valid options: ${CATEGORY_COLORS.join(', ')}`
|
|
232
|
+
);
|
|
295
233
|
}
|
|
296
234
|
|
|
297
235
|
try {
|
|
@@ -343,23 +281,9 @@ async function handleUpdateCategory(args) {
|
|
|
343
281
|
};
|
|
344
282
|
} catch (error) {
|
|
345
283
|
if (error.message === 'Authentication required') {
|
|
346
|
-
return
|
|
347
|
-
content: [
|
|
348
|
-
{
|
|
349
|
-
type: 'text',
|
|
350
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
351
|
-
},
|
|
352
|
-
],
|
|
353
|
-
};
|
|
284
|
+
return authRequiredError();
|
|
354
285
|
}
|
|
355
|
-
return {
|
|
356
|
-
content: [
|
|
357
|
-
{
|
|
358
|
-
type: 'text',
|
|
359
|
-
text: `Error updating category: ${error.message}`,
|
|
360
|
-
},
|
|
361
|
-
],
|
|
362
|
-
};
|
|
286
|
+
return toolError(`Error updating category: ${error.message}`);
|
|
363
287
|
}
|
|
364
288
|
}
|
|
365
289
|
|
|
@@ -371,14 +295,9 @@ async function handleDeleteCategory(args) {
|
|
|
371
295
|
const id = args.id || args.categoryId;
|
|
372
296
|
|
|
373
297
|
if (!id) {
|
|
374
|
-
return
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
type: 'text',
|
|
378
|
-
text: 'Category ID is required. Use manage-category with action=list to find category IDs.',
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
};
|
|
298
|
+
return toolError(
|
|
299
|
+
'Category ID is required. Use manage-category with action=list to find category IDs.'
|
|
300
|
+
);
|
|
382
301
|
}
|
|
383
302
|
|
|
384
303
|
try {
|
|
@@ -400,35 +319,16 @@ async function handleDeleteCategory(args) {
|
|
|
400
319
|
};
|
|
401
320
|
} catch (error) {
|
|
402
321
|
if (error.message === 'Authentication required') {
|
|
403
|
-
return
|
|
404
|
-
content: [
|
|
405
|
-
{
|
|
406
|
-
type: 'text',
|
|
407
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
408
|
-
},
|
|
409
|
-
],
|
|
410
|
-
};
|
|
322
|
+
return authRequiredError();
|
|
411
323
|
}
|
|
412
324
|
|
|
413
325
|
if (error.message.includes('not found') || error.message.includes('404')) {
|
|
414
|
-
return
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
type: 'text',
|
|
418
|
-
text: `Category not found. Use manage-category with action=list to see available categories.`,
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
};
|
|
326
|
+
return toolError(
|
|
327
|
+
`Category not found. Use manage-category with action=list to see available categories.`
|
|
328
|
+
);
|
|
422
329
|
}
|
|
423
330
|
|
|
424
|
-
return {
|
|
425
|
-
content: [
|
|
426
|
-
{
|
|
427
|
-
type: 'text',
|
|
428
|
-
text: `Error deleting category: ${error.message}`,
|
|
429
|
-
},
|
|
430
|
-
],
|
|
431
|
-
};
|
|
331
|
+
return toolError(`Error deleting category: ${error.message}`);
|
|
432
332
|
}
|
|
433
333
|
}
|
|
434
334
|
|
|
@@ -443,39 +343,22 @@ async function handleApplyCategory(args) {
|
|
|
443
343
|
const ids = messageIds || (messageId ? [messageId] : []);
|
|
444
344
|
|
|
445
345
|
if (ids.length === 0) {
|
|
446
|
-
return
|
|
447
|
-
content: [
|
|
448
|
-
{
|
|
449
|
-
type: 'text',
|
|
450
|
-
text: 'Message ID (messageId) or IDs (messageIds) required.',
|
|
451
|
-
},
|
|
452
|
-
],
|
|
453
|
-
};
|
|
346
|
+
return toolError('Message ID (messageId) or IDs (messageIds) required.');
|
|
454
347
|
}
|
|
455
348
|
|
|
456
349
|
const applyAction = action || 'set'; // 'set', 'add', 'remove'
|
|
457
350
|
|
|
458
351
|
if (!categories || !Array.isArray(categories)) {
|
|
459
|
-
return
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
type: 'text',
|
|
463
|
-
text: 'Categories array is required. Provide category display names.',
|
|
464
|
-
},
|
|
465
|
-
],
|
|
466
|
-
};
|
|
352
|
+
return toolError(
|
|
353
|
+
'Categories array is required. Provide category display names.'
|
|
354
|
+
);
|
|
467
355
|
}
|
|
468
356
|
|
|
469
357
|
// Empty array is only valid for action=set (clears all categories)
|
|
470
358
|
if (categories.length === 0 && applyAction !== 'set') {
|
|
471
|
-
return
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
type: 'text',
|
|
475
|
-
text: 'Categories array cannot be empty for add/remove. Use action=set with an empty array to clear all categories.',
|
|
476
|
-
},
|
|
477
|
-
],
|
|
478
|
-
};
|
|
359
|
+
return toolError(
|
|
360
|
+
'Categories array cannot be empty for add/remove. Use action=set with an empty array to clear all categories.'
|
|
361
|
+
);
|
|
479
362
|
}
|
|
480
363
|
|
|
481
364
|
try {
|
|
@@ -562,23 +445,9 @@ async function handleApplyCategory(args) {
|
|
|
562
445
|
};
|
|
563
446
|
} catch (error) {
|
|
564
447
|
if (error.message === 'Authentication required') {
|
|
565
|
-
return
|
|
566
|
-
content: [
|
|
567
|
-
{
|
|
568
|
-
type: 'text',
|
|
569
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
570
|
-
},
|
|
571
|
-
],
|
|
572
|
-
};
|
|
448
|
+
return authRequiredError();
|
|
573
449
|
}
|
|
574
|
-
return {
|
|
575
|
-
content: [
|
|
576
|
-
{
|
|
577
|
-
type: 'text',
|
|
578
|
-
text: `Error applying categories: ${error.message}`,
|
|
579
|
-
},
|
|
580
|
-
],
|
|
581
|
-
};
|
|
450
|
+
return toolError(`Error applying categories: ${error.message}`);
|
|
582
451
|
}
|
|
583
452
|
}
|
|
584
453
|
|
|
@@ -664,23 +533,9 @@ async function handleGetFocusedInboxOverrides(args) {
|
|
|
664
533
|
};
|
|
665
534
|
} catch (error) {
|
|
666
535
|
if (error.message === 'Authentication required') {
|
|
667
|
-
return
|
|
668
|
-
content: [
|
|
669
|
-
{
|
|
670
|
-
type: 'text',
|
|
671
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
672
|
-
},
|
|
673
|
-
],
|
|
674
|
-
};
|
|
536
|
+
return authRequiredError();
|
|
675
537
|
}
|
|
676
|
-
return {
|
|
677
|
-
content: [
|
|
678
|
-
{
|
|
679
|
-
type: 'text',
|
|
680
|
-
text: `Error getting Focused Inbox overrides: ${error.message}`,
|
|
681
|
-
},
|
|
682
|
-
],
|
|
683
|
-
};
|
|
538
|
+
return toolError(`Error getting Focused Inbox overrides: ${error.message}`);
|
|
684
539
|
}
|
|
685
540
|
}
|
|
686
541
|
|
|
@@ -693,26 +548,12 @@ async function handleSetFocusedInboxOverride(args) {
|
|
|
693
548
|
const overrideAction = args.action;
|
|
694
549
|
|
|
695
550
|
if (!emailAddress) {
|
|
696
|
-
return
|
|
697
|
-
content: [
|
|
698
|
-
{
|
|
699
|
-
type: 'text',
|
|
700
|
-
text: 'Email address is required.',
|
|
701
|
-
},
|
|
702
|
-
],
|
|
703
|
-
};
|
|
551
|
+
return toolError('Email address is required.');
|
|
704
552
|
}
|
|
705
553
|
|
|
706
554
|
const classification = classifyAs || 'focused';
|
|
707
555
|
if (!['focused', 'other'].includes(classification)) {
|
|
708
|
-
return
|
|
709
|
-
content: [
|
|
710
|
-
{
|
|
711
|
-
type: 'text',
|
|
712
|
-
text: "classifyAs must be 'focused' or 'other'.",
|
|
713
|
-
},
|
|
714
|
-
],
|
|
715
|
-
};
|
|
556
|
+
return toolError("classifyAs must be 'focused' or 'other'.");
|
|
716
557
|
}
|
|
717
558
|
|
|
718
559
|
try {
|
|
@@ -734,14 +575,7 @@ async function handleSetFocusedInboxOverride(args) {
|
|
|
734
575
|
// Handle delete action
|
|
735
576
|
if (overrideAction === 'delete') {
|
|
736
577
|
if (!existing) {
|
|
737
|
-
return {
|
|
738
|
-
content: [
|
|
739
|
-
{
|
|
740
|
-
type: 'text',
|
|
741
|
-
text: `No override found for ${emailAddress}.`,
|
|
742
|
-
},
|
|
743
|
-
],
|
|
744
|
-
};
|
|
578
|
+
return toolError(`No override found for ${emailAddress}.`);
|
|
745
579
|
}
|
|
746
580
|
|
|
747
581
|
await callGraphAPI(
|
|
@@ -806,23 +640,9 @@ async function handleSetFocusedInboxOverride(args) {
|
|
|
806
640
|
};
|
|
807
641
|
} catch (error) {
|
|
808
642
|
if (error.message === 'Authentication required') {
|
|
809
|
-
return
|
|
810
|
-
content: [
|
|
811
|
-
{
|
|
812
|
-
type: 'text',
|
|
813
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
814
|
-
},
|
|
815
|
-
],
|
|
816
|
-
};
|
|
643
|
+
return authRequiredError();
|
|
817
644
|
}
|
|
818
|
-
return {
|
|
819
|
-
content: [
|
|
820
|
-
{
|
|
821
|
-
type: 'text',
|
|
822
|
-
text: `Error setting Focused Inbox override: ${error.message}`,
|
|
823
|
-
},
|
|
824
|
-
],
|
|
825
|
-
};
|
|
645
|
+
return toolError(`Error setting Focused Inbox override: ${error.message}`);
|
|
826
646
|
}
|
|
827
647
|
}
|
|
828
648
|
|
|
@@ -832,12 +652,7 @@ const categoriesTools = [
|
|
|
832
652
|
name: 'manage-category',
|
|
833
653
|
description:
|
|
834
654
|
"Manage the user's master category list (the colour-coded labels available across mail/calendar/contacts). action=`list` (default) returns categories with id/displayName/color. action=`create` adds a new category — `displayName` required, `color` optional (preset0-preset24, e.g. preset0=Red, preset7=Blue). action=`update` (alias `set` — deprecated) changes name/colour by `id`. action=`delete` removes a category — this does NOT untag messages already labelled with it; existing messages retain the orphaned label until manually cleaned. Use `apply-category` to tag/untag specific messages.",
|
|
835
|
-
|
|
836
|
-
title: 'Master Categories',
|
|
837
|
-
readOnlyHint: false,
|
|
838
|
-
destructiveHint: false,
|
|
839
|
-
openWorldHint: false,
|
|
840
|
-
},
|
|
655
|
+
...toolMetadata('manage-category', 'Master Categories'),
|
|
841
656
|
inputSchema: {
|
|
842
657
|
type: 'object',
|
|
843
658
|
properties: {
|
|
@@ -884,7 +699,15 @@ const categoriesTools = [
|
|
|
884
699
|
switch (action) {
|
|
885
700
|
case 'create':
|
|
886
701
|
return handleCreateCategory(args);
|
|
887
|
-
case 'set':
|
|
702
|
+
case 'set': {
|
|
703
|
+
// Deprecated alias: works, but say so (#306).
|
|
704
|
+
const result = await handleUpdateCategory(args);
|
|
705
|
+
if (!result.isError && result.content?.[0]?.text) {
|
|
706
|
+
result.content[0].text +=
|
|
707
|
+
'\n\nNote: action=`set` is a deprecated alias for `update`; use `update`.';
|
|
708
|
+
}
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
888
711
|
case 'update':
|
|
889
712
|
return handleUpdateCategory(args);
|
|
890
713
|
case 'delete':
|
|
@@ -892,27 +715,17 @@ const categoriesTools = [
|
|
|
892
715
|
case 'list':
|
|
893
716
|
return handleListCategories(args);
|
|
894
717
|
default:
|
|
895
|
-
return
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
type: 'text',
|
|
899
|
-
text: `Unknown action '${action}'. Valid actions: list, create, update, delete.`,
|
|
900
|
-
},
|
|
901
|
-
],
|
|
902
|
-
};
|
|
718
|
+
return toolError(
|
|
719
|
+
`Unknown action '${action}'. Valid actions: list, create, update, delete.`
|
|
720
|
+
);
|
|
903
721
|
}
|
|
904
722
|
},
|
|
905
723
|
},
|
|
906
724
|
{
|
|
907
725
|
name: 'apply-category',
|
|
908
726
|
description:
|
|
909
|
-
"Tag or untag email messages with master categories (those created via `manage-category`). action=`set` (default) replaces the message's category set with the supplied `categories` array. action=`add` appends categories to whatever's already on the message. action=`remove` removes only the named categories, leaving the rest. Accepts either `messageId` (single) or `messageIds` (batch
|
|
910
|
-
|
|
911
|
-
title: 'Apply Categories',
|
|
912
|
-
readOnlyHint: false,
|
|
913
|
-
destructiveHint: false,
|
|
914
|
-
openWorldHint: false,
|
|
915
|
-
},
|
|
727
|
+
"Tag or untag email messages with master categories (those created via `manage-category`). action=`set` (default) replaces the message's category set with the supplied `categories` array. action=`add` appends categories to whatever's already on the message. action=`remove` removes only the named categories, leaving the rest. Accepts either `messageId` (single) or `messageIds` (batch: one request per message). `categories` are matched by display name — names must already exist in the target mailbox's master list. For your own mailbox, create them via `manage-category` first; for a shared mailbox, the names must already exist there (`manage-category` only manages the signed-in account's master list). Pass `sharedMailbox` (or alias `email`) to categorise messages in a shared/delegated mailbox (default: the signed-in account; requires Mail.ReadWrite.Shared + delegate access). Returns per-message confirmation.",
|
|
728
|
+
...toolMetadata('apply-category', 'Apply Categories'),
|
|
916
729
|
inputSchema: {
|
|
917
730
|
type: 'object',
|
|
918
731
|
properties: {
|
|
@@ -939,7 +752,7 @@ const categoriesTools = [
|
|
|
939
752
|
sharedMailbox: {
|
|
940
753
|
type: 'string',
|
|
941
754
|
description:
|
|
942
|
-
'Email address of
|
|
755
|
+
'Email address of the shared/delegated mailbox whose messages to categorise (default: the signed-in account). Requires delegate access + Mail.ReadWrite.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).',
|
|
943
756
|
},
|
|
944
757
|
email: {
|
|
945
758
|
type: 'string',
|
|
@@ -955,12 +768,7 @@ const categoriesTools = [
|
|
|
955
768
|
name: 'manage-focused-inbox',
|
|
956
769
|
description:
|
|
957
770
|
'Manage Focused Inbox sender overrides — explicit rules that force messages from a given sender into Focused or Other regardless of the ML classifier. action=`list` (default) returns existing overrides with id/sender/classifyAs. action=`set` creates or updates an override for `emailAddress` (optional `name`), routing future mail to `focused` (default) or `other`. action=`delete` removes the override for `emailAddress`. Note: this only works on accounts that have Focused Inbox enabled — personal Outlook.com accounts without it return an empty list.',
|
|
958
|
-
|
|
959
|
-
title: 'Focused Inbox',
|
|
960
|
-
readOnlyHint: false,
|
|
961
|
-
destructiveHint: false,
|
|
962
|
-
openWorldHint: false,
|
|
963
|
-
},
|
|
771
|
+
...toolMetadata('manage-focused-inbox', 'Focused Inbox'),
|
|
964
772
|
inputSchema: {
|
|
965
773
|
type: 'object',
|
|
966
774
|
properties: {
|
|
@@ -1004,14 +812,9 @@ const categoriesTools = [
|
|
|
1004
812
|
case 'list':
|
|
1005
813
|
return handleGetFocusedInboxOverrides(args);
|
|
1006
814
|
default:
|
|
1007
|
-
return
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
type: 'text',
|
|
1011
|
-
text: `Unknown action '${action}'. Valid actions: list, set, delete.`,
|
|
1012
|
-
},
|
|
1013
|
-
],
|
|
1014
|
-
};
|
|
815
|
+
return toolError(
|
|
816
|
+
`Unknown action '${action}'. Valid actions: list, set, delete.`
|
|
817
|
+
);
|
|
1015
818
|
}
|
|
1016
819
|
},
|
|
1017
820
|
},
|
package/config.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
} = require('./utils/field-presets');
|
|
16
16
|
const { VERBOSITY, DEFAULT_LIMITS } = require('./utils/response-formatter');
|
|
17
17
|
const { resolveClientId } = require('./auth/client-config');
|
|
18
|
+
const { redact } = require('./utils/logger');
|
|
18
19
|
|
|
19
20
|
// Ensure we have a home directory path — never fall back to /tmp (world-readable)
|
|
20
21
|
const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
@@ -90,7 +91,8 @@ function parseSharedMailboxMode(raw) {
|
|
|
90
91
|
if (['true', 'readwrite', '1'].includes(value)) return 'readwrite';
|
|
91
92
|
if (value && !['false', '0', 'off', 'no'].includes(value)) {
|
|
92
93
|
console.warn(
|
|
93
|
-
|
|
94
|
+
// redact(): people sometimes put the shared mailbox's address here.
|
|
95
|
+
`[outlook-assistant] OUTLOOK_SHARED_MAILBOX="${redact(raw)}" is not a recognised value. ` +
|
|
94
96
|
'Expected read, true/readwrite/1, or unset. Shared-mailbox support stays off.'
|
|
95
97
|
);
|
|
96
98
|
}
|
|
@@ -124,6 +126,27 @@ const BASE_SCOPES = [
|
|
|
124
126
|
// 'Place.Read.All', // find-meeting-rooms tool
|
|
125
127
|
];
|
|
126
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Parse OUTLOOK_READ_ONLY. On: true/1/yes/on; off: unset, empty,
|
|
131
|
+
* false/0/no/off (any case). Anything else fails closed (read-only on, with
|
|
132
|
+
* a warning), because whoever set the variable meant to restrict the server.
|
|
133
|
+
* @param {string|undefined} raw
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
function parseReadOnly(raw) {
|
|
137
|
+
const value = String(raw ?? '')
|
|
138
|
+
.trim()
|
|
139
|
+
.toLowerCase();
|
|
140
|
+
if (['true', '1', 'yes', 'on'].includes(value)) return true;
|
|
141
|
+
if (['', 'false', '0', 'no', 'off'].includes(value)) return false;
|
|
142
|
+
// Redacted and capped: the value is echoed only to spot a typo.
|
|
143
|
+
console.warn(
|
|
144
|
+
`[outlook-assistant] OUTLOOK_READ_ONLY="${redact(String(raw).slice(0, 40))}" is not a recognised value. ` +
|
|
145
|
+
'Expected true, 1, yes or on to switch it on, or false, 0, no or off (or unset) to switch it off. Treating it as on: read-only mode is on.'
|
|
146
|
+
);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
|
|
128
151
|
|
|
129
152
|
/**
|
|
@@ -155,6 +178,11 @@ module.exports = {
|
|
|
155
178
|
// Test mode setting
|
|
156
179
|
USE_TEST_MODE: process.env.USE_TEST_MODE === 'true',
|
|
157
180
|
|
|
181
|
+
// Read-only mode (OUTLOOK_READ_ONLY, #271): every tool call that isn't
|
|
182
|
+
// classified `read` in utils/risk-classes.js is refused before it runs.
|
|
183
|
+
READ_ONLY: parseReadOnly(process.env.OUTLOOK_READ_ONLY),
|
|
184
|
+
parseReadOnly,
|
|
185
|
+
|
|
158
186
|
// OAuth scope sets (exported so tests + the fallback logic can reference them)
|
|
159
187
|
BASE_SCOPES,
|
|
160
188
|
// `.Shared` scopes requested at sign-in for the configured mode ([] = off)
|