@elizaos/plugin-inbox 2.0.3-beta.6 → 2.0.3-beta.7
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/actions/inbox.d.ts +69 -0
- package/dist/actions/inbox.d.ts.map +1 -0
- package/dist/actions/inbox.js +345 -0
- package/dist/actions/inbox.js.map +1 -0
- package/dist/components/inbox/InboxSpatialView.d.ts +54 -0
- package/dist/components/inbox/InboxSpatialView.d.ts.map +1 -0
- package/dist/components/inbox/InboxSpatialView.js +171 -0
- package/dist/components/inbox/InboxSpatialView.js.map +1 -0
- package/dist/components/inbox/InboxView.d.ts +64 -0
- package/dist/components/inbox/InboxView.d.ts.map +1 -0
- package/dist/components/inbox/InboxView.js +169 -0
- package/dist/components/inbox/InboxView.js.map +1 -0
- package/dist/components/inbox/inbox-view-bundle.d.ts +2 -0
- package/dist/components/inbox/inbox-view-bundle.d.ts.map +1 -0
- package/dist/components/inbox/inbox-view-bundle.js +5 -0
- package/dist/components/inbox/inbox-view-bundle.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +3 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +1729 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +79 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/sql.d.ts +32 -0
- package/dist/db/sql.d.ts.map +1 -0
- package/dist/db/sql.js +130 -0
- package/dist/db/sql.js.map +1 -0
- package/dist/inbox/channel-deep-links.d.ts +7 -0
- package/dist/inbox/channel-deep-links.d.ts.map +1 -0
- package/dist/inbox/channel-deep-links.js +97 -0
- package/dist/inbox/channel-deep-links.js.map +1 -0
- package/dist/inbox/config.d.ts +7 -0
- package/dist/inbox/config.d.ts.map +1 -0
- package/dist/inbox/config.js +61 -0
- package/dist/inbox/config.js.map +1 -0
- package/dist/inbox/email-curation.d.ts +174 -0
- package/dist/inbox/email-curation.d.ts.map +1 -0
- package/dist/inbox/email-curation.js +1056 -0
- package/dist/inbox/email-curation.js.map +1 -0
- package/dist/inbox/email-unsubscribe-types.d.ts +71 -0
- package/dist/inbox/email-unsubscribe-types.d.ts.map +1 -0
- package/dist/inbox/email-unsubscribe-types.js +1 -0
- package/dist/inbox/email-unsubscribe-types.js.map +1 -0
- package/dist/inbox/gmail-normalize.d.ts +99 -0
- package/dist/inbox/gmail-normalize.d.ts.map +1 -0
- package/dist/inbox/gmail-normalize.js +937 -0
- package/dist/inbox/gmail-normalize.js.map +1 -0
- package/dist/inbox/google-gmail-seam.d.ts +52 -0
- package/dist/inbox/google-gmail-seam.d.ts.map +1 -0
- package/dist/inbox/google-gmail-seam.js +263 -0
- package/dist/inbox/google-gmail-seam.js.map +1 -0
- package/dist/inbox/message-fetcher.d.ts +47 -0
- package/dist/inbox/message-fetcher.d.ts.map +1 -0
- package/dist/inbox/message-fetcher.js +461 -0
- package/dist/inbox/message-fetcher.js.map +1 -0
- package/dist/inbox/migration.d.ts +46 -0
- package/dist/inbox/migration.d.ts.map +1 -0
- package/dist/inbox/migration.js +114 -0
- package/dist/inbox/migration.js.map +1 -0
- package/dist/inbox/reflection.d.ts +40 -0
- package/dist/inbox/reflection.d.ts.map +1 -0
- package/dist/inbox/reflection.js +142 -0
- package/dist/inbox/reflection.js.map +1 -0
- package/dist/inbox/repository.d.ts +58 -0
- package/dist/inbox/repository.d.ts.map +1 -0
- package/dist/inbox/repository.js +376 -0
- package/dist/inbox/repository.js.map +1 -0
- package/dist/inbox/service.d.ts +149 -0
- package/dist/inbox/service.d.ts.map +1 -0
- package/dist/inbox/service.js +247 -0
- package/dist/inbox/service.js.map +1 -0
- package/dist/inbox/triage-classifier.d.ts +28 -0
- package/dist/inbox/triage-classifier.d.ts.map +1 -0
- package/dist/inbox/triage-classifier.js +306 -0
- package/dist/inbox/triage-classifier.js.map +1 -0
- package/dist/inbox/types.d.ts +124 -0
- package/dist/inbox/types.d.ts.map +1 -0
- package/dist/inbox/types.js +1 -0
- package/dist/inbox/types.js.map +1 -0
- package/dist/inbox/unsubscribe-repository.d.ts +14 -0
- package/dist/inbox/unsubscribe-repository.d.ts.map +1 -0
- package/dist/inbox/unsubscribe-repository.js +112 -0
- package/dist/inbox/unsubscribe-repository.js.map +1 -0
- package/dist/inbox/unsubscribe-service.d.ts +41 -0
- package/dist/inbox/unsubscribe-service.d.ts.map +1 -0
- package/dist/inbox/unsubscribe-service.js +351 -0
- package/dist/inbox/unsubscribe-service.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +38 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/cross-channel-context.d.ts +21 -0
- package/dist/providers/cross-channel-context.d.ts.map +1 -0
- package/dist/providers/cross-channel-context.js +96 -0
- package/dist/providers/cross-channel-context.js.map +1 -0
- package/dist/providers/inbox-triage.d.ts +12 -0
- package/dist/providers/inbox-triage.d.ts.map +1 -0
- package/dist/providers/inbox-triage.js +98 -0
- package/dist/providers/inbox-triage.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +9 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +5 -0
- package/dist/register.js.map +1 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +25 -0
- package/dist/types.js.map +1 -0
- package/dist/views/bundle.js +315 -0
- package/dist/views/bundle.js.map +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
fail,
|
|
4
|
+
normalizeOptionalString,
|
|
5
|
+
requireNonEmptyString
|
|
6
|
+
} from "@elizaos/shared";
|
|
7
|
+
import {
|
|
8
|
+
createInboxGmailGateway
|
|
9
|
+
} from "./google-gmail-seam.js";
|
|
10
|
+
import { InboxUnsubscribeRepository } from "./unsubscribe-repository.js";
|
|
11
|
+
const DEFAULT_SCAN_MAX_MESSAGES = 200;
|
|
12
|
+
const MAX_SENDERS_RETURNED = 200;
|
|
13
|
+
function headerValue(headers, key) {
|
|
14
|
+
if (!headers) return null;
|
|
15
|
+
const exact = headers[key];
|
|
16
|
+
if (typeof exact === "string" && exact.trim()) return exact.trim();
|
|
17
|
+
const lowered = key.toLowerCase();
|
|
18
|
+
for (const [candidate, value] of Object.entries(headers)) {
|
|
19
|
+
if (candidate.toLowerCase() === lowered && typeof value === "string") {
|
|
20
|
+
return value.trim() || null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function senderDomain(email) {
|
|
26
|
+
if (!email) return null;
|
|
27
|
+
const at = email.lastIndexOf("@");
|
|
28
|
+
return at >= 0 ? email.slice(at + 1).toLowerCase() : null;
|
|
29
|
+
}
|
|
30
|
+
function listUnsubscribeEntries(value) {
|
|
31
|
+
if (!value) return [];
|
|
32
|
+
const bracketed = [...value.matchAll(/<([^>]+)>/g)].map((match) => match[1]?.trim() ?? "").filter(Boolean);
|
|
33
|
+
if (bracketed.length > 0) {
|
|
34
|
+
return bracketed;
|
|
35
|
+
}
|
|
36
|
+
return value.split(",").map((entry) => entry.trim().replace(/^<|>$/g, "")).filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
function listUnsubscribeOptions(value) {
|
|
39
|
+
let httpUrl = null;
|
|
40
|
+
let mailto = null;
|
|
41
|
+
for (const entry of listUnsubscribeEntries(value)) {
|
|
42
|
+
if (!httpUrl && /^https?:\/\//i.test(entry)) {
|
|
43
|
+
httpUrl = entry;
|
|
44
|
+
}
|
|
45
|
+
if (!mailto && /^mailto:/i.test(entry)) {
|
|
46
|
+
mailto = entry;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { httpUrl, mailto };
|
|
50
|
+
}
|
|
51
|
+
function unsubscribeMethod(args) {
|
|
52
|
+
const options = listUnsubscribeOptions(args.listUnsubscribe);
|
|
53
|
+
if (options.mailto) return "mailto";
|
|
54
|
+
if (!options.httpUrl) return "manual_only";
|
|
55
|
+
if (/one-click/i.test(args.listUnsubscribePost ?? "")) {
|
|
56
|
+
return "http_one_click";
|
|
57
|
+
}
|
|
58
|
+
return "http_get";
|
|
59
|
+
}
|
|
60
|
+
function parseMailtoUnsubscribe(value) {
|
|
61
|
+
const trimmed = value.trim().replace(/^<|>$/g, "");
|
|
62
|
+
if (!/^mailto:/i.test(trimmed)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const rest = trimmed.slice("mailto:".length);
|
|
66
|
+
const [addressPart, queryPart = ""] = rest.split("?", 2);
|
|
67
|
+
const recipient = decodeURIComponent(addressPart.trim());
|
|
68
|
+
if (!recipient) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const params = new URLSearchParams(queryPart);
|
|
72
|
+
const subject = params.get("subject");
|
|
73
|
+
const body = params.get("body");
|
|
74
|
+
return {
|
|
75
|
+
recipient,
|
|
76
|
+
subject: subject?.trim() ? subject : null,
|
|
77
|
+
body: body?.trim() ? body : null
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function performHttpUnsubscribe(args) {
|
|
81
|
+
const parsed = new URL(args.url);
|
|
82
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
83
|
+
fail(400, "Unsubscribe URL must be http or https.");
|
|
84
|
+
}
|
|
85
|
+
const response = await fetch(parsed.toString(), {
|
|
86
|
+
method: args.oneClick ? "POST" : "GET",
|
|
87
|
+
headers: args.oneClick ? { "Content-Type": "application/x-www-form-urlencoded" } : void 0,
|
|
88
|
+
body: args.oneClick ? "List-Unsubscribe=One-Click" : void 0,
|
|
89
|
+
redirect: "follow"
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
ok: response.ok,
|
|
93
|
+
status: response.status,
|
|
94
|
+
finalUrl: response.url || parsed.toString(),
|
|
95
|
+
method: args.oneClick ? "http_one_click" : "http_get"
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function headersOf(message) {
|
|
99
|
+
return message.metadata && typeof message.metadata === "object" ? message.metadata.headers : void 0;
|
|
100
|
+
}
|
|
101
|
+
class InboxUnsubscribeService {
|
|
102
|
+
constructor(runtime, deps = {}) {
|
|
103
|
+
this.runtime = runtime;
|
|
104
|
+
this.gmail = deps.gmail ?? createInboxGmailGateway(runtime, runtime.agentId);
|
|
105
|
+
this.repository = deps.repository ?? new InboxUnsubscribeRepository(runtime);
|
|
106
|
+
}
|
|
107
|
+
runtime;
|
|
108
|
+
gmail;
|
|
109
|
+
repository;
|
|
110
|
+
get agentId() {
|
|
111
|
+
return this.runtime.agentId;
|
|
112
|
+
}
|
|
113
|
+
async scanEmailSubscriptions(request = {}) {
|
|
114
|
+
const query = normalizeOptionalString(request.query) ?? "(category:promotions OR category:updates OR unsubscribe) newer_than:180d";
|
|
115
|
+
const maxMessages = Math.max(
|
|
116
|
+
10,
|
|
117
|
+
Math.min(
|
|
118
|
+
1e3,
|
|
119
|
+
Number.isFinite(request.maxMessages) ? Math.trunc(request.maxMessages) : DEFAULT_SCAN_MAX_MESSAGES
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
const grant = await this.gmail.requireGmailGrant();
|
|
123
|
+
const search = await this.gmail.searchGmail({
|
|
124
|
+
grant,
|
|
125
|
+
query,
|
|
126
|
+
maxResults: maxMessages,
|
|
127
|
+
includeSpamTrash: true
|
|
128
|
+
});
|
|
129
|
+
const senders = /* @__PURE__ */ new Map();
|
|
130
|
+
for (const message of search.messages) {
|
|
131
|
+
const headers = headersOf(message);
|
|
132
|
+
const listUnsubscribe = headerValue(headers, "List-Unsubscribe");
|
|
133
|
+
const listUnsubscribePost = headerValue(headers, "List-Unsubscribe-Post");
|
|
134
|
+
if (!message.fromEmail && !listUnsubscribe) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const senderEmail = message.fromEmail ?? message.from;
|
|
138
|
+
const existing = senders.get(senderEmail);
|
|
139
|
+
const options = listUnsubscribeOptions(listUnsubscribe);
|
|
140
|
+
const method = unsubscribeMethod({
|
|
141
|
+
listUnsubscribe,
|
|
142
|
+
listUnsubscribePost
|
|
143
|
+
});
|
|
144
|
+
if (!existing) {
|
|
145
|
+
senders.set(senderEmail, {
|
|
146
|
+
senderEmail,
|
|
147
|
+
senderDisplay: message.from,
|
|
148
|
+
senderDomain: senderDomain(senderEmail),
|
|
149
|
+
listId: headerValue(headers, "List-Id"),
|
|
150
|
+
messageCount: 1,
|
|
151
|
+
firstSeenAt: message.receivedAt,
|
|
152
|
+
latestSeenAt: message.receivedAt,
|
|
153
|
+
unsubscribeMethod: method,
|
|
154
|
+
unsubscribeHttpUrl: options.httpUrl,
|
|
155
|
+
unsubscribeMailto: options.mailto,
|
|
156
|
+
listUnsubscribePost,
|
|
157
|
+
sampleSubjects: [message.subject],
|
|
158
|
+
latestMessageId: message.id,
|
|
159
|
+
latestThreadId: message.threadId,
|
|
160
|
+
allMessageIds: [message.id],
|
|
161
|
+
allThreadIds: [message.threadId]
|
|
162
|
+
});
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
existing.messageCount += 1;
|
|
166
|
+
existing.latestSeenAt = message.receivedAt;
|
|
167
|
+
existing.latestMessageId = message.id;
|
|
168
|
+
existing.latestThreadId = message.threadId;
|
|
169
|
+
existing.allMessageIds.push(message.id);
|
|
170
|
+
existing.allThreadIds.push(message.threadId);
|
|
171
|
+
if (existing.sampleSubjects.length < 5) {
|
|
172
|
+
existing.sampleSubjects.push(message.subject);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const senderList = [...senders.values()].sort((left, right) => right.messageCount - left.messageCount).slice(0, MAX_SENDERS_RETURNED);
|
|
176
|
+
return {
|
|
177
|
+
syncedAt: search.syncedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
178
|
+
query,
|
|
179
|
+
summary: {
|
|
180
|
+
scannedMessageCount: search.messages.length,
|
|
181
|
+
uniqueSenderCount: senderList.length,
|
|
182
|
+
oneClickEligibleCount: senderList.filter(
|
|
183
|
+
(sender) => sender.unsubscribeMethod === "http_one_click"
|
|
184
|
+
).length,
|
|
185
|
+
mailtoOnlyCount: senderList.filter(
|
|
186
|
+
(sender) => sender.unsubscribeMethod === "mailto"
|
|
187
|
+
).length,
|
|
188
|
+
manualOnlyCount: senderList.filter(
|
|
189
|
+
(sender) => sender.unsubscribeMethod === "manual_only"
|
|
190
|
+
).length
|
|
191
|
+
},
|
|
192
|
+
senders: senderList
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async unsubscribeEmailSender(request) {
|
|
196
|
+
const senderEmail = requireNonEmptyString(
|
|
197
|
+
request.senderEmail,
|
|
198
|
+
"senderEmail"
|
|
199
|
+
).toLowerCase();
|
|
200
|
+
if (request.userAuthorization !== true) {
|
|
201
|
+
fail(
|
|
202
|
+
409,
|
|
203
|
+
"Email unsubscribe requires explicit user authorization (two-phase confirmation)."
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const grant = await this.gmail.requireGmailGrant();
|
|
207
|
+
const accountId = grant.connectorAccountId ?? fail(
|
|
208
|
+
409,
|
|
209
|
+
"Google connector account id is missing. Reconnect Google through connector account management."
|
|
210
|
+
);
|
|
211
|
+
const scan = await this.scanEmailSubscriptions({
|
|
212
|
+
query: `from:${senderEmail} (unsubscribe OR list:*) newer_than:365d`,
|
|
213
|
+
maxMessages: 100
|
|
214
|
+
});
|
|
215
|
+
const sender = scan.senders.find(
|
|
216
|
+
(candidate) => candidate.senderEmail.toLowerCase() === senderEmail
|
|
217
|
+
) ?? {
|
|
218
|
+
senderEmail,
|
|
219
|
+
senderDisplay: senderEmail,
|
|
220
|
+
senderDomain: senderDomain(senderEmail),
|
|
221
|
+
listId: normalizeOptionalString(request.listId) ?? null,
|
|
222
|
+
messageCount: 0,
|
|
223
|
+
firstSeenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
224
|
+
latestSeenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
225
|
+
unsubscribeMethod: "manual_only",
|
|
226
|
+
unsubscribeHttpUrl: null,
|
|
227
|
+
unsubscribeMailto: null,
|
|
228
|
+
listUnsubscribePost: null,
|
|
229
|
+
sampleSubjects: [],
|
|
230
|
+
latestMessageId: "",
|
|
231
|
+
latestThreadId: "",
|
|
232
|
+
allMessageIds: [],
|
|
233
|
+
allThreadIds: []
|
|
234
|
+
};
|
|
235
|
+
let method = sender.unsubscribeMethod;
|
|
236
|
+
let status = "manual_required";
|
|
237
|
+
let httpStatusCode = null;
|
|
238
|
+
let httpFinalUrl = null;
|
|
239
|
+
let filterCreated = false;
|
|
240
|
+
let filterId = null;
|
|
241
|
+
let threadsTrashed = 0;
|
|
242
|
+
let errorMessage = null;
|
|
243
|
+
try {
|
|
244
|
+
if (sender.unsubscribeHttpUrl) {
|
|
245
|
+
const http = await performHttpUnsubscribe({
|
|
246
|
+
url: sender.unsubscribeHttpUrl,
|
|
247
|
+
oneClick: sender.unsubscribeMethod === "http_one_click"
|
|
248
|
+
});
|
|
249
|
+
method = http.method;
|
|
250
|
+
httpStatusCode = http.status;
|
|
251
|
+
httpFinalUrl = http.finalUrl;
|
|
252
|
+
status = http.ok ? "succeeded" : "failed";
|
|
253
|
+
if (!http.ok) {
|
|
254
|
+
errorMessage = `HTTP unsubscribe returned ${http.status}.`;
|
|
255
|
+
}
|
|
256
|
+
} else if (sender.unsubscribeMailto) {
|
|
257
|
+
const mailto = parseMailtoUnsubscribe(sender.unsubscribeMailto);
|
|
258
|
+
if (!mailto) {
|
|
259
|
+
fail(400, "List-Unsubscribe mailto target is invalid.");
|
|
260
|
+
}
|
|
261
|
+
await this.gmail.sendMailtoUnsubscribeEmail(accountId, mailto);
|
|
262
|
+
method = "mailto";
|
|
263
|
+
status = "succeeded";
|
|
264
|
+
}
|
|
265
|
+
if (request.blockAfter || request.trashExisting) {
|
|
266
|
+
if (!grant.capabilities.includes("google.gmail.manage")) {
|
|
267
|
+
fail(
|
|
268
|
+
403,
|
|
269
|
+
"Blocking or trashing subscription email requires Gmail manage access."
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (request.blockAfter) {
|
|
274
|
+
const filter = await this.gmail.createGmailFilterForSender(
|
|
275
|
+
accountId,
|
|
276
|
+
senderEmail
|
|
277
|
+
);
|
|
278
|
+
filterCreated = true;
|
|
279
|
+
filterId = filter.filterId;
|
|
280
|
+
status = "succeeded";
|
|
281
|
+
}
|
|
282
|
+
if (request.trashExisting) {
|
|
283
|
+
const threadIds = [...new Set(sender.allThreadIds.filter(Boolean))];
|
|
284
|
+
for (const threadId of threadIds) {
|
|
285
|
+
await this.gmail.trashGmailThread(accountId, threadId);
|
|
286
|
+
threadsTrashed += 1;
|
|
287
|
+
}
|
|
288
|
+
if (threadIds.length > 0) {
|
|
289
|
+
status = "succeeded";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (status === "manual_required" && !sender.unsubscribeHttpUrl && !sender.unsubscribeMailto && !request.blockAfter && !request.trashExisting) {
|
|
293
|
+
status = "blocked_no_mechanism";
|
|
294
|
+
}
|
|
295
|
+
} catch (cause) {
|
|
296
|
+
status = "failed";
|
|
297
|
+
errorMessage = cause instanceof Error && cause.message.trim() ? cause.message : String(cause);
|
|
298
|
+
}
|
|
299
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
300
|
+
const record = {
|
|
301
|
+
id: crypto.randomUUID(),
|
|
302
|
+
agentId: this.agentId,
|
|
303
|
+
senderEmail,
|
|
304
|
+
senderDisplay: sender.senderDisplay,
|
|
305
|
+
senderDomain: sender.senderDomain,
|
|
306
|
+
listId: normalizeOptionalString(request.listId) ?? sender.listId,
|
|
307
|
+
method,
|
|
308
|
+
status,
|
|
309
|
+
httpStatusCode,
|
|
310
|
+
httpFinalUrl,
|
|
311
|
+
filterCreated,
|
|
312
|
+
filterId,
|
|
313
|
+
threadsTrashed,
|
|
314
|
+
errorMessage,
|
|
315
|
+
metadata: {
|
|
316
|
+
connectorAccountId: accountId,
|
|
317
|
+
grantId: grant.id,
|
|
318
|
+
messageCount: sender.messageCount,
|
|
319
|
+
latestMessageId: sender.latestMessageId,
|
|
320
|
+
latestThreadId: sender.latestThreadId,
|
|
321
|
+
blockAfter: request.blockAfter === true,
|
|
322
|
+
trashExisting: request.trashExisting === true
|
|
323
|
+
},
|
|
324
|
+
createdAt: now,
|
|
325
|
+
updatedAt: now
|
|
326
|
+
};
|
|
327
|
+
await this.repository.createEmailUnsubscribe(record);
|
|
328
|
+
return { record };
|
|
329
|
+
}
|
|
330
|
+
async listEmailUnsubscribes(limit = 100) {
|
|
331
|
+
return this.repository.listEmailUnsubscribes({
|
|
332
|
+
limit: Math.max(1, Math.min(500, limit))
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
summarizeEmailUnsubscribeScan(result) {
|
|
336
|
+
if (result.senders.length === 0) {
|
|
337
|
+
return `No active promotional senders found in the last scan (${result.summary.scannedMessageCount} messages checked).`;
|
|
338
|
+
}
|
|
339
|
+
const top = result.senders.slice(0, 5).map((sender) => {
|
|
340
|
+
return `- ${sender.senderDisplay} <${sender.senderEmail}>: ${sender.messageCount} msgs, ${sender.unsubscribeMethod}`;
|
|
341
|
+
});
|
|
342
|
+
return [
|
|
343
|
+
`Found ${result.summary.uniqueSenderCount} senders across ${result.summary.scannedMessageCount} messages.`,
|
|
344
|
+
...top
|
|
345
|
+
].join("\n");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
export {
|
|
349
|
+
InboxUnsubscribeService
|
|
350
|
+
};
|
|
351
|
+
//# sourceMappingURL=unsubscribe-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/inbox/unsubscribe-service.ts"],"sourcesContent":["/**\n * InboxUnsubscribeService — the email-unsubscribe back-end.\n *\n * Standalone successor to PA's `service-mixin-email-unsubscribe.ts` (a\n * `this.`-bound `LifeOpsService` mixin). It scans Gmail for promotional senders,\n * performs the List-Unsubscribe flow (HTTP one-click / GET / mailto), optionally\n * creates a block filter and trashes existing threads, and records the outcome.\n *\n * Dependencies are resolved through seams so this plugin carries no\n * `@elizaos/plugin-personal-assistant` dependency:\n * - Gmail access via {@link createInboxGmailGateway} (the `@elizaos/plugin-google`\n * runtime service).\n * - Persistence via {@link InboxUnsubscribeRepository} (raw SQL over the\n * `app_inbox.life_email_unsubscribes` table PA registers).\n *\n * Authorization: `unsubscribeEmailSender` requires `userAuthorization === true`.\n * The two-phase confirmation gate (`requireConfirmation`) lives in the PA route\n * layer that owns the HTTP surface; this service trusts the pre-confirmed flag.\n */\n\nimport crypto from \"node:crypto\";\nimport type { IAgentRuntime } from \"@elizaos/core\";\nimport {\n fail,\n type LifeOpsGmailMessageSummary,\n normalizeOptionalString,\n requireNonEmptyString,\n} from \"@elizaos/shared\";\nimport type {\n EmailSubscriptionScanResult,\n EmailSubscriptionSender,\n EmailUnsubscribeMethod,\n EmailUnsubscribeRecord,\n EmailUnsubscribeRequest,\n EmailUnsubscribeResult,\n EmailUnsubscribeScanRequest,\n EmailUnsubscribeStatus,\n} from \"./email-unsubscribe-types.js\";\nimport {\n createInboxGmailGateway,\n type InboxGmailGateway,\n} from \"./google-gmail-seam.js\";\nimport { InboxUnsubscribeRepository } from \"./unsubscribe-repository.js\";\n\nconst DEFAULT_SCAN_MAX_MESSAGES = 200;\nconst MAX_SENDERS_RETURNED = 200;\n\nfunction headerValue(\n headers: Record<string, unknown> | undefined,\n key: string,\n): string | null {\n if (!headers) return null;\n const exact = headers[key];\n if (typeof exact === \"string\" && exact.trim()) return exact.trim();\n const lowered = key.toLowerCase();\n for (const [candidate, value] of Object.entries(headers)) {\n if (candidate.toLowerCase() === lowered && typeof value === \"string\") {\n return value.trim() || null;\n }\n }\n return null;\n}\n\nfunction senderDomain(email: string | null): string | null {\n if (!email) return null;\n const at = email.lastIndexOf(\"@\");\n return at >= 0 ? email.slice(at + 1).toLowerCase() : null;\n}\n\nfunction listUnsubscribeEntries(value: string | null): string[] {\n if (!value) return [];\n const bracketed = [...value.matchAll(/<([^>]+)>/g)]\n .map((match) => match[1]?.trim() ?? \"\")\n .filter(Boolean);\n if (bracketed.length > 0) {\n return bracketed;\n }\n return value\n .split(\",\")\n .map((entry) => entry.trim().replace(/^<|>$/g, \"\"))\n .filter(Boolean);\n}\n\nfunction listUnsubscribeOptions(value: string | null): {\n httpUrl: string | null;\n mailto: string | null;\n} {\n let httpUrl: string | null = null;\n let mailto: string | null = null;\n for (const entry of listUnsubscribeEntries(value)) {\n if (!httpUrl && /^https?:\\/\\//i.test(entry)) {\n httpUrl = entry;\n }\n if (!mailto && /^mailto:/i.test(entry)) {\n mailto = entry;\n }\n }\n return { httpUrl, mailto };\n}\n\nfunction unsubscribeMethod(args: {\n listUnsubscribe: string | null;\n listUnsubscribePost: string | null;\n}): EmailSubscriptionSender[\"unsubscribeMethod\"] {\n const options = listUnsubscribeOptions(args.listUnsubscribe);\n if (options.mailto) return \"mailto\";\n if (!options.httpUrl) return \"manual_only\";\n if (/one-click/i.test(args.listUnsubscribePost ?? \"\")) {\n return \"http_one_click\";\n }\n return \"http_get\";\n}\n\nfunction parseMailtoUnsubscribe(value: string): {\n recipient: string;\n subject: string | null;\n body: string | null;\n} | null {\n const trimmed = value.trim().replace(/^<|>$/g, \"\");\n if (!/^mailto:/i.test(trimmed)) {\n return null;\n }\n const rest = trimmed.slice(\"mailto:\".length);\n const [addressPart, queryPart = \"\"] = rest.split(\"?\", 2);\n const recipient = decodeURIComponent(addressPart.trim());\n if (!recipient) {\n return null;\n }\n const params = new URLSearchParams(queryPart);\n const subject = params.get(\"subject\");\n const body = params.get(\"body\");\n return {\n recipient,\n subject: subject?.trim() ? subject : null,\n body: body?.trim() ? body : null,\n };\n}\n\nasync function performHttpUnsubscribe(args: {\n url: string;\n oneClick: boolean;\n}): Promise<{\n ok: boolean;\n status: number;\n finalUrl: string;\n method: Extract<EmailUnsubscribeMethod, \"http_one_click\" | \"http_get\">;\n}> {\n const parsed = new URL(args.url);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n fail(400, \"Unsubscribe URL must be http or https.\");\n }\n const response = await fetch(parsed.toString(), {\n method: args.oneClick ? \"POST\" : \"GET\",\n headers: args.oneClick\n ? { \"Content-Type\": \"application/x-www-form-urlencoded\" }\n : undefined,\n body: args.oneClick ? \"List-Unsubscribe=One-Click\" : undefined,\n redirect: \"follow\",\n });\n return {\n ok: response.ok,\n status: response.status,\n finalUrl: response.url || parsed.toString(),\n method: args.oneClick ? \"http_one_click\" : \"http_get\",\n };\n}\n\nfunction headersOf(\n message: LifeOpsGmailMessageSummary,\n): Record<string, unknown> | undefined {\n return message.metadata && typeof message.metadata === \"object\"\n ? (message.metadata.headers as Record<string, unknown> | undefined)\n : undefined;\n}\n\nexport interface InboxUnsubscribeServiceDeps {\n /** Override the Gmail gateway (tests inject a mock; default resolves plugin-google). */\n gmail?: InboxGmailGateway;\n /** Override the persistence repository (tests inject a fake or PGlite-backed one). */\n repository?: InboxUnsubscribeRepository;\n}\n\nexport class InboxUnsubscribeService {\n private readonly gmail: InboxGmailGateway;\n private readonly repository: InboxUnsubscribeRepository;\n\n constructor(\n private readonly runtime: IAgentRuntime,\n deps: InboxUnsubscribeServiceDeps = {},\n ) {\n this.gmail =\n deps.gmail ?? createInboxGmailGateway(runtime, runtime.agentId);\n this.repository =\n deps.repository ?? new InboxUnsubscribeRepository(runtime);\n }\n\n private get agentId(): string {\n return this.runtime.agentId;\n }\n\n async scanEmailSubscriptions(\n request: EmailUnsubscribeScanRequest = {},\n ): Promise<EmailSubscriptionScanResult> {\n const query =\n normalizeOptionalString(request.query) ??\n \"(category:promotions OR category:updates OR unsubscribe) newer_than:180d\";\n const maxMessages = Math.max(\n 10,\n Math.min(\n 1000,\n Number.isFinite(request.maxMessages)\n ? Math.trunc(request.maxMessages as number)\n : DEFAULT_SCAN_MAX_MESSAGES,\n ),\n );\n const grant = await this.gmail.requireGmailGrant();\n const search = await this.gmail.searchGmail({\n grant,\n query,\n maxResults: maxMessages,\n includeSpamTrash: true,\n });\n const senders = new Map<string, EmailSubscriptionSender>();\n for (const message of search.messages) {\n const headers = headersOf(message);\n const listUnsubscribe = headerValue(headers, \"List-Unsubscribe\");\n const listUnsubscribePost = headerValue(headers, \"List-Unsubscribe-Post\");\n if (!message.fromEmail && !listUnsubscribe) {\n continue;\n }\n const senderEmail = message.fromEmail ?? message.from;\n const existing = senders.get(senderEmail);\n const options = listUnsubscribeOptions(listUnsubscribe);\n const method = unsubscribeMethod({\n listUnsubscribe,\n listUnsubscribePost,\n });\n if (!existing) {\n senders.set(senderEmail, {\n senderEmail,\n senderDisplay: message.from,\n senderDomain: senderDomain(senderEmail),\n listId: headerValue(headers, \"List-Id\"),\n messageCount: 1,\n firstSeenAt: message.receivedAt,\n latestSeenAt: message.receivedAt,\n unsubscribeMethod: method,\n unsubscribeHttpUrl: options.httpUrl,\n unsubscribeMailto: options.mailto,\n listUnsubscribePost,\n sampleSubjects: [message.subject],\n latestMessageId: message.id,\n latestThreadId: message.threadId,\n allMessageIds: [message.id],\n allThreadIds: [message.threadId],\n });\n continue;\n }\n existing.messageCount += 1;\n existing.latestSeenAt = message.receivedAt;\n existing.latestMessageId = message.id;\n existing.latestThreadId = message.threadId;\n existing.allMessageIds.push(message.id);\n existing.allThreadIds.push(message.threadId);\n if (existing.sampleSubjects.length < 5) {\n existing.sampleSubjects.push(message.subject);\n }\n }\n const senderList = [...senders.values()]\n .sort((left, right) => right.messageCount - left.messageCount)\n .slice(0, MAX_SENDERS_RETURNED);\n return {\n syncedAt: search.syncedAt ?? new Date().toISOString(),\n query,\n summary: {\n scannedMessageCount: search.messages.length,\n uniqueSenderCount: senderList.length,\n oneClickEligibleCount: senderList.filter(\n (sender) => sender.unsubscribeMethod === \"http_one_click\",\n ).length,\n mailtoOnlyCount: senderList.filter(\n (sender) => sender.unsubscribeMethod === \"mailto\",\n ).length,\n manualOnlyCount: senderList.filter(\n (sender) => sender.unsubscribeMethod === \"manual_only\",\n ).length,\n },\n senders: senderList,\n };\n }\n\n async unsubscribeEmailSender(\n request: EmailUnsubscribeRequest,\n ): Promise<EmailUnsubscribeResult> {\n const senderEmail = requireNonEmptyString(\n request.senderEmail,\n \"senderEmail\",\n ).toLowerCase();\n if (request.userAuthorization !== true) {\n fail(\n 409,\n \"Email unsubscribe requires explicit user authorization (two-phase confirmation).\",\n );\n }\n\n const grant = await this.gmail.requireGmailGrant();\n const accountId =\n grant.connectorAccountId ??\n fail(\n 409,\n \"Google connector account id is missing. Reconnect Google through connector account management.\",\n );\n const scan = await this.scanEmailSubscriptions({\n query: `from:${senderEmail} (unsubscribe OR list:*) newer_than:365d`,\n maxMessages: 100,\n });\n const sender =\n scan.senders.find(\n (candidate) => candidate.senderEmail.toLowerCase() === senderEmail,\n ) ??\n ({\n senderEmail,\n senderDisplay: senderEmail,\n senderDomain: senderDomain(senderEmail),\n listId: normalizeOptionalString(request.listId) ?? null,\n messageCount: 0,\n firstSeenAt: new Date().toISOString(),\n latestSeenAt: new Date().toISOString(),\n unsubscribeMethod: \"manual_only\",\n unsubscribeHttpUrl: null,\n unsubscribeMailto: null,\n listUnsubscribePost: null,\n sampleSubjects: [],\n latestMessageId: \"\",\n latestThreadId: \"\",\n allMessageIds: [],\n allThreadIds: [],\n } satisfies EmailSubscriptionSender);\n\n let method: EmailUnsubscribeMethod = sender.unsubscribeMethod;\n let status: EmailUnsubscribeStatus = \"manual_required\";\n let httpStatusCode: number | null = null;\n let httpFinalUrl: string | null = null;\n let filterCreated = false;\n let filterId: string | null = null;\n let threadsTrashed = 0;\n let errorMessage: string | null = null;\n\n try {\n if (sender.unsubscribeHttpUrl) {\n const http = await performHttpUnsubscribe({\n url: sender.unsubscribeHttpUrl,\n oneClick: sender.unsubscribeMethod === \"http_one_click\",\n });\n method = http.method;\n httpStatusCode = http.status;\n httpFinalUrl = http.finalUrl;\n status = http.ok ? \"succeeded\" : \"failed\";\n if (!http.ok) {\n errorMessage = `HTTP unsubscribe returned ${http.status}.`;\n }\n } else if (sender.unsubscribeMailto) {\n const mailto = parseMailtoUnsubscribe(sender.unsubscribeMailto);\n if (!mailto) {\n fail(400, \"List-Unsubscribe mailto target is invalid.\");\n }\n await this.gmail.sendMailtoUnsubscribeEmail(accountId, mailto);\n method = \"mailto\";\n status = \"succeeded\";\n }\n\n if (request.blockAfter || request.trashExisting) {\n if (!grant.capabilities.includes(\"google.gmail.manage\")) {\n fail(\n 403,\n \"Blocking or trashing subscription email requires Gmail manage access.\",\n );\n }\n }\n\n if (request.blockAfter) {\n const filter = await this.gmail.createGmailFilterForSender(\n accountId,\n senderEmail,\n );\n filterCreated = true;\n filterId = filter.filterId;\n status = \"succeeded\";\n }\n\n if (request.trashExisting) {\n const threadIds = [...new Set(sender.allThreadIds.filter(Boolean))];\n for (const threadId of threadIds) {\n await this.gmail.trashGmailThread(accountId, threadId);\n threadsTrashed += 1;\n }\n if (threadIds.length > 0) {\n status = \"succeeded\";\n }\n }\n\n if (\n status === \"manual_required\" &&\n !sender.unsubscribeHttpUrl &&\n !sender.unsubscribeMailto &&\n !request.blockAfter &&\n !request.trashExisting\n ) {\n status = \"blocked_no_mechanism\";\n }\n } catch (cause) {\n status = \"failed\";\n errorMessage =\n cause instanceof Error && cause.message.trim()\n ? cause.message\n : String(cause);\n }\n\n const now = new Date().toISOString();\n const record: EmailUnsubscribeRecord = {\n id: crypto.randomUUID(),\n agentId: this.agentId,\n senderEmail,\n senderDisplay: sender.senderDisplay,\n senderDomain: sender.senderDomain,\n listId: normalizeOptionalString(request.listId) ?? sender.listId,\n method,\n status,\n httpStatusCode,\n httpFinalUrl,\n filterCreated,\n filterId,\n threadsTrashed,\n errorMessage,\n metadata: {\n connectorAccountId: accountId,\n grantId: grant.id,\n messageCount: sender.messageCount,\n latestMessageId: sender.latestMessageId,\n latestThreadId: sender.latestThreadId,\n blockAfter: request.blockAfter === true,\n trashExisting: request.trashExisting === true,\n },\n createdAt: now,\n updatedAt: now,\n };\n await this.repository.createEmailUnsubscribe(record);\n return { record };\n }\n\n async listEmailUnsubscribes(limit = 100): Promise<EmailUnsubscribeRecord[]> {\n return this.repository.listEmailUnsubscribes({\n limit: Math.max(1, Math.min(500, limit)),\n });\n }\n\n summarizeEmailUnsubscribeScan(result: EmailSubscriptionScanResult): string {\n if (result.senders.length === 0) {\n return `No active promotional senders found in the last scan (${result.summary.scannedMessageCount} messages checked).`;\n }\n const top = result.senders.slice(0, 5).map((sender) => {\n return `- ${sender.senderDisplay} <${sender.senderEmail}>: ${sender.messageCount} msgs, ${sender.unsubscribeMethod}`;\n });\n return [\n `Found ${result.summary.uniqueSenderCount} senders across ${result.summary.scannedMessageCount} messages.`,\n ...top,\n ].join(\"\\n\");\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { __resetInboxFetchersForTests, type InboxFetcher, type InboxFetchers, type InboxPlatform, inboxAction, setInboxFetchers, } from "./actions/inbox.js";
|
|
2
|
+
export { EMPTY_INBOX_SNAPSHOT, type InboxChannelFilter, type InboxSnapshot, InboxSpatialView, type InboxStatus, } from "./components/inbox/InboxSpatialView.js";
|
|
3
|
+
export { InboxView } from "./components/inbox/InboxView.js";
|
|
4
|
+
export { type EmailUnsubscribeRow, type InboxTriageEntryRow, type InboxTriageExampleRow, inboxDbSchema, inboxSchema, lifeEmailUnsubscribes, lifeInboxTriageEntries, lifeInboxTriageExamples, } from "./db/schema.js";
|
|
5
|
+
export * from "./inbox/email-curation.js";
|
|
6
|
+
export type { EmailSubscriptionScanResult, EmailSubscriptionScanSummary, EmailSubscriptionSender, EmailUnsubscribeMethod, EmailUnsubscribeRecord, EmailUnsubscribeRequest, EmailUnsubscribeResult, EmailUnsubscribeScanRequest, EmailUnsubscribeStatus, } from "./inbox/email-unsubscribe-types.js";
|
|
7
|
+
export * from "./inbox/gmail-normalize.js";
|
|
8
|
+
export { createInboxGmailGateway, type InboxGmailGateway, } from "./inbox/google-gmail-seam.js";
|
|
9
|
+
export { INBOX_MIGRATION_SERVICE_TYPE, InboxMigrationService, MIGRATED_INBOX_TABLES, } from "./inbox/migration.js";
|
|
10
|
+
export { InboxRepository } from "./inbox/repository.js";
|
|
11
|
+
export { InboxService, type SearchOptions, type TriagedMessage, type TriageOptions, type TriageRunResult, } from "./inbox/service.js";
|
|
12
|
+
export type { InboundMessage, OwnerAction, TriageClassification, TriageEntry, TriageExample, TriageResult, TriageUrgency, } from "./inbox/types.js";
|
|
13
|
+
export { InboxUnsubscribeRepository } from "./inbox/unsubscribe-repository.js";
|
|
14
|
+
export { InboxUnsubscribeService, type InboxUnsubscribeServiceDeps, } from "./inbox/unsubscribe-service.js";
|
|
15
|
+
export { default, inboxPlugin } from "./plugin.js";
|
|
16
|
+
import "./register.js";
|
|
17
|
+
export { inboxTriageProvider } from "./providers/inbox-triage.js";
|
|
18
|
+
export { registerInboxTerminalView, setInboxTerminalSnapshot, } from "./register-terminal-view.js";
|
|
19
|
+
export * from "./types.js";
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,WAAW,EACX,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,gBAAgB,EAChB,KAAK,WAAW,GACjB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAC7D,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,aAAa,EACb,WAAW,EACX,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AAKxB,cAAc,2BAA2B,CAAC;AAC1C,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC5B,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAI5C,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,EACL,uBAAuB,EACvB,KAAK,2BAA2B,GACjC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,eAAe,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,8BAA8B,CAAC;AAEtC,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__resetInboxFetchersForTests,
|
|
3
|
+
inboxAction,
|
|
4
|
+
setInboxFetchers
|
|
5
|
+
} from "./actions/inbox.js";
|
|
6
|
+
import {
|
|
7
|
+
EMPTY_INBOX_SNAPSHOT,
|
|
8
|
+
InboxSpatialView
|
|
9
|
+
} from "./components/inbox/InboxSpatialView.js";
|
|
10
|
+
import { InboxView } from "./components/inbox/InboxView.js";
|
|
11
|
+
import {
|
|
12
|
+
inboxDbSchema,
|
|
13
|
+
inboxSchema,
|
|
14
|
+
lifeEmailUnsubscribes,
|
|
15
|
+
lifeInboxTriageEntries,
|
|
16
|
+
lifeInboxTriageExamples
|
|
17
|
+
} from "./db/schema.js";
|
|
18
|
+
export * from "./inbox/email-curation.js";
|
|
19
|
+
export * from "./inbox/gmail-normalize.js";
|
|
20
|
+
import {
|
|
21
|
+
createInboxGmailGateway
|
|
22
|
+
} from "./inbox/google-gmail-seam.js";
|
|
23
|
+
import {
|
|
24
|
+
INBOX_MIGRATION_SERVICE_TYPE,
|
|
25
|
+
InboxMigrationService,
|
|
26
|
+
MIGRATED_INBOX_TABLES
|
|
27
|
+
} from "./inbox/migration.js";
|
|
28
|
+
import { InboxRepository } from "./inbox/repository.js";
|
|
29
|
+
import {
|
|
30
|
+
InboxService
|
|
31
|
+
} from "./inbox/service.js";
|
|
32
|
+
import { InboxUnsubscribeRepository } from "./inbox/unsubscribe-repository.js";
|
|
33
|
+
import {
|
|
34
|
+
InboxUnsubscribeService
|
|
35
|
+
} from "./inbox/unsubscribe-service.js";
|
|
36
|
+
import { default as default2, inboxPlugin } from "./plugin.js";
|
|
37
|
+
import "./register.js";
|
|
38
|
+
import { inboxTriageProvider } from "./providers/inbox-triage.js";
|
|
39
|
+
import {
|
|
40
|
+
registerInboxTerminalView,
|
|
41
|
+
setInboxTerminalSnapshot
|
|
42
|
+
} from "./register-terminal-view.js";
|
|
43
|
+
export * from "./types.js";
|
|
44
|
+
export {
|
|
45
|
+
EMPTY_INBOX_SNAPSHOT,
|
|
46
|
+
INBOX_MIGRATION_SERVICE_TYPE,
|
|
47
|
+
InboxMigrationService,
|
|
48
|
+
InboxRepository,
|
|
49
|
+
InboxService,
|
|
50
|
+
InboxSpatialView,
|
|
51
|
+
InboxUnsubscribeRepository,
|
|
52
|
+
InboxUnsubscribeService,
|
|
53
|
+
InboxView,
|
|
54
|
+
MIGRATED_INBOX_TABLES,
|
|
55
|
+
__resetInboxFetchersForTests,
|
|
56
|
+
createInboxGmailGateway,
|
|
57
|
+
default2 as default,
|
|
58
|
+
inboxAction,
|
|
59
|
+
inboxDbSchema,
|
|
60
|
+
inboxPlugin,
|
|
61
|
+
inboxSchema,
|
|
62
|
+
inboxTriageProvider,
|
|
63
|
+
lifeEmailUnsubscribes,
|
|
64
|
+
lifeInboxTriageEntries,
|
|
65
|
+
lifeInboxTriageExamples,
|
|
66
|
+
registerInboxTerminalView,
|
|
67
|
+
setInboxFetchers,
|
|
68
|
+
setInboxTerminalSnapshot
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n __resetInboxFetchersForTests,\n type InboxFetcher,\n type InboxFetchers,\n type InboxPlatform,\n inboxAction,\n setInboxFetchers,\n} from \"./actions/inbox.js\";\nexport {\n EMPTY_INBOX_SNAPSHOT,\n type InboxChannelFilter,\n type InboxSnapshot,\n InboxSpatialView,\n type InboxStatus,\n} from \"./components/inbox/InboxSpatialView.js\";\nexport { InboxView } from \"./components/inbox/InboxView.js\";\nexport {\n type EmailUnsubscribeRow,\n type InboxTriageEntryRow,\n type InboxTriageExampleRow,\n inboxDbSchema,\n inboxSchema,\n lifeEmailUnsubscribes,\n lifeInboxTriageEntries,\n lifeInboxTriageExamples,\n} from \"./db/schema.js\";\n// Email-curation decision engine. Pure (email + context → save/archive/delete/\n// review with evidence and citations); takes injected identity/policy hooks.\n// Also consumable via the narrow subpath\n// `@elizaos/plugin-inbox/inbox/email-curation`.\nexport * from \"./inbox/email-curation.js\";\nexport type {\n EmailSubscriptionScanResult,\n EmailSubscriptionScanSummary,\n EmailSubscriptionSender,\n EmailUnsubscribeMethod,\n EmailUnsubscribeRecord,\n EmailUnsubscribeRequest,\n EmailUnsubscribeResult,\n EmailUnsubscribeScanRequest,\n EmailUnsubscribeStatus,\n} from \"./inbox/email-unsubscribe-types.js\";\n// Gmail-domain normalization primitives. Pure; consumable via the narrow\n// subpath `@elizaos/plugin-inbox/inbox/gmail-normalize` (avoids pulling the\n// React view / plugin definition into service-layer callers).\nexport * from \"./inbox/gmail-normalize.js\";\nexport {\n createInboxGmailGateway,\n type InboxGmailGateway,\n} from \"./inbox/google-gmail-seam.js\";\nexport {\n INBOX_MIGRATION_SERVICE_TYPE,\n InboxMigrationService,\n MIGRATED_INBOX_TABLES,\n} from \"./inbox/migration.js\";\nexport { InboxRepository } from \"./inbox/repository.js\";\nexport {\n InboxService,\n type SearchOptions,\n type TriagedMessage,\n type TriageOptions,\n type TriageRunResult,\n} from \"./inbox/service.js\";\nexport type {\n InboundMessage,\n OwnerAction,\n TriageClassification,\n TriageEntry,\n TriageExample,\n TriageResult,\n TriageUrgency,\n} from \"./inbox/types.js\";\nexport { InboxUnsubscribeRepository } from \"./inbox/unsubscribe-repository.js\";\nexport {\n InboxUnsubscribeService,\n type InboxUnsubscribeServiceDeps,\n} from \"./inbox/unsubscribe-service.js\";\nexport { default, inboxPlugin } from \"./plugin.js\";\n// Side-effect: in a terminal host (no DOM), register the inbox spatial view so\n// it renders inline in the agent terminal. Inert in browser/mobile bundles.\nimport \"./register.js\";\n\nexport { inboxTriageProvider } from \"./providers/inbox-triage.js\";\nexport {\n registerInboxTerminalView,\n setInboxTerminalSnapshot,\n} from \"./register-terminal-view.js\";\n\nexport * from \"./types.js\";\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EAIA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAGA;AAAA,OAEK;AACP,SAAS,iBAAiB;AAC1B;AAAA,EAIE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAKP,cAAc;AAed,cAAc;AACd;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,OAKK;AAUP,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,OAEK;AACP,SAAS,WAAAA,UAAS,mBAAmB;AAGrC,OAAO;AAEP,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,cAAc;","names":["default"]}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAQ5C,eAAO,MAAM,WAAW,EAAE,MA2BzB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { inboxAction } from "./actions/inbox.js";
|
|
2
|
+
import { inboxDbSchema } from "./db/schema.js";
|
|
3
|
+
import { InboxMigrationService } from "./inbox/migration.js";
|
|
4
|
+
import { crossChannelContextProvider } from "./providers/cross-channel-context.js";
|
|
5
|
+
import { inboxTriageProvider } from "./providers/inbox-triage.js";
|
|
6
|
+
const inboxPlugin = {
|
|
7
|
+
name: "@elizaos/plugin-inbox",
|
|
8
|
+
description: "Unified cross-channel inbox triage with unresolved-item tracking. Hosts the INBOX umbrella action (list/search/summarize fan-out across email/Discord/Telegram/WhatsApp/X/Slack and similar non-SMS channels) and the inboxTriage provider, backed by the InboxService/InboxRepository triage back-end. The cross-channel inbox read route (`GET /api/lifeops/inbox`) and the connector-coupled getInbox/cross-channel-context surfaces stay in @elizaos/plugin-personal-assistant, which delegates the triage domain here. (Android SMS is handled by plugin-messages.)",
|
|
9
|
+
dependencies: ["@elizaos/plugin-sql"],
|
|
10
|
+
schema: inboxDbSchema,
|
|
11
|
+
services: [InboxMigrationService],
|
|
12
|
+
actions: [inboxAction],
|
|
13
|
+
providers: [inboxTriageProvider, crossChannelContextProvider],
|
|
14
|
+
views: [
|
|
15
|
+
{
|
|
16
|
+
id: "inbox",
|
|
17
|
+
label: "Inbox",
|
|
18
|
+
description: "Cross-channel inbox triage",
|
|
19
|
+
icon: "Inbox",
|
|
20
|
+
path: "/inbox",
|
|
21
|
+
// ONE declaration → GUI + XR + TUI, all drawn from the single InboxView
|
|
22
|
+
// spatial source. `modalities` is a plain literal here (plugin.ts is not
|
|
23
|
+
// in the view bundle).
|
|
24
|
+
modalities: ["gui", "xr", "tui"],
|
|
25
|
+
bundlePath: "dist/views/bundle.js",
|
|
26
|
+
componentExport: "InboxView",
|
|
27
|
+
tags: ["inbox", "triage", "communication", "email", "mail", "messages"],
|
|
28
|
+
visibleInManager: true,
|
|
29
|
+
desktopTabEnabled: true
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
var plugin_default = inboxPlugin;
|
|
34
|
+
export {
|
|
35
|
+
plugin_default as default,
|
|
36
|
+
inboxPlugin
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Plugin } from \"@elizaos/core\";\n\nimport { inboxAction } from \"./actions/inbox.js\";\nimport { inboxDbSchema } from \"./db/schema.js\";\nimport { InboxMigrationService } from \"./inbox/migration.js\";\nimport { crossChannelContextProvider } from \"./providers/cross-channel-context.js\";\nimport { inboxTriageProvider } from \"./providers/inbox-triage.js\";\n\nexport const inboxPlugin: Plugin = {\n name: \"@elizaos/plugin-inbox\",\n description:\n \"Unified cross-channel inbox triage with unresolved-item tracking. Hosts the INBOX umbrella action (list/search/summarize fan-out across email/Discord/Telegram/WhatsApp/X/Slack and similar non-SMS channels) and the inboxTriage provider, backed by the InboxService/InboxRepository triage back-end. The cross-channel inbox read route (`GET /api/lifeops/inbox`) and the connector-coupled getInbox/cross-channel-context surfaces stay in @elizaos/plugin-personal-assistant, which delegates the triage domain here. (Android SMS is handled by plugin-messages.)\",\n dependencies: [\"@elizaos/plugin-sql\"],\n schema: inboxDbSchema,\n services: [InboxMigrationService],\n actions: [inboxAction],\n providers: [inboxTriageProvider, crossChannelContextProvider],\n views: [\n {\n id: \"inbox\",\n label: \"Inbox\",\n description: \"Cross-channel inbox triage\",\n icon: \"Inbox\",\n path: \"/inbox\",\n // ONE declaration → GUI + XR + TUI, all drawn from the single InboxView\n // spatial source. `modalities` is a plain literal here (plugin.ts is not\n // in the view bundle).\n modalities: [\"gui\", \"xr\", \"tui\"],\n bundlePath: \"dist/views/bundle.js\",\n componentExport: \"InboxView\",\n tags: [\"inbox\", \"triage\", \"communication\", \"email\", \"mail\", \"messages\"],\n visibleInManager: true,\n desktopTabEnabled: true,\n },\n ],\n};\n\nexport default inboxPlugin;\n"],"mappings":"AAEA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAC5C,SAAS,2BAA2B;AAE7B,MAAM,cAAsB;AAAA,EACjC,MAAM;AAAA,EACN,aACE;AAAA,EACF,cAAc,CAAC,qBAAqB;AAAA,EACpC,QAAQ;AAAA,EACR,UAAU,CAAC,qBAAqB;AAAA,EAChC,SAAS,CAAC,WAAW;AAAA,EACrB,WAAW,CAAC,qBAAqB,2BAA2B;AAAA,EAC5D,OAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA;AAAA;AAAA;AAAA,MAIN,YAAY,CAAC,OAAO,MAAM,KAAK;AAAA,MAC/B,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,MAAM,CAAC,SAAS,UAAU,iBAAiB,SAAS,QAAQ,UAAU;AAAA,MACtE,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,IACrB;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CROSS_CHANNEL_CONTEXT provider.
|
|
3
|
+
*
|
|
4
|
+
* When the owner is in a conversation, this injects any recent triage entries
|
|
5
|
+
* from the same sender across *other* channels — so if someone DM'd on Discord
|
|
6
|
+
* and also emailed, both threads surface in the same planner turn.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. Match triage entries by entityId (most precise — UUID identity).
|
|
10
|
+
* 2. Fall back to case-insensitive senderName substring match.
|
|
11
|
+
*
|
|
12
|
+
* Owner-only (same gate as inboxTriage). Silently returns empty when:
|
|
13
|
+
* - the caller is not the owner,
|
|
14
|
+
* - the runtime DB is unavailable,
|
|
15
|
+
* - no sender can be resolved from the current message,
|
|
16
|
+
* - or no cross-channel entries exist for that sender.
|
|
17
|
+
*/
|
|
18
|
+
import type { Provider } from "@elizaos/core";
|
|
19
|
+
export declare const crossChannelContextProvider: Provider;
|
|
20
|
+
export default crossChannelContextProvider;
|
|
21
|
+
//# sourceMappingURL=cross-channel-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-channel-context.d.ts","sourceRoot":"","sources":["../../src/providers/cross-channel-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAGV,QAAQ,EAGT,MAAM,eAAe,CAAC;AAgFvB,eAAO,MAAM,2BAA2B,EAAE,QAqEzC,CAAC;AAEF,eAAe,2BAA2B,CAAC"}
|