@littlebearapps/outlook-assistant 3.12.1 → 3.14.0
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 +27 -3
- package/README.md +108 -33
- package/advanced/index.js +44 -174
- package/auth/auth-errors.js +23 -1
- package/auth/client-config.js +142 -0
- package/auth/index.js +4 -2
- package/auth/oauth-server.js +12 -2
- package/auth/token-manager.js +7 -3
- package/auth/token-storage.js +46 -33
- package/auth/tools.js +223 -93
- 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 +335 -0
- package/calendar/update.js +42 -86
- package/categories/index.js +59 -264
- package/config.js +36 -2
- package/contacts/index.js +72 -128
- package/email/attachments.js +42 -124
- package/email/conversations.js +44 -78
- package/email/delta.js +10 -34
- package/email/draft.js +140 -96
- package/email/export.js +141 -110
- package/email/folder-utils.js +3 -2
- package/email/headers.js +11 -49
- package/email/index.js +85 -109
- 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 +14 -49
- package/email/read.js +16 -50
- package/email/search.js +46 -86
- package/email/send.js +82 -48
- package/folder/create.js +6 -25
- package/folder/delete.js +117 -38
- package/folder/index.js +17 -16
- package/folder/list.js +5 -17
- package/folder/move.js +13 -42
- package/folder/resolve.js +11 -6
- package/folder/stats.js +6 -20
- package/index.js +23 -45
- package/llms-install.md +31 -7
- package/llms.txt +19 -10
- package/outlook-auth-server.js +10 -3
- package/package.json +6 -2
- package/request-handler.js +217 -116
- package/rules/create.js +27 -70
- package/rules/index.js +30 -92
- package/rules/list.js +5 -17
- package/rules/rule-builder.js +57 -20
- package/rules/update.js +26 -60
- package/server.js +37 -0
- package/settings/index.js +142 -143
- 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 +109 -25
- package/utils/server-instructions.js +62 -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: {
|
|
@@ -892,27 +707,17 @@ const categoriesTools = [
|
|
|
892
707
|
case 'list':
|
|
893
708
|
return handleListCategories(args);
|
|
894
709
|
default:
|
|
895
|
-
return
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
type: 'text',
|
|
899
|
-
text: `Unknown action '${action}'. Valid actions: list, create, update, delete.`,
|
|
900
|
-
},
|
|
901
|
-
],
|
|
902
|
-
};
|
|
710
|
+
return toolError(
|
|
711
|
+
`Unknown action '${action}'. Valid actions: list, create, update, delete.`
|
|
712
|
+
);
|
|
903
713
|
}
|
|
904
714
|
},
|
|
905
715
|
},
|
|
906
716
|
{
|
|
907
717
|
name: 'apply-category',
|
|
908
718
|
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 via Graph `$batch`). `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
|
|
910
|
-
|
|
911
|
-
title: 'Apply Categories',
|
|
912
|
-
readOnlyHint: false,
|
|
913
|
-
destructiveHint: false,
|
|
914
|
-
openWorldHint: false,
|
|
915
|
-
},
|
|
719
|
+
"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 via Graph `$batch`). `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.",
|
|
720
|
+
...toolMetadata('apply-category', 'Apply Categories'),
|
|
916
721
|
inputSchema: {
|
|
917
722
|
type: 'object',
|
|
918
723
|
properties: {
|
|
@@ -939,7 +744,7 @@ const categoriesTools = [
|
|
|
939
744
|
sharedMailbox: {
|
|
940
745
|
type: 'string',
|
|
941
746
|
description:
|
|
942
|
-
'Email address of
|
|
747
|
+
'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
748
|
},
|
|
944
749
|
email: {
|
|
945
750
|
type: 'string',
|
|
@@ -955,12 +760,7 @@ const categoriesTools = [
|
|
|
955
760
|
name: 'manage-focused-inbox',
|
|
956
761
|
description:
|
|
957
762
|
'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
|
-
},
|
|
763
|
+
...toolMetadata('manage-focused-inbox', 'Focused Inbox'),
|
|
964
764
|
inputSchema: {
|
|
965
765
|
type: 'object',
|
|
966
766
|
properties: {
|
|
@@ -1004,14 +804,9 @@ const categoriesTools = [
|
|
|
1004
804
|
case 'list':
|
|
1005
805
|
return handleGetFocusedInboxOverrides(args);
|
|
1006
806
|
default:
|
|
1007
|
-
return
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
type: 'text',
|
|
1011
|
-
text: `Unknown action '${action}'. Valid actions: list, set, delete.`,
|
|
1012
|
-
},
|
|
1013
|
-
],
|
|
1014
|
-
};
|
|
807
|
+
return toolError(
|
|
808
|
+
`Unknown action '${action}'. Valid actions: list, set, delete.`
|
|
809
|
+
);
|
|
1015
810
|
}
|
|
1016
811
|
},
|
|
1017
812
|
},
|
package/config.js
CHANGED
|
@@ -14,6 +14,8 @@ const {
|
|
|
14
14
|
getFolderFields,
|
|
15
15
|
} = require('./utils/field-presets');
|
|
16
16
|
const { VERBOSITY, DEFAULT_LIMITS } = require('./utils/response-formatter');
|
|
17
|
+
const { resolveClientId } = require('./auth/client-config');
|
|
18
|
+
const { redact } = require('./utils/logger');
|
|
17
19
|
|
|
18
20
|
// Ensure we have a home directory path — never fall back to /tmp (world-readable)
|
|
19
21
|
const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
@@ -89,7 +91,8 @@ function parseSharedMailboxMode(raw) {
|
|
|
89
91
|
if (['true', 'readwrite', '1'].includes(value)) return 'readwrite';
|
|
90
92
|
if (value && !['false', '0', 'off', 'no'].includes(value)) {
|
|
91
93
|
console.warn(
|
|
92
|
-
|
|
94
|
+
// redact(): people sometimes put the shared mailbox's address here.
|
|
95
|
+
`[outlook-assistant] OUTLOOK_SHARED_MAILBOX="${redact(raw)}" is not a recognised value. ` +
|
|
93
96
|
'Expected read, true/readwrite/1, or unset. Shared-mailbox support stays off.'
|
|
94
97
|
);
|
|
95
98
|
}
|
|
@@ -123,6 +126,27 @@ const BASE_SCOPES = [
|
|
|
123
126
|
// 'Place.Read.All', // find-meeting-rooms tool
|
|
124
127
|
];
|
|
125
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
|
+
|
|
126
150
|
const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
|
|
127
151
|
|
|
128
152
|
/**
|
|
@@ -154,6 +178,11 @@ module.exports = {
|
|
|
154
178
|
// Test mode setting
|
|
155
179
|
USE_TEST_MODE: process.env.USE_TEST_MODE === 'true',
|
|
156
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
|
+
|
|
157
186
|
// OAuth scope sets (exported so tests + the fallback logic can reference them)
|
|
158
187
|
BASE_SCOPES,
|
|
159
188
|
// `.Shared` scopes requested at sign-in for the configured mode ([] = off)
|
|
@@ -165,7 +194,12 @@ module.exports = {
|
|
|
165
194
|
|
|
166
195
|
// Authentication configuration
|
|
167
196
|
AUTH_CONFIG: {
|
|
168
|
-
|
|
197
|
+
// Getter, resolved on every read: OUTLOOK_CLIENT_ID → MS_CLIENT_ID →
|
|
198
|
+
// ~/.outlook-assistant-config.json (saved via `auth action=authenticate
|
|
199
|
+
// clientId=…`), so a saved ID takes effect without a restart.
|
|
200
|
+
get clientId() {
|
|
201
|
+
return resolveClientId();
|
|
202
|
+
},
|
|
169
203
|
clientSecret: process.env.OUTLOOK_CLIENT_SECRET || '',
|
|
170
204
|
redirectUri: 'http://localhost:3333/auth/callback',
|
|
171
205
|
// Base scopes, plus the `.Shared` scopes only when OUTLOOK_SHARED_MAILBOX
|