@agenticmail/enterprise 0.5.49 → 0.5.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-FKDN7ZV3.js +898 -0
- package/dist/chunk-G7BBCWAX.js +13428 -0
- package/dist/chunk-Q4WDMWLJ.js +2115 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +735 -3
- package/dist/runtime-ENGVD2AI.js +47 -0
- package/dist/server-JBOS22AY.js +12 -0
- package/dist/setup-6ATX2BNE.js +20 -0
- package/package.json +1 -14
- package/src/agent-tools/index.ts +22 -3
- package/src/agent-tools/tools/agenticmail.ts +785 -0
- package/src/agenticmail/index.ts +32 -0
- package/src/agenticmail/manager.ts +253 -0
- package/src/agenticmail/providers/google.ts +331 -0
- package/src/agenticmail/providers/index.ts +26 -0
- package/src/agenticmail/providers/microsoft.ts +260 -0
- package/src/agenticmail/types.ts +171 -0
- package/src/agenticmail-core/index.ts +36 -0
- package/src/agenticmail-core/pending-followup.ts +362 -0
- package/src/agenticmail-core/telemetry.ts +164 -0
- package/src/agenticmail-core/tools.ts +2395 -0
- package/src/index.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
executeTool,
|
|
36
36
|
runAgentLoop,
|
|
37
37
|
toolsToDefinitions
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-G7BBCWAX.js";
|
|
39
39
|
import "./chunk-TYW5XTOW.js";
|
|
40
40
|
import {
|
|
41
41
|
ValidationError,
|
|
@@ -50,11 +50,11 @@ import {
|
|
|
50
50
|
requireRole,
|
|
51
51
|
securityHeaders,
|
|
52
52
|
validate
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-Q4WDMWLJ.js";
|
|
54
54
|
import {
|
|
55
55
|
provision,
|
|
56
56
|
runSetupWizard
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-FKDN7ZV3.js";
|
|
58
58
|
import {
|
|
59
59
|
ENGINE_TABLES,
|
|
60
60
|
ENGINE_TABLES_POSTGRES,
|
|
@@ -123,6 +123,734 @@ import "./chunk-KFQGP6VL.js";
|
|
|
123
123
|
// src/engine/index.ts
|
|
124
124
|
init_tool_catalog();
|
|
125
125
|
init_guardrails();
|
|
126
|
+
|
|
127
|
+
// src/agenticmail/providers/microsoft.ts
|
|
128
|
+
var GRAPH_BASE = "https://graph.microsoft.com/v1.0";
|
|
129
|
+
var MicrosoftEmailProvider = class {
|
|
130
|
+
provider = "microsoft";
|
|
131
|
+
identity = null;
|
|
132
|
+
get token() {
|
|
133
|
+
if (!this.identity) throw new Error("Not connected");
|
|
134
|
+
return this.identity.accessToken;
|
|
135
|
+
}
|
|
136
|
+
async refreshIfNeeded() {
|
|
137
|
+
if (this.identity?.refreshToken) {
|
|
138
|
+
this.identity.accessToken = await this.identity.refreshToken();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async graphFetch(path, opts) {
|
|
142
|
+
await this.refreshIfNeeded();
|
|
143
|
+
const res = await fetch(`${GRAPH_BASE}${path}`, {
|
|
144
|
+
...opts,
|
|
145
|
+
headers: {
|
|
146
|
+
Authorization: `Bearer ${this.token}`,
|
|
147
|
+
"Content-Type": "application/json",
|
|
148
|
+
...opts?.headers
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
if (!res.ok) {
|
|
152
|
+
const text = await res.text().catch(() => "");
|
|
153
|
+
throw new Error(`Graph API ${res.status}: ${text}`);
|
|
154
|
+
}
|
|
155
|
+
if (res.status === 204) return null;
|
|
156
|
+
return res.json();
|
|
157
|
+
}
|
|
158
|
+
// ─── Connection ─────────────────────────────────────
|
|
159
|
+
async connect(identity) {
|
|
160
|
+
this.identity = identity;
|
|
161
|
+
await this.graphFetch("/me?$select=mail,displayName");
|
|
162
|
+
}
|
|
163
|
+
async disconnect() {
|
|
164
|
+
this.identity = null;
|
|
165
|
+
}
|
|
166
|
+
// ─── List / Read ────────────────────────────────────
|
|
167
|
+
async listMessages(folder, opts) {
|
|
168
|
+
const folderId = this.resolveFolderId(folder);
|
|
169
|
+
const top = opts?.limit || 20;
|
|
170
|
+
const skip = opts?.offset || 0;
|
|
171
|
+
const data = await this.graphFetch(
|
|
172
|
+
`/me/mailFolders/${folderId}/messages?$top=${top}&$skip=${skip}&$select=id,subject,from,toRecipients,receivedDateTime,isRead,flag,hasAttachments,bodyPreview&$orderby=receivedDateTime desc`
|
|
173
|
+
);
|
|
174
|
+
return (data.value || []).map((m) => this.toEnvelope(m));
|
|
175
|
+
}
|
|
176
|
+
async readMessage(uid) {
|
|
177
|
+
const data = await this.graphFetch(`/me/messages/${uid}?$select=id,subject,from,toRecipients,ccRecipients,bccRecipients,receivedDateTime,isRead,flag,hasAttachments,body,bodyPreview,replyTo,internetMessageId,internetMessageHeaders,conversationId`);
|
|
178
|
+
return this.toMessage(data);
|
|
179
|
+
}
|
|
180
|
+
async searchMessages(criteria) {
|
|
181
|
+
const filters = [];
|
|
182
|
+
if (criteria.from) filters.push(`from/emailAddress/address eq '${criteria.from}'`);
|
|
183
|
+
if (criteria.subject) filters.push(`contains(subject, '${criteria.subject}')`);
|
|
184
|
+
if (criteria.since) filters.push(`receivedDateTime ge ${criteria.since}`);
|
|
185
|
+
if (criteria.before) filters.push(`receivedDateTime lt ${criteria.before}`);
|
|
186
|
+
if (criteria.seen !== void 0) filters.push(`isRead eq ${criteria.seen}`);
|
|
187
|
+
let path = "/me/messages?$top=50&$select=id,subject,from,toRecipients,receivedDateTime,isRead,flag,hasAttachments,bodyPreview&$orderby=receivedDateTime desc";
|
|
188
|
+
if (filters.length) path += "&$filter=" + encodeURIComponent(filters.join(" and "));
|
|
189
|
+
if (criteria.text) path = `/me/messages?$search="${encodeURIComponent(criteria.text)}"&$top=50&$select=id,subject,from,toRecipients,receivedDateTime,isRead,flag,hasAttachments,bodyPreview`;
|
|
190
|
+
const data = await this.graphFetch(path);
|
|
191
|
+
return (data.value || []).map((m) => this.toEnvelope(m));
|
|
192
|
+
}
|
|
193
|
+
async listFolders() {
|
|
194
|
+
const data = await this.graphFetch("/me/mailFolders?$select=id,displayName,unreadItemCount,totalItemCount");
|
|
195
|
+
return (data.value || []).map((f) => ({
|
|
196
|
+
name: f.displayName,
|
|
197
|
+
path: f.id,
|
|
198
|
+
unread: f.unreadItemCount || 0,
|
|
199
|
+
total: f.totalItemCount || 0
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
async createFolder(name) {
|
|
203
|
+
await this.graphFetch("/me/mailFolders", {
|
|
204
|
+
method: "POST",
|
|
205
|
+
body: JSON.stringify({ displayName: name })
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// ─── Send ───────────────────────────────────────────
|
|
209
|
+
async send(options) {
|
|
210
|
+
const message = this.buildGraphMessage(options);
|
|
211
|
+
await this.graphFetch("/me/sendMail", {
|
|
212
|
+
method: "POST",
|
|
213
|
+
body: JSON.stringify({ message, saveToSentItems: true })
|
|
214
|
+
});
|
|
215
|
+
return { messageId: `graph-${Date.now()}` };
|
|
216
|
+
}
|
|
217
|
+
async reply(uid, body, replyAll = false) {
|
|
218
|
+
const endpoint = replyAll ? "replyAll" : "reply";
|
|
219
|
+
await this.graphFetch(`/me/messages/${uid}/${endpoint}`, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
body: JSON.stringify({ comment: body })
|
|
222
|
+
});
|
|
223
|
+
return { messageId: `graph-reply-${Date.now()}` };
|
|
224
|
+
}
|
|
225
|
+
async forward(uid, to, body) {
|
|
226
|
+
await this.graphFetch(`/me/messages/${uid}/forward`, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
comment: body || "",
|
|
230
|
+
toRecipients: [{ emailAddress: { address: to } }]
|
|
231
|
+
})
|
|
232
|
+
});
|
|
233
|
+
return { messageId: `graph-fwd-${Date.now()}` };
|
|
234
|
+
}
|
|
235
|
+
// ─── Organize ───────────────────────────────────────
|
|
236
|
+
async moveMessage(uid, toFolder) {
|
|
237
|
+
const folderId = this.resolveFolderId(toFolder);
|
|
238
|
+
await this.graphFetch(`/me/messages/${uid}/move`, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
body: JSON.stringify({ destinationId: folderId })
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async deleteMessage(uid) {
|
|
244
|
+
await this.graphFetch(`/me/messages/${uid}`, { method: "DELETE" });
|
|
245
|
+
}
|
|
246
|
+
async markRead(uid) {
|
|
247
|
+
await this.graphFetch(`/me/messages/${uid}`, {
|
|
248
|
+
method: "PATCH",
|
|
249
|
+
body: JSON.stringify({ isRead: true })
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async markUnread(uid) {
|
|
253
|
+
await this.graphFetch(`/me/messages/${uid}`, {
|
|
254
|
+
method: "PATCH",
|
|
255
|
+
body: JSON.stringify({ isRead: false })
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async flagMessage(uid) {
|
|
259
|
+
await this.graphFetch(`/me/messages/${uid}`, {
|
|
260
|
+
method: "PATCH",
|
|
261
|
+
body: JSON.stringify({ flag: { flagStatus: "flagged" } })
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async unflagMessage(uid) {
|
|
265
|
+
await this.graphFetch(`/me/messages/${uid}`, {
|
|
266
|
+
method: "PATCH",
|
|
267
|
+
body: JSON.stringify({ flag: { flagStatus: "notFlagged" } })
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// ─── Batch ──────────────────────────────────────────
|
|
271
|
+
async batchMarkRead(uids) {
|
|
272
|
+
await Promise.all(uids.map((uid) => this.markRead(uid)));
|
|
273
|
+
}
|
|
274
|
+
async batchMarkUnread(uids) {
|
|
275
|
+
await Promise.all(uids.map((uid) => this.markUnread(uid)));
|
|
276
|
+
}
|
|
277
|
+
async batchMove(uids, toFolder) {
|
|
278
|
+
await Promise.all(uids.map((uid) => this.moveMessage(uid, toFolder)));
|
|
279
|
+
}
|
|
280
|
+
async batchDelete(uids) {
|
|
281
|
+
await Promise.all(uids.map((uid) => this.deleteMessage(uid)));
|
|
282
|
+
}
|
|
283
|
+
// ─── Helpers ────────────────────────────────────────
|
|
284
|
+
resolveFolderId(folder) {
|
|
285
|
+
const map = {
|
|
286
|
+
INBOX: "inbox",
|
|
287
|
+
inbox: "inbox",
|
|
288
|
+
Sent: "sentItems",
|
|
289
|
+
sent: "sentItems",
|
|
290
|
+
sentitems: "sentItems",
|
|
291
|
+
Drafts: "drafts",
|
|
292
|
+
drafts: "drafts",
|
|
293
|
+
Trash: "deletedItems",
|
|
294
|
+
trash: "deletedItems",
|
|
295
|
+
deleteditems: "deletedItems",
|
|
296
|
+
Junk: "junkemail",
|
|
297
|
+
junk: "junkemail",
|
|
298
|
+
spam: "junkemail",
|
|
299
|
+
Archive: "archive",
|
|
300
|
+
archive: "archive"
|
|
301
|
+
};
|
|
302
|
+
return map[folder] || folder;
|
|
303
|
+
}
|
|
304
|
+
buildGraphMessage(options) {
|
|
305
|
+
const msg = {
|
|
306
|
+
subject: options.subject,
|
|
307
|
+
body: { contentType: options.html ? "HTML" : "Text", content: options.html || options.body },
|
|
308
|
+
toRecipients: options.to.split(",").map((e) => ({ emailAddress: { address: e.trim() } }))
|
|
309
|
+
};
|
|
310
|
+
if (options.cc) msg.ccRecipients = options.cc.split(",").map((e) => ({ emailAddress: { address: e.trim() } }));
|
|
311
|
+
if (options.bcc) msg.bccRecipients = options.bcc.split(",").map((e) => ({ emailAddress: { address: e.trim() } }));
|
|
312
|
+
if (options.inReplyTo) msg.internetMessageHeaders = [{ name: "In-Reply-To", value: options.inReplyTo }];
|
|
313
|
+
return msg;
|
|
314
|
+
}
|
|
315
|
+
toEnvelope(m) {
|
|
316
|
+
return {
|
|
317
|
+
uid: m.id,
|
|
318
|
+
from: { name: m.from?.emailAddress?.name, email: m.from?.emailAddress?.address || "" },
|
|
319
|
+
to: (m.toRecipients || []).map((r) => ({ name: r.emailAddress?.name, email: r.emailAddress?.address || "" })),
|
|
320
|
+
subject: m.subject || "",
|
|
321
|
+
date: m.receivedDateTime || "",
|
|
322
|
+
read: !!m.isRead,
|
|
323
|
+
flagged: m.flag?.flagStatus === "flagged",
|
|
324
|
+
hasAttachments: !!m.hasAttachments,
|
|
325
|
+
preview: m.bodyPreview || ""
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
toMessage(m) {
|
|
329
|
+
return {
|
|
330
|
+
uid: m.id,
|
|
331
|
+
from: { name: m.from?.emailAddress?.name, email: m.from?.emailAddress?.address || "" },
|
|
332
|
+
to: (m.toRecipients || []).map((r) => ({ name: r.emailAddress?.name, email: r.emailAddress?.address || "" })),
|
|
333
|
+
cc: (m.ccRecipients || []).map((r) => ({ name: r.emailAddress?.name, email: r.emailAddress?.address || "" })),
|
|
334
|
+
subject: m.subject || "",
|
|
335
|
+
body: m.body?.contentType === "HTML" ? "" : m.body?.content || "",
|
|
336
|
+
html: m.body?.contentType === "HTML" ? m.body?.content : void 0,
|
|
337
|
+
date: m.receivedDateTime || "",
|
|
338
|
+
read: !!m.isRead,
|
|
339
|
+
flagged: m.flag?.flagStatus === "flagged",
|
|
340
|
+
folder: "inbox",
|
|
341
|
+
messageId: m.internetMessageId,
|
|
342
|
+
attachments: []
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// src/agenticmail/providers/google.ts
|
|
348
|
+
var GMAIL_BASE = "https://gmail.googleapis.com/gmail/v1";
|
|
349
|
+
var GoogleEmailProvider = class {
|
|
350
|
+
provider = "google";
|
|
351
|
+
identity = null;
|
|
352
|
+
userId = "me";
|
|
353
|
+
get token() {
|
|
354
|
+
if (!this.identity) throw new Error("Not connected");
|
|
355
|
+
return this.identity.accessToken;
|
|
356
|
+
}
|
|
357
|
+
async refreshIfNeeded() {
|
|
358
|
+
if (this.identity?.refreshToken) {
|
|
359
|
+
this.identity.accessToken = await this.identity.refreshToken();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async gmailFetch(path, opts) {
|
|
363
|
+
await this.refreshIfNeeded();
|
|
364
|
+
const res = await fetch(`${GMAIL_BASE}/users/${this.userId}${path}`, {
|
|
365
|
+
...opts,
|
|
366
|
+
headers: {
|
|
367
|
+
Authorization: `Bearer ${this.token}`,
|
|
368
|
+
"Content-Type": "application/json",
|
|
369
|
+
...opts?.headers
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
if (!res.ok) {
|
|
373
|
+
const text = await res.text().catch(() => "");
|
|
374
|
+
throw new Error(`Gmail API ${res.status}: ${text}`);
|
|
375
|
+
}
|
|
376
|
+
if (res.status === 204) return null;
|
|
377
|
+
return res.json();
|
|
378
|
+
}
|
|
379
|
+
// ─── Connection ─────────────────────────────────────
|
|
380
|
+
async connect(identity) {
|
|
381
|
+
this.identity = identity;
|
|
382
|
+
await this.gmailFetch("/profile");
|
|
383
|
+
}
|
|
384
|
+
async disconnect() {
|
|
385
|
+
this.identity = null;
|
|
386
|
+
}
|
|
387
|
+
// ─── List / Read ────────────────────────────────────
|
|
388
|
+
async listMessages(folder, opts) {
|
|
389
|
+
const labelId = this.resolveLabelId(folder);
|
|
390
|
+
const maxResults = opts?.limit || 20;
|
|
391
|
+
const q = labelId === "INBOX" ? "" : "";
|
|
392
|
+
const data = await this.gmailFetch(`/messages?labelIds=${labelId}&maxResults=${maxResults}${q ? "&q=" + encodeURIComponent(q) : ""}`);
|
|
393
|
+
if (!data.messages?.length) return [];
|
|
394
|
+
const envelopes = [];
|
|
395
|
+
for (const msg of data.messages) {
|
|
396
|
+
try {
|
|
397
|
+
const detail = await this.gmailFetch(`/messages/${msg.id}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Subject&metadataHeaders=Date`);
|
|
398
|
+
envelopes.push(this.metadataToEnvelope(detail));
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return envelopes;
|
|
403
|
+
}
|
|
404
|
+
async readMessage(uid) {
|
|
405
|
+
const data = await this.gmailFetch(`/messages/${uid}?format=full`);
|
|
406
|
+
return this.fullToMessage(data);
|
|
407
|
+
}
|
|
408
|
+
async searchMessages(criteria) {
|
|
409
|
+
const parts = [];
|
|
410
|
+
if (criteria.from) parts.push(`from:${criteria.from}`);
|
|
411
|
+
if (criteria.to) parts.push(`to:${criteria.to}`);
|
|
412
|
+
if (criteria.subject) parts.push(`subject:${criteria.subject}`);
|
|
413
|
+
if (criteria.text) parts.push(criteria.text);
|
|
414
|
+
if (criteria.since) parts.push(`after:${criteria.since.split("T")[0]}`);
|
|
415
|
+
if (criteria.before) parts.push(`before:${criteria.before.split("T")[0]}`);
|
|
416
|
+
if (criteria.seen === true) parts.push("is:read");
|
|
417
|
+
if (criteria.seen === false) parts.push("is:unread");
|
|
418
|
+
const q = parts.join(" ");
|
|
419
|
+
const data = await this.gmailFetch(`/messages?q=${encodeURIComponent(q)}&maxResults=50`);
|
|
420
|
+
if (!data.messages?.length) return [];
|
|
421
|
+
const envelopes = [];
|
|
422
|
+
for (const msg of data.messages.slice(0, 20)) {
|
|
423
|
+
try {
|
|
424
|
+
const detail = await this.gmailFetch(`/messages/${msg.id}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Subject&metadataHeaders=Date`);
|
|
425
|
+
envelopes.push(this.metadataToEnvelope(detail));
|
|
426
|
+
} catch {
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return envelopes;
|
|
430
|
+
}
|
|
431
|
+
async listFolders() {
|
|
432
|
+
const data = await this.gmailFetch("/labels");
|
|
433
|
+
return (data.labels || []).map((l) => ({
|
|
434
|
+
name: l.name,
|
|
435
|
+
path: l.id,
|
|
436
|
+
unread: l.messagesUnread || 0,
|
|
437
|
+
total: l.messagesTotal || 0
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
440
|
+
async createFolder(name) {
|
|
441
|
+
await this.gmailFetch("/labels", {
|
|
442
|
+
method: "POST",
|
|
443
|
+
body: JSON.stringify({ name, labelListVisibility: "labelShow", messageListVisibility: "show" })
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
// ─── Send ───────────────────────────────────────────
|
|
447
|
+
async send(options) {
|
|
448
|
+
const raw = this.buildRawEmail(options);
|
|
449
|
+
const data = await this.gmailFetch("/messages/send", {
|
|
450
|
+
method: "POST",
|
|
451
|
+
body: JSON.stringify({ raw })
|
|
452
|
+
});
|
|
453
|
+
return { messageId: data.id };
|
|
454
|
+
}
|
|
455
|
+
async reply(uid, body, replyAll = false) {
|
|
456
|
+
const original = await this.readMessage(uid);
|
|
457
|
+
const to = replyAll ? [original.from.email, ...(original.to || []).map((t) => t.email), ...(original.cc || []).map((c) => c.email)].filter((e) => e !== this.identity?.email).join(", ") : original.from.email;
|
|
458
|
+
return this.send({
|
|
459
|
+
to,
|
|
460
|
+
subject: original.subject.startsWith("Re:") ? original.subject : `Re: ${original.subject}`,
|
|
461
|
+
body,
|
|
462
|
+
inReplyTo: original.messageId,
|
|
463
|
+
references: original.references ? [...original.references, original.messageId] : [original.messageId]
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
async forward(uid, to, body) {
|
|
467
|
+
const original = await this.readMessage(uid);
|
|
468
|
+
return this.send({
|
|
469
|
+
to,
|
|
470
|
+
subject: `Fwd: ${original.subject}`,
|
|
471
|
+
body: (body ? body + "\n\n" : "") + `---------- Forwarded message ----------
|
|
472
|
+
From: ${original.from.email}
|
|
473
|
+
Date: ${original.date}
|
|
474
|
+
Subject: ${original.subject}
|
|
475
|
+
|
|
476
|
+
${original.body}`
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
// ─── Organize ───────────────────────────────────────
|
|
480
|
+
async moveMessage(uid, toFolder, fromFolder) {
|
|
481
|
+
const addLabel = this.resolveLabelId(toFolder);
|
|
482
|
+
const removeLabel = fromFolder ? this.resolveLabelId(fromFolder) : "INBOX";
|
|
483
|
+
await this.gmailFetch(`/messages/${uid}/modify`, {
|
|
484
|
+
method: "POST",
|
|
485
|
+
body: JSON.stringify({ addLabelIds: [addLabel], removeLabelIds: [removeLabel] })
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
async deleteMessage(uid) {
|
|
489
|
+
await this.gmailFetch(`/messages/${uid}/trash`, { method: "POST" });
|
|
490
|
+
}
|
|
491
|
+
async markRead(uid) {
|
|
492
|
+
await this.gmailFetch(`/messages/${uid}/modify`, {
|
|
493
|
+
method: "POST",
|
|
494
|
+
body: JSON.stringify({ removeLabelIds: ["UNREAD"] })
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async markUnread(uid) {
|
|
498
|
+
await this.gmailFetch(`/messages/${uid}/modify`, {
|
|
499
|
+
method: "POST",
|
|
500
|
+
body: JSON.stringify({ addLabelIds: ["UNREAD"] })
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
async flagMessage(uid) {
|
|
504
|
+
await this.gmailFetch(`/messages/${uid}/modify`, {
|
|
505
|
+
method: "POST",
|
|
506
|
+
body: JSON.stringify({ addLabelIds: ["STARRED"] })
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
async unflagMessage(uid) {
|
|
510
|
+
await this.gmailFetch(`/messages/${uid}/modify`, {
|
|
511
|
+
method: "POST",
|
|
512
|
+
body: JSON.stringify({ removeLabelIds: ["STARRED"] })
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
// ─── Batch ──────────────────────────────────────────
|
|
516
|
+
async batchMarkRead(uids) {
|
|
517
|
+
await this.gmailFetch("/messages/batchModify", {
|
|
518
|
+
method: "POST",
|
|
519
|
+
body: JSON.stringify({ ids: uids, removeLabelIds: ["UNREAD"] })
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
async batchMarkUnread(uids) {
|
|
523
|
+
await this.gmailFetch("/messages/batchModify", {
|
|
524
|
+
method: "POST",
|
|
525
|
+
body: JSON.stringify({ ids: uids, addLabelIds: ["UNREAD"] })
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
async batchMove(uids, toFolder, fromFolder) {
|
|
529
|
+
const addLabel = this.resolveLabelId(toFolder);
|
|
530
|
+
const removeLabel = fromFolder ? this.resolveLabelId(fromFolder) : "INBOX";
|
|
531
|
+
await this.gmailFetch("/messages/batchModify", {
|
|
532
|
+
method: "POST",
|
|
533
|
+
body: JSON.stringify({ ids: uids, addLabelIds: [addLabel], removeLabelIds: [removeLabel] })
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
async batchDelete(uids) {
|
|
537
|
+
await Promise.all(uids.map((uid) => this.deleteMessage(uid)));
|
|
538
|
+
}
|
|
539
|
+
// ─── Helpers ────────────────────────────────────────
|
|
540
|
+
resolveLabelId(folder) {
|
|
541
|
+
const map = {
|
|
542
|
+
INBOX: "INBOX",
|
|
543
|
+
inbox: "INBOX",
|
|
544
|
+
Sent: "SENT",
|
|
545
|
+
sent: "SENT",
|
|
546
|
+
Drafts: "DRAFT",
|
|
547
|
+
drafts: "DRAFT",
|
|
548
|
+
Trash: "TRASH",
|
|
549
|
+
trash: "TRASH",
|
|
550
|
+
Spam: "SPAM",
|
|
551
|
+
spam: "SPAM",
|
|
552
|
+
Junk: "SPAM",
|
|
553
|
+
junk: "SPAM",
|
|
554
|
+
Starred: "STARRED",
|
|
555
|
+
starred: "STARRED",
|
|
556
|
+
Important: "IMPORTANT",
|
|
557
|
+
important: "IMPORTANT"
|
|
558
|
+
};
|
|
559
|
+
return map[folder] || folder;
|
|
560
|
+
}
|
|
561
|
+
buildRawEmail(options) {
|
|
562
|
+
const lines = [
|
|
563
|
+
`To: ${options.to}`,
|
|
564
|
+
`Subject: ${options.subject}`,
|
|
565
|
+
`Content-Type: text/plain; charset=utf-8`
|
|
566
|
+
];
|
|
567
|
+
if (options.cc) lines.splice(1, 0, `Cc: ${options.cc}`);
|
|
568
|
+
if (options.inReplyTo) lines.push(`In-Reply-To: ${options.inReplyTo}`);
|
|
569
|
+
if (options.references?.length) lines.push(`References: ${options.references.join(" ")}`);
|
|
570
|
+
lines.push("", options.body);
|
|
571
|
+
const raw = lines.join("\r\n");
|
|
572
|
+
return btoa(unescape(encodeURIComponent(raw))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
573
|
+
}
|
|
574
|
+
getHeader(msg, name) {
|
|
575
|
+
const headers = msg.payload?.headers || [];
|
|
576
|
+
const h = headers.find((h2) => h2.name.toLowerCase() === name.toLowerCase());
|
|
577
|
+
return h?.value || "";
|
|
578
|
+
}
|
|
579
|
+
metadataToEnvelope(msg) {
|
|
580
|
+
const from = this.getHeader(msg, "From");
|
|
581
|
+
const fromMatch = from.match(/^(.*?)\s*<(.+?)>$/) || [null, "", from];
|
|
582
|
+
return {
|
|
583
|
+
uid: msg.id,
|
|
584
|
+
from: { name: fromMatch[1]?.replace(/"/g, "").trim() || void 0, email: fromMatch[2] || from },
|
|
585
|
+
to: [{ email: this.getHeader(msg, "To") }],
|
|
586
|
+
subject: this.getHeader(msg, "Subject"),
|
|
587
|
+
date: this.getHeader(msg, "Date"),
|
|
588
|
+
read: !(msg.labelIds || []).includes("UNREAD"),
|
|
589
|
+
flagged: (msg.labelIds || []).includes("STARRED"),
|
|
590
|
+
hasAttachments: false,
|
|
591
|
+
preview: msg.snippet || ""
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
fullToMessage(msg) {
|
|
595
|
+
const from = this.getHeader(msg, "From");
|
|
596
|
+
const fromMatch = from.match(/^(.*?)\s*<(.+?)>$/) || [null, "", from];
|
|
597
|
+
let body = "";
|
|
598
|
+
let html;
|
|
599
|
+
const extractBody = (payload) => {
|
|
600
|
+
if (payload.mimeType === "text/plain" && payload.body?.data) {
|
|
601
|
+
body = Buffer.from(payload.body.data, "base64url").toString("utf-8");
|
|
602
|
+
}
|
|
603
|
+
if (payload.mimeType === "text/html" && payload.body?.data) {
|
|
604
|
+
html = Buffer.from(payload.body.data, "base64url").toString("utf-8");
|
|
605
|
+
}
|
|
606
|
+
if (payload.parts) payload.parts.forEach(extractBody);
|
|
607
|
+
};
|
|
608
|
+
if (msg.payload) extractBody(msg.payload);
|
|
609
|
+
return {
|
|
610
|
+
uid: msg.id,
|
|
611
|
+
from: { name: fromMatch[1]?.replace(/"/g, "").trim() || void 0, email: fromMatch[2] || from },
|
|
612
|
+
to: [{ email: this.getHeader(msg, "To") }],
|
|
613
|
+
cc: this.getHeader(msg, "Cc") ? [{ email: this.getHeader(msg, "Cc") }] : void 0,
|
|
614
|
+
subject: this.getHeader(msg, "Subject"),
|
|
615
|
+
body,
|
|
616
|
+
html,
|
|
617
|
+
date: this.getHeader(msg, "Date"),
|
|
618
|
+
read: !(msg.labelIds || []).includes("UNREAD"),
|
|
619
|
+
flagged: (msg.labelIds || []).includes("STARRED"),
|
|
620
|
+
folder: (msg.labelIds || []).includes("INBOX") ? "inbox" : "other",
|
|
621
|
+
messageId: this.getHeader(msg, "Message-ID"),
|
|
622
|
+
inReplyTo: this.getHeader(msg, "In-Reply-To") || void 0,
|
|
623
|
+
references: this.getHeader(msg, "References") ? this.getHeader(msg, "References").split(/\s+/) : void 0,
|
|
624
|
+
attachments: []
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/agenticmail/providers/index.ts
|
|
630
|
+
function createEmailProvider(provider) {
|
|
631
|
+
switch (provider) {
|
|
632
|
+
case "microsoft":
|
|
633
|
+
return new MicrosoftEmailProvider();
|
|
634
|
+
case "google":
|
|
635
|
+
return new GoogleEmailProvider();
|
|
636
|
+
case "imap":
|
|
637
|
+
throw new Error("Generic IMAP provider not yet implemented \u2014 use Microsoft or Google");
|
|
638
|
+
default:
|
|
639
|
+
throw new Error(`Unknown email provider: ${provider}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/agenticmail/manager.ts
|
|
644
|
+
var AgenticMailManager = class {
|
|
645
|
+
providers = /* @__PURE__ */ new Map();
|
|
646
|
+
identities = /* @__PURE__ */ new Map();
|
|
647
|
+
db;
|
|
648
|
+
constructor(opts) {
|
|
649
|
+
this.db = opts?.db;
|
|
650
|
+
}
|
|
651
|
+
setDb(db) {
|
|
652
|
+
this.db = db;
|
|
653
|
+
}
|
|
654
|
+
// ─── Agent Registration ─────────────────────────────
|
|
655
|
+
/**
|
|
656
|
+
* Register an agent's email identity from the org's OAuth/SSO.
|
|
657
|
+
* Called when an agent is created or when its OAuth token is refreshed.
|
|
658
|
+
*/
|
|
659
|
+
async registerAgent(identity) {
|
|
660
|
+
this.identities.set(identity.agentId, identity);
|
|
661
|
+
const provider = createEmailProvider(identity.provider);
|
|
662
|
+
await provider.connect(identity);
|
|
663
|
+
this.providers.set(identity.agentId, provider);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Unregister an agent (on deletion or token revocation).
|
|
667
|
+
*/
|
|
668
|
+
async unregisterAgent(agentId) {
|
|
669
|
+
const provider = this.providers.get(agentId);
|
|
670
|
+
if (provider) {
|
|
671
|
+
await provider.disconnect().catch(() => {
|
|
672
|
+
});
|
|
673
|
+
this.providers.delete(agentId);
|
|
674
|
+
}
|
|
675
|
+
this.identities.delete(agentId);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Get the email provider for an agent.
|
|
679
|
+
* Throws if agent is not registered.
|
|
680
|
+
*/
|
|
681
|
+
getProvider(agentId) {
|
|
682
|
+
const provider = this.providers.get(agentId);
|
|
683
|
+
if (!provider) throw new Error(`Agent ${agentId} has no email provider registered. Ensure the agent has been connected via org OAuth.`);
|
|
684
|
+
return provider;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Get the email identity for an agent.
|
|
688
|
+
*/
|
|
689
|
+
getIdentity(agentId) {
|
|
690
|
+
return this.identities.get(agentId);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Check if an agent has email access.
|
|
694
|
+
*/
|
|
695
|
+
hasEmail(agentId) {
|
|
696
|
+
return this.providers.has(agentId);
|
|
697
|
+
}
|
|
698
|
+
// ─── Inter-Agent Messaging ──────────────────────────
|
|
699
|
+
// These use the enterprise DB directly, not email.
|
|
700
|
+
// Agents in the same org can message each other without email.
|
|
701
|
+
/**
|
|
702
|
+
* Send a message from one agent to another (internal, no email).
|
|
703
|
+
*/
|
|
704
|
+
async sendAgentMessage(from, to, subject, body, priority = "normal") {
|
|
705
|
+
const msg = {
|
|
706
|
+
id: crypto.randomUUID(),
|
|
707
|
+
from,
|
|
708
|
+
to,
|
|
709
|
+
subject,
|
|
710
|
+
body,
|
|
711
|
+
priority,
|
|
712
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
713
|
+
read: false
|
|
714
|
+
};
|
|
715
|
+
if (this.db) {
|
|
716
|
+
await this.db.execute(
|
|
717
|
+
`INSERT INTO agent_messages (id, from_agent, to_agent, subject, body, priority, created_at, read)
|
|
718
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0)`,
|
|
719
|
+
[msg.id, msg.from, msg.to, msg.subject, msg.body, msg.priority, msg.createdAt]
|
|
720
|
+
).catch(() => {
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return msg;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Get unread messages for an agent.
|
|
727
|
+
*/
|
|
728
|
+
async getAgentMessages(agentId, opts) {
|
|
729
|
+
if (!this.db) return [];
|
|
730
|
+
try {
|
|
731
|
+
let sql = "SELECT * FROM agent_messages WHERE to_agent = ?";
|
|
732
|
+
const params = [agentId];
|
|
733
|
+
if (opts?.unreadOnly) {
|
|
734
|
+
sql += " AND read = 0";
|
|
735
|
+
}
|
|
736
|
+
sql += " ORDER BY created_at DESC LIMIT ?";
|
|
737
|
+
params.push(opts?.limit || 20);
|
|
738
|
+
const rows = await this.db.query(sql, params);
|
|
739
|
+
return rows.map((r) => ({
|
|
740
|
+
id: r.id,
|
|
741
|
+
from: r.from_agent,
|
|
742
|
+
to: r.to_agent,
|
|
743
|
+
subject: r.subject,
|
|
744
|
+
body: r.body,
|
|
745
|
+
priority: r.priority,
|
|
746
|
+
createdAt: r.created_at,
|
|
747
|
+
read: !!r.read
|
|
748
|
+
}));
|
|
749
|
+
} catch {
|
|
750
|
+
return [];
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// ─── Task Management ────────────────────────────────
|
|
754
|
+
// Tasks also use the enterprise DB directly.
|
|
755
|
+
/**
|
|
756
|
+
* Create a task assigned to an agent.
|
|
757
|
+
*/
|
|
758
|
+
async createTask(assigner, assignee, title, description, priority = "normal") {
|
|
759
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
760
|
+
const task = {
|
|
761
|
+
id: crypto.randomUUID(),
|
|
762
|
+
assigner,
|
|
763
|
+
assignee,
|
|
764
|
+
title,
|
|
765
|
+
description,
|
|
766
|
+
status: "pending",
|
|
767
|
+
priority,
|
|
768
|
+
createdAt: now,
|
|
769
|
+
updatedAt: now
|
|
770
|
+
};
|
|
771
|
+
if (this.db) {
|
|
772
|
+
await this.db.execute(
|
|
773
|
+
`INSERT INTO agent_tasks (id, assigner, assignee, title, description, status, priority, created_at, updated_at)
|
|
774
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
775
|
+
[task.id, task.assigner, task.assignee, task.title, task.description || null, task.status, task.priority, task.createdAt, task.updatedAt]
|
|
776
|
+
).catch(() => {
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return task;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Get tasks for an agent.
|
|
783
|
+
*/
|
|
784
|
+
async getAgentTasks(agentId, direction = "incoming", status) {
|
|
785
|
+
if (!this.db) return [];
|
|
786
|
+
try {
|
|
787
|
+
const col = direction === "incoming" ? "assignee" : "assigner";
|
|
788
|
+
let sql = `SELECT * FROM agent_tasks WHERE ${col} = ?`;
|
|
789
|
+
const params = [agentId];
|
|
790
|
+
if (status) {
|
|
791
|
+
sql += " AND status = ?";
|
|
792
|
+
params.push(status);
|
|
793
|
+
}
|
|
794
|
+
sql += " ORDER BY created_at DESC LIMIT 50";
|
|
795
|
+
const rows = await this.db.query(sql, params);
|
|
796
|
+
return rows.map((r) => ({
|
|
797
|
+
id: r.id,
|
|
798
|
+
assigner: r.assigner,
|
|
799
|
+
assignee: r.assignee,
|
|
800
|
+
title: r.title,
|
|
801
|
+
description: r.description,
|
|
802
|
+
status: r.status,
|
|
803
|
+
priority: r.priority,
|
|
804
|
+
result: r.result ? JSON.parse(r.result) : void 0,
|
|
805
|
+
createdAt: r.created_at,
|
|
806
|
+
updatedAt: r.updated_at
|
|
807
|
+
}));
|
|
808
|
+
} catch {
|
|
809
|
+
return [];
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Update task status.
|
|
814
|
+
*/
|
|
815
|
+
async updateTask(taskId, updates) {
|
|
816
|
+
if (!this.db) return;
|
|
817
|
+
const sets = ["updated_at = ?"];
|
|
818
|
+
const params = [(/* @__PURE__ */ new Date()).toISOString()];
|
|
819
|
+
if (updates.status) {
|
|
820
|
+
sets.push("status = ?");
|
|
821
|
+
params.push(updates.status);
|
|
822
|
+
}
|
|
823
|
+
if (updates.result !== void 0) {
|
|
824
|
+
sets.push("result = ?");
|
|
825
|
+
params.push(JSON.stringify(updates.result));
|
|
826
|
+
}
|
|
827
|
+
params.push(taskId);
|
|
828
|
+
await this.db.execute(`UPDATE agent_tasks SET ${sets.join(", ")} WHERE id = ?`, params).catch(() => {
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
// ─── Lifecycle ──────────────────────────────────────
|
|
832
|
+
/**
|
|
833
|
+
* Get all registered agents and their email status.
|
|
834
|
+
*/
|
|
835
|
+
getRegisteredAgents() {
|
|
836
|
+
const agents = [];
|
|
837
|
+
for (const [agentId, identity] of this.identities) {
|
|
838
|
+
agents.push({ agentId, email: identity.email, provider: identity.provider });
|
|
839
|
+
}
|
|
840
|
+
return agents;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Shutdown — disconnect all providers.
|
|
844
|
+
*/
|
|
845
|
+
async shutdown() {
|
|
846
|
+
for (const provider of this.providers.values()) {
|
|
847
|
+
await provider.disconnect().catch(() => {
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
this.providers.clear();
|
|
851
|
+
this.identities.clear();
|
|
852
|
+
}
|
|
853
|
+
};
|
|
126
854
|
export {
|
|
127
855
|
AGENTICMAIL_TOOLS,
|
|
128
856
|
ALL_TOOLS,
|
|
@@ -133,6 +861,7 @@ export {
|
|
|
133
861
|
AgentLifecycleManager,
|
|
134
862
|
AgentMemoryManager,
|
|
135
863
|
AgentRuntime,
|
|
864
|
+
AgenticMailManager,
|
|
136
865
|
ApprovalEngine,
|
|
137
866
|
BUILTIN_SKILLS,
|
|
138
867
|
CORE_TOOLS,
|
|
@@ -148,6 +877,7 @@ export {
|
|
|
148
877
|
EmailChannel,
|
|
149
878
|
EngineDatabase,
|
|
150
879
|
FollowUpScheduler,
|
|
880
|
+
GoogleEmailProvider,
|
|
151
881
|
GuardrailEngine,
|
|
152
882
|
HealthMonitor,
|
|
153
883
|
KeyedRateLimiter,
|
|
@@ -155,6 +885,7 @@ export {
|
|
|
155
885
|
MIGRATIONS,
|
|
156
886
|
MIGRATIONS_TABLE,
|
|
157
887
|
MIGRATIONS_TABLE_POSTGRES,
|
|
888
|
+
MicrosoftEmailProvider,
|
|
158
889
|
OnboardingManager,
|
|
159
890
|
OrgPolicyEngine,
|
|
160
891
|
PLAN_LIMITS,
|
|
@@ -183,6 +914,7 @@ export {
|
|
|
183
914
|
createAdminRoutes,
|
|
184
915
|
createAgentRuntime,
|
|
185
916
|
createAuthRoutes,
|
|
917
|
+
createEmailProvider,
|
|
186
918
|
createNoopHooks,
|
|
187
919
|
createRuntimeHooks,
|
|
188
920
|
createServer,
|