@holon-run/agentinbox 1.7.1 → 1.8.0

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.
@@ -11,6 +11,9 @@ const terminal_1 = require("./terminal");
11
11
  const runtime_gate_1 = require("./runtime_gate");
12
12
  const logging_1 = require("./logging");
13
13
  const source_hosts_1 = require("./source_hosts");
14
+ const email_body_1 = require("./email_body");
15
+ const email_attachment_1 = require("./email_attachment");
16
+ const email_attachment_content_1 = require("./email_attachment_content");
14
17
  const DEFAULT_SUBSCRIPTION_POLL_LIMIT = 100;
15
18
  const DEFAULT_ACTIVATION_WINDOW_MS = 3_000;
16
19
  const DEFAULT_ACTIVATION_MAX_ITEMS = 20;
@@ -51,7 +54,9 @@ class AgentInboxService {
51
54
  lastAckedInboxGcAt = 0;
52
55
  lastLifecycleCleanupAt = 0;
53
56
  lastOfflineAgentGcAt = 0;
54
- constructor(store, adapters, activationDispatcher = new ActivationDispatcher(), backend, activationPolicy, terminalDispatcher = new terminal_1.TerminalDispatcher(), activationGate, logger = new logging_1.NoopLogger(), gatingPolicy) {
57
+ emailBodyReader;
58
+ emailAttachmentContent;
59
+ constructor(store, adapters, activationDispatcher = new ActivationDispatcher(), backend, activationPolicy, terminalDispatcher = new terminal_1.TerminalDispatcher(), activationGate, logger = new logging_1.NoopLogger(), gatingPolicy, emailBodyUxcClient, emailAttachmentOptions) {
55
60
  this.store = store;
56
61
  this.adapters = adapters;
57
62
  this.activationDispatcher = activationDispatcher;
@@ -62,6 +67,8 @@ class AgentInboxService {
62
67
  this.terminalDispatcher = terminalDispatcher;
63
68
  this.logger = logger;
64
69
  this.activationGate = activationGate ?? new runtime_gate_1.DefaultActivationGate(undefined, undefined, this.logger.child("gate"), gatingPolicy);
70
+ this.emailBodyReader = new email_body_1.EmailBodyReader(store, emailBodyUxcClient);
71
+ this.emailAttachmentContent = new email_attachment_content_1.EmailAttachmentContentManager(store, `${store.getDataDirectory()}/attachments`, emailBodyUxcClient, emailAttachmentOptions);
65
72
  }
66
73
  activationDispatcher;
67
74
  terminalDispatcher;
@@ -634,6 +641,7 @@ class AgentInboxService {
634
641
  this.getAgent(agentId);
635
642
  const affectedSourceIds = Array.from(new Set(this.store.listSubscriptionsForAgent(agentId).map((subscription) => subscription.sourceId)));
636
643
  this.store.deleteAgent(agentId);
644
+ this.emailAttachmentContent.gc();
637
645
  for (const sourceId of affectedSourceIds) {
638
646
  this.refreshSourceIdleState(sourceId);
639
647
  }
@@ -1149,6 +1157,51 @@ class AgentInboxService {
1149
1157
  listInboxItems(agentId, options) {
1150
1158
  return this.store.listInboxEntries(this.ensureInboxForAgent(agentId).inboxId, options);
1151
1159
  }
1160
+ async readInboxEmailBody(agentId, entryId, options) {
1161
+ const inbox = this.store.getInboxByAgentId(agentId);
1162
+ if (!inbox) {
1163
+ throw new Error(`unknown inbox entry: ${entryId}`);
1164
+ }
1165
+ const entry = this.store.getInboxEntryForInbox(inbox.inboxId, entryId);
1166
+ if (!entry) {
1167
+ throw new Error(`unknown inbox entry: ${entryId}`);
1168
+ }
1169
+ if (entry.kind !== "item") {
1170
+ return {
1171
+ status: "unavailable",
1172
+ entryId: entry.entryId,
1173
+ code: "unsupported",
1174
+ retryable: false,
1175
+ message: "Digest entries do not represent one email body.",
1176
+ };
1177
+ }
1178
+ const source = entry.sourceId ? this.store.getSource(entry.sourceId) : null;
1179
+ if (!source) {
1180
+ return {
1181
+ status: "unavailable",
1182
+ entryId: entry.entryId,
1183
+ code: "source_unavailable",
1184
+ retryable: false,
1185
+ message: "The source for this inbox entry is no longer available.",
1186
+ };
1187
+ }
1188
+ return this.emailBodyReader.read(inbox.inboxId, entry, source, options);
1189
+ }
1190
+ getInboxEmailAttachment(agentId, attachmentRef) {
1191
+ return this.emailAttachmentContent.inspect(agentId, attachmentRef);
1192
+ }
1193
+ async materializeInboxEmailAttachment(agentId, attachmentRef) {
1194
+ return this.emailAttachmentContent.materialize(agentId, attachmentRef);
1195
+ }
1196
+ openInboxEmailAttachmentContent(agentId, attachmentRef) {
1197
+ return this.emailAttachmentContent.openContent(agentId, attachmentRef);
1198
+ }
1199
+ deleteInboxEmailAttachmentContent(agentId, attachmentRef) {
1200
+ return this.emailAttachmentContent.delete(agentId, attachmentRef);
1201
+ }
1202
+ projectPublicInboxEntry(value) {
1203
+ return (0, email_attachment_1.projectPublicInboxEntry)(value, (itemId, selector) => this.store.getEmailAttachmentMaterialization(itemId, selector));
1204
+ }
1152
1205
  listRawInboxItems(agentId, options) {
1153
1206
  return this.store.listInboxItems(this.ensureInboxForAgent(agentId).inboxId, options);
1154
1207
  }
@@ -1462,12 +1515,14 @@ class AgentInboxService {
1462
1515
  return {
1463
1516
  deleted: this.store.deleteAckedInboxItems(inbox.inboxId, retentionCutoffIso(this.ackedRetentionMs)),
1464
1517
  retentionMs: this.ackedRetentionMs,
1518
+ attachments: this.emailAttachmentContent.gc(),
1465
1519
  };
1466
1520
  }
1467
1521
  gcAckedInboxItems() {
1468
1522
  return {
1469
1523
  deleted: this.store.deleteAckedInboxItemsGlobal(retentionCutoffIso(this.ackedRetentionMs)),
1470
1524
  retentionMs: this.ackedRetentionMs,
1525
+ attachments: this.emailAttachmentContent.gc(),
1471
1526
  };
1472
1527
  }
1473
1528
  gc() {
@@ -2676,9 +2731,11 @@ class AgentInboxService {
2676
2731
  items: target.mode === "activation_with_items"
2677
2732
  ? input.entries
2678
2733
  .filter((entry) => entry.kind === "item")
2679
- .map((entry) => entry.item)
2734
+ .map((entry) => this.projectPublicInboxEntry(entry.item))
2735
+ : undefined,
2736
+ entries: target.mode === "activation_with_items" && input.entries.length > 0
2737
+ ? input.entries.map((entry) => this.projectPublicInboxEntry(entry))
2680
2738
  : undefined,
2681
- entries: target.mode === "activation_with_items" && input.entries.length > 0 ? input.entries : undefined,
2682
2739
  createdAt: (0, util_1.nowIso)(),
2683
2740
  deliveredAt: null,
2684
2741
  };
@@ -3075,6 +3132,7 @@ class AgentInboxService {
3075
3132
  }
3076
3133
  if (agents.length > 0) {
3077
3134
  this.store.save();
3135
+ this.emailAttachmentContent.gc();
3078
3136
  }
3079
3137
  if (agents.length > 0) {
3080
3138
  this.rescheduleTimerSync();
@@ -207,7 +207,8 @@ const SOURCE_SCHEMAS = {
207
207
  { name: "date", type: "string|null", description: "Message date header value." },
208
208
  { name: "hasAttachments", type: "boolean", description: "Whether the message carries attachments." },
209
209
  { name: "attachmentCount", type: "number|null", description: "Attachment count; null when the provider did not expand attachment metadata." },
210
- { name: "attachments", type: "object[]", description: "Attachment metadata entries with opaque retrieval handles; binary content is never embedded." },
210
+ { name: "attachmentsComplete", type: "boolean", description: "Whether the attachment metadata list is known to be complete." },
211
+ { name: "attachments", type: "object[]", description: "Safe attachment metadata with an opaque attachmentRef; provider retrieval handles and binary content are never exposed." },
211
212
  ],
212
213
  payloadExamples: [
213
214
  {
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EmailDeliveryAdapter = exports.RpcEmailUxcClient = exports.GRAPH_MESSAGES_ENDPOINT = exports.GMAIL_MESSAGES_ENDPOINT = exports.EMAIL_EVENT_VERSION = exports.EMAIL_EVENT_TYPE = exports.EMAIL_MAILBOX_MAX_INITIAL_FETCH_LIMIT = exports.EMAIL_MAILBOX_DEFAULT_INITIAL_FETCH_LIMIT = exports.EMAIL_MAILBOX_DEFAULT_POLL_INTERVAL_SECS = exports.EMAIL_DEFAULT_MAILBOX = void 0;
3
+ exports.EmailDeliveryAdapter = exports.RpcEmailUxcClient = exports.GRAPH_MESSAGES_ENDPOINT = exports.GMAIL_MESSAGES_ENDPOINT = exports.EMAIL_EVENT_VERSION = exports.EMAIL_EVENT_TYPE = exports.EMAIL_ATTACHMENT_DEFAULT_RETENTION_SECS = exports.EMAIL_ATTACHMENT_DEFAULT_MAX_MESSAGE_BYTES = exports.EMAIL_ATTACHMENT_DEFAULT_MAX_BYTES = exports.EMAIL_ATTACHMENT_DEFAULT_MAX_COUNT = exports.EMAIL_MAILBOX_MAX_INITIAL_FETCH_LIMIT = exports.EMAIL_MAILBOX_DEFAULT_INITIAL_FETCH_LIMIT = exports.EMAIL_MAILBOX_DEFAULT_POLL_INTERVAL_SECS = exports.EMAIL_DEFAULT_MAILBOX = void 0;
4
4
  exports.parseEmailMailboxSourceConfig = parseEmailMailboxSourceConfig;
5
+ exports.parseEmailAttachmentPolicy = parseEmailAttachmentPolicy;
5
6
  exports.buildEmailMailboxSourceSpec = buildEmailMailboxSourceSpec;
6
7
  exports.normalizeEmailMailboxEvent = normalizeEmailMailboxEvent;
7
8
  exports.emailDeliveryOperationsForHandle = emailDeliveryOperationsForHandle;
@@ -11,6 +12,10 @@ exports.EMAIL_DEFAULT_MAILBOX = "INBOX";
11
12
  exports.EMAIL_MAILBOX_DEFAULT_POLL_INTERVAL_SECS = 60;
12
13
  exports.EMAIL_MAILBOX_DEFAULT_INITIAL_FETCH_LIMIT = 25;
13
14
  exports.EMAIL_MAILBOX_MAX_INITIAL_FETCH_LIMIT = 100;
15
+ exports.EMAIL_ATTACHMENT_DEFAULT_MAX_COUNT = 20;
16
+ exports.EMAIL_ATTACHMENT_DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
17
+ exports.EMAIL_ATTACHMENT_DEFAULT_MAX_MESSAGE_BYTES = 50 * 1024 * 1024;
18
+ exports.EMAIL_ATTACHMENT_DEFAULT_RETENTION_SECS = 24 * 60 * 60;
14
19
  exports.EMAIL_EVENT_TYPE = "email_event";
15
20
  exports.EMAIL_EVENT_VERSION = "v1";
16
21
  exports.GMAIL_MESSAGES_ENDPOINT = "https://gmail.googleapis.com/gmail/v1/users/me/messages";
@@ -98,6 +103,7 @@ function parseEmailMailboxSourceConfig(source) {
98
103
  throw new Error("email_mailbox config.mailbox must be a non-empty string when provided");
99
104
  }
100
105
  const addressAllowlist = asStringArray(config.addressAllowlist ?? config.addressAllowList);
106
+ const attachmentPolicy = parseEmailAttachmentPolicy(config.attachmentPolicy ?? config.attachment_policy);
101
107
  return {
102
108
  provider: provider,
103
109
  endpoint,
@@ -109,6 +115,23 @@ function parseEmailMailboxSourceConfig(source) {
109
115
  ...(smtpEndpoint ? { smtpEndpoint } : {}),
110
116
  ...(fromAddress ? { fromAddress } : {}),
111
117
  ...(addressAllowlist && addressAllowlist.length > 0 ? { addressAllowlist } : {}),
118
+ attachmentPolicy,
119
+ };
120
+ }
121
+ function parseEmailAttachmentPolicy(value) {
122
+ const input = asRecord(value);
123
+ const mode = asString(input.mode) ?? "metadata";
124
+ if (mode !== "metadata" && mode !== "store_reference") {
125
+ throw new Error(`email_mailbox config.attachmentPolicy.mode must be metadata or store_reference; got ${mode}`);
126
+ }
127
+ return {
128
+ mode,
129
+ maxAttachmentsPerMessage: positiveIntegerOrDefault(input.maxAttachmentsPerMessage ?? input.max_attachments_per_message, exports.EMAIL_ATTACHMENT_DEFAULT_MAX_COUNT, "maxAttachmentsPerMessage"),
130
+ maxBytesPerAttachment: positiveIntegerOrDefault(input.maxBytesPerAttachment ?? input.max_bytes_per_attachment, exports.EMAIL_ATTACHMENT_DEFAULT_MAX_BYTES, "maxBytesPerAttachment"),
131
+ maxBytesPerMessage: positiveIntegerOrDefault(input.maxBytesPerMessage ?? input.max_bytes_per_message, exports.EMAIL_ATTACHMENT_DEFAULT_MAX_MESSAGE_BYTES, "maxBytesPerMessage"),
132
+ allowContentTypes: normalizedContentTypes(input.allowContentTypes ?? input.allow_content_types),
133
+ denyContentTypes: normalizedContentTypes(input.denyContentTypes ?? input.deny_content_types),
134
+ retentionSecs: positiveIntegerOrDefault(input.retentionSecs ?? input.retention_secs, exports.EMAIL_ATTACHMENT_DEFAULT_RETENTION_SECS, "retentionSecs"),
112
135
  };
113
136
  }
114
137
  function buildEmailMailboxSourceSpec(config) {
@@ -232,6 +255,7 @@ function normalizeEmailMailboxEvent(source, config, raw) {
232
255
  const mailbox = asString(payload.mailbox) ?? config.mailbox;
233
256
  const uid = stringFromUnknown(message.uid);
234
257
  const messageId = asString(message.message_id) ?? uid;
258
+ const stableKey = asString(message.stable_key);
235
259
  if (!uid && !messageId) {
236
260
  return null;
237
261
  }
@@ -247,10 +271,18 @@ function normalizeEmailMailboxEvent(source, config, raw) {
247
271
  : [];
248
272
  const attachmentCountRaw = message.attachment_count;
249
273
  const attachmentCount = numberFromUnknown(attachmentCountRaw) ?? null;
274
+ const hasAttachments = attachments.length > 0
275
+ || (attachmentCount !== null && attachmentCount > 0)
276
+ || message.has_attachments === true;
277
+ const attachmentsComplete = attachmentCount !== null
278
+ ? attachmentCount === attachments.length
279
+ : message.has_attachments === false && attachments.length === 0;
250
280
  const date = asString(message.date);
251
281
  return {
252
282
  sourceId: source.sourceId,
253
- sourceNativeId: `email:${provider}:${account}:${mailbox}:${uid ?? messageId}`,
283
+ sourceNativeId: stableKey
284
+ ? `email:${stableKey}`
285
+ : `email:${provider}:${account}:${mailbox}:${uid ?? messageId}`,
254
286
  eventVariant: "email.message.received",
255
287
  occurredAt: occurredAtFromEmailDate(date),
256
288
  metadata: {
@@ -267,8 +299,9 @@ function normalizeEmailMailboxEvent(source, config, raw) {
267
299
  subject: asString(message.subject),
268
300
  textPreview: asString(message.snippet),
269
301
  date,
270
- hasAttachments: attachments.length > 0 || message.has_attachments === true,
302
+ hasAttachments,
271
303
  attachmentCount,
304
+ attachmentsComplete,
272
305
  attachments,
273
306
  },
274
307
  rawPayload: payload,
@@ -552,3 +585,16 @@ function numberFromUnknown(value) {
552
585
  }
553
586
  return undefined;
554
587
  }
588
+ function positiveIntegerOrDefault(value, fallback, field) {
589
+ if (value === undefined || value === null) {
590
+ return fallback;
591
+ }
592
+ const parsed = numberFromUnknown(value);
593
+ if (parsed === undefined || !Number.isSafeInteger(parsed) || parsed < 1) {
594
+ throw new Error(`email_mailbox config.attachmentPolicy.${field} must be a positive integer; got ${String(value)}`);
595
+ }
596
+ return parsed;
597
+ }
598
+ function normalizedContentTypes(value) {
599
+ return [...new Set((asStringArray(value) ?? []).map((item) => item.trim().toLowerCase()))];
600
+ }
@@ -618,6 +618,7 @@ const TELEGRAM_BOT_MODULE = {
618
618
  },
619
619
  options: {
620
620
  auth: config.uxcAuth,
621
+ schema_url: telegram_1.TELEGRAM_BOT_SCHEMA_URL,
621
622
  artifact_compaction: false,
622
623
  },
623
624
  };
@@ -679,7 +680,8 @@ const EMAIL_MAILBOX_MODULE = {
679
680
  { name: "textPreview", type: "string|null", description: "Body snippet." },
680
681
  { name: "hasAttachments", type: "boolean", description: "Whether the message carries attachments." },
681
682
  { name: "attachmentCount", type: "number|null", description: "Attachment count; null when provider metadata was not expanded." },
682
- { name: "attachments", type: "object[]", description: "Attachment metadata with opaque retrieval handles." },
683
+ { name: "attachmentsComplete", type: "boolean", description: "Whether the attachment metadata list is known to be complete." },
684
+ { name: "attachments", type: "object[]", description: "Safe attachment metadata with an opaque attachmentRef; provider retrieval handles are not exposed." },
683
685
  ],
684
686
  eventVariantExamples: ["email.message.received"],
685
687
  };
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TelegramDeliveryAdapter = exports.TelegramBotApiClient = exports.TELEGRAM_BOT_API_ENDPOINT = void 0;
3
+ exports.TelegramDeliveryAdapter = exports.TelegramBotApiClient = exports.TELEGRAM_BOT_SCHEMA_URL = exports.TELEGRAM_BOT_API_ENDPOINT = void 0;
4
4
  exports.telegramDeliveryOperationsForHandle = telegramDeliveryOperationsForHandle;
5
5
  exports.invokeTelegramDeliveryOperation = invokeTelegramDeliveryOperation;
6
6
  exports.normalizeTelegramBotUpdate = normalizeTelegramBotUpdate;
7
7
  exports.parseTelegramSourceConfig = parseTelegramSourceConfig;
8
8
  exports.TELEGRAM_BOT_API_ENDPOINT = "https://api.telegram.org";
9
+ exports.TELEGRAM_BOT_SCHEMA_URL = "https://raw.githubusercontent.com/holon-run/uxc/main/skills/telegram-openapi-skill/references/telegram-bot.openapi.json";
9
10
  class TelegramBotApiClient {
10
11
  client;
11
12
  constructor(client = { fetch: (url, init) => fetch(url, init) }) {