@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,461 @@
1
+ import {
2
+ expandConnectorSourceFilter,
3
+ normalizeConnectorSource
4
+ } from "@elizaos/shared";
5
+ import { buildDeepLink, resolveChannelName } from "./channel-deep-links.js";
6
+ const PUBLIC_CHANNEL_PARTICIPANT_THRESHOLD = 15;
7
+ const MAX_ROOMS_SCANNED = 200;
8
+ const THREAD_CONTEXT_LIMIT = 5;
9
+ const SNIPPET_MAX_LENGTH = 200;
10
+ const INTERNAL_URL = new URL("http://127.0.0.1/");
11
+ const PHONE_BACKED_SOURCES = /* @__PURE__ */ new Set([
12
+ "imessage",
13
+ "sms",
14
+ "bluebubbles",
15
+ "blooio",
16
+ "twilio",
17
+ "whatsapp"
18
+ ]);
19
+ async function fetchChatMessages(runtime, opts) {
20
+ const limit = opts.limit ?? 200;
21
+ const sourceTags = buildSourceFilter(opts.sources);
22
+ const sinceMs = parseOptionalTimestamp(opts.sinceIso, "sinceIso");
23
+ const allRoomIds = await runtime.getRoomsForParticipant(runtime.agentId);
24
+ if (allRoomIds.length === 0) return [];
25
+ const roomIds = allRoomIds.slice(0, MAX_ROOMS_SCANNED);
26
+ const rooms = await Promise.all(roomIds.map((id) => runtime.getRoom(id)));
27
+ const sourceRooms = [];
28
+ for (const room of rooms) {
29
+ if (!room) continue;
30
+ const roomSource = extractRoomSource(room);
31
+ if (sourceMatchesFilter(roomSource, sourceTags)) {
32
+ sourceRooms.push(room);
33
+ }
34
+ }
35
+ if (sourceRooms.length === 0) return [];
36
+ const sourceRoomIds = sourceRooms.map((r) => r.id);
37
+ const memories = await runtime.getMemoriesByRoomIds({
38
+ roomIds: sourceRoomIds,
39
+ tableName: "messages",
40
+ limit: limit * 3
41
+ // over-fetch for filtering
42
+ });
43
+ const filtered = memories.filter((m) => {
44
+ if (m.entityId === runtime.agentId) return false;
45
+ const src = extractMemorySource(m);
46
+ if (!sourceMatchesFilter(src, sourceTags)) return false;
47
+ const createdAt = parseRequiredTimestamp(
48
+ m.createdAt,
49
+ "chat memory createdAt"
50
+ );
51
+ if (sinceMs > 0 && createdAt < sinceMs) return false;
52
+ return true;
53
+ });
54
+ filtered.sort(
55
+ (a, b) => parseRequiredTimestamp(b.createdAt, "chat memory createdAt") - parseRequiredTimestamp(a.createdAt, "chat memory createdAt")
56
+ );
57
+ const roomMap = /* @__PURE__ */ new Map();
58
+ for (const room of sourceRooms) {
59
+ roomMap.set(room.id, room);
60
+ }
61
+ const worldIds = [
62
+ ...new Set(
63
+ sourceRooms.map((room) => room.worldId).filter((worldId) => Boolean(worldId))
64
+ )
65
+ ];
66
+ const worlds = await Promise.all(worldIds.map((id) => runtime.getWorld(id)));
67
+ const worldMap = /* @__PURE__ */ new Map();
68
+ for (const world of worlds) {
69
+ if (world) {
70
+ worldMap.set(world.id, world);
71
+ }
72
+ }
73
+ const messagesByRoom = /* @__PURE__ */ new Map();
74
+ for (const m of filtered) {
75
+ const roomId = requireNonEmptyString(m.roomId, "chat memory roomId");
76
+ const arr = messagesByRoom.get(roomId) ?? [];
77
+ arr.push(m);
78
+ messagesByRoom.set(roomId, arr);
79
+ }
80
+ const participantCountByRoom = /* @__PURE__ */ new Map();
81
+ await Promise.all(
82
+ sourceRooms.map(async (room) => {
83
+ const ids = await runtime.getParticipantsForRoom(room.id);
84
+ participantCountByRoom.set(room.id, ids.length);
85
+ })
86
+ );
87
+ const results = [];
88
+ for (const memory of filtered.slice(0, limit)) {
89
+ const memoryId = requireNonEmptyString(memory.id, "chat memory id");
90
+ const roomId = requireNonEmptyString(memory.roomId, "chat memory roomId");
91
+ const createdAt = parseRequiredTimestamp(
92
+ memory.createdAt,
93
+ "chat memory createdAt"
94
+ );
95
+ const room = roomMap.get(roomId);
96
+ const source = normalizeConnectorSource(extractMemorySource(memory) ?? "");
97
+ if (!source) continue;
98
+ const text = extractText(memory);
99
+ if (!text) continue;
100
+ const phoneIdentity = extractPhoneIdentity(memory, source);
101
+ const senderName = extractSenderName(memory) ?? "Unknown";
102
+ const participantCount = participantCountByRoom.get(roomId);
103
+ const chatType = classifyChatType(room, participantCount);
104
+ const channelType = chatType === "dm" ? "dm" : "group";
105
+ const channelName = resolveChannelName(source, room?.name, senderName);
106
+ const world = room?.worldId ? worldMap.get(room.worldId) : void 0;
107
+ const deepLink = buildDeepLink(source, {
108
+ messageId: memoryId,
109
+ roomMeta: metadataForRoom(room),
110
+ worldMeta: metadataForWorld(world)
111
+ });
112
+ const roomMessages = messagesByRoom.get(roomId) ?? [];
113
+ const threadMessages = roomMessages.filter(
114
+ (m) => m.id !== memoryId && parseRequiredTimestamp(m.createdAt, "chat memory createdAt") <= createdAt
115
+ ).slice(0, THREAD_CONTEXT_LIMIT).map((m) => {
116
+ const name = extractSenderName(m) ?? "Unknown";
117
+ return `${name}: ${extractText(m).slice(0, 100)}`;
118
+ });
119
+ results.push({
120
+ id: memoryId,
121
+ source,
122
+ roomId,
123
+ entityId: memory.entityId,
124
+ senderName,
125
+ channelName,
126
+ channelType,
127
+ text,
128
+ snippet: text.slice(0, SNIPPET_MAX_LENGTH),
129
+ timestamp: createdAt,
130
+ deepLink: deepLink ?? void 0,
131
+ threadMessages: threadMessages.length > 0 ? threadMessages : void 0,
132
+ threadId: roomId,
133
+ chatType,
134
+ participantCount,
135
+ ...phoneIdentity
136
+ });
137
+ }
138
+ return results;
139
+ }
140
+ function extractPhoneIdentity(memory, source) {
141
+ if (!PHONE_BACKED_SOURCES.has(source)) {
142
+ return {};
143
+ }
144
+ const metadata = asRecord(memory.metadata) ?? {};
145
+ const content = asRecord(memory.content) ?? {};
146
+ const nested = asRecord(metadata.imessage) ?? asRecord(metadata.bluebubbles) ?? asRecord(metadata.blooio) ?? asRecord(metadata.twilio) ?? asRecord(metadata.whatsapp);
147
+ const phoneNumber = firstString(
148
+ metadata.localPhoneNumber,
149
+ metadata.phoneNumber,
150
+ metadata.recipientPhoneNumber,
151
+ metadata.toPhoneNumber,
152
+ metadata.destinationCallerId,
153
+ content.localPhoneNumber,
154
+ content.phoneNumber,
155
+ nested?.localPhoneNumber,
156
+ nested?.phoneNumber,
157
+ nested?.recipientPhoneNumber,
158
+ nested?.toPhoneNumber,
159
+ nested?.destinationCallerId
160
+ );
161
+ const phoneAccountId = firstString(
162
+ metadata.phoneAccountId,
163
+ metadata.localPhoneAccountId,
164
+ nested?.phoneAccountId,
165
+ nested?.localPhoneAccountId,
166
+ phoneNumber,
167
+ metadata.accountId
168
+ );
169
+ const phoneAccountLabel = firstString(
170
+ metadata.phoneAccountLabel,
171
+ metadata.localPhoneAccountLabel,
172
+ nested?.phoneAccountLabel,
173
+ nested?.localPhoneAccountLabel,
174
+ phoneNumber,
175
+ phoneAccountId
176
+ );
177
+ return {
178
+ ...phoneAccountId ? { phoneAccountId } : {},
179
+ ...phoneAccountLabel ? { phoneAccountLabel } : {},
180
+ ...phoneNumber ? { phoneNumber } : {}
181
+ };
182
+ }
183
+ function asRecord(value) {
184
+ return value && typeof value === "object" ? value : void 0;
185
+ }
186
+ function firstString(...values) {
187
+ for (const value of values) {
188
+ if (typeof value !== "string") continue;
189
+ const trimmed = value.trim();
190
+ if (trimmed.length > 0) return trimmed;
191
+ }
192
+ return void 0;
193
+ }
194
+ async function fetchGmailMessages(source, opts) {
195
+ const status = await source.getGoogleConnectorStatus(INTERNAL_URL);
196
+ if (!status.connected) return [];
197
+ const capabilities = status.grantedCapabilities;
198
+ if (!capabilities.includes("google.gmail.triage")) return [];
199
+ const limit = opts.limit ?? 50;
200
+ const triageFeed = await source.getGmailTriage(
201
+ INTERNAL_URL,
202
+ opts.grantId ? { grantId: opts.grantId, maxResults: limit } : { maxResults: limit }
203
+ );
204
+ if (triageFeed.messages.length === 0) return [];
205
+ const sinceMs = parseOptionalTimestamp(opts.sinceIso, "sinceIso");
206
+ const results = [];
207
+ for (const msg of triageFeed.messages.slice(0, limit)) {
208
+ const messageId = requireNonEmptyString(msg.id, "Gmail message id");
209
+ const externalId = requireNonEmptyString(
210
+ msg.externalId,
211
+ "Gmail external message id"
212
+ );
213
+ const receivedMs = parseRequiredTimestamp(
214
+ msg.receivedAt,
215
+ "Gmail receivedAt"
216
+ );
217
+ if (sinceMs > 0 && receivedMs < sinceMs) continue;
218
+ const from = msg.from || msg.fromEmail || "Unknown sender";
219
+ const gmailAccountSegment = encodeURIComponent(msg.accountEmail ?? "0");
220
+ const gmailLink = msg.htmlLink ?? `https://mail.google.com/mail/u/${gmailAccountSegment}/#inbox/${externalId}`;
221
+ results.push({
222
+ id: messageId,
223
+ source: "gmail",
224
+ senderName: from,
225
+ senderEmail: msg.fromEmail ?? void 0,
226
+ channelName: `Email from ${from}`,
227
+ channelType: "dm",
228
+ text: msg.snippet || msg.subject || "",
229
+ snippet: (msg.snippet || msg.subject || "").slice(0, SNIPPET_MAX_LENGTH),
230
+ timestamp: receivedMs,
231
+ deepLink: gmailLink,
232
+ gmailMessageId: externalId,
233
+ gmailIsImportant: msg.isImportant,
234
+ gmailLikelyReplyNeeded: msg.likelyReplyNeeded,
235
+ threadId: msg.threadId,
236
+ chatType: "dm",
237
+ gmailAccountId: msg.grantId,
238
+ gmailAccountEmail: msg.accountEmail ?? void 0
239
+ });
240
+ }
241
+ return results;
242
+ }
243
+ async function fetchXDmMessages(source, opts) {
244
+ const status = await source.getXConnectorStatus();
245
+ if (!status.connected || !status.dmRead) return [];
246
+ const limit = opts.limit ?? 50;
247
+ await source.syncXDms({ limit });
248
+ const dms = await source.getXDms({ limit });
249
+ const sinceMs = parseOptionalTimestamp(opts.sinceIso, "sinceIso");
250
+ const results = [];
251
+ for (const dm of dms) {
252
+ if (!dm.isInbound) continue;
253
+ const receivedMs = parseRequiredTimestamp(dm.receivedAt, "X DM receivedAt");
254
+ if (sinceMs > 0 && receivedMs < sinceMs) continue;
255
+ const sender = dm.senderHandle ? `@${dm.senderHandle}` : dm.senderId;
256
+ const metadata = dm.metadata;
257
+ const participantIds = Array.isArray(metadata.participantIds) ? metadata.participantIds.filter(
258
+ (participantId2) => typeof participantId2 === "string"
259
+ ) : [];
260
+ const participantId = typeof metadata.participantId === "string" && metadata.participantId.trim() ? metadata.participantId.trim() : dm.senderId;
261
+ const isGroup = participantIds.length > 2;
262
+ const xParticipantCount = participantIds.length || (isGroup ? void 0 : 2);
263
+ results.push({
264
+ id: dm.id,
265
+ source: "x_dm",
266
+ entityId: participantId,
267
+ xConversationId: dm.conversationId,
268
+ xParticipantId: participantId,
269
+ senderName: sender || "X user",
270
+ channelName: isGroup ? "X group DM" : `X DM from ${sender || "unknown"}`,
271
+ channelType: isGroup ? "group" : "dm",
272
+ text: dm.text,
273
+ snippet: dm.text.slice(0, SNIPPET_MAX_LENGTH),
274
+ timestamp: receivedMs,
275
+ threadId: dm.conversationId,
276
+ chatType: isGroup ? "group" : "dm",
277
+ participantCount: xParticipantCount,
278
+ lastSeenAt: dm.readAt ?? void 0,
279
+ repliedAt: dm.repliedAt ?? void 0
280
+ });
281
+ }
282
+ return results;
283
+ }
284
+ async function fetchAllMessages(runtime, opts) {
285
+ const requestedSources = opts.sources ? buildSourceFilter(opts.sources) : null;
286
+ const includeGmail = opts.includeGmail !== false && sourceMatchesFilter("gmail", requestedSources);
287
+ const gmailMessagesPromise = includeGmail ? opts.gmailSource ? fetchGmailMessages(opts.gmailSource, {
288
+ sinceIso: opts.sinceIso,
289
+ limit: opts.limit,
290
+ grantId: opts.gmailGrantId
291
+ }) : Promise.reject(
292
+ new Error(
293
+ "fetchAllMessages requires gmailSource when Gmail is included"
294
+ )
295
+ ) : Promise.resolve([]);
296
+ const xDmMessagesPromise = opts.xDmSource && sourceMatchesFilter("x_dm", requestedSources) ? fetchXDmMessages(opts.xDmSource, {
297
+ sinceIso: opts.sinceIso,
298
+ limit: opts.limit
299
+ }) : Promise.resolve([]);
300
+ const chatSources = opts.sources?.filter((source) => {
301
+ const normalized = normalizeConnectorSource(source);
302
+ return normalized !== "gmail" && normalized !== "x_dm";
303
+ });
304
+ const chatMessagesPromise = chatSources && chatSources.length === 0 ? Promise.resolve([]) : fetchChatMessages(runtime, {
305
+ sources: chatSources,
306
+ sinceIso: opts.sinceIso,
307
+ limit: opts.limit
308
+ });
309
+ const [chatMessages, gmailMessages, xDmMessages] = await Promise.all([
310
+ chatMessagesPromise,
311
+ gmailMessagesPromise,
312
+ xDmMessagesPromise
313
+ ]);
314
+ const combined = [...chatMessages, ...gmailMessages, ...xDmMessages];
315
+ combined.sort((a, b) => b.timestamp - a.timestamp);
316
+ return opts.limit ? combined.slice(0, opts.limit) : combined;
317
+ }
318
+ function parseOptionalTimestamp(value, label) {
319
+ if (!value) return 0;
320
+ return parseRequiredTimestamp(value, label);
321
+ }
322
+ function parseRequiredTimestamp(value, label) {
323
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
324
+ if (Number.isFinite(parsed)) {
325
+ return parsed;
326
+ }
327
+ throw new Error(`[InboxMessageFetcher] invalid ${label}`);
328
+ }
329
+ function requireNonEmptyString(value, label) {
330
+ if (typeof value === "string" && value.trim().length > 0) {
331
+ return value;
332
+ }
333
+ throw new Error(`[InboxMessageFetcher] missing ${label}`);
334
+ }
335
+ function extractMemorySource(memory) {
336
+ const content = memory.content;
337
+ const source = content?.source;
338
+ if (typeof source !== "string") return null;
339
+ const trimmed = source.trim();
340
+ return trimmed.length > 0 ? trimmed : null;
341
+ }
342
+ function buildSourceFilter(sources) {
343
+ if (!sources) return null;
344
+ const expanded = expandConnectorSourceFilter(sources);
345
+ for (const source of sources) {
346
+ const raw = source.trim().toLowerCase();
347
+ if (!raw) continue;
348
+ expanded.add(raw);
349
+ const normalized = normalizeConnectorSource(raw);
350
+ if (normalized) {
351
+ expanded.add(normalized);
352
+ }
353
+ }
354
+ return expanded;
355
+ }
356
+ function sourceMatchesFilter(source, sourceTags) {
357
+ if (!source) return false;
358
+ if (!sourceTags) return true;
359
+ const raw = source.trim().toLowerCase();
360
+ if (!raw) return false;
361
+ const normalized = normalizeConnectorSource(raw);
362
+ return sourceTags.has(raw) || !!normalized && sourceTags.has(normalized);
363
+ }
364
+ function extractText(memory) {
365
+ const content = memory.content;
366
+ const text = content?.text;
367
+ return typeof text === "string" ? text : "";
368
+ }
369
+ function extractSenderName(memory) {
370
+ const meta = memory.metadata;
371
+ const entityName = meta?.entityName;
372
+ if (typeof entityName === "string" && entityName.length > 0) {
373
+ return entityName;
374
+ }
375
+ return null;
376
+ }
377
+ function metadataRecord(value) {
378
+ return value && typeof value === "object" ? value : {};
379
+ }
380
+ function metadataForRoom(room) {
381
+ if (!room) return {};
382
+ return {
383
+ ...metadataRecord(room.metadata),
384
+ roomId: room.id,
385
+ roomName: room.name,
386
+ serverId: room.serverId
387
+ };
388
+ }
389
+ function metadataForWorld(world) {
390
+ return world ? metadataRecord(world.metadata) : {};
391
+ }
392
+ function extractRoomSource(room) {
393
+ const source = room.source;
394
+ if (typeof source === "string" && source.trim().length > 0) {
395
+ return normalizeConnectorSource(source.trim());
396
+ }
397
+ return null;
398
+ }
399
+ const DIRECT_ROOM_TYPES = /* @__PURE__ */ new Set(["dm", "direct", "private", "voice_dm"]);
400
+ const GROUP_ROOM_TYPES = /* @__PURE__ */ new Set(["group", "voice_group"]);
401
+ const PUBLIC_ROOM_TYPES = /* @__PURE__ */ new Set([
402
+ "announcement_thread",
403
+ "broadcast",
404
+ "channel",
405
+ "feed",
406
+ "forum",
407
+ "guild",
408
+ "guild_forum",
409
+ "guild_news",
410
+ "guild_stage_voice",
411
+ "guild_text",
412
+ "guild_voice",
413
+ "news",
414
+ "private_thread",
415
+ "public",
416
+ "public_thread",
417
+ "stage",
418
+ "supergroup",
419
+ "text",
420
+ "thread",
421
+ "voice",
422
+ "world"
423
+ ]);
424
+ function normalizeRoomType(value) {
425
+ return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[\s-]+/g, "_");
426
+ }
427
+ function classifyRoomTypeValue(value) {
428
+ if (!value) return null;
429
+ const normalized = normalizeRoomType(value);
430
+ if (DIRECT_ROOM_TYPES.has(normalized)) return "dm";
431
+ if (GROUP_ROOM_TYPES.has(normalized)) return "group";
432
+ if (PUBLIC_ROOM_TYPES.has(normalized)) return "channel";
433
+ return null;
434
+ }
435
+ function readRoomMetadataString(room, key) {
436
+ const metadata = metadataRecord(room?.metadata);
437
+ const value = metadata[key];
438
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
439
+ }
440
+ function classifyChatType(room, participantCount) {
441
+ const roomType = typeof room?.type === "string" ? room.type.trim() : null;
442
+ const explicit = classifyRoomTypeValue(roomType) ?? classifyRoomTypeValue(readRoomMetadataString(room, "chatType")) ?? classifyRoomTypeValue(readRoomMetadataString(room, "roomType"));
443
+ if (explicit === "dm") {
444
+ return typeof participantCount === "number" && participantCount > 2 ? "group" : "dm";
445
+ }
446
+ if (explicit) return explicit;
447
+ if (typeof participantCount === "number" && participantCount > PUBLIC_CHANNEL_PARTICIPANT_THRESHOLD) {
448
+ return "channel";
449
+ }
450
+ if (typeof participantCount === "number" && participantCount > 2) {
451
+ return "group";
452
+ }
453
+ return "channel";
454
+ }
455
+ export {
456
+ fetchAllMessages,
457
+ fetchChatMessages,
458
+ fetchGmailMessages,
459
+ fetchXDmMessages
460
+ };
461
+ //# sourceMappingURL=message-fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/inbox/message-fetcher.ts"],"sourcesContent":["import type { IAgentRuntime, Memory, Room, UUID, World } from \"@elizaos/core\";\nimport {\n expandConnectorSourceFilter,\n type GetLifeOpsGmailTriageRequest,\n type LifeOpsGmailTriageFeed,\n type LifeOpsGoogleConnectorStatus,\n type LifeOpsXConnectorStatus,\n type LifeOpsXDm,\n normalizeConnectorSource,\n} from \"@elizaos/shared\";\nimport { buildDeepLink, resolveChannelName } from \"./channel-deep-links.js\";\nimport type { InboundMessage } from \"./types.js\";\n\n/**\n * Discord public channels are typically larger than DMs / threads. We use this\n * threshold to treat sufficiently-large groups as broadcast channels.\n */\nconst PUBLIC_CHANNEL_PARTICIPANT_THRESHOLD = 15;\n\nconst MAX_ROOMS_SCANNED = 200;\nconst THREAD_CONTEXT_LIMIT = 5;\nconst SNIPPET_MAX_LENGTH = 200;\nconst INTERNAL_URL = new URL(\"http://127.0.0.1/\");\n\nconst PHONE_BACKED_SOURCES = new Set([\n \"imessage\",\n \"sms\",\n \"bluebubbles\",\n \"blooio\",\n \"twilio\",\n \"whatsapp\",\n]);\n\nexport interface GmailInboxSource {\n getGoogleConnectorStatus(\n requestUrl: URL,\n ): Promise<LifeOpsGoogleConnectorStatus>;\n getGmailTriage(\n requestUrl: URL,\n request?: GetLifeOpsGmailTriageRequest,\n ): Promise<LifeOpsGmailTriageFeed>;\n}\n\nexport interface XDmInboxSource {\n getXConnectorStatus(): Promise<LifeOpsXConnectorStatus>;\n syncXDms(opts?: { limit?: number }): Promise<{ synced: number }>;\n getXDms(opts?: { limit?: number }): Promise<LifeOpsXDm[]>;\n}\n\nexport async function fetchChatMessages(\n runtime: IAgentRuntime,\n opts: {\n /** Only scan these sources. Defaults to all connector-tagged chat rooms. */\n sources?: string[];\n /** Only return messages newer than this ISO timestamp. */\n sinceIso?: string;\n /** Max messages to return. */\n limit?: number;\n },\n): Promise<InboundMessage[]> {\n const limit = opts.limit ?? 200;\n const sourceTags = buildSourceFilter(opts.sources);\n const sinceMs = parseOptionalTimestamp(opts.sinceIso, \"sinceIso\");\n\n const allRoomIds = await runtime.getRoomsForParticipant(runtime.agentId);\n if (allRoomIds.length === 0) return [];\n\n const roomIds = allRoomIds.slice(0, MAX_ROOMS_SCANNED) as UUID[];\n const rooms = await Promise.all(roomIds.map((id) => runtime.getRoom(id)));\n const sourceRooms: Room[] = [];\n for (const room of rooms) {\n if (!room) continue;\n const roomSource = extractRoomSource(room);\n if (sourceMatchesFilter(roomSource, sourceTags)) {\n sourceRooms.push(room);\n }\n }\n\n if (sourceRooms.length === 0) return [];\n\n const sourceRoomIds = sourceRooms.map((r) => r.id) as UUID[];\n const memories = await runtime.getMemoriesByRoomIds({\n roomIds: sourceRoomIds,\n tableName: \"messages\",\n limit: limit * 3, // over-fetch for filtering\n });\n\n const filtered = memories.filter((m) => {\n if (m.entityId === runtime.agentId) return false;\n const src = extractMemorySource(m);\n if (!sourceMatchesFilter(src, sourceTags)) return false;\n const createdAt = parseRequiredTimestamp(\n m.createdAt,\n \"chat memory createdAt\",\n );\n if (sinceMs > 0 && createdAt < sinceMs) return false;\n return true;\n });\n\n filtered.sort(\n (a, b) =>\n parseRequiredTimestamp(b.createdAt, \"chat memory createdAt\") -\n parseRequiredTimestamp(a.createdAt, \"chat memory createdAt\"),\n );\n\n const roomMap = new Map<string, Room>();\n for (const room of sourceRooms) {\n roomMap.set(room.id, room);\n }\n\n const worldIds = [\n ...new Set(\n sourceRooms\n .map((room) => room.worldId)\n .filter((worldId): worldId is UUID => Boolean(worldId)),\n ),\n ];\n const worlds = await Promise.all(worldIds.map((id) => runtime.getWorld(id)));\n const worldMap = new Map<string, World>();\n for (const world of worlds) {\n if (world) {\n worldMap.set(world.id, world);\n }\n }\n\n const messagesByRoom = new Map<string, typeof filtered>();\n for (const m of filtered) {\n const roomId = requireNonEmptyString(m.roomId, \"chat memory roomId\");\n const arr = messagesByRoom.get(roomId) ?? [];\n arr.push(m);\n messagesByRoom.set(roomId, arr);\n }\n\n // Fetch participant counts per room exactly once. Used to classify DMs,\n // group DMs, and public channels without letting unknown rooms default to DM.\n const participantCountByRoom = new Map<string, number>();\n await Promise.all(\n sourceRooms.map(async (room) => {\n const ids = await runtime.getParticipantsForRoom(room.id);\n participantCountByRoom.set(room.id, ids.length);\n }),\n );\n\n const results: InboundMessage[] = [];\n for (const memory of filtered.slice(0, limit)) {\n const memoryId = requireNonEmptyString(memory.id, \"chat memory id\");\n const roomId = requireNonEmptyString(memory.roomId, \"chat memory roomId\");\n const createdAt = parseRequiredTimestamp(\n memory.createdAt,\n \"chat memory createdAt\",\n );\n const room = roomMap.get(roomId);\n const source = normalizeConnectorSource(extractMemorySource(memory) ?? \"\");\n if (!source) continue;\n const text = extractText(memory);\n if (!text) continue;\n const phoneIdentity = extractPhoneIdentity(memory, source);\n\n const senderName = extractSenderName(memory) ?? \"Unknown\";\n const participantCount = participantCountByRoom.get(roomId);\n const chatType = classifyChatType(room, participantCount);\n const channelType = chatType === \"dm\" ? \"dm\" : \"group\";\n const channelName = resolveChannelName(source, room?.name, senderName);\n const world = room?.worldId ? worldMap.get(room.worldId) : undefined;\n const deepLink = buildDeepLink(source, {\n messageId: memoryId,\n roomMeta: metadataForRoom(room),\n worldMeta: metadataForWorld(world),\n });\n\n const roomMessages = messagesByRoom.get(roomId) ?? [];\n const threadMessages = roomMessages\n .filter(\n (m) =>\n m.id !== memoryId &&\n parseRequiredTimestamp(m.createdAt, \"chat memory createdAt\") <=\n createdAt,\n )\n .slice(0, THREAD_CONTEXT_LIMIT)\n .map((m) => {\n const name = extractSenderName(m) ?? \"Unknown\";\n return `${name}: ${extractText(m).slice(0, 100)}`;\n });\n\n results.push({\n id: memoryId,\n source,\n roomId,\n entityId: memory.entityId,\n senderName,\n channelName,\n channelType,\n text,\n snippet: text.slice(0, SNIPPET_MAX_LENGTH),\n timestamp: createdAt,\n deepLink: deepLink ?? undefined,\n threadMessages: threadMessages.length > 0 ? threadMessages : undefined,\n threadId: roomId,\n chatType,\n participantCount,\n ...phoneIdentity,\n });\n }\n\n return results;\n}\n\nfunction extractPhoneIdentity(\n memory: Memory,\n source: string,\n): Pick<\n InboundMessage,\n \"phoneAccountId\" | \"phoneAccountLabel\" | \"phoneNumber\"\n> {\n if (!PHONE_BACKED_SOURCES.has(source)) {\n return {};\n }\n const metadata = asRecord(memory.metadata) ?? {};\n const content = asRecord(memory.content) ?? {};\n const nested =\n asRecord(metadata.imessage) ??\n asRecord(metadata.bluebubbles) ??\n asRecord(metadata.blooio) ??\n asRecord(metadata.twilio) ??\n asRecord(metadata.whatsapp);\n\n const phoneNumber = firstString(\n metadata.localPhoneNumber,\n metadata.phoneNumber,\n metadata.recipientPhoneNumber,\n metadata.toPhoneNumber,\n metadata.destinationCallerId,\n content.localPhoneNumber,\n content.phoneNumber,\n nested?.localPhoneNumber,\n nested?.phoneNumber,\n nested?.recipientPhoneNumber,\n nested?.toPhoneNumber,\n nested?.destinationCallerId,\n );\n const phoneAccountId = firstString(\n metadata.phoneAccountId,\n metadata.localPhoneAccountId,\n nested?.phoneAccountId,\n nested?.localPhoneAccountId,\n phoneNumber,\n metadata.accountId,\n );\n const phoneAccountLabel = firstString(\n metadata.phoneAccountLabel,\n metadata.localPhoneAccountLabel,\n nested?.phoneAccountLabel,\n nested?.localPhoneAccountLabel,\n phoneNumber,\n phoneAccountId,\n );\n\n return {\n ...(phoneAccountId ? { phoneAccountId } : {}),\n ...(phoneAccountLabel ? { phoneAccountLabel } : {}),\n ...(phoneNumber ? { phoneNumber } : {}),\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\"\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction firstString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value !== \"string\") continue;\n const trimmed = value.trim();\n if (trimmed.length > 0) return trimmed;\n }\n return undefined;\n}\n\nexport async function fetchGmailMessages(\n source: GmailInboxSource,\n opts: {\n sinceIso?: string;\n limit?: number;\n /** Filter to a single Gmail account by Google grant id. */\n grantId?: string;\n },\n): Promise<InboundMessage[]> {\n const status = await source.getGoogleConnectorStatus(INTERNAL_URL);\n if (!status.connected) return [];\n const capabilities = status.grantedCapabilities;\n if (!capabilities.includes(\"google.gmail.triage\")) return [];\n\n const limit = opts.limit ?? 50;\n\n // When no grantId is supplied, the service-side getGmailTriage already\n // aggregates across every Google grant and tags each summary with grantId\n // and accountEmail. We forward those onto the InboundMessage so the inbox\n // mixin can group by account and render account chips.\n const triageFeed = await source.getGmailTriage(\n INTERNAL_URL,\n opts.grantId\n ? { grantId: opts.grantId, maxResults: limit }\n : { maxResults: limit },\n );\n if (triageFeed.messages.length === 0) return [];\n\n const sinceMs = parseOptionalTimestamp(opts.sinceIso, \"sinceIso\");\n\n const results: InboundMessage[] = [];\n for (const msg of triageFeed.messages.slice(0, limit)) {\n const messageId = requireNonEmptyString(msg.id, \"Gmail message id\");\n const externalId = requireNonEmptyString(\n msg.externalId,\n \"Gmail external message id\",\n );\n const receivedMs = parseRequiredTimestamp(\n msg.receivedAt,\n \"Gmail receivedAt\",\n );\n if (sinceMs > 0 && receivedMs < sinceMs) continue;\n\n const from = msg.from || msg.fromEmail || \"Unknown sender\";\n const gmailAccountSegment = encodeURIComponent(msg.accountEmail ?? \"0\");\n const gmailLink =\n msg.htmlLink ??\n `https://mail.google.com/mail/u/${gmailAccountSegment}/#inbox/${externalId}`;\n\n results.push({\n id: messageId,\n source: \"gmail\",\n senderName: from,\n senderEmail: msg.fromEmail ?? undefined,\n channelName: `Email from ${from}`,\n channelType: \"dm\",\n text: msg.snippet || msg.subject || \"\",\n snippet: (msg.snippet || msg.subject || \"\").slice(0, SNIPPET_MAX_LENGTH),\n timestamp: receivedMs,\n deepLink: gmailLink,\n gmailMessageId: externalId,\n gmailIsImportant: msg.isImportant,\n gmailLikelyReplyNeeded: msg.likelyReplyNeeded,\n threadId: msg.threadId,\n chatType: \"dm\",\n gmailAccountId: msg.grantId,\n gmailAccountEmail: msg.accountEmail ?? undefined,\n });\n }\n\n return results;\n}\n\nexport async function fetchXDmMessages(\n source: XDmInboxSource,\n opts: {\n sinceIso?: string;\n limit?: number;\n },\n): Promise<InboundMessage[]> {\n const status = await source.getXConnectorStatus();\n if (!status.connected || !status.dmRead) return [];\n\n const limit = opts.limit ?? 50;\n await source.syncXDms({ limit });\n const dms = await source.getXDms({ limit });\n const sinceMs = parseOptionalTimestamp(opts.sinceIso, \"sinceIso\");\n const results: InboundMessage[] = [];\n\n for (const dm of dms) {\n if (!dm.isInbound) continue;\n const receivedMs = parseRequiredTimestamp(dm.receivedAt, \"X DM receivedAt\");\n if (sinceMs > 0 && receivedMs < sinceMs) continue;\n const sender = dm.senderHandle ? `@${dm.senderHandle}` : dm.senderId;\n const metadata = dm.metadata;\n const participantIds = Array.isArray(metadata.participantIds)\n ? metadata.participantIds.filter(\n (participantId): participantId is string =>\n typeof participantId === \"string\",\n )\n : [];\n const participantId =\n typeof metadata.participantId === \"string\" &&\n metadata.participantId.trim()\n ? metadata.participantId.trim()\n : dm.senderId;\n const isGroup = participantIds.length > 2;\n const xParticipantCount =\n participantIds.length || (isGroup ? undefined : 2);\n results.push({\n id: dm.id,\n source: \"x_dm\",\n entityId: participantId,\n xConversationId: dm.conversationId,\n xParticipantId: participantId,\n senderName: sender || \"X user\",\n channelName: isGroup ? \"X group DM\" : `X DM from ${sender || \"unknown\"}`,\n channelType: isGroup ? \"group\" : \"dm\",\n text: dm.text,\n snippet: dm.text.slice(0, SNIPPET_MAX_LENGTH),\n timestamp: receivedMs,\n threadId: dm.conversationId,\n chatType: isGroup ? \"group\" : \"dm\",\n participantCount: xParticipantCount,\n lastSeenAt: dm.readAt ?? undefined,\n repliedAt: dm.repliedAt ?? undefined,\n });\n }\n\n return results;\n}\n\nexport async function fetchAllMessages(\n runtime: IAgentRuntime,\n opts: {\n sources?: string[];\n sinceIso?: string;\n limit?: number;\n includeGmail?: boolean;\n gmailSource?: GmailInboxSource;\n xDmSource?: XDmInboxSource;\n /** Filter Gmail to a single account by Google grant id. */\n gmailGrantId?: string;\n },\n): Promise<InboundMessage[]> {\n const requestedSources = opts.sources\n ? buildSourceFilter(opts.sources)\n : null;\n const includeGmail =\n opts.includeGmail !== false &&\n sourceMatchesFilter(\"gmail\", requestedSources);\n const gmailMessagesPromise = includeGmail\n ? opts.gmailSource\n ? fetchGmailMessages(opts.gmailSource, {\n sinceIso: opts.sinceIso,\n limit: opts.limit,\n grantId: opts.gmailGrantId,\n })\n : Promise.reject(\n new Error(\n \"fetchAllMessages requires gmailSource when Gmail is included\",\n ),\n )\n : Promise.resolve([]);\n const xDmMessagesPromise =\n opts.xDmSource && sourceMatchesFilter(\"x_dm\", requestedSources)\n ? fetchXDmMessages(opts.xDmSource, {\n sinceIso: opts.sinceIso,\n limit: opts.limit,\n })\n : Promise.resolve([]);\n const chatSources = opts.sources?.filter((source) => {\n const normalized = normalizeConnectorSource(source);\n return normalized !== \"gmail\" && normalized !== \"x_dm\";\n });\n const chatMessagesPromise =\n chatSources && chatSources.length === 0\n ? Promise.resolve([])\n : fetchChatMessages(runtime, {\n sources: chatSources,\n sinceIso: opts.sinceIso,\n limit: opts.limit,\n });\n\n const [chatMessages, gmailMessages, xDmMessages] = await Promise.all([\n chatMessagesPromise,\n gmailMessagesPromise,\n xDmMessagesPromise,\n ]);\n\n const combined = [...chatMessages, ...gmailMessages, ...xDmMessages];\n combined.sort((a, b) => b.timestamp - a.timestamp);\n return opts.limit ? combined.slice(0, opts.limit) : combined;\n}\n\nfunction parseOptionalTimestamp(\n value: string | undefined,\n label: string,\n): number {\n if (!value) return 0;\n return parseRequiredTimestamp(value, label);\n}\n\nfunction parseRequiredTimestamp(value: unknown, label: string): number {\n const parsed =\n typeof value === \"number\"\n ? value\n : typeof value === \"string\"\n ? Date.parse(value)\n : Number.NaN;\n if (Number.isFinite(parsed)) {\n return parsed;\n }\n throw new Error(`[InboxMessageFetcher] invalid ${label}`);\n}\n\nfunction requireNonEmptyString(value: unknown, label: string): string {\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value;\n }\n throw new Error(`[InboxMessageFetcher] missing ${label}`);\n}\n\nfunction extractMemorySource(memory: Memory): string | null {\n const content = memory.content as { source?: unknown } | undefined;\n const source = content?.source;\n if (typeof source !== \"string\") return null;\n const trimmed = source.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction buildSourceFilter(\n sources: readonly string[] | undefined,\n): Set<string> | null {\n if (!sources) return null;\n const expanded = expandConnectorSourceFilter(sources);\n for (const source of sources) {\n const raw = source.trim().toLowerCase();\n if (!raw) continue;\n expanded.add(raw);\n const normalized = normalizeConnectorSource(raw);\n if (normalized) {\n expanded.add(normalized);\n }\n }\n return expanded;\n}\n\nfunction sourceMatchesFilter(\n source: string | null,\n sourceTags: ReadonlySet<string> | null,\n): source is string {\n if (!source) return false;\n if (!sourceTags) return true;\n const raw = source.trim().toLowerCase();\n if (!raw) return false;\n const normalized = normalizeConnectorSource(raw);\n return sourceTags.has(raw) || (!!normalized && sourceTags.has(normalized));\n}\n\nfunction extractText(memory: Memory): string {\n const content = memory.content as { text?: unknown } | undefined;\n const text = content?.text;\n return typeof text === \"string\" ? text : \"\";\n}\n\nfunction extractSenderName(memory: Memory): string | null {\n const meta = memory.metadata as Record<string, unknown> | undefined;\n const entityName = meta?.entityName;\n if (typeof entityName === \"string\" && entityName.length > 0) {\n return entityName;\n }\n return null;\n}\n\nfunction metadataRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\"\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction metadataForRoom(room: Room | undefined): Record<string, unknown> {\n if (!room) return {};\n return {\n ...metadataRecord(room.metadata),\n roomId: room.id,\n roomName: room.name,\n serverId: room.serverId,\n };\n}\n\nfunction metadataForWorld(world: World | undefined): Record<string, unknown> {\n return world ? metadataRecord(world.metadata) : {};\n}\n\nfunction extractRoomSource(room: Room): string | null {\n const source = (room as Room & { source?: unknown }).source;\n if (typeof source === \"string\" && source.trim().length > 0) {\n return normalizeConnectorSource(source.trim());\n }\n return null;\n}\n\ntype ChatClassification = \"dm\" | \"group\" | \"channel\";\n\nconst DIRECT_ROOM_TYPES = new Set([\"dm\", \"direct\", \"private\", \"voice_dm\"]);\nconst GROUP_ROOM_TYPES = new Set([\"group\", \"voice_group\"]);\nconst PUBLIC_ROOM_TYPES = new Set([\n \"announcement_thread\",\n \"broadcast\",\n \"channel\",\n \"feed\",\n \"forum\",\n \"guild\",\n \"guild_forum\",\n \"guild_news\",\n \"guild_stage_voice\",\n \"guild_text\",\n \"guild_voice\",\n \"news\",\n \"private_thread\",\n \"public\",\n \"public_thread\",\n \"stage\",\n \"supergroup\",\n \"text\",\n \"thread\",\n \"voice\",\n \"world\",\n]);\n\nfunction normalizeRoomType(value: string): string {\n return value\n .trim()\n .replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .toLowerCase()\n .replace(/[\\s-]+/g, \"_\");\n}\n\nfunction classifyRoomTypeValue(\n value: string | null,\n): ChatClassification | null {\n if (!value) return null;\n const normalized = normalizeRoomType(value);\n if (DIRECT_ROOM_TYPES.has(normalized)) return \"dm\";\n if (GROUP_ROOM_TYPES.has(normalized)) return \"group\";\n if (PUBLIC_ROOM_TYPES.has(normalized)) return \"channel\";\n return null;\n}\n\nfunction readRoomMetadataString(\n room: Room | undefined,\n key: string,\n): string | null {\n const metadata = metadataRecord(room?.metadata);\n const value = metadata[key];\n return typeof value === \"string\" && value.trim().length > 0\n ? value.trim()\n : null;\n}\n\n/**\n * Classify a room as DM, small group, or public channel/broadcast.\n * Discord text channels (`GUILD_TEXT`, `voice`, etc.) are treated as channels.\n * Anything with more than {@link PUBLIC_CHANNEL_PARTICIPANT_THRESHOLD}\n * participants is also treated as a channel so the inbox can hide it.\n */\nfunction classifyChatType(\n room: Room | undefined,\n participantCount: number | undefined,\n): ChatClassification {\n const roomType = typeof room?.type === \"string\" ? room.type.trim() : null;\n const explicit =\n classifyRoomTypeValue(roomType) ??\n classifyRoomTypeValue(readRoomMetadataString(room, \"chatType\")) ??\n classifyRoomTypeValue(readRoomMetadataString(room, \"roomType\"));\n\n if (explicit === \"dm\") {\n return typeof participantCount === \"number\" && participantCount > 2\n ? \"group\"\n : \"dm\";\n }\n if (explicit) return explicit;\n\n if (\n typeof participantCount === \"number\" &&\n participantCount > PUBLIC_CHANNEL_PARTICIPANT_THRESHOLD\n ) {\n return \"channel\";\n }\n if (typeof participantCount === \"number\" && participantCount > 2) {\n return \"group\";\n }\n return \"channel\";\n}\n"],"mappings":"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","names":["participantId"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Non-destructive data migration for the inbox-triage tables carved out of
3
+ * @elizaos/plugin-personal-assistant.
4
+ *
5
+ * The three tables (`life_inbox_triage_entries`, `life_inbox_triage_examples`,
6
+ * `life_email_unsubscribes`) used to live in the `app_lifeops` PostgreSQL
7
+ * schema, created by plugin-personal-assistant. They now live in `app_inbox`,
8
+ * created by this plugin's drizzle schema. Existing installs still hold the
9
+ * owner's triage rows in `app_lifeops`, so on first boot we copy them across —
10
+ * once, idempotently, and WITHOUT ever touching the source.
11
+ *
12
+ * Guards (per table, independently):
13
+ * 1. Skip if the source table does not exist (fresh install / already dropped).
14
+ * 2. Skip if the target table is non-empty (migration already ran, or the
15
+ * plugin owns live data).
16
+ * 3. Otherwise copy every source row that is not already present in the target
17
+ * (a doubly-safe NOT EXISTS guard on the primary key).
18
+ *
19
+ * The source table is NEVER dropped or altered. The source and target share the
20
+ * exact column shape (PA's `app_lifeops` drizzle def and this plugin's
21
+ * `app_inbox` def are column-identical), so the `SELECT s.*` copy is safe.
22
+ */
23
+ import { type IAgentRuntime, Service } from "@elizaos/core";
24
+ export declare const INBOX_MIGRATION_LOG_PREFIX = "[Inbox]";
25
+ export declare const INBOX_MIGRATION_SERVICE_TYPE = "inbox_migration";
26
+ export declare const MIGRATED_INBOX_TABLES: readonly ["life_inbox_triage_entries", "life_inbox_triage_examples", "life_email_unsubscribes"];
27
+ export type MigratedInboxTable = (typeof MIGRATED_INBOX_TABLES)[number];
28
+ export type SqlExecutor = (sql: string) => Promise<Array<Record<string, unknown>>>;
29
+ export interface TableMigrationResult {
30
+ table: MigratedInboxTable;
31
+ outcome: "copied" | "source-missing" | "target-non-empty";
32
+ }
33
+ export declare function migrateInboxTable(exec: SqlExecutor, table: MigratedInboxTable): Promise<TableMigrationResult>;
34
+ export declare function migrateInboxTables(exec: SqlExecutor): Promise<TableMigrationResult[]>;
35
+ /**
36
+ * Service whose `start()` performs the one-time, guarded, non-destructive copy
37
+ * of the owner's inbox-triage rows from `app_lifeops` into `app_inbox`.
38
+ */
39
+ export declare class InboxMigrationService extends Service {
40
+ static readonly serviceType = "inbox_migration";
41
+ capabilityDescription: string;
42
+ static start(runtime: IAgentRuntime): Promise<InboxMigrationService>;
43
+ private run;
44
+ stop(): Promise<void>;
45
+ }
46
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/inbox/migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAU,OAAO,EAAE,MAAM,eAAe,CAAC;AAEpE,eAAO,MAAM,0BAA0B,YAAY,CAAC;AACpD,eAAO,MAAM,4BAA4B,oBAAoB,CAAC;AAK9D,eAAO,MAAM,qBAAqB,iGAIxB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,MAAM,KACR,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,QAAQ,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;CAC3D;AA0BD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAkB/B;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAOjC;AAmCD;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,OAAO;IAChD,gBAAyB,WAAW,qBAAgC;IAE3D,qBAAqB,SAC4F;WAE7G,KAAK,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,qBAAqB,CAAC;YAM5D,GAAG;IAqBF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CACrC"}