@holon-run/agentinbox 1.6.0 → 1.7.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.
- package/dist/src/adapters.js +13 -0
- package/dist/src/service.js +17 -1
- package/dist/src/source_hosts.js +16 -0
- package/dist/src/source_schema.js +51 -0
- package/dist/src/sources/email.js +532 -0
- package/dist/src/sources/remote.js +1 -0
- package/dist/src/sources/remote_modules.js +76 -2
- package/package.json +1 -1
package/dist/src/adapters.js
CHANGED
|
@@ -5,6 +5,7 @@ const paths_1 = require("./paths");
|
|
|
5
5
|
const feishu_1 = require("./sources/feishu");
|
|
6
6
|
const github_1 = require("./sources/github");
|
|
7
7
|
const telegram_1 = require("./sources/telegram");
|
|
8
|
+
const email_1 = require("./sources/email");
|
|
8
9
|
const remote_1 = require("./sources/remote");
|
|
9
10
|
const remote_modules_1 = require("./sources/remote_modules");
|
|
10
11
|
const source_resolution_1 = require("./source_resolution");
|
|
@@ -42,6 +43,7 @@ class AdapterRegistry {
|
|
|
42
43
|
feishuDelivery;
|
|
43
44
|
githubDelivery = new github_1.GithubDeliveryAdapter();
|
|
44
45
|
telegramDelivery;
|
|
46
|
+
emailDelivery;
|
|
45
47
|
homeDir;
|
|
46
48
|
remoteModuleRegistry;
|
|
47
49
|
constructor(store, appendSourceEvent, options) {
|
|
@@ -51,6 +53,7 @@ class AdapterRegistry {
|
|
|
51
53
|
});
|
|
52
54
|
this.feishuDelivery = new feishu_1.FeishuDeliveryAdapter(options?.feishuClient);
|
|
53
55
|
this.telegramDelivery = new telegram_1.TelegramDeliveryAdapter(options?.telegramClient);
|
|
56
|
+
this.emailDelivery = new email_1.EmailDeliveryAdapter(options?.emailClient);
|
|
54
57
|
this.remoteSource = new remote_1.RemoteSourceRuntime(store, appendSourceEvent, {
|
|
55
58
|
homeDir: this.homeDir,
|
|
56
59
|
client: options?.remoteSourceClient,
|
|
@@ -76,6 +79,9 @@ class AdapterRegistry {
|
|
|
76
79
|
if (type === "telegram_bot") {
|
|
77
80
|
return this.remoteSource;
|
|
78
81
|
}
|
|
82
|
+
if (type === "email_mailbox") {
|
|
83
|
+
return this.remoteSource;
|
|
84
|
+
}
|
|
79
85
|
return this.localEventSource;
|
|
80
86
|
}
|
|
81
87
|
deliveryAdapterFor(provider) {
|
|
@@ -88,6 +94,9 @@ class AdapterRegistry {
|
|
|
88
94
|
if (provider === "telegram") {
|
|
89
95
|
return this.telegramDelivery;
|
|
90
96
|
}
|
|
97
|
+
if (provider === "email") {
|
|
98
|
+
return this.emailDelivery;
|
|
99
|
+
}
|
|
91
100
|
return this.defaultDelivery;
|
|
92
101
|
}
|
|
93
102
|
async start() {
|
|
@@ -251,6 +260,9 @@ class AdapterRegistry {
|
|
|
251
260
|
if (handle.provider === "telegram") {
|
|
252
261
|
return this.remoteModuleRegistry.resolve(syntheticBuiltinSource("telegram_bot"), this.homeDir);
|
|
253
262
|
}
|
|
263
|
+
if (handle.provider === "email") {
|
|
264
|
+
return this.remoteModuleRegistry.resolve(syntheticBuiltinSource("email_mailbox"), this.homeDir);
|
|
265
|
+
}
|
|
254
266
|
return null;
|
|
255
267
|
}
|
|
256
268
|
}
|
|
@@ -262,6 +274,7 @@ function isExplicitFollowPreviewRef(value) {
|
|
|
262
274
|
value === "github_repo_ci" ||
|
|
263
275
|
value === "feishu_bot" ||
|
|
264
276
|
value === "telegram_bot" ||
|
|
277
|
+
value === "email_mailbox" ||
|
|
265
278
|
value.startsWith("remote:"));
|
|
266
279
|
}
|
|
267
280
|
function hasRemoteSourceModulePath(config) {
|
package/dist/src/service.js
CHANGED
|
@@ -3313,7 +3313,8 @@ function sourceTypeForPreviewRef(sourceRef) {
|
|
|
3313
3313
|
sourceRef === "github_repo" ||
|
|
3314
3314
|
sourceRef === "github_repo_ci" ||
|
|
3315
3315
|
sourceRef === "feishu_bot" ||
|
|
3316
|
-
sourceRef === "telegram_bot"
|
|
3316
|
+
sourceRef === "telegram_bot" ||
|
|
3317
|
+
sourceRef === "email_mailbox") {
|
|
3317
3318
|
return sourceRef;
|
|
3318
3319
|
}
|
|
3319
3320
|
if (sourceRef.startsWith("remote:")) {
|
|
@@ -3615,6 +3616,9 @@ function providerForSourceType(sourceType) {
|
|
|
3615
3616
|
if (sourceType === "telegram_bot") {
|
|
3616
3617
|
return "telegram";
|
|
3617
3618
|
}
|
|
3619
|
+
if (sourceType === "email_mailbox") {
|
|
3620
|
+
return "email";
|
|
3621
|
+
}
|
|
3618
3622
|
return null;
|
|
3619
3623
|
}
|
|
3620
3624
|
function authHeaders(auth) {
|
|
@@ -4209,6 +4213,8 @@ function listHostStreamKinds(hostType) {
|
|
|
4209
4213
|
return ["message_events"];
|
|
4210
4214
|
case "telegram":
|
|
4211
4215
|
return ["message_updates"];
|
|
4216
|
+
case "email":
|
|
4217
|
+
return ["message_events"];
|
|
4212
4218
|
case "local_event":
|
|
4213
4219
|
return ["events"];
|
|
4214
4220
|
case "remote_source":
|
|
@@ -4218,6 +4224,7 @@ function listHostStreamKinds(hostType) {
|
|
|
4218
4224
|
const KNOWN_DELIVERY_SURFACES = {
|
|
4219
4225
|
feishu: ["message_reply", "chat_message"],
|
|
4220
4226
|
github: ["issue_comment", "pull_request_comment", "review_comment"],
|
|
4227
|
+
email: ["message_reply", "message_send"],
|
|
4221
4228
|
};
|
|
4222
4229
|
function unsupportedDeliverySurfaceMessage(handle) {
|
|
4223
4230
|
const supported = KNOWN_DELIVERY_SURFACES[handle.provider];
|
|
@@ -4298,6 +4305,12 @@ function getHostConfigFields(hostType) {
|
|
|
4298
4305
|
{ name: "tokenEnv", type: "string", description: "Environment variable containing the Telegram Bot API token.", required: false },
|
|
4299
4306
|
{ name: "botUsername", type: "string", description: "Optional bot username used for shared host identity.", required: false },
|
|
4300
4307
|
];
|
|
4308
|
+
case "email":
|
|
4309
|
+
return [
|
|
4310
|
+
{ name: "uxcAuth", type: "string", description: "UXC auth profile holding mailbox credentials.", required: false },
|
|
4311
|
+
{ name: "smtpEndpoint", type: "string", description: "Default smtp:// endpoint for outbound delivery.", required: false },
|
|
4312
|
+
{ name: "fromAddress", type: "string", description: "Default outbound From address.", required: false },
|
|
4313
|
+
];
|
|
4301
4314
|
case "local_event":
|
|
4302
4315
|
return [];
|
|
4303
4316
|
case "remote_source":
|
|
@@ -4317,6 +4330,9 @@ function sourceTypeForStreamRegistration(hostType, streamKind) {
|
|
|
4317
4330
|
if (hostType === "telegram") {
|
|
4318
4331
|
return "telegram_bot";
|
|
4319
4332
|
}
|
|
4333
|
+
if (hostType === "email") {
|
|
4334
|
+
return "email_mailbox";
|
|
4335
|
+
}
|
|
4320
4336
|
if (hostType === "local_event") {
|
|
4321
4337
|
return "local_event";
|
|
4322
4338
|
}
|
package/dist/src/source_hosts.js
CHANGED
|
@@ -60,6 +60,22 @@ function resolveSourceRegistration(input) {
|
|
|
60
60
|
sourceType: input.sourceType,
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
if (input.sourceType === "email_mailbox") {
|
|
64
|
+
const provider = stringOrDefault(config.provider, "imap");
|
|
65
|
+
return {
|
|
66
|
+
hostType: "email",
|
|
67
|
+
hostKey: `email:${provider}:${stringOrDefault(config.account, stringOrDefault(config.uxcAuth, input.sourceKey))}`,
|
|
68
|
+
hostConfig: {
|
|
69
|
+
...(valueOrUndefined(config.uxcAuth) ? { uxcAuth: config.uxcAuth } : {}),
|
|
70
|
+
...(valueOrUndefined(config.smtpEndpoint) ? { smtpEndpoint: config.smtpEndpoint } : {}),
|
|
71
|
+
...(valueOrUndefined(config.fromAddress) ? { fromAddress: config.fromAddress } : {}),
|
|
72
|
+
},
|
|
73
|
+
streamKind: "message_events",
|
|
74
|
+
streamKey: input.sourceKey,
|
|
75
|
+
streamConfig: config,
|
|
76
|
+
sourceType: input.sourceType,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
63
79
|
if (input.sourceType === "local_event") {
|
|
64
80
|
return {
|
|
65
81
|
hostType: "local_event",
|
|
@@ -189,6 +189,57 @@ const SOURCE_SCHEMAS = {
|
|
|
189
189
|
{ name: "endpoint", type: "string", required: false, description: "Optional Telegram Bot API endpoint override." },
|
|
190
190
|
],
|
|
191
191
|
},
|
|
192
|
+
email_mailbox: {
|
|
193
|
+
sourceType: "email_mailbox",
|
|
194
|
+
metadataFields: [
|
|
195
|
+
{ name: "provider", type: "string", description: "Email provider: imap, gmail, graph, or jmap." },
|
|
196
|
+
{ name: "account", type: "string", description: "Mailbox account alias." },
|
|
197
|
+
{ name: "mailbox", type: "string", description: "Mailbox name such as INBOX." },
|
|
198
|
+
{ name: "messageId", type: "string|null", description: "RFC Message-ID header when present." },
|
|
199
|
+
{ name: "providerMessageId", type: "string|null", description: "Provider-native message id (IMAP UID or provider message id)." },
|
|
200
|
+
{ name: "threadId", type: "string|null", description: "Thread reference derived from References/In-Reply-To headers." },
|
|
201
|
+
{ name: "from", type: "string|null", description: "Normalized sender address." },
|
|
202
|
+
{ name: "fromName", type: "string|null", description: "Sender display name when present." },
|
|
203
|
+
{ name: "to", type: "object[]", description: "Normalized recipients with address and optional name." },
|
|
204
|
+
{ name: "cc", type: "object[]", description: "Normalized cc recipients with address and optional name." },
|
|
205
|
+
{ name: "subject", type: "string|null", description: "Message subject." },
|
|
206
|
+
{ name: "textPreview", type: "string|null", description: "Short body snippet." },
|
|
207
|
+
{ name: "date", type: "string|null", description: "Message date header value." },
|
|
208
|
+
{ name: "hasAttachments", type: "boolean", description: "Whether the message carries attachments." },
|
|
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." },
|
|
211
|
+
],
|
|
212
|
+
payloadExamples: [
|
|
213
|
+
{
|
|
214
|
+
type: "email_event",
|
|
215
|
+
version: "v1",
|
|
216
|
+
provider: "imap",
|
|
217
|
+
account: "user@example.com",
|
|
218
|
+
mailbox: "INBOX",
|
|
219
|
+
event_kind: "message_received",
|
|
220
|
+
message: {
|
|
221
|
+
uid: "42",
|
|
222
|
+
message_id: "<msg@example.com>",
|
|
223
|
+
from: "sender@example.com",
|
|
224
|
+
to: [{ raw: "user@example.com" }],
|
|
225
|
+
subject: "Quarterly report",
|
|
226
|
+
snippet: "Please review...",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
eventVariantExamples: ["email.message.received"],
|
|
231
|
+
configFields: [
|
|
232
|
+
{ name: "provider", type: "string", required: true, description: "Email provider: imap, gmail, graph, or jmap." },
|
|
233
|
+
{ name: "uxcAuth", type: "string", required: true, description: "UXC auth profile name holding mailbox credentials; credentials never inline." },
|
|
234
|
+
{ name: "endpoint", type: "string", required: false, description: "imap:// or imaps:// endpoint for imap; provider API endpoint for gmail/graph/jmap (jmap requires it)." },
|
|
235
|
+
{ name: "account", type: "string", required: false, description: "Account alias; defaults to the auth profile username." },
|
|
236
|
+
{ name: "mailbox", type: "string", required: false, description: "Mailbox to watch; defaults to INBOX." },
|
|
237
|
+
{ name: "pollIntervalSecs", type: "number", required: false, description: "Provider poll interval in seconds (min 15, default 60)." },
|
|
238
|
+
{ name: "smtpEndpoint", type: "string", required: false, description: "Default smtp:// endpoint for outbound reply/send delivery." },
|
|
239
|
+
{ name: "fromAddress", type: "string", required: false, description: "Default outbound From address for delivery." },
|
|
240
|
+
{ name: "addressAllowlist", type: "string[]", required: false, description: "Optional from/to address allowlist applied before inbox routing." },
|
|
241
|
+
],
|
|
242
|
+
},
|
|
192
243
|
};
|
|
193
244
|
function getSourceSchema(sourceType) {
|
|
194
245
|
const schema = SOURCE_SCHEMAS[sourceType];
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"use strict";
|
|
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_DEFAULT_POLL_INTERVAL_SECS = exports.EMAIL_DEFAULT_MAILBOX = void 0;
|
|
4
|
+
exports.parseEmailMailboxSourceConfig = parseEmailMailboxSourceConfig;
|
|
5
|
+
exports.buildEmailMailboxSourceSpec = buildEmailMailboxSourceSpec;
|
|
6
|
+
exports.normalizeEmailMailboxEvent = normalizeEmailMailboxEvent;
|
|
7
|
+
exports.emailDeliveryOperationsForHandle = emailDeliveryOperationsForHandle;
|
|
8
|
+
exports.invokeEmailDeliveryOperation = invokeEmailDeliveryOperation;
|
|
9
|
+
const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
|
|
10
|
+
exports.EMAIL_DEFAULT_MAILBOX = "INBOX";
|
|
11
|
+
exports.EMAIL_MAILBOX_DEFAULT_POLL_INTERVAL_SECS = 60;
|
|
12
|
+
exports.EMAIL_EVENT_TYPE = "email_event";
|
|
13
|
+
exports.EMAIL_EVENT_VERSION = "v1";
|
|
14
|
+
exports.GMAIL_MESSAGES_ENDPOINT = "https://gmail.googleapis.com/gmail/v1/users/me/messages";
|
|
15
|
+
exports.GRAPH_MESSAGES_ENDPOINT = "https://graph.microsoft.com/v1.0/me/messages?$expand=attachments";
|
|
16
|
+
const EMAIL_MAILBOX_PROVIDERS = new Set(["imap", "gmail", "graph", "jmap"]);
|
|
17
|
+
const EMAIL_DEFAULT_ENDPOINTS = {
|
|
18
|
+
gmail: exports.GMAIL_MESSAGES_ENDPOINT,
|
|
19
|
+
graph: exports.GRAPH_MESSAGES_ENDPOINT,
|
|
20
|
+
};
|
|
21
|
+
function parseEmailMailboxSourceConfig(source) {
|
|
22
|
+
const config = source.config ?? {};
|
|
23
|
+
const providerRaw = asString(config.provider) ?? asString(config.emailProvider);
|
|
24
|
+
if (!providerRaw) {
|
|
25
|
+
throw new Error("email_mailbox requires config.provider (imap, gmail, graph, or jmap)");
|
|
26
|
+
}
|
|
27
|
+
const provider = providerRaw.trim().toLowerCase();
|
|
28
|
+
if (!EMAIL_MAILBOX_PROVIDERS.has(provider)) {
|
|
29
|
+
throw new Error(`email_mailbox config.provider must be one of imap, gmail, graph, jmap; got ${providerRaw}`);
|
|
30
|
+
}
|
|
31
|
+
const endpointRaw = asString(config.endpoint);
|
|
32
|
+
let endpoint;
|
|
33
|
+
if (provider === "imap") {
|
|
34
|
+
if (!endpointRaw) {
|
|
35
|
+
throw new Error("email_mailbox with provider=imap requires config.endpoint (imap:// or imaps://)");
|
|
36
|
+
}
|
|
37
|
+
if (!/^imaps?:\/\//i.test(endpointRaw)) {
|
|
38
|
+
throw new Error(`email_mailbox imap endpoint must start with imap:// or imaps://; got ${endpointRaw}`);
|
|
39
|
+
}
|
|
40
|
+
endpoint = endpointRaw;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const fallback = EMAIL_DEFAULT_ENDPOINTS[provider];
|
|
44
|
+
if (endpointRaw) {
|
|
45
|
+
if (!/^https?:\/\//i.test(endpointRaw)) {
|
|
46
|
+
throw new Error(`email_mailbox ${provider} endpoint must start with http:// or https://; got ${endpointRaw}`);
|
|
47
|
+
}
|
|
48
|
+
endpoint = endpointRaw;
|
|
49
|
+
}
|
|
50
|
+
else if (fallback) {
|
|
51
|
+
endpoint = fallback;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw new Error(`email_mailbox with provider=${provider} requires config.endpoint (${provider} API URL)`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const uxcAuth = asString(config.uxcAuth) ?? asString(config.auth);
|
|
58
|
+
if (!uxcAuth || uxcAuth.trim().length === 0) {
|
|
59
|
+
throw new Error("email_mailbox requires config.uxcAuth (UXC auth profile name; credentials stay in UXC)");
|
|
60
|
+
}
|
|
61
|
+
const pollIntervalRaw = config.pollIntervalSecs;
|
|
62
|
+
let pollIntervalSecs = exports.EMAIL_MAILBOX_DEFAULT_POLL_INTERVAL_SECS;
|
|
63
|
+
if (pollIntervalRaw !== undefined && pollIntervalRaw !== null) {
|
|
64
|
+
const parsed = numberFromUnknown(pollIntervalRaw);
|
|
65
|
+
if (parsed === undefined || !Number.isInteger(parsed) || parsed < 15) {
|
|
66
|
+
throw new Error(`email_mailbox config.pollIntervalSecs must be an integer >= 15; got ${String(pollIntervalRaw)}`);
|
|
67
|
+
}
|
|
68
|
+
pollIntervalSecs = parsed;
|
|
69
|
+
}
|
|
70
|
+
const smtpEndpoint = asString(config.smtpEndpoint);
|
|
71
|
+
if (smtpEndpoint && !/^smtp:\/\/./i.test(smtpEndpoint)) {
|
|
72
|
+
throw new Error(`email_mailbox config.smtpEndpoint must use smtp:// (uxc SMTP surface); got ${smtpEndpoint}`);
|
|
73
|
+
}
|
|
74
|
+
const fromAddress = asString(config.fromAddress);
|
|
75
|
+
if (fromAddress && !fromAddress.includes("@")) {
|
|
76
|
+
throw new Error(`email_mailbox config.fromAddress must be an email address; got ${fromAddress}`);
|
|
77
|
+
}
|
|
78
|
+
const account = asString(config.account) ?? undefined;
|
|
79
|
+
if (account !== undefined && account.trim().length === 0) {
|
|
80
|
+
throw new Error("email_mailbox config.account must be a non-empty string when provided");
|
|
81
|
+
}
|
|
82
|
+
const mailbox = asString(config.mailbox) ?? exports.EMAIL_DEFAULT_MAILBOX;
|
|
83
|
+
if (mailbox.trim().length === 0) {
|
|
84
|
+
throw new Error("email_mailbox config.mailbox must be a non-empty string when provided");
|
|
85
|
+
}
|
|
86
|
+
const addressAllowlist = asStringArray(config.addressAllowlist ?? config.addressAllowList);
|
|
87
|
+
return {
|
|
88
|
+
provider: provider,
|
|
89
|
+
endpoint,
|
|
90
|
+
uxcAuth: uxcAuth.trim(),
|
|
91
|
+
account: account?.trim() || undefined,
|
|
92
|
+
mailbox,
|
|
93
|
+
pollIntervalSecs,
|
|
94
|
+
...(smtpEndpoint ? { smtpEndpoint } : {}),
|
|
95
|
+
...(fromAddress ? { fromAddress } : {}),
|
|
96
|
+
...(addressAllowlist && addressAllowlist.length > 0 ? { addressAllowlist } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function buildEmailMailboxSourceSpec(config) {
|
|
100
|
+
const args = compactRecord({
|
|
101
|
+
mailbox: config.mailbox,
|
|
102
|
+
account: config.account,
|
|
103
|
+
});
|
|
104
|
+
if (config.provider === "imap") {
|
|
105
|
+
return {
|
|
106
|
+
endpoint: config.endpoint,
|
|
107
|
+
mode: "stream",
|
|
108
|
+
transport_hint: "email_imap_idle",
|
|
109
|
+
args,
|
|
110
|
+
options: {
|
|
111
|
+
auth: config.uxcAuth,
|
|
112
|
+
artifact_compaction: false,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
endpoint: config.endpoint,
|
|
118
|
+
mode: "poll",
|
|
119
|
+
transport_hint: "email_provider_poll",
|
|
120
|
+
args: {
|
|
121
|
+
provider: config.provider,
|
|
122
|
+
...args,
|
|
123
|
+
},
|
|
124
|
+
poll_config: {
|
|
125
|
+
interval_secs: config.pollIntervalSecs,
|
|
126
|
+
extract_items_pointer: "/items",
|
|
127
|
+
checkpoint_strategy: {
|
|
128
|
+
type: "item_key",
|
|
129
|
+
item_key_pointer: "/message/uid",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
options: {
|
|
133
|
+
auth: config.uxcAuth,
|
|
134
|
+
artifact_compaction: false,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function emailActorParts(value) {
|
|
139
|
+
if (typeof value === "string") {
|
|
140
|
+
const raw = value.trim();
|
|
141
|
+
if (raw.length === 0) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const angled = raw.match(/<([^>]+)>\s*$/);
|
|
145
|
+
if (angled) {
|
|
146
|
+
const name = raw.slice(0, raw.lastIndexOf("<")).trim().replace(/^"|"$/g, "").trim();
|
|
147
|
+
return { address: angled[1].trim(), name: name.length > 0 ? name : null, raw };
|
|
148
|
+
}
|
|
149
|
+
return { address: raw, name: null, raw };
|
|
150
|
+
}
|
|
151
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const record = value;
|
|
155
|
+
if (typeof record.raw === "string") {
|
|
156
|
+
return emailActorParts(record.raw);
|
|
157
|
+
}
|
|
158
|
+
const address = asString(record.address) ?? asString(record.email);
|
|
159
|
+
if (address) {
|
|
160
|
+
return { address, name: asString(record.name), raw: address };
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function emailActorAddresses(value) {
|
|
165
|
+
if (!Array.isArray(value)) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const addresses = [];
|
|
169
|
+
for (const item of value) {
|
|
170
|
+
const parts = emailActorParts(item);
|
|
171
|
+
if (parts?.address) {
|
|
172
|
+
addresses.push(parts.address);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return addresses;
|
|
176
|
+
}
|
|
177
|
+
function emailActorList(value) {
|
|
178
|
+
if (!Array.isArray(value)) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return value
|
|
182
|
+
.map((item) => emailActorParts(item))
|
|
183
|
+
.filter((parts) => Boolean(parts?.address))
|
|
184
|
+
.map((parts) => ({
|
|
185
|
+
address: parts.address,
|
|
186
|
+
...(parts.name ? { name: parts.name } : {}),
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
function normalizeEmailMailboxEvent(source, config, raw) {
|
|
190
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const payload = raw;
|
|
194
|
+
if (payload.type !== exports.EMAIL_EVENT_TYPE) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const version = asString(payload.version);
|
|
198
|
+
if (version !== null && version !== exports.EMAIL_EVENT_VERSION) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
if (payload.event_kind !== "message_received") {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const message = asRecord(payload.message);
|
|
205
|
+
if (Object.keys(message).length === 0) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const provider = asString(payload.provider) ?? config.provider;
|
|
209
|
+
const account = asString(payload.account) ?? config.account ?? config.uxcAuth;
|
|
210
|
+
const mailbox = asString(payload.mailbox) ?? config.mailbox;
|
|
211
|
+
const uid = stringFromUnknown(message.uid);
|
|
212
|
+
const messageId = asString(message.message_id) ?? uid;
|
|
213
|
+
if (!uid && !messageId) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const fromParts = emailActorParts(message.from);
|
|
217
|
+
const toAddresses = emailActorAddresses(message.to);
|
|
218
|
+
if (config.addressAllowlist &&
|
|
219
|
+
config.addressAllowlist.length > 0 &&
|
|
220
|
+
!addressMatchesAllowlist(config.addressAllowlist, fromParts?.address ?? null, toAddresses)) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const attachments = Array.isArray(message.attachments)
|
|
224
|
+
? message.attachments.filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item))
|
|
225
|
+
: [];
|
|
226
|
+
const attachmentCountRaw = message.attachment_count;
|
|
227
|
+
const attachmentCount = numberFromUnknown(attachmentCountRaw) ?? null;
|
|
228
|
+
const date = asString(message.date);
|
|
229
|
+
return {
|
|
230
|
+
sourceId: source.sourceId,
|
|
231
|
+
sourceNativeId: `email:${provider}:${account}:${mailbox}:${uid ?? messageId}`,
|
|
232
|
+
eventVariant: "email.message.received",
|
|
233
|
+
occurredAt: occurredAtFromEmailDate(date),
|
|
234
|
+
metadata: {
|
|
235
|
+
provider,
|
|
236
|
+
account,
|
|
237
|
+
mailbox,
|
|
238
|
+
messageId: messageId ?? null,
|
|
239
|
+
providerMessageId: uid ?? null,
|
|
240
|
+
threadId: asString(message.thread_id),
|
|
241
|
+
from: fromParts ? (fromParts.address ?? fromParts.raw) : null,
|
|
242
|
+
fromName: fromParts?.name ?? null,
|
|
243
|
+
to: emailActorList(message.to),
|
|
244
|
+
cc: emailActorList(message.cc),
|
|
245
|
+
subject: asString(message.subject),
|
|
246
|
+
textPreview: asString(message.snippet),
|
|
247
|
+
date,
|
|
248
|
+
hasAttachments: attachments.length > 0 || message.has_attachments === true,
|
|
249
|
+
attachmentCount,
|
|
250
|
+
attachments,
|
|
251
|
+
},
|
|
252
|
+
rawPayload: payload,
|
|
253
|
+
deliveryHandle: {
|
|
254
|
+
provider: "email",
|
|
255
|
+
surface: "message_reply",
|
|
256
|
+
targetRef: fromParts ? (fromParts.address ?? fromParts.raw) : "",
|
|
257
|
+
threadRef: messageId ?? null,
|
|
258
|
+
replyMode: "reply",
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function addressMatchesAllowlist(allowlist, fromAddress, toAddresses) {
|
|
263
|
+
const normalized = allowlist
|
|
264
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
265
|
+
.filter((entry) => entry.length > 0);
|
|
266
|
+
if (normalized.length === 0) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
const lowerFrom = fromAddress?.toLowerCase() ?? null;
|
|
270
|
+
if (lowerFrom && normalized.includes(lowerFrom)) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return toAddresses.some((address) => normalized.includes(address.toLowerCase()));
|
|
274
|
+
}
|
|
275
|
+
function occurredAtFromEmailDate(date) {
|
|
276
|
+
if (date) {
|
|
277
|
+
const parsed = Date.parse(date);
|
|
278
|
+
if (Number.isFinite(parsed)) {
|
|
279
|
+
return new Date(parsed).toISOString();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return new Date().toISOString();
|
|
283
|
+
}
|
|
284
|
+
class RpcEmailUxcClient {
|
|
285
|
+
client;
|
|
286
|
+
constructor(client = new uxc_daemon_client_1.UxcDaemonClient({ env: process.env })) {
|
|
287
|
+
this.client = client;
|
|
288
|
+
}
|
|
289
|
+
request(method, params) {
|
|
290
|
+
return this.client.request(method, params);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
exports.RpcEmailUxcClient = RpcEmailUxcClient;
|
|
294
|
+
function emailDeliveryOperationsForHandle(handle) {
|
|
295
|
+
if (handle.provider !== "email") {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
if (handle.surface !== "message_reply" && handle.surface !== "message_send") {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
const isReply = handle.surface === "message_reply";
|
|
302
|
+
const commonProperties = {
|
|
303
|
+
text: { type: "string", minLength: 1 },
|
|
304
|
+
subject: { type: "string", minLength: 1 },
|
|
305
|
+
to: { type: "array", items: { type: "string", minLength: 1 } },
|
|
306
|
+
cc: { type: "array", items: { type: "string", minLength: 1 } },
|
|
307
|
+
from: { type: "string", minLength: 1 },
|
|
308
|
+
smtpEndpoint: { type: "string", minLength: 1 },
|
|
309
|
+
uxcAuth: { type: "string", minLength: 1 },
|
|
310
|
+
correlationKey: { type: "string", minLength: 1 },
|
|
311
|
+
};
|
|
312
|
+
const operations = [
|
|
313
|
+
{
|
|
314
|
+
name: "send_text",
|
|
315
|
+
title: isReply ? "Reply With Text" : "Send Text Email",
|
|
316
|
+
inputSchema: {
|
|
317
|
+
type: "object",
|
|
318
|
+
additionalProperties: false,
|
|
319
|
+
required: ["text", "subject"],
|
|
320
|
+
properties: commonProperties,
|
|
321
|
+
},
|
|
322
|
+
canonicalTextAlias: true,
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
if (isReply) {
|
|
326
|
+
operations.push({
|
|
327
|
+
name: "reply_text",
|
|
328
|
+
title: "Reply With Text (Threaded)",
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: "object",
|
|
331
|
+
additionalProperties: false,
|
|
332
|
+
required: ["text"],
|
|
333
|
+
properties: {
|
|
334
|
+
...commonProperties,
|
|
335
|
+
replyAll: { type: "boolean" },
|
|
336
|
+
inReplyTo: { type: "string", minLength: 1 },
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return operations;
|
|
342
|
+
}
|
|
343
|
+
async function invokeEmailDeliveryOperation(handle, operation, input, options) {
|
|
344
|
+
if (handle.provider !== "email") {
|
|
345
|
+
throw new Error(`email delivery operation requires an email handle, got ${handle.provider}`);
|
|
346
|
+
}
|
|
347
|
+
if (operation !== "send_text" && operation !== "reply_text") {
|
|
348
|
+
throw new Error(`unknown email delivery operation: ${operation}`);
|
|
349
|
+
}
|
|
350
|
+
const isReply = operation === "reply_text" || handle.surface === "message_reply";
|
|
351
|
+
const text = asString(input.text);
|
|
352
|
+
if (!text || text.trim().length === 0) {
|
|
353
|
+
throw new Error(`${operation} requires input.text`);
|
|
354
|
+
}
|
|
355
|
+
const config = sourceConfigOrNull(options?.source);
|
|
356
|
+
const to = asStringArray(input.to) ?? (handle.targetRef ? [handle.targetRef] : []);
|
|
357
|
+
const cc = asStringArray(input.cc) ?? [];
|
|
358
|
+
if (to.length === 0 && cc.length === 0) {
|
|
359
|
+
throw new Error(`${operation} requires at least one recipient in input.to (or input.cc)`);
|
|
360
|
+
}
|
|
361
|
+
const subject = resolveEmailSubject(operation, input, isReply);
|
|
362
|
+
const smtpEndpoint = asString(input.smtpEndpoint) ?? config?.smtpEndpoint ?? null;
|
|
363
|
+
if (!smtpEndpoint) {
|
|
364
|
+
throw new Error(`${operation} requires input.smtpEndpoint or source config smtpEndpoint (uxc supports smtp://)`);
|
|
365
|
+
}
|
|
366
|
+
const from = asString(input.from) ?? config?.fromAddress ?? addressLikeAccount(config?.account) ?? null;
|
|
367
|
+
if (!from) {
|
|
368
|
+
throw new Error(`${operation} requires input.from, source config fromAddress, or an account-like config.account value`);
|
|
369
|
+
}
|
|
370
|
+
const uxcAuth = asString(input.uxcAuth) ?? config?.uxcAuth ?? null;
|
|
371
|
+
const correlationKey = asString(input.correlationKey);
|
|
372
|
+
const inReplyTo = asString(input.inReplyTo) ?? (isReply ? handle.threadRef ?? null : null);
|
|
373
|
+
const params = compactRecord({
|
|
374
|
+
smtp_url: smtpEndpoint,
|
|
375
|
+
from,
|
|
376
|
+
to,
|
|
377
|
+
cc: cc.length > 0 ? cc : undefined,
|
|
378
|
+
subject,
|
|
379
|
+
text,
|
|
380
|
+
in_reply_to: inReplyTo ?? undefined,
|
|
381
|
+
auth: uxcAuth ?? undefined,
|
|
382
|
+
message_id: correlationKey ? correlationMessageId(correlationKey) : undefined,
|
|
383
|
+
});
|
|
384
|
+
const method = isReply ? "email.reply" : "email.send";
|
|
385
|
+
if (isReply) {
|
|
386
|
+
params.reply_handle = compactRecord({
|
|
387
|
+
message_id: handle.threadRef ?? undefined,
|
|
388
|
+
account: config?.account ?? undefined,
|
|
389
|
+
mailbox: config?.mailbox ?? undefined,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
const client = options?.client ?? new RpcEmailUxcClient();
|
|
393
|
+
let result;
|
|
394
|
+
try {
|
|
395
|
+
result = await client.request(method, params);
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
throw wrapEmailRpcError(error, method);
|
|
399
|
+
}
|
|
400
|
+
const note = isReply
|
|
401
|
+
? `sent email reply${result.message_id ? ` (message-id ${result.message_id})` : ""}`
|
|
402
|
+
: `sent email${result.message_id ? ` (message-id ${result.message_id})` : ""}`;
|
|
403
|
+
return { status: "sent", note };
|
|
404
|
+
}
|
|
405
|
+
function resolveEmailSubject(operation, input, isReply) {
|
|
406
|
+
const subject = asString(input.subject);
|
|
407
|
+
if (!isReply) {
|
|
408
|
+
if (!subject || subject.trim().length === 0) {
|
|
409
|
+
throw new Error(`${operation} requires input.subject`);
|
|
410
|
+
}
|
|
411
|
+
return subject;
|
|
412
|
+
}
|
|
413
|
+
const base = subject && subject.trim().length > 0 ? subject.trim() : "your message";
|
|
414
|
+
if (/^re:/i.test(base)) {
|
|
415
|
+
return base;
|
|
416
|
+
}
|
|
417
|
+
return `Re: ${base}`;
|
|
418
|
+
}
|
|
419
|
+
function correlationMessageId(correlationKey) {
|
|
420
|
+
const trimmed = correlationKey.trim();
|
|
421
|
+
if (trimmed.startsWith("<") && trimmed.endsWith(">")) {
|
|
422
|
+
return trimmed;
|
|
423
|
+
}
|
|
424
|
+
const safe = trimmed.replace(/[<>\s]/g, "");
|
|
425
|
+
return `<agentinbox-${safe}@localhost>`;
|
|
426
|
+
}
|
|
427
|
+
function sourceConfigOrNull(source) {
|
|
428
|
+
if (!source || source.sourceType !== "email_mailbox") {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
return parseEmailMailboxSourceConfig(source);
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function addressLikeAccount(account) {
|
|
439
|
+
if (!account) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
return account.includes("@") ? account : null;
|
|
443
|
+
}
|
|
444
|
+
function wrapEmailRpcError(error, method) {
|
|
445
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
446
|
+
const code = error.code;
|
|
447
|
+
if (code === -32601 || code === -32602) {
|
|
448
|
+
return new Error(`connected uxc daemon does not support ${method} (code ${code}); upgrade uxc to a version with email send/reply RPC support`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
452
|
+
}
|
|
453
|
+
class EmailDeliveryAdapter {
|
|
454
|
+
client;
|
|
455
|
+
constructor(client) {
|
|
456
|
+
this.client = client ?? new RpcEmailUxcClient();
|
|
457
|
+
}
|
|
458
|
+
async send(request, attempt) {
|
|
459
|
+
const handle = {
|
|
460
|
+
provider: attempt.provider,
|
|
461
|
+
surface: attempt.surface,
|
|
462
|
+
targetRef: attempt.targetRef,
|
|
463
|
+
threadRef: attempt.threadRef ?? null,
|
|
464
|
+
replyMode: attempt.replyMode ?? null,
|
|
465
|
+
};
|
|
466
|
+
const result = await invokeEmailDeliveryOperation(handle, "send_text", request.payload, {
|
|
467
|
+
client: this.client,
|
|
468
|
+
});
|
|
469
|
+
return { status: "sent", note: result.note };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
exports.EmailDeliveryAdapter = EmailDeliveryAdapter;
|
|
473
|
+
function compactRecord(input) {
|
|
474
|
+
const output = {};
|
|
475
|
+
for (const [key, value] of Object.entries(input)) {
|
|
476
|
+
if (value === undefined || value === null) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
output[key] = value;
|
|
483
|
+
}
|
|
484
|
+
return output;
|
|
485
|
+
}
|
|
486
|
+
function asRecord(value) {
|
|
487
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
488
|
+
return {};
|
|
489
|
+
}
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
492
|
+
function asString(value) {
|
|
493
|
+
return typeof value === "string" ? value : null;
|
|
494
|
+
}
|
|
495
|
+
function asStringArray(value) {
|
|
496
|
+
if (Array.isArray(value)) {
|
|
497
|
+
return value
|
|
498
|
+
.map((item) => stringFromUnknown(item))
|
|
499
|
+
.filter((item) => Boolean(item && item.trim().length > 0));
|
|
500
|
+
}
|
|
501
|
+
if (typeof value === "string") {
|
|
502
|
+
return value
|
|
503
|
+
.split(",")
|
|
504
|
+
.map((item) => item.trim())
|
|
505
|
+
.filter((item) => item.length > 0);
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
function stringFromUnknown(value) {
|
|
510
|
+
if (typeof value === "string") {
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
514
|
+
return String(value);
|
|
515
|
+
}
|
|
516
|
+
if (typeof value === "bigint") {
|
|
517
|
+
return value.toString();
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
function numberFromUnknown(value) {
|
|
522
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
523
|
+
return value;
|
|
524
|
+
}
|
|
525
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
526
|
+
const parsed = Number(value);
|
|
527
|
+
if (Number.isFinite(parsed)) {
|
|
528
|
+
return parsed;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
@@ -15,9 +15,10 @@ const github_1 = require("./github");
|
|
|
15
15
|
const github_ci_1 = require("./github_ci");
|
|
16
16
|
const feishu_1 = require("./feishu");
|
|
17
17
|
const telegram_1 = require("./telegram");
|
|
18
|
+
const email_1 = require("./email");
|
|
18
19
|
const REMOTE_USER_MODULE_ROOT_DIR = "source-modules";
|
|
19
|
-
const BUILTIN_MODULE_IDS = new Set(["builtin.github_repo", "builtin.github_repo_ci", "builtin.feishu_bot", "builtin.telegram_bot"]);
|
|
20
|
-
const BUILTIN_REMOTE_SOURCE_TYPES = ["github_repo", "github_repo_ci", "feishu_bot", "telegram_bot"];
|
|
20
|
+
const BUILTIN_MODULE_IDS = new Set(["builtin.github_repo", "builtin.github_repo_ci", "builtin.feishu_bot", "builtin.telegram_bot", "builtin.email_mailbox"]);
|
|
21
|
+
const BUILTIN_REMOTE_SOURCE_TYPES = ["github_repo", "github_repo_ci", "feishu_bot", "telegram_bot", "email_mailbox"];
|
|
21
22
|
class RemoteSourceModuleRegistry {
|
|
22
23
|
moduleCache = new Map();
|
|
23
24
|
githubRepoModule;
|
|
@@ -37,6 +38,9 @@ class RemoteSourceModuleRegistry {
|
|
|
37
38
|
if (source.sourceType === "telegram_bot") {
|
|
38
39
|
return Promise.resolve(TELEGRAM_BOT_MODULE);
|
|
39
40
|
}
|
|
41
|
+
if (source.sourceType === "email_mailbox") {
|
|
42
|
+
return Promise.resolve(EMAIL_MAILBOX_MODULE);
|
|
43
|
+
}
|
|
40
44
|
if (source.sourceType !== "remote_source") {
|
|
41
45
|
throw new Error(`unsupported source type for remote module: ${source.sourceType}`);
|
|
42
46
|
}
|
|
@@ -86,6 +90,9 @@ function builtInModuleIdForSourceType(sourceType) {
|
|
|
86
90
|
if (sourceType === "telegram_bot") {
|
|
87
91
|
return "builtin.telegram_bot";
|
|
88
92
|
}
|
|
93
|
+
if (sourceType === "email_mailbox") {
|
|
94
|
+
return "builtin.email_mailbox";
|
|
95
|
+
}
|
|
89
96
|
return null;
|
|
90
97
|
}
|
|
91
98
|
function builtinRemoteSourceTypes() {
|
|
@@ -631,6 +638,73 @@ const TELEGRAM_BOT_MODULE = {
|
|
|
631
638
|
};
|
|
632
639
|
},
|
|
633
640
|
};
|
|
641
|
+
const EMAIL_MAILBOX_MODULE = {
|
|
642
|
+
id: "builtin.email_mailbox",
|
|
643
|
+
listDeliveryOperations(input) {
|
|
644
|
+
return (0, email_1.emailDeliveryOperationsForHandle)(input.handle);
|
|
645
|
+
},
|
|
646
|
+
async invokeDeliveryOperation(input) {
|
|
647
|
+
return (0, email_1.invokeEmailDeliveryOperation)(input.handle, input.operation, input.input, {
|
|
648
|
+
source: input.source,
|
|
649
|
+
});
|
|
650
|
+
},
|
|
651
|
+
describeCapabilities() {
|
|
652
|
+
return {
|
|
653
|
+
sourceKind: "email_mailbox",
|
|
654
|
+
aliases: ["email_mailbox", "email"],
|
|
655
|
+
configSchema: [
|
|
656
|
+
{ name: "provider", type: "string", required: true, description: "Email provider: imap, gmail, graph, or jmap." },
|
|
657
|
+
{ name: "uxcAuth", type: "string", required: true, description: "UXC auth profile name holding mailbox credentials." },
|
|
658
|
+
{ name: "endpoint", type: "string", required: false, description: "imap:// or imaps:// endpoint for imap; provider API endpoint otherwise (required for jmap)." },
|
|
659
|
+
{ name: "account", type: "string", required: false, description: "Account alias; defaults to the auth profile username." },
|
|
660
|
+
{ name: "mailbox", type: "string", required: false, description: "Mailbox to watch; defaults to INBOX." },
|
|
661
|
+
{ name: "pollIntervalSecs", type: "number", required: false, description: "Provider poll interval in seconds (min 15, default 60)." },
|
|
662
|
+
{ name: "smtpEndpoint", type: "string", required: false, description: "Default smtp:// endpoint for outbound delivery." },
|
|
663
|
+
{ name: "fromAddress", type: "string", required: false, description: "Default outbound From address." },
|
|
664
|
+
{ name: "addressAllowlist", type: "string[]", required: false, description: "Optional from/to address allowlist." },
|
|
665
|
+
],
|
|
666
|
+
metadataFields: [
|
|
667
|
+
{ name: "provider", type: "string", description: "Email provider." },
|
|
668
|
+
{ name: "account", type: "string", description: "Mailbox account alias." },
|
|
669
|
+
{ name: "mailbox", type: "string", description: "Mailbox name." },
|
|
670
|
+
{ name: "messageId", type: "string|null", description: "RFC Message-ID when present." },
|
|
671
|
+
{ name: "providerMessageId", type: "string|null", description: "Provider-native message id." },
|
|
672
|
+
{ name: "threadId", type: "string|null", description: "Thread reference from References/In-Reply-To headers." },
|
|
673
|
+
{ name: "from", type: "string|null", description: "Normalized sender address." },
|
|
674
|
+
{ name: "fromName", type: "string|null", description: "Sender display name." },
|
|
675
|
+
{ name: "to", type: "object[]", description: "Normalized recipients." },
|
|
676
|
+
{ name: "cc", type: "object[]", description: "Normalized cc recipients." },
|
|
677
|
+
{ name: "subject", type: "string|null", description: "Message subject." },
|
|
678
|
+
{ name: "textPreview", type: "string|null", description: "Body snippet." },
|
|
679
|
+
{ name: "hasAttachments", type: "boolean", description: "Whether the message carries attachments." },
|
|
680
|
+
{ name: "attachmentCount", type: "number|null", description: "Attachment count; null when provider metadata was not expanded." },
|
|
681
|
+
{ name: "attachments", type: "object[]", description: "Attachment metadata with opaque retrieval handles." },
|
|
682
|
+
],
|
|
683
|
+
eventVariantExamples: ["email.message.received"],
|
|
684
|
+
};
|
|
685
|
+
},
|
|
686
|
+
validateConfig(source) {
|
|
687
|
+
(0, email_1.parseEmailMailboxSourceConfig)(source);
|
|
688
|
+
},
|
|
689
|
+
buildManagedSourceSpec(source) {
|
|
690
|
+
return (0, email_1.buildEmailMailboxSourceSpec)((0, email_1.parseEmailMailboxSourceConfig)(source));
|
|
691
|
+
},
|
|
692
|
+
mapRawEvent(rawPayload, source) {
|
|
693
|
+
const config = (0, email_1.parseEmailMailboxSourceConfig)(source);
|
|
694
|
+
const normalized = (0, email_1.normalizeEmailMailboxEvent)(source, config, rawPayload);
|
|
695
|
+
if (!normalized) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
sourceNativeId: normalized.sourceNativeId,
|
|
700
|
+
eventVariant: normalized.eventVariant,
|
|
701
|
+
metadata: normalized.metadata ?? {},
|
|
702
|
+
rawPayload: normalized.rawPayload ?? rawPayload,
|
|
703
|
+
occurredAt: normalized.occurredAt,
|
|
704
|
+
deliveryHandle: normalized.deliveryHandle,
|
|
705
|
+
};
|
|
706
|
+
},
|
|
707
|
+
};
|
|
634
708
|
function moduleConfigForSource(source) {
|
|
635
709
|
const config = asRecord(source.config);
|
|
636
710
|
if (source.sourceType === "remote_source") {
|