aamp-openclaw-plugin 0.1.37 → 0.1.39
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/README.md +15 -1
- package/bin/aamp-openclaw-plugin.mjs +50 -44
- package/dist/file-store.js +73 -0
- package/dist/file-store.js.map +2 -2
- package/dist/index.js +718 -134
- package/dist/index.js.map +4 -4
- package/openclaw.plugin.json +21 -0
- package/package.json +57 -1
- package/skills/SKILL.md +0 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// ../sdk/
|
|
1
|
+
// ../sdk/dist/jmap-push.js
|
|
2
2
|
import WebSocket from "ws";
|
|
3
3
|
|
|
4
|
-
// ../sdk/
|
|
4
|
+
// ../sdk/dist/types.js
|
|
5
5
|
var AAMP_PROTOCOL_VERSION = "1.1";
|
|
6
6
|
var AAMP_HEADER = {
|
|
7
7
|
VERSION: "X-AAMP-Version",
|
|
8
8
|
INTENT: "X-AAMP-Intent",
|
|
9
9
|
TASK_ID: "X-AAMP-TaskId",
|
|
10
|
-
|
|
10
|
+
SESSION_KEY: "X-AAMP-Session-Key",
|
|
11
11
|
DISPATCH_CONTEXT: "X-AAMP-Dispatch-Context",
|
|
12
12
|
PRIORITY: "X-AAMP-Priority",
|
|
13
13
|
EXPIRES_AT: "X-AAMP-Expires-At",
|
|
@@ -19,11 +19,13 @@ var AAMP_HEADER = {
|
|
|
19
19
|
BLOCKED_REASON: "X-AAMP-BlockedReason",
|
|
20
20
|
SUGGESTED_OPTIONS: "X-AAMP-SuggestedOptions",
|
|
21
21
|
STREAM_ID: "X-AAMP-Stream-Id",
|
|
22
|
+
PAIR_CODE: "X-AAMP-Pair-Code",
|
|
23
|
+
DISPATCH_CONTEXT_RULES: "X-AAMP-Dispatch-Context-Rules",
|
|
22
24
|
PARENT_TASK_ID: "X-AAMP-ParentTaskId",
|
|
23
25
|
CARD_SUMMARY: "X-AAMP-Card-Summary"
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
// ../sdk/
|
|
28
|
+
// ../sdk/dist/parser.js
|
|
27
29
|
function normalizeBodyText(value) {
|
|
28
30
|
return value?.replace(/\r\n/g, "\n").trim() ?? "";
|
|
29
31
|
}
|
|
@@ -158,6 +160,20 @@ function encodeStructuredResult(value) {
|
|
|
158
160
|
const json = JSON.stringify(value);
|
|
159
161
|
return Buffer.from(json, "utf-8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
160
162
|
}
|
|
163
|
+
function decodeBase64UrlJson(value) {
|
|
164
|
+
if (!value)
|
|
165
|
+
return void 0;
|
|
166
|
+
try {
|
|
167
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
168
|
+
const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
|
|
169
|
+
return JSON.parse(Buffer.from(normalized + padding, "base64").toString("utf-8"));
|
|
170
|
+
} catch {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function encodeBase64UrlJson(value) {
|
|
175
|
+
return Buffer.from(JSON.stringify(value), "utf-8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
176
|
+
}
|
|
161
177
|
function parseAampHeaders(meta) {
|
|
162
178
|
const headers = normalizeHeaders(meta.headers);
|
|
163
179
|
const intent = getAampHeader(headers, AAMP_HEADER.INTENT);
|
|
@@ -169,8 +185,8 @@ function parseAampHeaders(meta) {
|
|
|
169
185
|
const to = meta.to.replace(/^<|>$/g, "");
|
|
170
186
|
const decodedSubject = decodeMimeEncodedWords(meta.subject);
|
|
171
187
|
if (intent === "task.dispatch") {
|
|
172
|
-
const contextLinksStr = getAampHeader(headers, AAMP_HEADER.CONTEXT_LINKS) ?? "";
|
|
173
188
|
const dispatchContext = parseDispatchContextHeader(getAampHeader(headers, AAMP_HEADER.DISPATCH_CONTEXT));
|
|
189
|
+
const sessionKey = getAampHeader(headers, AAMP_HEADER.SESSION_KEY);
|
|
174
190
|
const parentTaskId = getAampHeader(headers, AAMP_HEADER.PARENT_TASK_ID);
|
|
175
191
|
const priority = getAampHeader(headers, AAMP_HEADER.PRIORITY) ?? "normal";
|
|
176
192
|
const expiresAt = getAampHeader(headers, AAMP_HEADER.EXPIRES_AT);
|
|
@@ -178,10 +194,10 @@ function parseAampHeaders(meta) {
|
|
|
178
194
|
protocolVersion,
|
|
179
195
|
intent: "task.dispatch",
|
|
180
196
|
taskId,
|
|
197
|
+
...sessionKey ? { sessionKey } : {},
|
|
181
198
|
title: decodedSubject.replace(/^\[AAMP Task\]\s*/, "").trim() || "Untitled Task",
|
|
182
199
|
priority: priority === "urgent" || priority === "high" ? priority : "normal",
|
|
183
200
|
...expiresAt ? { expiresAt } : {},
|
|
184
|
-
contextLinks: contextLinksStr ? contextLinksStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
185
201
|
...dispatchContext ? { dispatchContext } : {},
|
|
186
202
|
...parentTaskId ? { parentTaskId } : {},
|
|
187
203
|
from,
|
|
@@ -270,6 +286,43 @@ function parseAampHeaders(meta) {
|
|
|
270
286
|
};
|
|
271
287
|
return streamOpened;
|
|
272
288
|
}
|
|
289
|
+
if (intent === "pair.request") {
|
|
290
|
+
const pairCode = getAampHeader(headers, AAMP_HEADER.PAIR_CODE) ?? "";
|
|
291
|
+
if (!pairCode)
|
|
292
|
+
return null;
|
|
293
|
+
const pairRequest = {
|
|
294
|
+
protocolVersion,
|
|
295
|
+
intent: "pair.request",
|
|
296
|
+
taskId,
|
|
297
|
+
pairCode,
|
|
298
|
+
dispatchContextRules: decodeBase64UrlJson(getAampHeader(headers, AAMP_HEADER.DISPATCH_CONTEXT_RULES)) ?? {},
|
|
299
|
+
from,
|
|
300
|
+
to,
|
|
301
|
+
messageId: meta.messageId,
|
|
302
|
+
subject: meta.subject,
|
|
303
|
+
bodyText: ""
|
|
304
|
+
};
|
|
305
|
+
return pairRequest;
|
|
306
|
+
}
|
|
307
|
+
if (intent === "pair.respond") {
|
|
308
|
+
const rawStatus = getAampHeader(headers, AAMP_HEADER.STATUS);
|
|
309
|
+
const status = rawStatus === "completed" ? "completed" : "rejected";
|
|
310
|
+
const reason = getAampHeader(headers, AAMP_HEADER.ERROR_MSG) || void 0;
|
|
311
|
+
const pairRespond = {
|
|
312
|
+
protocolVersion,
|
|
313
|
+
intent: "pair.respond",
|
|
314
|
+
taskId,
|
|
315
|
+
status,
|
|
316
|
+
success: status === "completed",
|
|
317
|
+
reason,
|
|
318
|
+
from,
|
|
319
|
+
to,
|
|
320
|
+
messageId: meta.messageId,
|
|
321
|
+
subject: meta.subject,
|
|
322
|
+
bodyText: normalizeBodyText(meta.bodyText)
|
|
323
|
+
};
|
|
324
|
+
return pairRespond;
|
|
325
|
+
}
|
|
273
326
|
if (intent === "card.query") {
|
|
274
327
|
const cardQuery = {
|
|
275
328
|
protocolVersion,
|
|
@@ -310,8 +363,8 @@ function buildDispatchHeaders(params) {
|
|
|
310
363
|
if (params.expiresAt) {
|
|
311
364
|
headers[AAMP_HEADER.EXPIRES_AT] = params.expiresAt;
|
|
312
365
|
}
|
|
313
|
-
if (params.
|
|
314
|
-
headers[AAMP_HEADER.
|
|
366
|
+
if (params.sessionKey?.trim()) {
|
|
367
|
+
headers[AAMP_HEADER.SESSION_KEY] = params.sessionKey.trim();
|
|
315
368
|
}
|
|
316
369
|
const dispatchContext = serializeDispatchContextHeader(params.dispatchContext);
|
|
317
370
|
if (dispatchContext) {
|
|
@@ -344,6 +397,27 @@ function buildStreamOpenedHeaders(opts) {
|
|
|
344
397
|
[AAMP_HEADER.STREAM_ID]: opts.streamId
|
|
345
398
|
};
|
|
346
399
|
}
|
|
400
|
+
function buildPairRequestHeaders(opts) {
|
|
401
|
+
return {
|
|
402
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
403
|
+
[AAMP_HEADER.INTENT]: "pair.request",
|
|
404
|
+
[AAMP_HEADER.TASK_ID]: opts.taskId,
|
|
405
|
+
[AAMP_HEADER.PAIR_CODE]: opts.pairCode,
|
|
406
|
+
[AAMP_HEADER.DISPATCH_CONTEXT_RULES]: encodeBase64UrlJson(opts.dispatchContextRules ?? {})
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function buildPairRespondHeaders(opts) {
|
|
410
|
+
const headers = {
|
|
411
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
412
|
+
[AAMP_HEADER.INTENT]: "pair.respond",
|
|
413
|
+
[AAMP_HEADER.TASK_ID]: opts.taskId,
|
|
414
|
+
[AAMP_HEADER.STATUS]: opts.success ? "completed" : "rejected"
|
|
415
|
+
};
|
|
416
|
+
if (!opts.success && opts.reason?.trim()) {
|
|
417
|
+
headers[AAMP_HEADER.ERROR_MSG] = opts.reason.replace(/[\r\n]/g, " ").trim();
|
|
418
|
+
}
|
|
419
|
+
return headers;
|
|
420
|
+
}
|
|
347
421
|
function buildResultHeaders(params) {
|
|
348
422
|
const headers = {
|
|
349
423
|
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
@@ -381,7 +455,7 @@ function buildCardResponseHeaders(params) {
|
|
|
381
455
|
};
|
|
382
456
|
}
|
|
383
457
|
|
|
384
|
-
// ../sdk/
|
|
458
|
+
// ../sdk/dist/tiny-emitter.js
|
|
385
459
|
var TinyEmitter = class {
|
|
386
460
|
listeners = /* @__PURE__ */ new Map();
|
|
387
461
|
onceWrappers = /* @__PURE__ */ new WeakMap();
|
|
@@ -434,7 +508,7 @@ var TinyEmitter = class {
|
|
|
434
508
|
}
|
|
435
509
|
};
|
|
436
510
|
|
|
437
|
-
// ../sdk/
|
|
511
|
+
// ../sdk/dist/jmap-push.js
|
|
438
512
|
function describeError(err) {
|
|
439
513
|
if (!(err instanceof Error))
|
|
440
514
|
return String(err);
|
|
@@ -729,6 +803,12 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
729
803
|
case "task.stream.opened":
|
|
730
804
|
this.emit("task.stream.opened", aampMsg);
|
|
731
805
|
break;
|
|
806
|
+
case "pair.request":
|
|
807
|
+
this.emit("pair.request", aampMsg);
|
|
808
|
+
break;
|
|
809
|
+
case "pair.respond":
|
|
810
|
+
this.emit("pair.respond", aampMsg);
|
|
811
|
+
break;
|
|
732
812
|
case "card.query":
|
|
733
813
|
this.emit("card.query", aampMsg);
|
|
734
814
|
break;
|
|
@@ -1114,10 +1194,177 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
1114
1194
|
}
|
|
1115
1195
|
};
|
|
1116
1196
|
|
|
1117
|
-
// ../sdk/
|
|
1197
|
+
// ../sdk/dist/pairing.js
|
|
1198
|
+
import { randomBytes } from "node:crypto";
|
|
1199
|
+
function normalizeMailbox(value) {
|
|
1200
|
+
const mailbox = value.trim().toLowerCase();
|
|
1201
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(mailbox)) {
|
|
1202
|
+
throw new Error(`Invalid AAMP mailbox in pairing URL: ${value}`);
|
|
1203
|
+
}
|
|
1204
|
+
return mailbox;
|
|
1205
|
+
}
|
|
1206
|
+
function normalizeDispatchContextRules(rules) {
|
|
1207
|
+
if (!rules)
|
|
1208
|
+
return void 0;
|
|
1209
|
+
const normalized = Object.fromEntries(Object.entries(rules).map(([key, values]) => [
|
|
1210
|
+
key.trim().toLowerCase(),
|
|
1211
|
+
(Array.isArray(values) ? values : []).map((value) => value.trim()).filter(Boolean)
|
|
1212
|
+
]).filter(([key, values]) => Boolean(key) && values.length > 0));
|
|
1213
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
1214
|
+
}
|
|
1215
|
+
function encodeBase64UrlJson2(value) {
|
|
1216
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
1217
|
+
}
|
|
1218
|
+
function decodeDispatchContextRules(value) {
|
|
1219
|
+
const candidates = [
|
|
1220
|
+
() => Buffer.from(value, "base64url").toString("utf8"),
|
|
1221
|
+
() => value
|
|
1222
|
+
];
|
|
1223
|
+
for (const read of candidates) {
|
|
1224
|
+
try {
|
|
1225
|
+
const parsed = JSON.parse(read());
|
|
1226
|
+
return normalizeDispatchContextRules(parsed);
|
|
1227
|
+
} catch {
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
throw new Error("Invalid dispatch_context_rules in pairing URL");
|
|
1231
|
+
}
|
|
1232
|
+
function buildPairingUrl(payload) {
|
|
1233
|
+
const mailbox = normalizeMailbox(payload.mailbox);
|
|
1234
|
+
const pairCode = payload.pairCode.trim();
|
|
1235
|
+
if (!pairCode)
|
|
1236
|
+
throw new Error("pairCode cannot be empty");
|
|
1237
|
+
const url = new URL("aamp://connect");
|
|
1238
|
+
url.searchParams.set("mailbox", mailbox);
|
|
1239
|
+
url.searchParams.set("pair_code", pairCode);
|
|
1240
|
+
const rules = normalizeDispatchContextRules(payload.dispatchContextRules);
|
|
1241
|
+
if (rules) {
|
|
1242
|
+
url.searchParams.set("dispatch_context_rules", encodeBase64UrlJson2(rules));
|
|
1243
|
+
}
|
|
1244
|
+
return url.toString();
|
|
1245
|
+
}
|
|
1246
|
+
function createPairingCode(options) {
|
|
1247
|
+
const pairCode = options.pairCode?.trim() || randomBytes(6).toString("base64url");
|
|
1248
|
+
const dispatchContextRules = normalizeDispatchContextRules(options.dispatchContextRules);
|
|
1249
|
+
return {
|
|
1250
|
+
mailbox: normalizeMailbox(options.mailbox),
|
|
1251
|
+
pairCode,
|
|
1252
|
+
expiresAt: new Date(Date.now() + (options.ttlSeconds ?? 300) * 1e3).toISOString(),
|
|
1253
|
+
connectUrl: buildPairingUrl({
|
|
1254
|
+
mailbox: options.mailbox,
|
|
1255
|
+
pairCode,
|
|
1256
|
+
...dispatchContextRules ? { dispatchContextRules } : {}
|
|
1257
|
+
}),
|
|
1258
|
+
...dispatchContextRules ? { dispatchContextRules } : {}
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
function parsePairingUrl(input) {
|
|
1262
|
+
let url;
|
|
1263
|
+
try {
|
|
1264
|
+
url = new URL(input.trim());
|
|
1265
|
+
} catch {
|
|
1266
|
+
throw new Error("Invalid pairing URL");
|
|
1267
|
+
}
|
|
1268
|
+
if (url.protocol !== "aamp:" || url.hostname !== "connect") {
|
|
1269
|
+
throw new Error("Pairing URL must start with aamp://connect");
|
|
1270
|
+
}
|
|
1271
|
+
const mailbox = url.searchParams.get("mailbox") ?? "";
|
|
1272
|
+
const pairCode = url.searchParams.get("pair_code") ?? "";
|
|
1273
|
+
if (!pairCode.trim())
|
|
1274
|
+
throw new Error("Pairing URL is missing pair_code");
|
|
1275
|
+
const rawRules = url.searchParams.get("dispatch_context_rules") ?? url.searchParams.get("dispatchContextRules");
|
|
1276
|
+
const dispatchContextRules = rawRules ? decodeDispatchContextRules(rawRules) : void 0;
|
|
1277
|
+
return {
|
|
1278
|
+
mailbox: normalizeMailbox(mailbox),
|
|
1279
|
+
pairCode: pairCode.trim(),
|
|
1280
|
+
...dispatchContextRules ? { dispatchContextRules } : {}
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
function isPairingUrl(input) {
|
|
1284
|
+
try {
|
|
1285
|
+
parsePairingUrl(input);
|
|
1286
|
+
return true;
|
|
1287
|
+
} catch {
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function consumePairingCode(state, options) {
|
|
1292
|
+
if (state.consumedAt)
|
|
1293
|
+
return null;
|
|
1294
|
+
if (normalizeMailbox(state.mailbox) !== normalizeMailbox(options.mailbox))
|
|
1295
|
+
return null;
|
|
1296
|
+
if (state.pairCode !== options.pairCode.trim())
|
|
1297
|
+
return null;
|
|
1298
|
+
if (new Date(state.expiresAt).getTime() <= (options.now ?? /* @__PURE__ */ new Date()).getTime())
|
|
1299
|
+
return null;
|
|
1300
|
+
return {
|
|
1301
|
+
...state,
|
|
1302
|
+
pairCode: "",
|
|
1303
|
+
connectUrl: "",
|
|
1304
|
+
consumedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString()
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
function createPairedSenderPolicy(request, pairedAt = /* @__PURE__ */ new Date()) {
|
|
1308
|
+
return {
|
|
1309
|
+
sender: normalizeMailbox(request.from),
|
|
1310
|
+
dispatchContextRules: normalizeDispatchContextRules(request.dispatchContextRules) ?? {},
|
|
1311
|
+
pairedAt: pairedAt.toISOString()
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
function upsertPairedSenderPolicy(policies, policy) {
|
|
1315
|
+
const sender = normalizeMailbox(policy.sender);
|
|
1316
|
+
return [
|
|
1317
|
+
...policies.filter((item) => normalizeMailbox(item.sender) !== sender),
|
|
1318
|
+
{
|
|
1319
|
+
...policy,
|
|
1320
|
+
sender,
|
|
1321
|
+
dispatchContextRules: normalizeDispatchContextRules(policy.dispatchContextRules) ?? {}
|
|
1322
|
+
}
|
|
1323
|
+
];
|
|
1324
|
+
}
|
|
1325
|
+
function matchPairedSenderPolicy(policies, sender, dispatchContext) {
|
|
1326
|
+
if (policies.length === 0) {
|
|
1327
|
+
return { allowed: false, reason: "no paired sender policies configured" };
|
|
1328
|
+
}
|
|
1329
|
+
const normalizedSender = normalizeMailbox(sender);
|
|
1330
|
+
const policy = policies.find((item) => normalizeMailbox(item.sender) === normalizedSender);
|
|
1331
|
+
if (!policy) {
|
|
1332
|
+
return { allowed: false, reason: `sender ${sender} is not paired` };
|
|
1333
|
+
}
|
|
1334
|
+
for (const [key, allowedValues] of Object.entries(policy.dispatchContextRules ?? {})) {
|
|
1335
|
+
if (!Array.isArray(allowedValues) || allowedValues.length === 0)
|
|
1336
|
+
continue;
|
|
1337
|
+
const observed = dispatchContext?.[key];
|
|
1338
|
+
if (!observed || !allowedValues.includes(observed)) {
|
|
1339
|
+
return { allowed: false, reason: `dispatchContext does not match paired sender policy for ${sender}` };
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return { allowed: true };
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// ../sdk/dist/smtp-sender.js
|
|
1118
1346
|
import { createTransport } from "nodemailer";
|
|
1119
1347
|
import { randomUUID } from "crypto";
|
|
1120
1348
|
var sanitize = (s) => s.replace(/[\r\n]/g, " ").trim();
|
|
1349
|
+
var HTTP_SEND_MAX_ATTEMPTS = 4;
|
|
1350
|
+
var HTTP_SEND_RETRY_BASE_DELAY_MS = 500;
|
|
1351
|
+
function sleep2(ms) {
|
|
1352
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1353
|
+
}
|
|
1354
|
+
function isRetryableHttpStatus(status) {
|
|
1355
|
+
return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
1356
|
+
}
|
|
1357
|
+
function describeError2(err) {
|
|
1358
|
+
if (!(err instanceof Error))
|
|
1359
|
+
return String(err);
|
|
1360
|
+
const details = err;
|
|
1361
|
+
const parts = [err.message];
|
|
1362
|
+
if (details.code)
|
|
1363
|
+
parts.push(`code=${details.code}`);
|
|
1364
|
+
if (details.cause instanceof Error)
|
|
1365
|
+
parts.push(`cause=${describeError2(details.cause)}`);
|
|
1366
|
+
return parts.join(" | ");
|
|
1367
|
+
}
|
|
1121
1368
|
function deriveMailboxServiceDefaults(email, baseUrl2) {
|
|
1122
1369
|
const domain = email.split("@")[1]?.trim();
|
|
1123
1370
|
const resolvedBaseUrl = baseUrl2?.trim() || (domain ? `https://${domain}` : void 0);
|
|
@@ -1130,6 +1377,7 @@ function deriveMailboxServiceDefaults(email, baseUrl2) {
|
|
|
1130
1377
|
var SmtpSender = class _SmtpSender {
|
|
1131
1378
|
config;
|
|
1132
1379
|
transport;
|
|
1380
|
+
fetch;
|
|
1133
1381
|
discoveredApiUrlPromise = null;
|
|
1134
1382
|
jmapSessionPromise = null;
|
|
1135
1383
|
sentMailboxIdPromise = null;
|
|
@@ -1142,12 +1390,16 @@ var SmtpSender = class _SmtpSender {
|
|
|
1142
1390
|
password: config.password,
|
|
1143
1391
|
httpBaseUrl: derived.httpBaseUrl,
|
|
1144
1392
|
authToken: Buffer.from(`${config.email}:${config.password}`).toString("base64"),
|
|
1393
|
+
fetch: config.fetch,
|
|
1394
|
+
forceHttpSend: config.forceHttpSend,
|
|
1395
|
+
persistSentCopy: config.persistSentCopy,
|
|
1145
1396
|
secure: config.secure,
|
|
1146
1397
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1147
1398
|
});
|
|
1148
1399
|
}
|
|
1149
1400
|
constructor(config) {
|
|
1150
1401
|
this.config = config;
|
|
1402
|
+
this.fetch = config.fetch ?? fetch;
|
|
1151
1403
|
this.transport = createTransport({
|
|
1152
1404
|
host: config.host,
|
|
1153
1405
|
port: config.port,
|
|
@@ -1168,6 +1420,9 @@ var SmtpSender = class _SmtpSender {
|
|
|
1168
1420
|
return email.split("@")[1]?.toLowerCase() ?? "";
|
|
1169
1421
|
}
|
|
1170
1422
|
shouldUseHttpFallback(to) {
|
|
1423
|
+
if (this.config.forceHttpSend) {
|
|
1424
|
+
return Boolean(this.config.httpBaseUrl && this.config.authToken);
|
|
1425
|
+
}
|
|
1171
1426
|
return Boolean(this.config.httpBaseUrl && this.config.authToken && this.senderDomain() && this.senderDomain() === this.recipientDomain(to));
|
|
1172
1427
|
}
|
|
1173
1428
|
async resolveAampApiUrl() {
|
|
@@ -1177,7 +1432,7 @@ var SmtpSender = class _SmtpSender {
|
|
|
1177
1432
|
}
|
|
1178
1433
|
if (!this.discoveredApiUrlPromise) {
|
|
1179
1434
|
this.discoveredApiUrlPromise = (async () => {
|
|
1180
|
-
const discoveryRes = await fetch(`${base}/.well-known/aamp`);
|
|
1435
|
+
const discoveryRes = await this.fetch(`${base}/.well-known/aamp`);
|
|
1181
1436
|
if (!discoveryRes.ok) {
|
|
1182
1437
|
throw new Error(`AAMP discovery failed: ${discoveryRes.status}`);
|
|
1183
1438
|
}
|
|
@@ -1199,33 +1454,49 @@ var SmtpSender = class _SmtpSender {
|
|
|
1199
1454
|
if (!this.config.authToken) {
|
|
1200
1455
|
throw new Error("HTTP send fallback is not configured");
|
|
1201
1456
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1457
|
+
let lastError = null;
|
|
1458
|
+
for (let attempt = 1; attempt <= HTTP_SEND_MAX_ATTEMPTS; attempt += 1) {
|
|
1459
|
+
const apiUrl = new URL(await this.resolveAampApiUrl());
|
|
1460
|
+
apiUrl.searchParams.set("action", "aamp.mailbox.send");
|
|
1461
|
+
try {
|
|
1462
|
+
const res = await this.fetch(apiUrl, {
|
|
1463
|
+
method: "POST",
|
|
1464
|
+
headers: {
|
|
1465
|
+
Authorization: `Basic ${this.config.authToken}`,
|
|
1466
|
+
"Content-Type": "application/json"
|
|
1467
|
+
},
|
|
1468
|
+
body: JSON.stringify({
|
|
1469
|
+
to: opts.to,
|
|
1470
|
+
subject: opts.subject,
|
|
1471
|
+
text: opts.text,
|
|
1472
|
+
aampHeaders: opts.aampHeaders,
|
|
1473
|
+
attachments: opts.attachments?.map((a) => ({
|
|
1474
|
+
filename: a.filename,
|
|
1475
|
+
contentType: a.contentType,
|
|
1476
|
+
content: typeof a.content === "string" ? a.content : a.content.toString("base64")
|
|
1477
|
+
}))
|
|
1478
|
+
})
|
|
1479
|
+
});
|
|
1480
|
+
const data = await res.json().catch(() => ({}));
|
|
1481
|
+
if (res.ok)
|
|
1482
|
+
return { messageId: data.messageId };
|
|
1483
|
+
lastError = new Error(data.details || `HTTP send failed: ${res.status}`);
|
|
1484
|
+
if (!isRetryableHttpStatus(res.status) || attempt === HTTP_SEND_MAX_ATTEMPTS)
|
|
1485
|
+
break;
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1488
|
+
if (attempt === HTTP_SEND_MAX_ATTEMPTS) {
|
|
1489
|
+
lastError = new Error(`HTTP send failed after ${attempt} attempts: ${describeError2(lastError)}`);
|
|
1490
|
+
break;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
await sleep2(HTTP_SEND_RETRY_BASE_DELAY_MS * attempt);
|
|
1225
1494
|
}
|
|
1226
|
-
|
|
1495
|
+
throw lastError ?? new Error("HTTP send failed");
|
|
1227
1496
|
}
|
|
1228
1497
|
canPersistSentCopy() {
|
|
1498
|
+
if (this.config.persistSentCopy === false)
|
|
1499
|
+
return false;
|
|
1229
1500
|
return Boolean(this.config.httpBaseUrl && this.config.authToken);
|
|
1230
1501
|
}
|
|
1231
1502
|
getJmapAuthHeader() {
|
|
@@ -1241,7 +1512,7 @@ var SmtpSender = class _SmtpSender {
|
|
|
1241
1512
|
}
|
|
1242
1513
|
if (!this.jmapSessionPromise) {
|
|
1243
1514
|
this.jmapSessionPromise = (async () => {
|
|
1244
|
-
const res = await fetch(`${base}/.well-known/jmap`, {
|
|
1515
|
+
const res = await this.fetch(`${base}/.well-known/jmap`, {
|
|
1245
1516
|
headers: { Authorization: this.getJmapAuthHeader() }
|
|
1246
1517
|
});
|
|
1247
1518
|
if (!res.ok) {
|
|
@@ -1267,7 +1538,7 @@ var SmtpSender = class _SmtpSender {
|
|
|
1267
1538
|
}
|
|
1268
1539
|
async jmapCall(methodCalls) {
|
|
1269
1540
|
const session = await this.resolveJmapSession();
|
|
1270
|
-
const res = await fetch(session.apiUrl, {
|
|
1541
|
+
const res = await this.fetch(session.apiUrl, {
|
|
1271
1542
|
method: "POST",
|
|
1272
1543
|
headers: {
|
|
1273
1544
|
Authorization: this.getJmapAuthHeader(),
|
|
@@ -1364,7 +1635,6 @@ var SmtpSender = class _SmtpSender {
|
|
|
1364
1635
|
taskId,
|
|
1365
1636
|
priority: opts.priority,
|
|
1366
1637
|
expiresAt: opts.expiresAt,
|
|
1367
|
-
contextLinks: opts.contextLinks ?? [],
|
|
1368
1638
|
dispatchContext: opts.dispatchContext,
|
|
1369
1639
|
parentTaskId: opts.parentTaskId
|
|
1370
1640
|
});
|
|
@@ -1372,13 +1642,11 @@ var SmtpSender = class _SmtpSender {
|
|
|
1372
1642
|
from: this.config.user,
|
|
1373
1643
|
to: opts.to,
|
|
1374
1644
|
subject: `[AAMP Task] ${sanitize(opts.title)}`,
|
|
1375
|
-
text: [
|
|
1645
|
+
text: opts.rawBodyText ?? [
|
|
1376
1646
|
`Task: ${opts.title}`,
|
|
1377
1647
|
`Task ID: ${taskId}`,
|
|
1378
1648
|
`Priority: ${opts.priority ?? "normal"}`,
|
|
1379
1649
|
opts.expiresAt ? `Expires At: ${opts.expiresAt}` : `Expires At: none`,
|
|
1380
|
-
opts.contextLinks?.length ? `Context:
|
|
1381
|
-
${opts.contextLinks.map((l) => ` ${l}`).join("\n")}` : "",
|
|
1382
1650
|
opts.bodyText ?? "",
|
|
1383
1651
|
``,
|
|
1384
1652
|
`--- This email was sent by AAMP. Reply directly to submit your result. ---`
|
|
@@ -1440,7 +1708,7 @@ ${opts.contextLinks.map((l) => ` ${l}`).join("\n")}` : "",
|
|
|
1440
1708
|
from: this.config.user,
|
|
1441
1709
|
to: opts.to,
|
|
1442
1710
|
subject: `[AAMP Result] Task ${opts.taskId} \u2014 ${opts.status}`,
|
|
1443
|
-
text: [
|
|
1711
|
+
text: opts.rawBodyText ?? [
|
|
1444
1712
|
`AAMP Task Result`,
|
|
1445
1713
|
``,
|
|
1446
1714
|
`Task ID: ${opts.taskId}`,
|
|
@@ -1514,7 +1782,7 @@ Error: ${opts.errorMsg}` : ""
|
|
|
1514
1782
|
from: this.config.user,
|
|
1515
1783
|
to: opts.to,
|
|
1516
1784
|
subject: `[AAMP Help] Task ${opts.taskId} needs assistance`,
|
|
1517
|
-
text: [
|
|
1785
|
+
text: opts.rawBodyText ?? [
|
|
1518
1786
|
`AAMP Task Help Request`,
|
|
1519
1787
|
``,
|
|
1520
1788
|
`Task ID: ${opts.taskId}`,
|
|
@@ -1721,6 +1989,110 @@ Stream ID: ${opts.streamId}`,
|
|
|
1721
1989
|
references: opts.inReplyTo
|
|
1722
1990
|
});
|
|
1723
1991
|
}
|
|
1992
|
+
async sendPairRequest(opts) {
|
|
1993
|
+
const taskId = opts.taskId ?? randomUUID();
|
|
1994
|
+
const aampHeaders = buildPairRequestHeaders({
|
|
1995
|
+
taskId,
|
|
1996
|
+
pairCode: opts.pairCode,
|
|
1997
|
+
dispatchContextRules: opts.dispatchContextRules ?? {}
|
|
1998
|
+
});
|
|
1999
|
+
const text = [
|
|
2000
|
+
"AAMP Pair Request",
|
|
2001
|
+
"",
|
|
2002
|
+
`Pair code: ${opts.pairCode}`,
|
|
2003
|
+
`Dispatch context rules: ${JSON.stringify(opts.dispatchContextRules ?? {})}`
|
|
2004
|
+
].join("\n");
|
|
2005
|
+
const mailOpts = {
|
|
2006
|
+
from: this.config.user,
|
|
2007
|
+
to: opts.to,
|
|
2008
|
+
subject: "[AAMP Pair] Connection request",
|
|
2009
|
+
text,
|
|
2010
|
+
headers: aampHeaders
|
|
2011
|
+
};
|
|
2012
|
+
if (this.shouldUseHttpFallback(opts.to)) {
|
|
2013
|
+
const info2 = await this.sendViaHttp({
|
|
2014
|
+
to: opts.to,
|
|
2015
|
+
subject: mailOpts.subject,
|
|
2016
|
+
text,
|
|
2017
|
+
aampHeaders
|
|
2018
|
+
});
|
|
2019
|
+
await this.saveToSentBestEffort({
|
|
2020
|
+
from: this.config.user,
|
|
2021
|
+
to: opts.to,
|
|
2022
|
+
subject: mailOpts.subject,
|
|
2023
|
+
text,
|
|
2024
|
+
aampHeaders,
|
|
2025
|
+
messageId: info2.messageId
|
|
2026
|
+
});
|
|
2027
|
+
return { taskId, messageId: info2.messageId ?? "" };
|
|
2028
|
+
}
|
|
2029
|
+
const info = await this.transport.sendMail(mailOpts);
|
|
2030
|
+
await this.saveToSentBestEffort({
|
|
2031
|
+
from: this.config.user,
|
|
2032
|
+
to: opts.to,
|
|
2033
|
+
subject: mailOpts.subject,
|
|
2034
|
+
text,
|
|
2035
|
+
aampHeaders,
|
|
2036
|
+
messageId: info.messageId
|
|
2037
|
+
});
|
|
2038
|
+
return { taskId, messageId: info.messageId ?? "" };
|
|
2039
|
+
}
|
|
2040
|
+
async sendPairRespond(opts) {
|
|
2041
|
+
const aampHeaders = buildPairRespondHeaders({
|
|
2042
|
+
taskId: opts.taskId,
|
|
2043
|
+
success: opts.success,
|
|
2044
|
+
reason: opts.reason
|
|
2045
|
+
});
|
|
2046
|
+
const status = opts.success ? "completed" : "rejected";
|
|
2047
|
+
const text = [
|
|
2048
|
+
"AAMP Pair Response",
|
|
2049
|
+
"",
|
|
2050
|
+
`Task ID: ${opts.taskId}`,
|
|
2051
|
+
`Status: ${status}`,
|
|
2052
|
+
...opts.reason?.trim() ? ["", `Reason: ${opts.reason.trim()}`] : []
|
|
2053
|
+
].join("\n");
|
|
2054
|
+
const mailOpts = {
|
|
2055
|
+
from: this.config.user,
|
|
2056
|
+
to: opts.to,
|
|
2057
|
+
subject: `[AAMP Pair] ${status}`,
|
|
2058
|
+
text,
|
|
2059
|
+
headers: aampHeaders
|
|
2060
|
+
};
|
|
2061
|
+
if (opts.inReplyTo) {
|
|
2062
|
+
mailOpts.inReplyTo = opts.inReplyTo;
|
|
2063
|
+
mailOpts.references = opts.inReplyTo;
|
|
2064
|
+
}
|
|
2065
|
+
if (this.shouldUseHttpFallback(opts.to)) {
|
|
2066
|
+
const info2 = await this.sendViaHttp({
|
|
2067
|
+
to: opts.to,
|
|
2068
|
+
subject: mailOpts.subject,
|
|
2069
|
+
text,
|
|
2070
|
+
aampHeaders
|
|
2071
|
+
});
|
|
2072
|
+
await this.saveToSentBestEffort({
|
|
2073
|
+
from: this.config.user,
|
|
2074
|
+
to: opts.to,
|
|
2075
|
+
subject: mailOpts.subject,
|
|
2076
|
+
text,
|
|
2077
|
+
aampHeaders,
|
|
2078
|
+
messageId: info2.messageId,
|
|
2079
|
+
inReplyTo: opts.inReplyTo,
|
|
2080
|
+
references: opts.inReplyTo
|
|
2081
|
+
});
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
const info = await this.transport.sendMail(mailOpts);
|
|
2085
|
+
await this.saveToSentBestEffort({
|
|
2086
|
+
from: this.config.user,
|
|
2087
|
+
to: opts.to,
|
|
2088
|
+
subject: mailOpts.subject,
|
|
2089
|
+
text,
|
|
2090
|
+
aampHeaders,
|
|
2091
|
+
messageId: info.messageId,
|
|
2092
|
+
inReplyTo: opts.inReplyTo,
|
|
2093
|
+
references: opts.inReplyTo
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
1724
2096
|
async sendCardQuery(opts) {
|
|
1725
2097
|
const taskId = opts.taskId ?? randomUUID();
|
|
1726
2098
|
const aampHeaders = buildCardQueryHeaders({ taskId });
|
|
@@ -1830,7 +2202,7 @@ Stream ID: ${opts.streamId}`,
|
|
|
1830
2202
|
}
|
|
1831
2203
|
};
|
|
1832
2204
|
|
|
1833
|
-
// ../sdk/
|
|
2205
|
+
// ../sdk/dist/thread.js
|
|
1834
2206
|
function singleLine(value, maxLength = 220) {
|
|
1835
2207
|
const normalized = (value ?? "").replace(/\s+/g, " ").trim();
|
|
1836
2208
|
if (!normalized)
|
|
@@ -1884,7 +2256,7 @@ function renderThreadHistoryForAgent(events, options = {}) {
|
|
|
1884
2256
|
].join("\n");
|
|
1885
2257
|
}
|
|
1886
2258
|
|
|
1887
|
-
// ../sdk/
|
|
2259
|
+
// ../sdk/dist/client.js
|
|
1888
2260
|
function buildRegisteredCommandDispatchPayload(opts) {
|
|
1889
2261
|
const command = opts.command.trim();
|
|
1890
2262
|
if (!command) {
|
|
@@ -1961,6 +2333,9 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
1961
2333
|
password: config.smtpPassword,
|
|
1962
2334
|
httpBaseUrl: config.httpSendBaseUrl ?? resolvedBaseUrl,
|
|
1963
2335
|
authToken: mailboxToken,
|
|
2336
|
+
fetch: config.fetch,
|
|
2337
|
+
forceHttpSend: config.forceHttpSend,
|
|
2338
|
+
persistSentCopy: config.persistSentCopy,
|
|
1964
2339
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1965
2340
|
});
|
|
1966
2341
|
this.jmapClient.on("task.dispatch", (task) => {
|
|
@@ -1981,6 +2356,12 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
1981
2356
|
this.jmapClient.on("task.stream.opened", (stream) => {
|
|
1982
2357
|
this.emit("task.stream.opened", stream);
|
|
1983
2358
|
});
|
|
2359
|
+
this.jmapClient.on("pair.request", (request) => {
|
|
2360
|
+
this.emit("pair.request", request);
|
|
2361
|
+
});
|
|
2362
|
+
this.jmapClient.on("pair.respond", (response) => {
|
|
2363
|
+
this.emit("pair.respond", response);
|
|
2364
|
+
});
|
|
1984
2365
|
this.jmapClient.on("card.query", (query) => {
|
|
1985
2366
|
this.emit("card.query", query);
|
|
1986
2367
|
});
|
|
@@ -2018,12 +2399,23 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2018
2399
|
smtpPassword: config.smtpPassword,
|
|
2019
2400
|
reconnectInterval: config.reconnectInterval,
|
|
2020
2401
|
taskDispatchConcurrency: config.taskDispatchConcurrency,
|
|
2402
|
+
fetch: config.fetch,
|
|
2403
|
+
forceHttpSend: config.forceHttpSend,
|
|
2404
|
+
persistSentCopy: config.persistSentCopy,
|
|
2021
2405
|
rejectUnauthorized: config.rejectUnauthorized
|
|
2022
2406
|
});
|
|
2023
2407
|
}
|
|
2024
|
-
static
|
|
2408
|
+
static createPairingCode = createPairingCode;
|
|
2409
|
+
static buildPairingUrl = buildPairingUrl;
|
|
2410
|
+
static parsePairingUrl = parsePairingUrl;
|
|
2411
|
+
static isPairingUrl = isPairingUrl;
|
|
2412
|
+
static consumePairingCode = consumePairingCode;
|
|
2413
|
+
static createPairedSenderPolicy = createPairedSenderPolicy;
|
|
2414
|
+
static upsertPairedSenderPolicy = upsertPairedSenderPolicy;
|
|
2415
|
+
static matchPairedSenderPolicy = matchPairedSenderPolicy;
|
|
2416
|
+
static async discoverAampService(aampHost, fetchImpl = fetch) {
|
|
2025
2417
|
const base = aampHost.replace(/\/$/, "");
|
|
2026
|
-
const res = await
|
|
2418
|
+
const res = await fetchImpl(`${base}/.well-known/aamp`);
|
|
2027
2419
|
if (!res.ok) {
|
|
2028
2420
|
throw new Error(`AAMP discovery failed: ${res.status} ${res.statusText}`);
|
|
2029
2421
|
}
|
|
@@ -2034,7 +2426,8 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2034
2426
|
return discovery;
|
|
2035
2427
|
}
|
|
2036
2428
|
static async callDiscoveredApi(base, opts) {
|
|
2037
|
-
const
|
|
2429
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
2430
|
+
const discovery = await _AampClient.discoverAampService(base, fetchImpl);
|
|
2038
2431
|
const apiUrl = new URL(discovery.api.url, `${base}/`);
|
|
2039
2432
|
apiUrl.searchParams.set("action", opts.action);
|
|
2040
2433
|
for (const [key, value] of Object.entries(opts.query ?? {})) {
|
|
@@ -2042,7 +2435,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2042
2435
|
continue;
|
|
2043
2436
|
apiUrl.searchParams.set(key, String(value));
|
|
2044
2437
|
}
|
|
2045
|
-
return
|
|
2438
|
+
return fetchImpl(apiUrl, {
|
|
2046
2439
|
method: opts.method ?? "GET",
|
|
2047
2440
|
headers: {
|
|
2048
2441
|
...opts.authToken ? { Authorization: `Basic ${opts.authToken}` } : {},
|
|
@@ -2088,6 +2481,22 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2088
2481
|
baseUrl: base
|
|
2089
2482
|
};
|
|
2090
2483
|
}
|
|
2484
|
+
static async checkMailbox(opts) {
|
|
2485
|
+
const base = opts.aampHost.replace(/\/$/, "");
|
|
2486
|
+
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2487
|
+
action: "aamp.mailbox.check",
|
|
2488
|
+
query: { email: opts.email }
|
|
2489
|
+
});
|
|
2490
|
+
if (!res.ok) {
|
|
2491
|
+
const body = await res.text().catch(() => "");
|
|
2492
|
+
throw new Error(`Mailbox check failed: ${res.status} ${body || res.statusText}`);
|
|
2493
|
+
}
|
|
2494
|
+
const payload = await res.json();
|
|
2495
|
+
return {
|
|
2496
|
+
aamp: Boolean(payload.aamp),
|
|
2497
|
+
...payload.domain ? { domain: payload.domain } : {}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2091
2500
|
// =====================================================
|
|
2092
2501
|
// Lifecycle
|
|
2093
2502
|
// =====================================================
|
|
@@ -2134,7 +2543,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2134
2543
|
rawBodyText: JSON.stringify(payload, null, 2),
|
|
2135
2544
|
priority: opts.priority,
|
|
2136
2545
|
expiresAt: opts.expiresAt,
|
|
2137
|
-
|
|
2546
|
+
sessionKey: opts.sessionKey,
|
|
2138
2547
|
dispatchContext: opts.dispatchContext,
|
|
2139
2548
|
parentTaskId: opts.parentTaskId,
|
|
2140
2549
|
attachments: opts.attachments
|
|
@@ -2158,6 +2567,12 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2158
2567
|
async sendStreamOpened(opts) {
|
|
2159
2568
|
return this.smtpSender.sendStreamOpened(opts);
|
|
2160
2569
|
}
|
|
2570
|
+
async sendPairRequest(opts) {
|
|
2571
|
+
return this.smtpSender.sendPairRequest(opts);
|
|
2572
|
+
}
|
|
2573
|
+
async sendPairRespond(opts) {
|
|
2574
|
+
return this.smtpSender.sendPairRespond(opts);
|
|
2575
|
+
}
|
|
2161
2576
|
async sendCardQuery(opts) {
|
|
2162
2577
|
return this.smtpSender.sendCardQuery(opts);
|
|
2163
2578
|
}
|
|
@@ -2171,6 +2586,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2171
2586
|
action: "aamp.directory.upsert",
|
|
2172
2587
|
method: "POST",
|
|
2173
2588
|
authToken: mailboxToken,
|
|
2589
|
+
fetch: this.config.fetch,
|
|
2174
2590
|
body: opts
|
|
2175
2591
|
});
|
|
2176
2592
|
if (!res.ok) {
|
|
@@ -2186,6 +2602,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2186
2602
|
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2187
2603
|
action: "aamp.directory.list",
|
|
2188
2604
|
authToken: mailboxToken,
|
|
2605
|
+
fetch: this.config.fetch,
|
|
2189
2606
|
query: {
|
|
2190
2607
|
scope: opts.scope,
|
|
2191
2608
|
includeSelf: opts.includeSelf,
|
|
@@ -2205,6 +2622,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2205
2622
|
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2206
2623
|
action: "aamp.directory.search",
|
|
2207
2624
|
authToken: mailboxToken,
|
|
2625
|
+
fetch: this.config.fetch,
|
|
2208
2626
|
query: {
|
|
2209
2627
|
q: opts.query,
|
|
2210
2628
|
scope: opts.scope,
|
|
@@ -2225,6 +2643,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2225
2643
|
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2226
2644
|
action: "aamp.mailbox.thread",
|
|
2227
2645
|
authToken: mailboxToken,
|
|
2646
|
+
fetch: this.config.fetch,
|
|
2228
2647
|
query: {
|
|
2229
2648
|
taskId,
|
|
2230
2649
|
includeStreamOpened: opts.includeStreamOpened
|
|
@@ -2250,7 +2669,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2250
2669
|
};
|
|
2251
2670
|
}
|
|
2252
2671
|
async resolveStreamCapability() {
|
|
2253
|
-
const discovery = await _AampClient.discoverAampService(this.config.baseUrl);
|
|
2672
|
+
const discovery = await _AampClient.discoverAampService(this.config.baseUrl, this.config.fetch);
|
|
2254
2673
|
const stream = discovery.capabilities?.stream;
|
|
2255
2674
|
if (!stream?.transport) {
|
|
2256
2675
|
throw new Error("AAMP stream capability is not available on this service");
|
|
@@ -2263,6 +2682,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2263
2682
|
action: stream.createAction ?? "aamp.stream.create",
|
|
2264
2683
|
method: "POST",
|
|
2265
2684
|
authToken: this.config.mailboxToken,
|
|
2685
|
+
fetch: this.config.fetch,
|
|
2266
2686
|
body: opts
|
|
2267
2687
|
});
|
|
2268
2688
|
if (!res.ok) {
|
|
@@ -2309,6 +2729,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2309
2729
|
action: stream.appendAction ?? "aamp.stream.append",
|
|
2310
2730
|
method: "POST",
|
|
2311
2731
|
authToken: this.config.mailboxToken,
|
|
2732
|
+
fetch: this.config.fetch,
|
|
2312
2733
|
body: opts
|
|
2313
2734
|
});
|
|
2314
2735
|
if (!res.ok) {
|
|
@@ -2414,6 +2835,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2414
2835
|
action: stream.closeAction ?? "aamp.stream.close",
|
|
2415
2836
|
method: "POST",
|
|
2416
2837
|
authToken: this.config.mailboxToken,
|
|
2838
|
+
fetch: this.config.fetch,
|
|
2417
2839
|
body: opts
|
|
2418
2840
|
});
|
|
2419
2841
|
if (!res.ok) {
|
|
@@ -2427,6 +2849,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2427
2849
|
const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
|
|
2428
2850
|
action: stream.getAction ?? "aamp.stream.get",
|
|
2429
2851
|
authToken: this.config.mailboxToken,
|
|
2852
|
+
fetch: this.config.fetch,
|
|
2430
2853
|
query: {
|
|
2431
2854
|
...opts.taskId ? { taskId: opts.taskId } : {},
|
|
2432
2855
|
...opts.streamId ? { streamId: opts.streamId } : {}
|
|
@@ -2453,7 +2876,8 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2453
2876
|
if (opts.signal) {
|
|
2454
2877
|
opts.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
2455
2878
|
}
|
|
2456
|
-
const
|
|
2879
|
+
const fetchImpl = this.config.fetch ?? fetch;
|
|
2880
|
+
const res = await fetchImpl(url, {
|
|
2457
2881
|
headers: {
|
|
2458
2882
|
Authorization: `Basic ${this.config.mailboxToken}`,
|
|
2459
2883
|
Accept: "text/event-stream"
|
|
@@ -2564,12 +2988,19 @@ import { readFileSync as readFileSync2 } from "node:fs";
|
|
|
2564
2988
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2565
2989
|
import { dirname, join } from "node:path";
|
|
2566
2990
|
import { homedir } from "node:os";
|
|
2991
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2567
2992
|
function defaultCredentialsPath() {
|
|
2568
2993
|
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
|
|
2569
2994
|
}
|
|
2570
2995
|
function defaultTaskStatePath() {
|
|
2571
2996
|
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".task-state.json");
|
|
2572
2997
|
}
|
|
2998
|
+
function defaultPairingPath() {
|
|
2999
|
+
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".pairing.json");
|
|
3000
|
+
}
|
|
3001
|
+
function defaultSenderPoliciesPath() {
|
|
3002
|
+
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".sender-policies.json");
|
|
3003
|
+
}
|
|
2573
3004
|
function loadCachedIdentity(file) {
|
|
2574
3005
|
const resolved = file ?? defaultCredentialsPath();
|
|
2575
3006
|
if (!existsSync(resolved))
|
|
@@ -2616,6 +3047,66 @@ function saveTaskState(state, file) {
|
|
|
2616
3047
|
terminalTaskIds: state.terminalTaskIds ?? []
|
|
2617
3048
|
}, null, 2), "utf-8");
|
|
2618
3049
|
}
|
|
3050
|
+
function createPairingCode2(params) {
|
|
3051
|
+
const pairCode = randomBytes2(6).toString("base64url");
|
|
3052
|
+
const expiresAt = new Date(Date.now() + (params.ttlSeconds ?? 300) * 1e3).toISOString();
|
|
3053
|
+
const connectUrl = `aamp://connect?mailbox=${encodeURIComponent(params.mailbox.toLowerCase())}&pair_code=${encodeURIComponent(pairCode)}`;
|
|
3054
|
+
const state = {
|
|
3055
|
+
mailbox: params.mailbox.toLowerCase(),
|
|
3056
|
+
pairCode,
|
|
3057
|
+
expiresAt,
|
|
3058
|
+
connectUrl
|
|
3059
|
+
};
|
|
3060
|
+
const resolved = params.file ?? defaultPairingPath();
|
|
3061
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
3062
|
+
writeFileSync(resolved, JSON.stringify(state, null, 2), "utf-8");
|
|
3063
|
+
return state;
|
|
3064
|
+
}
|
|
3065
|
+
function consumePairingCode2(params) {
|
|
3066
|
+
const resolved = params.file ?? defaultPairingPath();
|
|
3067
|
+
if (!existsSync(resolved))
|
|
3068
|
+
return null;
|
|
3069
|
+
const state = JSON.parse(readFileSync(resolved, "utf-8"));
|
|
3070
|
+
if (state.mailbox.toLowerCase() !== params.mailbox.toLowerCase())
|
|
3071
|
+
return null;
|
|
3072
|
+
if (state.pairCode !== params.pairCode)
|
|
3073
|
+
return null;
|
|
3074
|
+
if (state.consumedAt)
|
|
3075
|
+
return null;
|
|
3076
|
+
if (new Date(state.expiresAt).getTime() <= Date.now())
|
|
3077
|
+
return null;
|
|
3078
|
+
writeFileSync(resolved, JSON.stringify({ ...state, pairCode: "", consumedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
|
|
3079
|
+
return state;
|
|
3080
|
+
}
|
|
3081
|
+
function isPairedSenderPolicy(value) {
|
|
3082
|
+
if (!value || typeof value !== "object")
|
|
3083
|
+
return false;
|
|
3084
|
+
const policy = value;
|
|
3085
|
+
return typeof policy.sender === "string" && typeof policy.dispatchContextRules === "object" && typeof policy.pairedAt === "string";
|
|
3086
|
+
}
|
|
3087
|
+
function loadPairedSenderPolicies(file) {
|
|
3088
|
+
const resolved = file ?? defaultSenderPoliciesPath();
|
|
3089
|
+
if (!existsSync(resolved))
|
|
3090
|
+
return [];
|
|
3091
|
+
try {
|
|
3092
|
+
const data = JSON.parse(readFileSync(resolved, "utf-8"));
|
|
3093
|
+
return Array.isArray(data) ? data.filter(isPairedSenderPolicy) : [];
|
|
3094
|
+
} catch {
|
|
3095
|
+
return [];
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
function addPairedSenderPolicy(file, policy) {
|
|
3099
|
+
const resolved = file ?? defaultSenderPoliciesPath();
|
|
3100
|
+
const policies = loadPairedSenderPolicies(resolved);
|
|
3101
|
+
const normalizedSender = policy.sender.toLowerCase();
|
|
3102
|
+
const next = [
|
|
3103
|
+
...policies.filter((item) => item.sender.toLowerCase() !== normalizedSender),
|
|
3104
|
+
{ ...policy, sender: normalizedSender }
|
|
3105
|
+
];
|
|
3106
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
3107
|
+
writeFileSync(resolved, JSON.stringify(next, null, 2), "utf-8");
|
|
3108
|
+
return next;
|
|
3109
|
+
}
|
|
2619
3110
|
function ensureDir(dir) {
|
|
2620
3111
|
mkdirSync(dir, { recursive: true });
|
|
2621
3112
|
}
|
|
@@ -2630,7 +3121,7 @@ function writeBinaryFile(path, content) {
|
|
|
2630
3121
|
// src/index.ts
|
|
2631
3122
|
function matchSenderPolicy(task, senderPolicies) {
|
|
2632
3123
|
if (!senderPolicies?.length)
|
|
2633
|
-
return { allowed:
|
|
3124
|
+
return { allowed: false, reason: "no configured senderPolicies" };
|
|
2634
3125
|
const sender = task.from.toLowerCase();
|
|
2635
3126
|
const policy = senderPolicies.find((item) => item.sender.trim().toLowerCase() === sender);
|
|
2636
3127
|
if (!policy) {
|
|
@@ -2659,12 +3150,62 @@ function matchSenderPolicy(task, senderPolicies) {
|
|
|
2659
3150
|
}
|
|
2660
3151
|
return { allowed: true };
|
|
2661
3152
|
}
|
|
3153
|
+
function rulesMatch(rules, dispatchContext) {
|
|
3154
|
+
for (const [key, allowedValues] of Object.entries(rules ?? {})) {
|
|
3155
|
+
if (!Array.isArray(allowedValues) || allowedValues.length === 0)
|
|
3156
|
+
continue;
|
|
3157
|
+
const observed = dispatchContext?.[key];
|
|
3158
|
+
if (!observed || !allowedValues.includes(observed))
|
|
3159
|
+
return false;
|
|
3160
|
+
}
|
|
3161
|
+
return true;
|
|
3162
|
+
}
|
|
3163
|
+
function matchPairedSenderPolicy2(task, senderPolicies) {
|
|
3164
|
+
if (senderPolicies.length === 0)
|
|
3165
|
+
return { allowed: false, reason: "no paired sender policies configured" };
|
|
3166
|
+
const sender = task.from.toLowerCase();
|
|
3167
|
+
const policy = senderPolicies.find((item) => item.sender.trim().toLowerCase() === sender);
|
|
3168
|
+
if (!policy) {
|
|
3169
|
+
return { allowed: false, reason: `sender ${task.from} is not paired` };
|
|
3170
|
+
}
|
|
3171
|
+
if (!rulesMatch(policy.dispatchContextRules, task.dispatchContext)) {
|
|
3172
|
+
return { allowed: false, reason: `dispatchContext does not match paired sender policy for ${task.from}` };
|
|
3173
|
+
}
|
|
3174
|
+
return { allowed: true };
|
|
3175
|
+
}
|
|
3176
|
+
function matchCombinedSenderPolicy(task, configuredPolicies, pairedPolicies) {
|
|
3177
|
+
const hasConfiguredPolicies = (configuredPolicies?.length ?? 0) > 0;
|
|
3178
|
+
const hasPairedPolicies = pairedPolicies.length > 0;
|
|
3179
|
+
if (!hasConfiguredPolicies && !hasPairedPolicies) {
|
|
3180
|
+
return { allowed: false, reason: "no sender policy configured" };
|
|
3181
|
+
}
|
|
3182
|
+
const configuredDecision = hasConfiguredPolicies ? matchSenderPolicy(task, configuredPolicies) : { allowed: false, reason: "no configured senderPolicies" };
|
|
3183
|
+
const pairedDecision = hasPairedPolicies ? matchPairedSenderPolicy2(task, pairedPolicies) : { allowed: false, reason: "no paired sender policies configured" };
|
|
3184
|
+
if (pairedDecision.allowed)
|
|
3185
|
+
return pairedDecision;
|
|
3186
|
+
if (configuredDecision.allowed)
|
|
3187
|
+
return configuredDecision;
|
|
3188
|
+
return configuredDecision.reason ? configuredDecision : pairedDecision;
|
|
3189
|
+
}
|
|
2662
3190
|
function baseUrl(aampHost) {
|
|
2663
3191
|
if (aampHost.startsWith("http://") || aampHost.startsWith("https://")) {
|
|
2664
3192
|
return aampHost.replace(/\/$/, "");
|
|
2665
3193
|
}
|
|
2666
3194
|
return `https://${aampHost}`;
|
|
2667
3195
|
}
|
|
3196
|
+
async function renderTerminalQr(value) {
|
|
3197
|
+
try {
|
|
3198
|
+
const qrcode = await import("qrcode-terminal");
|
|
3199
|
+
const generator = qrcode.default?.generate ?? qrcode.generate;
|
|
3200
|
+
if (!generator)
|
|
3201
|
+
return "";
|
|
3202
|
+
return await new Promise((resolve) => {
|
|
3203
|
+
generator(value, { small: true }, (qr) => resolve(qr));
|
|
3204
|
+
});
|
|
3205
|
+
} catch {
|
|
3206
|
+
return "";
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
2668
3209
|
var pendingTasks = /* @__PURE__ */ new Map();
|
|
2669
3210
|
var activeTaskStreams = /* @__PURE__ */ new Map();
|
|
2670
3211
|
var terminalTaskIds = new Set(loadTaskState(defaultTaskStatePath()).terminalTaskIds ?? []);
|
|
@@ -2688,6 +3229,7 @@ var transportMonitorTimer = null;
|
|
|
2688
3229
|
var historicalReconcileCompleted = false;
|
|
2689
3230
|
var channelRuntime = null;
|
|
2690
3231
|
var channelCfg = null;
|
|
3232
|
+
var pairedSenderPolicies = [];
|
|
2691
3233
|
async function ensureTaskStream(task) {
|
|
2692
3234
|
if (!aampClient?.isConnected())
|
|
2693
3235
|
return null;
|
|
@@ -2756,17 +3298,8 @@ function isTaskAwaitingHelpReply(task) {
|
|
|
2756
3298
|
return task.awaitingHelpReply === true;
|
|
2757
3299
|
}
|
|
2758
3300
|
function isConversationalTask(task) {
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
function firstDispatchContextValue(context, keys) {
|
|
2762
|
-
if (!context)
|
|
2763
|
-
return void 0;
|
|
2764
|
-
for (const key of keys) {
|
|
2765
|
-
const value = context[key]?.trim();
|
|
2766
|
-
if (value)
|
|
2767
|
-
return value;
|
|
2768
|
-
}
|
|
2769
|
-
return void 0;
|
|
3301
|
+
const source = task.dispatchContext?.source?.trim().toLowerCase();
|
|
3302
|
+
return source === "feishu" || source === "wechat";
|
|
2770
3303
|
}
|
|
2771
3304
|
function threadAlreadyTerminal(events) {
|
|
2772
3305
|
return (events ?? []).some(
|
|
@@ -2814,8 +3347,8 @@ function buildOpenClawMainSessionKey(mainKey, config) {
|
|
|
2814
3347
|
function buildAampConversationSessionKey(value, config) {
|
|
2815
3348
|
return buildOpenClawMainSessionKey(`${AAMP_SESSION_PREFIX}default:${value}`, config);
|
|
2816
3349
|
}
|
|
2817
|
-
function buildAampStickySessionKey(
|
|
2818
|
-
const stickyValue =
|
|
3350
|
+
function buildAampStickySessionKey(sessionKey, config) {
|
|
3351
|
+
const stickyValue = sessionKey?.trim();
|
|
2819
3352
|
if (!stickyValue)
|
|
2820
3353
|
return void 0;
|
|
2821
3354
|
return buildAampConversationSessionKey(`session:${stickyValue}`, config);
|
|
@@ -2827,10 +3360,10 @@ function buildAampWakeSessionKey(kind, id) {
|
|
|
2827
3360
|
return `${AAMP_SESSION_PREFIX}wake:${kind}:${id}`;
|
|
2828
3361
|
}
|
|
2829
3362
|
function buildSessionKeyForPendingTask(task, config) {
|
|
2830
|
-
return buildAampStickySessionKey(task.
|
|
3363
|
+
return buildAampStickySessionKey(task.sessionKey, config) ?? buildAampTaskSessionKey(task.taskId, config);
|
|
2831
3364
|
}
|
|
2832
3365
|
function buildWakeSessionKeyForPendingTask(task, config) {
|
|
2833
|
-
return buildAampStickySessionKey(task.
|
|
3366
|
+
return buildAampStickySessionKey(task.sessionKey, config) ?? buildAampWakeSessionKey("task", task.taskId);
|
|
2834
3367
|
}
|
|
2835
3368
|
function findPendingEntryForSession(sessionKey, config) {
|
|
2836
3369
|
if (typeof sessionKey !== "string" || !isAampSessionKey(sessionKey))
|
|
@@ -2939,7 +3472,6 @@ function queuePendingTask(task) {
|
|
|
2939
3472
|
threadContextText: task.threadContextText ?? "",
|
|
2940
3473
|
priority: task.priority ?? "normal",
|
|
2941
3474
|
...task.expiresAt ? { expiresAt: task.expiresAt } : {},
|
|
2942
|
-
contextLinks: task.contextLinks ?? [],
|
|
2943
3475
|
messageId: task.messageId ?? "",
|
|
2944
3476
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2945
3477
|
});
|
|
@@ -2952,39 +3484,15 @@ function queuePendingTask(task) {
|
|
|
2952
3484
|
}
|
|
2953
3485
|
async function registerNode(cfg) {
|
|
2954
3486
|
const slug = (cfg.slug ?? "openclaw-agent").toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
2955
|
-
const
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
}
|
|
2960
|
-
const discovery = await discoveryRes.json();
|
|
2961
|
-
const apiUrl = discovery.api?.url;
|
|
2962
|
-
if (!apiUrl) {
|
|
2963
|
-
throw new Error("AAMP discovery did not return api.url");
|
|
2964
|
-
}
|
|
2965
|
-
const apiBase = new URL(apiUrl, `${base}/`).toString();
|
|
2966
|
-
const res = await fetch(`${apiBase}?action=aamp.mailbox.register`, {
|
|
2967
|
-
method: "POST",
|
|
2968
|
-
headers: { "Content-Type": "application/json" },
|
|
2969
|
-
body: JSON.stringify({ slug, description: "OpenClaw AAMP agent node" })
|
|
3487
|
+
const credData = await AampClient.registerMailbox({
|
|
3488
|
+
aampHost: cfg.aampHost,
|
|
3489
|
+
slug,
|
|
3490
|
+
description: "OpenClaw AAMP agent node"
|
|
2970
3491
|
});
|
|
2971
|
-
if (!res.ok) {
|
|
2972
|
-
const err = await res.json().catch(() => ({}));
|
|
2973
|
-
throw new Error(`AAMP registration failed (${res.status}): ${err.error ?? res.statusText}`);
|
|
2974
|
-
}
|
|
2975
|
-
const regData = await res.json();
|
|
2976
|
-
const credRes = await fetch(
|
|
2977
|
-
`${apiBase}?action=aamp.mailbox.credentials&code=${encodeURIComponent(regData.registrationCode)}`
|
|
2978
|
-
);
|
|
2979
|
-
if (!credRes.ok) {
|
|
2980
|
-
const err = await credRes.json().catch(() => ({}));
|
|
2981
|
-
throw new Error(`AAMP credential exchange failed (${credRes.status}): ${err.error ?? credRes.statusText}`);
|
|
2982
|
-
}
|
|
2983
|
-
const credData = await credRes.json();
|
|
2984
3492
|
return {
|
|
2985
3493
|
email: credData.email,
|
|
2986
|
-
mailboxToken: credData.
|
|
2987
|
-
smtpPassword: credData.
|
|
3494
|
+
mailboxToken: credData.mailboxToken,
|
|
3495
|
+
smtpPassword: credData.smtpPassword
|
|
2988
3496
|
};
|
|
2989
3497
|
}
|
|
2990
3498
|
async function resolveIdentity(cfg) {
|
|
@@ -3026,6 +3534,14 @@ var src_default = {
|
|
|
3026
3534
|
type: "string",
|
|
3027
3535
|
description: "Absolute path to cache AAMP credentials between gateway restarts. Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json. Delete this file to force re-registration with a new mailbox."
|
|
3028
3536
|
},
|
|
3537
|
+
pairingFile: {
|
|
3538
|
+
type: "string",
|
|
3539
|
+
description: "Absolute path to store the current one-time AAMP pairing code. Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.pairing.json."
|
|
3540
|
+
},
|
|
3541
|
+
senderPoliciesFile: {
|
|
3542
|
+
type: "string",
|
|
3543
|
+
description: "Absolute path to persist senders approved by pair.request. Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.sender-policies.json."
|
|
3544
|
+
},
|
|
3029
3545
|
senderPolicies: {
|
|
3030
3546
|
type: "array",
|
|
3031
3547
|
description: "Per-sender authorization policies. Each sender can optionally require specific X-AAMP-Dispatch-Context key/value pairs before a task is accepted.",
|
|
@@ -3116,6 +3632,70 @@ var src_default = {
|
|
|
3116
3632
|
});
|
|
3117
3633
|
api.logger.info(`[AAMP] Directory profile synced${cardText ? " (card text registered)" : ""}`);
|
|
3118
3634
|
}
|
|
3635
|
+
async function sendPairResponse(request, success, reason) {
|
|
3636
|
+
if (!aampClient)
|
|
3637
|
+
return;
|
|
3638
|
+
try {
|
|
3639
|
+
await aampClient.sendPairRespond({
|
|
3640
|
+
to: request.from,
|
|
3641
|
+
taskId: request.taskId,
|
|
3642
|
+
success,
|
|
3643
|
+
reason,
|
|
3644
|
+
inReplyTo: request.messageId
|
|
3645
|
+
});
|
|
3646
|
+
} catch (err) {
|
|
3647
|
+
api.logger.warn(`[AAMP] Failed to send pair.respond to ${request.from}: ${err.message}`);
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
async function handlePairRequest(request) {
|
|
3651
|
+
if (!agentEmail)
|
|
3652
|
+
return;
|
|
3653
|
+
if (request.to.trim().toLowerCase() !== agentEmail.trim().toLowerCase())
|
|
3654
|
+
return;
|
|
3655
|
+
const consumed = consumePairingCode2({
|
|
3656
|
+
file: cfg.pairingFile ?? defaultPairingPath(),
|
|
3657
|
+
mailbox: agentEmail,
|
|
3658
|
+
pairCode: request.pairCode
|
|
3659
|
+
});
|
|
3660
|
+
if (!consumed) {
|
|
3661
|
+
const reason = "invalid or expired pair code";
|
|
3662
|
+
api.logger.warn(`[AAMP] Rejected pair.request from ${request.from}: ${reason}`);
|
|
3663
|
+
await sendPairResponse(request, false, reason);
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
pairedSenderPolicies = addPairedSenderPolicy(cfg.senderPoliciesFile ?? defaultSenderPoliciesPath(), {
|
|
3667
|
+
sender: request.from.trim().toLowerCase(),
|
|
3668
|
+
dispatchContextRules: request.dispatchContextRules ?? {},
|
|
3669
|
+
pairedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3670
|
+
});
|
|
3671
|
+
api.logger.info(`[AAMP] Paired sender ${request.from}; sender policy saved to ${cfg.senderPoliciesFile ?? defaultSenderPoliciesPath()}`);
|
|
3672
|
+
await sendPairResponse(request, true);
|
|
3673
|
+
}
|
|
3674
|
+
async function renderPairingCodeForCurrentAgent() {
|
|
3675
|
+
const identity = agentEmail ? { email: agentEmail } : loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath());
|
|
3676
|
+
const email = identity?.email?.trim();
|
|
3677
|
+
if (!email) {
|
|
3678
|
+
return "Error: AAMP mailbox identity is not ready. Start the AAMP plugin service first.";
|
|
3679
|
+
}
|
|
3680
|
+
const pairing = createPairingCode2({
|
|
3681
|
+
mailbox: email,
|
|
3682
|
+
file: cfg.pairingFile ?? defaultPairingPath()
|
|
3683
|
+
});
|
|
3684
|
+
const qr = await renderTerminalQr(pairing.connectUrl);
|
|
3685
|
+
api.logger.info(`[AAMP] Pair with AAMP App before ${pairing.expiresAt}: ${pairing.connectUrl}`);
|
|
3686
|
+
if (qr)
|
|
3687
|
+
api.logger.info(`
|
|
3688
|
+
${qr}`);
|
|
3689
|
+
return [
|
|
3690
|
+
`Pair ${email} with AAMP App or another AAMP runtime.`,
|
|
3691
|
+
`Expires: ${pairing.expiresAt}`,
|
|
3692
|
+
qr ? `
|
|
3693
|
+
Scan this QR code:
|
|
3694
|
+
${qr}` : "\nCould not render a terminal QR code.",
|
|
3695
|
+
`
|
|
3696
|
+
Pairing URL: ${pairing.connectUrl}`
|
|
3697
|
+
].join("\n");
|
|
3698
|
+
}
|
|
3119
3699
|
function wakeAgentForPendingTask(task) {
|
|
3120
3700
|
const fallbackSessionKey = buildWakeSessionKeyForPendingTask(task, api.config);
|
|
3121
3701
|
const openClawSessionKey = buildSessionKeyForPendingTask(task, api.config);
|
|
@@ -3199,6 +3779,16 @@ var src_default = {
|
|
|
3199
3779
|
lastTransportMode = "disconnected";
|
|
3200
3780
|
lastLoggedTransportMode = "disconnected";
|
|
3201
3781
|
api.logger.info(`[AAMP] Mailbox identity ready \u2014 ${agentEmail}`);
|
|
3782
|
+
pairedSenderPolicies = loadPairedSenderPolicies(cfg.senderPoliciesFile ?? defaultSenderPoliciesPath());
|
|
3783
|
+
const pairing = createPairingCode2({
|
|
3784
|
+
mailbox: agentEmail,
|
|
3785
|
+
file: cfg.pairingFile ?? defaultPairingPath()
|
|
3786
|
+
});
|
|
3787
|
+
api.logger.info(`[AAMP] Pair with AAMP App before ${pairing.expiresAt}: ${pairing.connectUrl}`);
|
|
3788
|
+
const qr = await renderTerminalQr(pairing.connectUrl);
|
|
3789
|
+
if (qr)
|
|
3790
|
+
api.logger.info(`
|
|
3791
|
+
${qr}`);
|
|
3202
3792
|
const base = baseUrl(cfg.aampHost);
|
|
3203
3793
|
aampClient = AampClient.fromMailboxIdentity({
|
|
3204
3794
|
email: identity.email,
|
|
@@ -3217,7 +3807,7 @@ var src_default = {
|
|
|
3217
3807
|
api.logger.info(`[AAMP] Skipping already-terminal task ${task.taskId}`);
|
|
3218
3808
|
return;
|
|
3219
3809
|
}
|
|
3220
|
-
const decision =
|
|
3810
|
+
const decision = matchCombinedSenderPolicy(task, cfg.senderPolicies, pairedSenderPolicies);
|
|
3221
3811
|
if (!decision.allowed) {
|
|
3222
3812
|
api.logger.warn(`[AAMP] \u2717 rejected by senderPolicies: ${task.from} task=${task.taskId} reason=${decision.reason}`);
|
|
3223
3813
|
void aampClient.sendResult({
|
|
@@ -3274,6 +3864,11 @@ var src_default = {
|
|
|
3274
3864
|
api.logger.info(`[AAMP] Cancelled task ${cancel.taskId} \u2014 removed from pending queue`);
|
|
3275
3865
|
}
|
|
3276
3866
|
});
|
|
3867
|
+
aampClient.on("pair.request", (request) => {
|
|
3868
|
+
void handlePairRequest(request).catch((err) => {
|
|
3869
|
+
api.logger.warn(`[AAMP] Failed to handle pair.request: ${err.message}`);
|
|
3870
|
+
});
|
|
3871
|
+
});
|
|
3277
3872
|
aampClient.on("task.result", (result) => {
|
|
3278
3873
|
if (result.from.toLowerCase() === agentEmail.toLowerCase())
|
|
3279
3874
|
return;
|
|
@@ -3341,7 +3936,6 @@ ${truncatedOutput}${attachmentInfo}` : `Agent ${result.from} rejected the sub-ta
|
|
|
3341
3936
|
|
|
3342
3937
|
Reason: ${result.errorMsg ?? "unknown"}`,
|
|
3343
3938
|
priority: "urgent",
|
|
3344
|
-
contextLinks: [],
|
|
3345
3939
|
messageId: "",
|
|
3346
3940
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3347
3941
|
});
|
|
@@ -3424,7 +4018,6 @@ Question: ${help.question}
|
|
|
3424
4018
|
Blocked reason: ${help.blockedReason}${help.suggestedOptions?.length ? `
|
|
3425
4019
|
Suggested options: ${help.suggestedOptions.join(", ")}` : ""}`,
|
|
3426
4020
|
priority: "urgent",
|
|
3427
|
-
contextLinks: [],
|
|
3428
4021
|
messageId: "",
|
|
3429
4022
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3430
4023
|
});
|
|
@@ -3726,8 +4319,6 @@ ${Object.entries(task.dispatchContext).map(([key, value]) => ` - ${key}: ${valu
|
|
|
3726
4319
|
task.threadContextText ? `${task.threadContextText}` : "",
|
|
3727
4320
|
task.bodyText ? `Latest user message:
|
|
3728
4321
|
${task.bodyText}` : "",
|
|
3729
|
-
task.contextLinks.length ? `Context Links:
|
|
3730
|
-
${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
3731
4322
|
task.expiresAt ? `Expires: ${task.expiresAt}` : `Expires: none`,
|
|
3732
4323
|
`Received: ${task.receivedAt}`,
|
|
3733
4324
|
otherActionableTasks.length > 0 ? `
|
|
@@ -3768,8 +4359,6 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
|
3768
4359
|
task.threadContextText ? `${task.threadContextText}` : "",
|
|
3769
4360
|
task.bodyText ? `Description:
|
|
3770
4361
|
${task.bodyText}` : "",
|
|
3771
|
-
task.contextLinks.length ? `Context Links:
|
|
3772
|
-
${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
3773
4362
|
task.expiresAt ? `Expires: ${task.expiresAt}` : `Expires: none`,
|
|
3774
4363
|
`Received: ${task.receivedAt}`,
|
|
3775
4364
|
otherActionableTasks.length > 0 ? `
|
|
@@ -4091,6 +4680,14 @@ ${lines.join("\n")}`
|
|
|
4091
4680
|
};
|
|
4092
4681
|
}
|
|
4093
4682
|
}, { name: "aamp_pending_tasks" });
|
|
4683
|
+
api.registerTool({
|
|
4684
|
+
name: "aamp_pairing_code",
|
|
4685
|
+
description: "Generate a fresh five-minute AAMP pairing code for this OpenClaw agent and show a QR code. Use this when the user asks to pair AAMP App or another AAMP runtime with this agent.",
|
|
4686
|
+
parameters: { type: "object", properties: {} },
|
|
4687
|
+
execute: async () => ({
|
|
4688
|
+
content: [{ type: "text", text: await renderPairingCodeForCurrentAgent() }]
|
|
4689
|
+
})
|
|
4690
|
+
}, { name: "aamp_pairing_code" });
|
|
4094
4691
|
api.registerTool({
|
|
4095
4692
|
name: "aamp_cancel_task",
|
|
4096
4693
|
description: "Cancel a pending AAMP task and notify the dispatcher.",
|
|
@@ -4138,11 +4735,6 @@ ${lines.join("\n")}`
|
|
|
4138
4735
|
parentTaskId: { type: "string", description: "If you are processing a pending AAMP task, pass its Task ID here to establish parent-child nesting. Omit for top-level tasks." },
|
|
4139
4736
|
priority: { type: "string", enum: ["urgent", "high", "normal"], description: "Task priority (optional)" },
|
|
4140
4737
|
expiresAt: { type: "string", description: "Absolute expiry time in ISO 8601 format (optional)" },
|
|
4141
|
-
contextLinks: {
|
|
4142
|
-
type: "array",
|
|
4143
|
-
items: { type: "string" },
|
|
4144
|
-
description: "URLs providing context (optional)"
|
|
4145
|
-
},
|
|
4146
4738
|
attachments: {
|
|
4147
4739
|
type: "array",
|
|
4148
4740
|
description: "File attachments. Each item: { filename, contentType, path (local file path) }",
|
|
@@ -4177,7 +4769,6 @@ ${lines.join("\n")}`
|
|
|
4177
4769
|
parentTaskId: params.parentTaskId,
|
|
4178
4770
|
priority: params.priority,
|
|
4179
4771
|
expiresAt: params.expiresAt,
|
|
4180
|
-
contextLinks: params.contextLinks,
|
|
4181
4772
|
attachments
|
|
4182
4773
|
});
|
|
4183
4774
|
dispatchedSubtasks.set(result.taskId, {
|
|
@@ -4207,17 +4798,9 @@ ${lines.join("\n")}`
|
|
|
4207
4798
|
const dir = "/tmp/aamp-files";
|
|
4208
4799
|
ensureDir(dir);
|
|
4209
4800
|
const downloaded = [];
|
|
4210
|
-
const base = baseUrl(cfg.aampHost);
|
|
4211
|
-
const identity = loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath());
|
|
4212
|
-
const authHeader = identity ? `Basic ${Buffer.from(identity.email + ":" + identity.smtpPassword).toString("base64")}` : "";
|
|
4213
4801
|
for (const att of r.attachments) {
|
|
4214
4802
|
try {
|
|
4215
|
-
const
|
|
4216
|
-
api.logger.info(`[AAMP] Fetching ${dlUrl}`);
|
|
4217
|
-
const dlRes = await fetch(dlUrl, { headers: { Authorization: authHeader } });
|
|
4218
|
-
if (!dlRes.ok)
|
|
4219
|
-
throw new Error(`HTTP ${dlRes.status}`);
|
|
4220
|
-
const buffer = Buffer.from(await dlRes.arrayBuffer());
|
|
4803
|
+
const buffer = await aampClient.downloadBlob(att.blobId, att.filename);
|
|
4221
4804
|
const filepath = `${dir}/${att.filename}`;
|
|
4222
4805
|
writeBinaryFile(filepath, buffer);
|
|
4223
4806
|
downloaded.push(`${att.filename} (${(buffer.length / 1024).toFixed(1)} KB) \u2192 ${filepath}`);
|
|
@@ -4289,18 +4872,10 @@ Question: ${h.question}`,
|
|
|
4289
4872
|
return { content: [{ type: "text", text: "Error: email parameter is required" }] };
|
|
4290
4873
|
}
|
|
4291
4874
|
try {
|
|
4292
|
-
const
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
const apiUrl = discovery.api?.url;
|
|
4297
|
-
if (!apiUrl)
|
|
4298
|
-
throw new Error("AAMP discovery did not return api.url");
|
|
4299
|
-
const apiBase = new URL(apiUrl, `${base}/`).toString();
|
|
4300
|
-
const res = await fetch(`${apiBase}?action=aamp.mailbox.check&email=${encodeURIComponent(email)}`);
|
|
4301
|
-
if (!res.ok)
|
|
4302
|
-
throw new Error(`HTTP ${res.status}`);
|
|
4303
|
-
const data = await res.json();
|
|
4875
|
+
const data = await AampClient.checkMailbox({
|
|
4876
|
+
aampHost: base,
|
|
4877
|
+
email
|
|
4878
|
+
});
|
|
4304
4879
|
return {
|
|
4305
4880
|
content: [{
|
|
4306
4881
|
type: "text",
|
|
@@ -4374,6 +4949,15 @@ Question: ${h.question}`,
|
|
|
4374
4949
|
};
|
|
4375
4950
|
}
|
|
4376
4951
|
});
|
|
4952
|
+
api.registerCommand({
|
|
4953
|
+
name: "aamp-pair",
|
|
4954
|
+
description: "Show a fresh AAMP pairing QR code for this OpenClaw agent",
|
|
4955
|
+
acceptsArgs: false,
|
|
4956
|
+
requireAuth: false,
|
|
4957
|
+
handler: async () => ({
|
|
4958
|
+
text: await renderPairingCodeForCurrentAgent()
|
|
4959
|
+
})
|
|
4960
|
+
});
|
|
4377
4961
|
}
|
|
4378
4962
|
};
|
|
4379
4963
|
export {
|