@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/mail-tips.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
8
8
|
const { ensureAuthenticated } = require('../auth');
|
|
9
|
+
const { toolError, authRequiredError } = require('../utils/tool-error');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* All available mail tip types from Microsoft Graph API
|
|
@@ -25,17 +26,27 @@ const MAIL_TIP_TYPES = [
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Format mail tips into readable markdown
|
|
29
|
+
*
|
|
30
|
+
* Graph's mailTips resource reports `mailboxFull`, `deliveryRestricted` and
|
|
31
|
+
* `isModerated` as booleans. The older `mailboxFullStatus`,
|
|
32
|
+
* `deliveryRestriction` and `moderationStatus` shapes are still recognised.
|
|
28
33
|
* @param {Array} mailTips - Array of mail tip objects from Graph API
|
|
29
|
-
* @returns {string
|
|
34
|
+
* @returns {{formatted: string, warningCount: number, issues: Array<{address: string, type: string}>}}
|
|
35
|
+
* `issues` lists each flagged condition per recipient, with `type` one of
|
|
36
|
+
* outOfOffice, mailboxFull, customTip, deliveryRestricted, moderated,
|
|
37
|
+
* external or externalMembers. Every issue is also a warning in the text,
|
|
38
|
+
* so the text never shows ✓ for a recipient send-email would refuse
|
|
30
39
|
*/
|
|
31
40
|
function formatMailTips(mailTips) {
|
|
32
41
|
const lines = [];
|
|
42
|
+
const issues = [];
|
|
33
43
|
let warningCount = 0;
|
|
34
44
|
|
|
35
45
|
for (const tip of mailTips) {
|
|
36
46
|
const email = tip.emailAddress?.address || 'Unknown';
|
|
37
47
|
const tipLines = [];
|
|
38
48
|
const warnings = [];
|
|
49
|
+
const flag = (type) => issues.push({ address: email, type });
|
|
39
50
|
|
|
40
51
|
// Out-of-office / automatic replies
|
|
41
52
|
if (
|
|
@@ -43,6 +54,7 @@ function formatMailTips(mailTips) {
|
|
|
43
54
|
tip.automaticReplies.message.trim() !== ''
|
|
44
55
|
) {
|
|
45
56
|
warnings.push('Out of Office');
|
|
57
|
+
flag('outOfOffice');
|
|
46
58
|
const reply = tip.automaticReplies;
|
|
47
59
|
tipLines.push(
|
|
48
60
|
` **Out of Office**: ${reply.message.replace(/\n/g, ' ').substring(0, 200)}`
|
|
@@ -57,8 +69,9 @@ function formatMailTips(mailTips) {
|
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
// Mailbox full
|
|
60
|
-
if (tip.mailboxFullStatus) {
|
|
72
|
+
if (tip.mailboxFull === true || tip.mailboxFullStatus === true) {
|
|
61
73
|
warnings.push('Mailbox Full');
|
|
74
|
+
flag('mailboxFull');
|
|
62
75
|
tipLines.push(
|
|
63
76
|
` **Mailbox Full**: Recipient's mailbox is full — delivery may fail`
|
|
64
77
|
);
|
|
@@ -67,30 +80,38 @@ function formatMailTips(mailTips) {
|
|
|
67
80
|
// Custom mail tip (admin-configured)
|
|
68
81
|
if (tip.customMailTip) {
|
|
69
82
|
warnings.push('Custom Tip');
|
|
83
|
+
flag('customTip');
|
|
70
84
|
tipLines.push(` **Notice**: ${tip.customMailTip}`);
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
// Delivery restriction
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
if (
|
|
89
|
+
tip.deliveryRestricted === true ||
|
|
90
|
+
tip.deliveryRestriction?.isDeliveryRestricted
|
|
91
|
+
) {
|
|
92
|
+
warnings.push('Delivery Restricted');
|
|
93
|
+
flag('deliveryRestricted');
|
|
94
|
+
tipLines.push(
|
|
95
|
+
` **Delivery Restricted**: ${tip.deliveryRestriction?.message || 'Cannot deliver to this recipient'}`
|
|
96
|
+
);
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
// Moderation status
|
|
85
|
-
if (
|
|
100
|
+
if (
|
|
101
|
+
tip.isModerated === true ||
|
|
102
|
+
(tip.moderationStatus && tip.moderationStatus !== 'notModerated')
|
|
103
|
+
) {
|
|
86
104
|
warnings.push('Moderated');
|
|
105
|
+
flag('moderated');
|
|
87
106
|
tipLines.push(
|
|
88
107
|
` **Moderated**: Messages to this recipient require approval`
|
|
89
108
|
);
|
|
90
109
|
}
|
|
91
110
|
|
|
92
|
-
// Recipient scope
|
|
93
|
-
if (tip.recipientScope
|
|
111
|
+
// Recipient scope: external, externalPartner or externalNonPartner
|
|
112
|
+
if (/^external/i.test(tip.recipientScope || '')) {
|
|
113
|
+
warnings.push('External');
|
|
114
|
+
flag('external');
|
|
94
115
|
tipLines.push(` **External**: Recipient is outside your organisation`);
|
|
95
116
|
}
|
|
96
117
|
|
|
@@ -101,12 +122,27 @@ function formatMailTips(mailTips) {
|
|
|
101
122
|
}
|
|
102
123
|
|
|
103
124
|
// Group member counts
|
|
104
|
-
if (tip.
|
|
125
|
+
if (tip.externalMemberCount > 0) {
|
|
126
|
+
warnings.push('External Members');
|
|
127
|
+
flag('externalMembers');
|
|
128
|
+
}
|
|
129
|
+
if (tip.totalMemberCount > 0 || tip.externalMemberCount > 0) {
|
|
105
130
|
tipLines.push(
|
|
106
131
|
` *Group members*: ${tip.totalMemberCount} total (${tip.externalMemberCount || 0} external)`
|
|
107
132
|
);
|
|
108
133
|
}
|
|
109
134
|
|
|
135
|
+
// Graph couldn't check this recipient, so its other fields say nothing.
|
|
136
|
+
if (tip.error) {
|
|
137
|
+
warnings.push('Not Checked');
|
|
138
|
+
const code = /^[A-Za-z0-9]{1,64}$/.test(tip.error.code || '')
|
|
139
|
+
? ` (${tip.error.code})`
|
|
140
|
+
: '';
|
|
141
|
+
tipLines.push(
|
|
142
|
+
` **Not checked**: Graph returned an error for this recipient${code}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
// Build section for this recipient
|
|
111
147
|
const statusIcon = warnings.length > 0 ? '⚠' : '✓';
|
|
112
148
|
lines.push(`### ${statusIcon} ${email}`);
|
|
@@ -124,7 +160,29 @@ function formatMailTips(mailTips) {
|
|
|
124
160
|
lines.push('');
|
|
125
161
|
}
|
|
126
162
|
|
|
127
|
-
return { formatted: lines.join('\n'), warningCount };
|
|
163
|
+
return { formatted: lines.join('\n'), warningCount, issues };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Whether Graph returned anything actionable for a recipient: any field a
|
|
168
|
+
* personal Outlook.com account leaves out. A recipientScope of `none` (or
|
|
169
|
+
* none at all) says nothing.
|
|
170
|
+
* @param {object} tip - One mailTips object from Graph
|
|
171
|
+
* @returns {boolean}
|
|
172
|
+
*/
|
|
173
|
+
function hasTipContent(tip) {
|
|
174
|
+
return Boolean(
|
|
175
|
+
tip.error ||
|
|
176
|
+
tip.mailboxFull ||
|
|
177
|
+
tip.deliveryRestricted ||
|
|
178
|
+
tip.isModerated ||
|
|
179
|
+
tip.automaticReplies?.message ||
|
|
180
|
+
tip.maxMessageSize ||
|
|
181
|
+
tip.totalMemberCount ||
|
|
182
|
+
tip.externalMemberCount ||
|
|
183
|
+
tip.customMailTip ||
|
|
184
|
+
(tip.recipientScope && tip.recipientScope !== 'none')
|
|
185
|
+
);
|
|
128
186
|
}
|
|
129
187
|
|
|
130
188
|
/**
|
|
@@ -136,14 +194,7 @@ async function handleGetMailTips(args) {
|
|
|
136
194
|
const { recipients, tipTypes } = args;
|
|
137
195
|
|
|
138
196
|
if (!recipients || recipients.length === 0) {
|
|
139
|
-
return
|
|
140
|
-
content: [
|
|
141
|
-
{
|
|
142
|
-
type: 'text',
|
|
143
|
-
text: 'At least one recipient email address is required.',
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
};
|
|
197
|
+
return toolError('At least one recipient email address is required.');
|
|
147
198
|
}
|
|
148
199
|
|
|
149
200
|
// Normalise recipients to a clean array of email strings
|
|
@@ -171,14 +222,7 @@ async function handleGetMailTips(args) {
|
|
|
171
222
|
recipientList = recipientList.filter((e) => e.length > 0);
|
|
172
223
|
|
|
173
224
|
if (recipientList.length === 0) {
|
|
174
|
-
return
|
|
175
|
-
content: [
|
|
176
|
-
{
|
|
177
|
-
type: 'text',
|
|
178
|
-
text: 'At least one valid recipient email address is required.',
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
};
|
|
225
|
+
return toolError('At least one valid recipient email address is required.');
|
|
182
226
|
}
|
|
183
227
|
|
|
184
228
|
try {
|
|
@@ -206,28 +250,23 @@ async function handleGetMailTips(args) {
|
|
|
206
250
|
text: 'No mail tips returned. Mail Tips is M365-only — personal Outlook.com accounts return empty responses, so recipient validation is unavailable on this account.',
|
|
207
251
|
},
|
|
208
252
|
],
|
|
253
|
+
_meta: {
|
|
254
|
+
recipientCount: 0,
|
|
255
|
+
warningCount: 0,
|
|
256
|
+
allEmpty: true,
|
|
257
|
+
issues: [],
|
|
258
|
+
},
|
|
209
259
|
};
|
|
210
260
|
}
|
|
211
261
|
|
|
212
|
-
const { formatted, warningCount } = formatMailTips(mailTips);
|
|
262
|
+
const { formatted, warningCount, issues } = formatMailTips(mailTips);
|
|
213
263
|
|
|
214
264
|
// F-23: Detect a "fully empty" tips response — every recipient
|
|
215
265
|
// returned with no actionable fields. Personal Outlook.com
|
|
216
266
|
// accounts surface this as a successful empty response rather
|
|
217
267
|
// than a feature-unsupported error, leading to false confidence
|
|
218
268
|
// when callers see "No issues detected".
|
|
219
|
-
const allEmpty = mailTips.every((tip) =>
|
|
220
|
-
const hasContent =
|
|
221
|
-
tip.recipientNotFound ||
|
|
222
|
-
tip.mailboxFull ||
|
|
223
|
-
tip.deliveryRestricted ||
|
|
224
|
-
tip.isModerated ||
|
|
225
|
-
tip.automaticReplies?.message ||
|
|
226
|
-
tip.maxMessageSize ||
|
|
227
|
-
tip.totalMemberCount ||
|
|
228
|
-
tip.customMailTip;
|
|
229
|
-
return !hasContent;
|
|
230
|
-
});
|
|
269
|
+
const allEmpty = mailTips.every((tip) => !hasTipContent(tip));
|
|
231
270
|
|
|
232
271
|
let header = `# Mail Tips\n\n`;
|
|
233
272
|
header += `**Recipients checked**: ${mailTips.length}\n`;
|
|
@@ -244,24 +283,14 @@ async function handleGetMailTips(args) {
|
|
|
244
283
|
recipientCount: mailTips.length,
|
|
245
284
|
warningCount,
|
|
246
285
|
allEmpty,
|
|
286
|
+
issues,
|
|
247
287
|
},
|
|
248
288
|
};
|
|
249
289
|
} catch (error) {
|
|
250
290
|
if (error.message === 'Authentication required') {
|
|
251
|
-
return
|
|
252
|
-
content: [
|
|
253
|
-
{
|
|
254
|
-
type: 'text',
|
|
255
|
-
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
256
|
-
},
|
|
257
|
-
],
|
|
258
|
-
};
|
|
291
|
+
return authRequiredError();
|
|
259
292
|
}
|
|
260
|
-
return {
|
|
261
|
-
content: [
|
|
262
|
-
{ type: 'text', text: `Error getting mail tips: ${error.message}` },
|
|
263
|
-
],
|
|
264
|
-
};
|
|
293
|
+
return toolError(`Error getting mail tips: ${error.message}`);
|
|
265
294
|
}
|
|
266
295
|
}
|
|
267
296
|
|
package/email/mark-as-read.js
CHANGED
|
@@ -5,6 +5,8 @@ const _config = require('../config'); // Reserved for future use
|
|
|
5
5
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
6
|
const { ensureAuthenticated } = require('../auth');
|
|
7
7
|
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
8
|
+
const { toolError, authRequiredError } = require('../utils/tool-error');
|
|
9
|
+
const { log } = require('../utils/logger');
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Mark email as read handler
|
|
@@ -17,14 +19,7 @@ async function handleMarkAsRead(args) {
|
|
|
17
19
|
const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
|
|
18
20
|
|
|
19
21
|
if (!emailId) {
|
|
20
|
-
return
|
|
21
|
-
content: [
|
|
22
|
-
{
|
|
23
|
-
type: 'text',
|
|
24
|
-
text: 'Email ID is required.',
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
};
|
|
22
|
+
return toolError('Email ID is required.');
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
try {
|
|
@@ -56,60 +51,29 @@ async function handleMarkAsRead(args) {
|
|
|
56
51
|
],
|
|
57
52
|
};
|
|
58
53
|
} catch (error) {
|
|
59
|
-
|
|
54
|
+
log.debug(
|
|
60
55
|
`Error marking email as ${isRead ? 'read' : 'unread'}: ${error.message}`
|
|
61
56
|
);
|
|
62
57
|
|
|
63
58
|
// Improved error handling with more specific messages
|
|
64
59
|
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
type: 'text',
|
|
69
|
-
text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`,
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
};
|
|
60
|
+
return toolError(
|
|
61
|
+
`The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`
|
|
62
|
+
);
|
|
73
63
|
} else if (error.message.includes('UNAUTHORIZED')) {
|
|
74
|
-
return
|
|
75
|
-
content: [
|
|
76
|
-
{
|
|
77
|
-
type: 'text',
|
|
78
|
-
text: 'Authentication failed. Please re-authenticate and try again.',
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
};
|
|
64
|
+
return authRequiredError();
|
|
82
65
|
} else {
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
type: 'text',
|
|
87
|
-
text: `Failed to mark email as ${isRead ? 'read' : 'unread'}: ${error.message}`,
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
};
|
|
66
|
+
return toolError(
|
|
67
|
+
`Failed to mark email as ${isRead ? 'read' : 'unread'}: ${error.message}`
|
|
68
|
+
);
|
|
91
69
|
}
|
|
92
70
|
}
|
|
93
71
|
} catch (error) {
|
|
94
72
|
if (error.message === 'Authentication required') {
|
|
95
|
-
return
|
|
96
|
-
content: [
|
|
97
|
-
{
|
|
98
|
-
type: 'text',
|
|
99
|
-
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
};
|
|
73
|
+
return authRequiredError();
|
|
103
74
|
}
|
|
104
75
|
|
|
105
|
-
return {
|
|
106
|
-
content: [
|
|
107
|
-
{
|
|
108
|
-
type: 'text',
|
|
109
|
-
text: `Error accessing email: ${error.message}`,
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
};
|
|
76
|
+
return toolError(`Error accessing email: ${error.message}`);
|
|
113
77
|
}
|
|
114
78
|
}
|
|
115
79
|
|
package/email/mime.js
CHANGED
|
@@ -7,6 +7,24 @@
|
|
|
7
7
|
const { callGraphAPIRaw } = require('../utils/graph-api');
|
|
8
8
|
const { ensureAuthenticated } = require('../auth');
|
|
9
9
|
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
10
|
+
const { toolError, authRequiredError } = require('../utils/tool-error');
|
|
11
|
+
const { log } = require('../utils/logger');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The longest prefix of `text` that fits in `maxBytes` of UTF-8 without
|
|
15
|
+
* splitting a character (which would render as U+FFFD).
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @param {number} maxBytes
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
function utf8Prefix(text, maxBytes) {
|
|
21
|
+
const bytes = Buffer.from(text, 'utf8');
|
|
22
|
+
if (bytes.length <= maxBytes) return text;
|
|
23
|
+
let end = maxBytes;
|
|
24
|
+
// Step back over continuation bytes (10xxxxxx) to a character start.
|
|
25
|
+
while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--;
|
|
26
|
+
return bytes.subarray(0, end).toString('utf8');
|
|
27
|
+
}
|
|
10
28
|
|
|
11
29
|
/**
|
|
12
30
|
* Parse MIME headers from raw content
|
|
@@ -98,14 +116,7 @@ async function handleGetMimeContent(args) {
|
|
|
98
116
|
const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
|
|
99
117
|
|
|
100
118
|
if (!emailId) {
|
|
101
|
-
return
|
|
102
|
-
content: [
|
|
103
|
-
{
|
|
104
|
-
type: 'text',
|
|
105
|
-
text: 'Email ID is required.',
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
};
|
|
119
|
+
return toolError('Email ID is required.');
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
try {
|
|
@@ -117,14 +128,9 @@ async function handleGetMimeContent(args) {
|
|
|
117
128
|
const mimeContent = await callGraphAPIRaw(accessToken, emailId, prefix);
|
|
118
129
|
|
|
119
130
|
if (!mimeContent) {
|
|
120
|
-
return
|
|
121
|
-
content
|
|
122
|
-
|
|
123
|
-
type: 'text',
|
|
124
|
-
text: `Failed to retrieve MIME content for email ${emailId}.`,
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
};
|
|
131
|
+
return toolError(
|
|
132
|
+
`Failed to retrieve MIME content for email ${emailId}.`
|
|
133
|
+
);
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
const stats = getMimeStats(mimeContent);
|
|
@@ -133,7 +139,13 @@ async function handleGetMimeContent(args) {
|
|
|
133
139
|
// Check size limit
|
|
134
140
|
if (maxSize > 0 && stats.bytes > maxSize) {
|
|
135
141
|
if (headersOnly) {
|
|
136
|
-
// Return just headers if over limit
|
|
142
|
+
// Return just headers if over limit, themselves capped at
|
|
143
|
+
// maxSize (#306): a large header block used to come back whole.
|
|
144
|
+
const headerBytes = Buffer.byteLength(parsed.headerSection, 'utf8');
|
|
145
|
+
const headerText =
|
|
146
|
+
headerBytes > maxSize
|
|
147
|
+
? `${utf8Prefix(parsed.headerSection, maxSize)}\n… (headers cut at maxSize ${maxSize} bytes of ${headerBytes})`
|
|
148
|
+
: parsed.headerSection;
|
|
137
149
|
return {
|
|
138
150
|
content: [
|
|
139
151
|
{
|
|
@@ -142,7 +154,7 @@ async function handleGetMimeContent(args) {
|
|
|
142
154
|
`# MIME Content (Headers Only - Content Truncated)\n\n` +
|
|
143
155
|
`**Size**: ${stats.formattedSize} (exceeds ${maxSize} byte limit)\n` +
|
|
144
156
|
`**Lines**: ${stats.lines}\n\n` +
|
|
145
|
-
`## MIME Headers\n\n\`\`\`\n${
|
|
157
|
+
`## MIME Headers\n\n\`\`\`\n${headerText}\n\`\`\``,
|
|
146
158
|
},
|
|
147
159
|
],
|
|
148
160
|
_meta: {
|
|
@@ -174,6 +186,7 @@ async function handleGetMimeContent(args) {
|
|
|
174
186
|
truncated: true,
|
|
175
187
|
maxSizeExceeded: true,
|
|
176
188
|
},
|
|
189
|
+
isError: true,
|
|
177
190
|
};
|
|
178
191
|
}
|
|
179
192
|
|
|
@@ -238,53 +251,28 @@ async function handleGetMimeContent(args) {
|
|
|
238
251
|
},
|
|
239
252
|
};
|
|
240
253
|
} catch (error) {
|
|
241
|
-
|
|
254
|
+
log.debug(`Error getting MIME content: ${error.message}`);
|
|
242
255
|
|
|
243
256
|
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
244
|
-
return
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
type: 'text',
|
|
248
|
-
text: `The email ID seems invalid or doesn't belong to your mailbox.`,
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
};
|
|
257
|
+
return toolError(
|
|
258
|
+
`The email ID seems invalid or doesn't belong to your mailbox.`
|
|
259
|
+
);
|
|
252
260
|
}
|
|
253
261
|
|
|
254
|
-
return {
|
|
255
|
-
content: [
|
|
256
|
-
{
|
|
257
|
-
type: 'text',
|
|
258
|
-
text: `Failed to get MIME content: ${error.message}`,
|
|
259
|
-
},
|
|
260
|
-
],
|
|
261
|
-
};
|
|
262
|
+
return toolError(`Failed to get MIME content: ${error.message}`);
|
|
262
263
|
}
|
|
263
264
|
} catch (error) {
|
|
264
265
|
if (error.message === 'Authentication required') {
|
|
265
|
-
return
|
|
266
|
-
content: [
|
|
267
|
-
{
|
|
268
|
-
type: 'text',
|
|
269
|
-
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
};
|
|
266
|
+
return authRequiredError();
|
|
273
267
|
}
|
|
274
268
|
|
|
275
|
-
return {
|
|
276
|
-
content: [
|
|
277
|
-
{
|
|
278
|
-
type: 'text',
|
|
279
|
-
text: `Error accessing email: ${error.message}`,
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
};
|
|
269
|
+
return toolError(`Error accessing email: ${error.message}`);
|
|
283
270
|
}
|
|
284
271
|
}
|
|
285
272
|
|
|
286
273
|
module.exports = {
|
|
287
274
|
handleGetMimeContent,
|
|
275
|
+
utf8Prefix,
|
|
288
276
|
parseMimeHeaders,
|
|
289
277
|
getMimeStats,
|
|
290
278
|
};
|
package/email/read.js
CHANGED
|
@@ -9,9 +9,12 @@ const { ensureAuthenticated } = require('../auth');
|
|
|
9
9
|
const {
|
|
10
10
|
formatEmailContent,
|
|
11
11
|
VERBOSITY,
|
|
12
|
+
DEFAULT_LIMITS,
|
|
12
13
|
} = require('../utils/response-formatter');
|
|
13
14
|
const { getEmailFields } = require('../utils/field-presets');
|
|
14
15
|
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
16
|
+
const { toolError, authRequiredError } = require('../utils/tool-error');
|
|
17
|
+
const { log } = require('../utils/logger');
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* Get field preset based on verbosity and options
|
|
@@ -48,17 +51,11 @@ async function handleReadEmail(args) {
|
|
|
48
51
|
const includeHeaders = args.includeHeaders || false;
|
|
49
52
|
// Message IDs are mailbox-scoped: an ID issued by a shared/delegated mailbox
|
|
50
53
|
// is not resolvable under /me. Route to /users/{mailbox} when supplied.
|
|
51
|
-
const
|
|
54
|
+
const sharedMailbox = args.sharedMailbox || args.email || null;
|
|
55
|
+
const prefix = buildMailboxPrefix(sharedMailbox);
|
|
52
56
|
|
|
53
57
|
if (!emailId) {
|
|
54
|
-
return
|
|
55
|
-
content: [
|
|
56
|
-
{
|
|
57
|
-
type: 'text',
|
|
58
|
-
text: 'Email ID is required.',
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
58
|
+
return toolError('Email ID is required.');
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
try {
|
|
@@ -85,20 +82,15 @@ async function handleReadEmail(args) {
|
|
|
85
82
|
);
|
|
86
83
|
|
|
87
84
|
if (!email) {
|
|
88
|
-
return {
|
|
89
|
-
content: [
|
|
90
|
-
{
|
|
91
|
-
type: 'text',
|
|
92
|
-
text: `Email with ID ${emailId} not found.`,
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
};
|
|
85
|
+
return toolError(`Email with ID ${emailId} not found.`);
|
|
96
86
|
}
|
|
97
87
|
|
|
98
88
|
// Format using shared formatter (returns Markdown)
|
|
99
89
|
const formattedOutput = formatEmailContent(email, verbosity, {
|
|
100
90
|
includeHeaders: includeHeaders,
|
|
101
91
|
includeAllHeaders: false, // Only important headers by default
|
|
92
|
+
sharedMailbox,
|
|
93
|
+
maxFullBodyChars: DEFAULT_LIMITS.maxFullBodyChars,
|
|
102
94
|
});
|
|
103
95
|
|
|
104
96
|
return {
|
|
@@ -116,49 +108,23 @@ async function handleReadEmail(args) {
|
|
|
116
108
|
},
|
|
117
109
|
};
|
|
118
110
|
} catch (error) {
|
|
119
|
-
|
|
111
|
+
log.debug(`Error reading email: ${error.message}`);
|
|
120
112
|
|
|
121
113
|
// Improved error handling with more specific messages
|
|
122
114
|
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
type: 'text',
|
|
127
|
-
text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`,
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
};
|
|
115
|
+
return toolError(
|
|
116
|
+
`The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`
|
|
117
|
+
);
|
|
131
118
|
} else {
|
|
132
|
-
return {
|
|
133
|
-
content: [
|
|
134
|
-
{
|
|
135
|
-
type: 'text',
|
|
136
|
-
text: `Failed to read email: ${error.message}`,
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
};
|
|
119
|
+
return toolError(`Failed to read email: ${error.message}`);
|
|
140
120
|
}
|
|
141
121
|
}
|
|
142
122
|
} catch (error) {
|
|
143
123
|
if (error.message === 'Authentication required') {
|
|
144
|
-
return
|
|
145
|
-
content: [
|
|
146
|
-
{
|
|
147
|
-
type: 'text',
|
|
148
|
-
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
};
|
|
124
|
+
return authRequiredError();
|
|
152
125
|
}
|
|
153
126
|
|
|
154
|
-
return {
|
|
155
|
-
content: [
|
|
156
|
-
{
|
|
157
|
-
type: 'text',
|
|
158
|
-
text: `Error accessing email: ${error.message}`,
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
};
|
|
127
|
+
return toolError(`Error accessing email: ${error.message}`);
|
|
162
128
|
}
|
|
163
129
|
}
|
|
164
130
|
|