@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.
Files changed (119) hide show
  1. package/dist/actions/inbox.d.ts +69 -0
  2. package/dist/actions/inbox.d.ts.map +1 -0
  3. package/dist/actions/inbox.js +345 -0
  4. package/dist/actions/inbox.js.map +1 -0
  5. package/dist/components/inbox/InboxSpatialView.d.ts +54 -0
  6. package/dist/components/inbox/InboxSpatialView.d.ts.map +1 -0
  7. package/dist/components/inbox/InboxSpatialView.js +171 -0
  8. package/dist/components/inbox/InboxSpatialView.js.map +1 -0
  9. package/dist/components/inbox/InboxView.d.ts +64 -0
  10. package/dist/components/inbox/InboxView.d.ts.map +1 -0
  11. package/dist/components/inbox/InboxView.js +169 -0
  12. package/dist/components/inbox/InboxView.js.map +1 -0
  13. package/dist/components/inbox/inbox-view-bundle.d.ts +2 -0
  14. package/dist/components/inbox/inbox-view-bundle.d.ts.map +1 -0
  15. package/dist/components/inbox/inbox-view-bundle.js +5 -0
  16. package/dist/components/inbox/inbox-view-bundle.js.map +1 -0
  17. package/dist/db/index.d.ts +3 -0
  18. package/dist/db/index.d.ts.map +1 -0
  19. package/dist/db/index.js +3 -0
  20. package/dist/db/index.js.map +1 -0
  21. package/dist/db/schema.d.ts +1729 -0
  22. package/dist/db/schema.d.ts.map +1 -0
  23. package/dist/db/schema.js +79 -0
  24. package/dist/db/schema.js.map +1 -0
  25. package/dist/db/sql.d.ts +32 -0
  26. package/dist/db/sql.d.ts.map +1 -0
  27. package/dist/db/sql.js +130 -0
  28. package/dist/db/sql.js.map +1 -0
  29. package/dist/inbox/channel-deep-links.d.ts +7 -0
  30. package/dist/inbox/channel-deep-links.d.ts.map +1 -0
  31. package/dist/inbox/channel-deep-links.js +97 -0
  32. package/dist/inbox/channel-deep-links.js.map +1 -0
  33. package/dist/inbox/config.d.ts +7 -0
  34. package/dist/inbox/config.d.ts.map +1 -0
  35. package/dist/inbox/config.js +61 -0
  36. package/dist/inbox/config.js.map +1 -0
  37. package/dist/inbox/email-curation.d.ts +174 -0
  38. package/dist/inbox/email-curation.d.ts.map +1 -0
  39. package/dist/inbox/email-curation.js +1056 -0
  40. package/dist/inbox/email-curation.js.map +1 -0
  41. package/dist/inbox/email-unsubscribe-types.d.ts +71 -0
  42. package/dist/inbox/email-unsubscribe-types.d.ts.map +1 -0
  43. package/dist/inbox/email-unsubscribe-types.js +1 -0
  44. package/dist/inbox/email-unsubscribe-types.js.map +1 -0
  45. package/dist/inbox/gmail-normalize.d.ts +99 -0
  46. package/dist/inbox/gmail-normalize.d.ts.map +1 -0
  47. package/dist/inbox/gmail-normalize.js +937 -0
  48. package/dist/inbox/gmail-normalize.js.map +1 -0
  49. package/dist/inbox/google-gmail-seam.d.ts +52 -0
  50. package/dist/inbox/google-gmail-seam.d.ts.map +1 -0
  51. package/dist/inbox/google-gmail-seam.js +263 -0
  52. package/dist/inbox/google-gmail-seam.js.map +1 -0
  53. package/dist/inbox/message-fetcher.d.ts +47 -0
  54. package/dist/inbox/message-fetcher.d.ts.map +1 -0
  55. package/dist/inbox/message-fetcher.js +461 -0
  56. package/dist/inbox/message-fetcher.js.map +1 -0
  57. package/dist/inbox/migration.d.ts +46 -0
  58. package/dist/inbox/migration.d.ts.map +1 -0
  59. package/dist/inbox/migration.js +114 -0
  60. package/dist/inbox/migration.js.map +1 -0
  61. package/dist/inbox/reflection.d.ts +40 -0
  62. package/dist/inbox/reflection.d.ts.map +1 -0
  63. package/dist/inbox/reflection.js +142 -0
  64. package/dist/inbox/reflection.js.map +1 -0
  65. package/dist/inbox/repository.d.ts +58 -0
  66. package/dist/inbox/repository.d.ts.map +1 -0
  67. package/dist/inbox/repository.js +376 -0
  68. package/dist/inbox/repository.js.map +1 -0
  69. package/dist/inbox/service.d.ts +149 -0
  70. package/dist/inbox/service.d.ts.map +1 -0
  71. package/dist/inbox/service.js +247 -0
  72. package/dist/inbox/service.js.map +1 -0
  73. package/dist/inbox/triage-classifier.d.ts +28 -0
  74. package/dist/inbox/triage-classifier.d.ts.map +1 -0
  75. package/dist/inbox/triage-classifier.js +306 -0
  76. package/dist/inbox/triage-classifier.js.map +1 -0
  77. package/dist/inbox/types.d.ts +124 -0
  78. package/dist/inbox/types.d.ts.map +1 -0
  79. package/dist/inbox/types.js +1 -0
  80. package/dist/inbox/types.js.map +1 -0
  81. package/dist/inbox/unsubscribe-repository.d.ts +14 -0
  82. package/dist/inbox/unsubscribe-repository.d.ts.map +1 -0
  83. package/dist/inbox/unsubscribe-repository.js +112 -0
  84. package/dist/inbox/unsubscribe-repository.js.map +1 -0
  85. package/dist/inbox/unsubscribe-service.d.ts +41 -0
  86. package/dist/inbox/unsubscribe-service.d.ts.map +1 -0
  87. package/dist/inbox/unsubscribe-service.js +351 -0
  88. package/dist/inbox/unsubscribe-service.js.map +1 -0
  89. package/dist/index.d.ts +20 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +70 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/plugin.d.ts +4 -0
  94. package/dist/plugin.d.ts.map +1 -0
  95. package/dist/plugin.js +38 -0
  96. package/dist/plugin.js.map +1 -0
  97. package/dist/providers/cross-channel-context.d.ts +21 -0
  98. package/dist/providers/cross-channel-context.d.ts.map +1 -0
  99. package/dist/providers/cross-channel-context.js +96 -0
  100. package/dist/providers/cross-channel-context.js.map +1 -0
  101. package/dist/providers/inbox-triage.d.ts +12 -0
  102. package/dist/providers/inbox-triage.d.ts.map +1 -0
  103. package/dist/providers/inbox-triage.js +98 -0
  104. package/dist/providers/inbox-triage.js.map +1 -0
  105. package/dist/register-terminal-view.d.ts +15 -0
  106. package/dist/register-terminal-view.d.ts.map +1 -0
  107. package/dist/register-terminal-view.js +21 -0
  108. package/dist/register-terminal-view.js.map +1 -0
  109. package/dist/register.d.ts +9 -0
  110. package/dist/register.d.ts.map +1 -0
  111. package/dist/register.js +5 -0
  112. package/dist/register.js.map +1 -0
  113. package/dist/types.d.ts +42 -0
  114. package/dist/types.d.ts.map +1 -0
  115. package/dist/types.js +25 -0
  116. package/dist/types.js.map +1 -0
  117. package/dist/views/bundle.js +315 -0
  118. package/dist/views/bundle.js.map +1 -0
  119. 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":[]}
@@ -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"]}
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "@elizaos/core";
2
+ export declare const inboxPlugin: Plugin;
3
+ export default inboxPlugin;
4
+ //# sourceMappingURL=plugin.d.ts.map
@@ -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"}