aamp-openclaw-plugin 0.1.38 → 0.1.40
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 +43 -3
- package/dist/file-store.js +73 -0
- package/dist/file-store.js.map +2 -2
- package/dist/index.js +668 -58
- package/dist/index.js.map +4 -4
- package/openclaw.plugin.json +21 -0
- package/package.json +57 -2
- package/skills/SKILL.md +0 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ var AAMP_HEADER = {
|
|
|
8
8
|
INTENT: "X-AAMP-Intent",
|
|
9
9
|
TASK_ID: "X-AAMP-TaskId",
|
|
10
10
|
SESSION_KEY: "X-AAMP-Session-Key",
|
|
11
|
-
CONTEXT_LINKS: "X-AAMP-ContextLinks",
|
|
12
11
|
DISPATCH_CONTEXT: "X-AAMP-Dispatch-Context",
|
|
13
12
|
PRIORITY: "X-AAMP-Priority",
|
|
14
13
|
EXPIRES_AT: "X-AAMP-Expires-At",
|
|
@@ -20,6 +19,8 @@ var AAMP_HEADER = {
|
|
|
20
19
|
BLOCKED_REASON: "X-AAMP-BlockedReason",
|
|
21
20
|
SUGGESTED_OPTIONS: "X-AAMP-SuggestedOptions",
|
|
22
21
|
STREAM_ID: "X-AAMP-Stream-Id",
|
|
22
|
+
PAIR_CODE: "X-AAMP-Pair-Code",
|
|
23
|
+
DISPATCH_CONTEXT_RULES: "X-AAMP-Dispatch-Context-Rules",
|
|
23
24
|
PARENT_TASK_ID: "X-AAMP-ParentTaskId",
|
|
24
25
|
CARD_SUMMARY: "X-AAMP-Card-Summary"
|
|
25
26
|
};
|
|
@@ -159,6 +160,20 @@ function encodeStructuredResult(value) {
|
|
|
159
160
|
const json = JSON.stringify(value);
|
|
160
161
|
return Buffer.from(json, "utf-8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
161
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
|
+
}
|
|
162
177
|
function parseAampHeaders(meta) {
|
|
163
178
|
const headers = normalizeHeaders(meta.headers);
|
|
164
179
|
const intent = getAampHeader(headers, AAMP_HEADER.INTENT);
|
|
@@ -170,7 +185,6 @@ function parseAampHeaders(meta) {
|
|
|
170
185
|
const to = meta.to.replace(/^<|>$/g, "");
|
|
171
186
|
const decodedSubject = decodeMimeEncodedWords(meta.subject);
|
|
172
187
|
if (intent === "task.dispatch") {
|
|
173
|
-
const contextLinksStr = getAampHeader(headers, AAMP_HEADER.CONTEXT_LINKS) ?? "";
|
|
174
188
|
const dispatchContext = parseDispatchContextHeader(getAampHeader(headers, AAMP_HEADER.DISPATCH_CONTEXT));
|
|
175
189
|
const sessionKey = getAampHeader(headers, AAMP_HEADER.SESSION_KEY);
|
|
176
190
|
const parentTaskId = getAampHeader(headers, AAMP_HEADER.PARENT_TASK_ID);
|
|
@@ -184,7 +198,6 @@ function parseAampHeaders(meta) {
|
|
|
184
198
|
title: decodedSubject.replace(/^\[AAMP Task\]\s*/, "").trim() || "Untitled Task",
|
|
185
199
|
priority: priority === "urgent" || priority === "high" ? priority : "normal",
|
|
186
200
|
...expiresAt ? { expiresAt } : {},
|
|
187
|
-
contextLinks: contextLinksStr ? contextLinksStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
188
201
|
...dispatchContext ? { dispatchContext } : {},
|
|
189
202
|
...parentTaskId ? { parentTaskId } : {},
|
|
190
203
|
from,
|
|
@@ -273,6 +286,43 @@ function parseAampHeaders(meta) {
|
|
|
273
286
|
};
|
|
274
287
|
return streamOpened;
|
|
275
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
|
+
}
|
|
276
326
|
if (intent === "card.query") {
|
|
277
327
|
const cardQuery = {
|
|
278
328
|
protocolVersion,
|
|
@@ -316,9 +366,6 @@ function buildDispatchHeaders(params) {
|
|
|
316
366
|
if (params.sessionKey?.trim()) {
|
|
317
367
|
headers[AAMP_HEADER.SESSION_KEY] = params.sessionKey.trim();
|
|
318
368
|
}
|
|
319
|
-
if (params.contextLinks.length > 0) {
|
|
320
|
-
headers[AAMP_HEADER.CONTEXT_LINKS] = params.contextLinks.join(",");
|
|
321
|
-
}
|
|
322
369
|
const dispatchContext = serializeDispatchContextHeader(params.dispatchContext);
|
|
323
370
|
if (dispatchContext) {
|
|
324
371
|
headers[AAMP_HEADER.DISPATCH_CONTEXT] = dispatchContext;
|
|
@@ -350,6 +397,27 @@ function buildStreamOpenedHeaders(opts) {
|
|
|
350
397
|
[AAMP_HEADER.STREAM_ID]: opts.streamId
|
|
351
398
|
};
|
|
352
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
|
+
}
|
|
353
421
|
function buildResultHeaders(params) {
|
|
354
422
|
const headers = {
|
|
355
423
|
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
@@ -735,6 +803,12 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
735
803
|
case "task.stream.opened":
|
|
736
804
|
this.emit("task.stream.opened", aampMsg);
|
|
737
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;
|
|
738
812
|
case "card.query":
|
|
739
813
|
this.emit("card.query", aampMsg);
|
|
740
814
|
break;
|
|
@@ -1120,10 +1194,177 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
1120
1194
|
}
|
|
1121
1195
|
};
|
|
1122
1196
|
|
|
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
|
+
|
|
1123
1345
|
// ../sdk/dist/smtp-sender.js
|
|
1124
1346
|
import { createTransport } from "nodemailer";
|
|
1125
1347
|
import { randomUUID } from "crypto";
|
|
1126
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
|
+
}
|
|
1127
1368
|
function deriveMailboxServiceDefaults(email, baseUrl2) {
|
|
1128
1369
|
const domain = email.split("@")[1]?.trim();
|
|
1129
1370
|
const resolvedBaseUrl = baseUrl2?.trim() || (domain ? `https://${domain}` : void 0);
|
|
@@ -1136,6 +1377,7 @@ function deriveMailboxServiceDefaults(email, baseUrl2) {
|
|
|
1136
1377
|
var SmtpSender = class _SmtpSender {
|
|
1137
1378
|
config;
|
|
1138
1379
|
transport;
|
|
1380
|
+
fetch;
|
|
1139
1381
|
discoveredApiUrlPromise = null;
|
|
1140
1382
|
jmapSessionPromise = null;
|
|
1141
1383
|
sentMailboxIdPromise = null;
|
|
@@ -1148,12 +1390,16 @@ var SmtpSender = class _SmtpSender {
|
|
|
1148
1390
|
password: config.password,
|
|
1149
1391
|
httpBaseUrl: derived.httpBaseUrl,
|
|
1150
1392
|
authToken: Buffer.from(`${config.email}:${config.password}`).toString("base64"),
|
|
1393
|
+
fetch: config.fetch,
|
|
1394
|
+
forceHttpSend: config.forceHttpSend,
|
|
1395
|
+
persistSentCopy: config.persistSentCopy,
|
|
1151
1396
|
secure: config.secure,
|
|
1152
1397
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1153
1398
|
});
|
|
1154
1399
|
}
|
|
1155
1400
|
constructor(config) {
|
|
1156
1401
|
this.config = config;
|
|
1402
|
+
this.fetch = config.fetch ?? fetch;
|
|
1157
1403
|
this.transport = createTransport({
|
|
1158
1404
|
host: config.host,
|
|
1159
1405
|
port: config.port,
|
|
@@ -1174,6 +1420,9 @@ var SmtpSender = class _SmtpSender {
|
|
|
1174
1420
|
return email.split("@")[1]?.toLowerCase() ?? "";
|
|
1175
1421
|
}
|
|
1176
1422
|
shouldUseHttpFallback(to) {
|
|
1423
|
+
if (this.config.forceHttpSend) {
|
|
1424
|
+
return Boolean(this.config.httpBaseUrl && this.config.authToken);
|
|
1425
|
+
}
|
|
1177
1426
|
return Boolean(this.config.httpBaseUrl && this.config.authToken && this.senderDomain() && this.senderDomain() === this.recipientDomain(to));
|
|
1178
1427
|
}
|
|
1179
1428
|
async resolveAampApiUrl() {
|
|
@@ -1183,7 +1432,7 @@ var SmtpSender = class _SmtpSender {
|
|
|
1183
1432
|
}
|
|
1184
1433
|
if (!this.discoveredApiUrlPromise) {
|
|
1185
1434
|
this.discoveredApiUrlPromise = (async () => {
|
|
1186
|
-
const discoveryRes = await fetch(`${base}/.well-known/aamp`);
|
|
1435
|
+
const discoveryRes = await this.fetch(`${base}/.well-known/aamp`);
|
|
1187
1436
|
if (!discoveryRes.ok) {
|
|
1188
1437
|
throw new Error(`AAMP discovery failed: ${discoveryRes.status}`);
|
|
1189
1438
|
}
|
|
@@ -1205,33 +1454,49 @@ var SmtpSender = class _SmtpSender {
|
|
|
1205
1454
|
if (!this.config.authToken) {
|
|
1206
1455
|
throw new Error("HTTP send fallback is not configured");
|
|
1207
1456
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
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);
|
|
1231
1494
|
}
|
|
1232
|
-
|
|
1495
|
+
throw lastError ?? new Error("HTTP send failed");
|
|
1233
1496
|
}
|
|
1234
1497
|
canPersistSentCopy() {
|
|
1498
|
+
if (this.config.persistSentCopy === false)
|
|
1499
|
+
return false;
|
|
1235
1500
|
return Boolean(this.config.httpBaseUrl && this.config.authToken);
|
|
1236
1501
|
}
|
|
1237
1502
|
getJmapAuthHeader() {
|
|
@@ -1247,7 +1512,7 @@ var SmtpSender = class _SmtpSender {
|
|
|
1247
1512
|
}
|
|
1248
1513
|
if (!this.jmapSessionPromise) {
|
|
1249
1514
|
this.jmapSessionPromise = (async () => {
|
|
1250
|
-
const res = await fetch(`${base}/.well-known/jmap`, {
|
|
1515
|
+
const res = await this.fetch(`${base}/.well-known/jmap`, {
|
|
1251
1516
|
headers: { Authorization: this.getJmapAuthHeader() }
|
|
1252
1517
|
});
|
|
1253
1518
|
if (!res.ok) {
|
|
@@ -1273,7 +1538,7 @@ var SmtpSender = class _SmtpSender {
|
|
|
1273
1538
|
}
|
|
1274
1539
|
async jmapCall(methodCalls) {
|
|
1275
1540
|
const session = await this.resolveJmapSession();
|
|
1276
|
-
const res = await fetch(session.apiUrl, {
|
|
1541
|
+
const res = await this.fetch(session.apiUrl, {
|
|
1277
1542
|
method: "POST",
|
|
1278
1543
|
headers: {
|
|
1279
1544
|
Authorization: this.getJmapAuthHeader(),
|
|
@@ -1370,7 +1635,6 @@ var SmtpSender = class _SmtpSender {
|
|
|
1370
1635
|
taskId,
|
|
1371
1636
|
priority: opts.priority,
|
|
1372
1637
|
expiresAt: opts.expiresAt,
|
|
1373
|
-
contextLinks: opts.contextLinks ?? [],
|
|
1374
1638
|
dispatchContext: opts.dispatchContext,
|
|
1375
1639
|
parentTaskId: opts.parentTaskId
|
|
1376
1640
|
});
|
|
@@ -1383,8 +1647,6 @@ var SmtpSender = class _SmtpSender {
|
|
|
1383
1647
|
`Task ID: ${taskId}`,
|
|
1384
1648
|
`Priority: ${opts.priority ?? "normal"}`,
|
|
1385
1649
|
opts.expiresAt ? `Expires At: ${opts.expiresAt}` : `Expires At: none`,
|
|
1386
|
-
opts.contextLinks?.length ? `Context:
|
|
1387
|
-
${opts.contextLinks.map((l) => ` ${l}`).join("\n")}` : "",
|
|
1388
1650
|
opts.bodyText ?? "",
|
|
1389
1651
|
``,
|
|
1390
1652
|
`--- This email was sent by AAMP. Reply directly to submit your result. ---`
|
|
@@ -1727,6 +1989,110 @@ Stream ID: ${opts.streamId}`,
|
|
|
1727
1989
|
references: opts.inReplyTo
|
|
1728
1990
|
});
|
|
1729
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
|
+
}
|
|
1730
2096
|
async sendCardQuery(opts) {
|
|
1731
2097
|
const taskId = opts.taskId ?? randomUUID();
|
|
1732
2098
|
const aampHeaders = buildCardQueryHeaders({ taskId });
|
|
@@ -1967,6 +2333,9 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
1967
2333
|
password: config.smtpPassword,
|
|
1968
2334
|
httpBaseUrl: config.httpSendBaseUrl ?? resolvedBaseUrl,
|
|
1969
2335
|
authToken: mailboxToken,
|
|
2336
|
+
fetch: config.fetch,
|
|
2337
|
+
forceHttpSend: config.forceHttpSend,
|
|
2338
|
+
persistSentCopy: config.persistSentCopy,
|
|
1970
2339
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1971
2340
|
});
|
|
1972
2341
|
this.jmapClient.on("task.dispatch", (task) => {
|
|
@@ -1987,6 +2356,12 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
1987
2356
|
this.jmapClient.on("task.stream.opened", (stream) => {
|
|
1988
2357
|
this.emit("task.stream.opened", stream);
|
|
1989
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
|
+
});
|
|
1990
2365
|
this.jmapClient.on("card.query", (query) => {
|
|
1991
2366
|
this.emit("card.query", query);
|
|
1992
2367
|
});
|
|
@@ -2024,12 +2399,23 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2024
2399
|
smtpPassword: config.smtpPassword,
|
|
2025
2400
|
reconnectInterval: config.reconnectInterval,
|
|
2026
2401
|
taskDispatchConcurrency: config.taskDispatchConcurrency,
|
|
2402
|
+
fetch: config.fetch,
|
|
2403
|
+
forceHttpSend: config.forceHttpSend,
|
|
2404
|
+
persistSentCopy: config.persistSentCopy,
|
|
2027
2405
|
rejectUnauthorized: config.rejectUnauthorized
|
|
2028
2406
|
});
|
|
2029
2407
|
}
|
|
2030
|
-
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) {
|
|
2031
2417
|
const base = aampHost.replace(/\/$/, "");
|
|
2032
|
-
const res = await
|
|
2418
|
+
const res = await fetchImpl(`${base}/.well-known/aamp`);
|
|
2033
2419
|
if (!res.ok) {
|
|
2034
2420
|
throw new Error(`AAMP discovery failed: ${res.status} ${res.statusText}`);
|
|
2035
2421
|
}
|
|
@@ -2040,7 +2426,8 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2040
2426
|
return discovery;
|
|
2041
2427
|
}
|
|
2042
2428
|
static async callDiscoveredApi(base, opts) {
|
|
2043
|
-
const
|
|
2429
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
2430
|
+
const discovery = await _AampClient.discoverAampService(base, fetchImpl);
|
|
2044
2431
|
const apiUrl = new URL(discovery.api.url, `${base}/`);
|
|
2045
2432
|
apiUrl.searchParams.set("action", opts.action);
|
|
2046
2433
|
for (const [key, value] of Object.entries(opts.query ?? {})) {
|
|
@@ -2048,7 +2435,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2048
2435
|
continue;
|
|
2049
2436
|
apiUrl.searchParams.set(key, String(value));
|
|
2050
2437
|
}
|
|
2051
|
-
return
|
|
2438
|
+
return fetchImpl(apiUrl, {
|
|
2052
2439
|
method: opts.method ?? "GET",
|
|
2053
2440
|
headers: {
|
|
2054
2441
|
...opts.authToken ? { Authorization: `Basic ${opts.authToken}` } : {},
|
|
@@ -2157,7 +2544,6 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2157
2544
|
priority: opts.priority,
|
|
2158
2545
|
expiresAt: opts.expiresAt,
|
|
2159
2546
|
sessionKey: opts.sessionKey,
|
|
2160
|
-
contextLinks: opts.contextLinks,
|
|
2161
2547
|
dispatchContext: opts.dispatchContext,
|
|
2162
2548
|
parentTaskId: opts.parentTaskId,
|
|
2163
2549
|
attachments: opts.attachments
|
|
@@ -2181,6 +2567,12 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2181
2567
|
async sendStreamOpened(opts) {
|
|
2182
2568
|
return this.smtpSender.sendStreamOpened(opts);
|
|
2183
2569
|
}
|
|
2570
|
+
async sendPairRequest(opts) {
|
|
2571
|
+
return this.smtpSender.sendPairRequest(opts);
|
|
2572
|
+
}
|
|
2573
|
+
async sendPairRespond(opts) {
|
|
2574
|
+
return this.smtpSender.sendPairRespond(opts);
|
|
2575
|
+
}
|
|
2184
2576
|
async sendCardQuery(opts) {
|
|
2185
2577
|
return this.smtpSender.sendCardQuery(opts);
|
|
2186
2578
|
}
|
|
@@ -2194,6 +2586,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2194
2586
|
action: "aamp.directory.upsert",
|
|
2195
2587
|
method: "POST",
|
|
2196
2588
|
authToken: mailboxToken,
|
|
2589
|
+
fetch: this.config.fetch,
|
|
2197
2590
|
body: opts
|
|
2198
2591
|
});
|
|
2199
2592
|
if (!res.ok) {
|
|
@@ -2209,6 +2602,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2209
2602
|
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2210
2603
|
action: "aamp.directory.list",
|
|
2211
2604
|
authToken: mailboxToken,
|
|
2605
|
+
fetch: this.config.fetch,
|
|
2212
2606
|
query: {
|
|
2213
2607
|
scope: opts.scope,
|
|
2214
2608
|
includeSelf: opts.includeSelf,
|
|
@@ -2228,6 +2622,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2228
2622
|
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2229
2623
|
action: "aamp.directory.search",
|
|
2230
2624
|
authToken: mailboxToken,
|
|
2625
|
+
fetch: this.config.fetch,
|
|
2231
2626
|
query: {
|
|
2232
2627
|
q: opts.query,
|
|
2233
2628
|
scope: opts.scope,
|
|
@@ -2248,6 +2643,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2248
2643
|
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2249
2644
|
action: "aamp.mailbox.thread",
|
|
2250
2645
|
authToken: mailboxToken,
|
|
2646
|
+
fetch: this.config.fetch,
|
|
2251
2647
|
query: {
|
|
2252
2648
|
taskId,
|
|
2253
2649
|
includeStreamOpened: opts.includeStreamOpened
|
|
@@ -2273,7 +2669,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2273
2669
|
};
|
|
2274
2670
|
}
|
|
2275
2671
|
async resolveStreamCapability() {
|
|
2276
|
-
const discovery = await _AampClient.discoverAampService(this.config.baseUrl);
|
|
2672
|
+
const discovery = await _AampClient.discoverAampService(this.config.baseUrl, this.config.fetch);
|
|
2277
2673
|
const stream = discovery.capabilities?.stream;
|
|
2278
2674
|
if (!stream?.transport) {
|
|
2279
2675
|
throw new Error("AAMP stream capability is not available on this service");
|
|
@@ -2286,6 +2682,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2286
2682
|
action: stream.createAction ?? "aamp.stream.create",
|
|
2287
2683
|
method: "POST",
|
|
2288
2684
|
authToken: this.config.mailboxToken,
|
|
2685
|
+
fetch: this.config.fetch,
|
|
2289
2686
|
body: opts
|
|
2290
2687
|
});
|
|
2291
2688
|
if (!res.ok) {
|
|
@@ -2332,6 +2729,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2332
2729
|
action: stream.appendAction ?? "aamp.stream.append",
|
|
2333
2730
|
method: "POST",
|
|
2334
2731
|
authToken: this.config.mailboxToken,
|
|
2732
|
+
fetch: this.config.fetch,
|
|
2335
2733
|
body: opts
|
|
2336
2734
|
});
|
|
2337
2735
|
if (!res.ok) {
|
|
@@ -2437,6 +2835,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2437
2835
|
action: stream.closeAction ?? "aamp.stream.close",
|
|
2438
2836
|
method: "POST",
|
|
2439
2837
|
authToken: this.config.mailboxToken,
|
|
2838
|
+
fetch: this.config.fetch,
|
|
2440
2839
|
body: opts
|
|
2441
2840
|
});
|
|
2442
2841
|
if (!res.ok) {
|
|
@@ -2450,6 +2849,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2450
2849
|
const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
|
|
2451
2850
|
action: stream.getAction ?? "aamp.stream.get",
|
|
2452
2851
|
authToken: this.config.mailboxToken,
|
|
2852
|
+
fetch: this.config.fetch,
|
|
2453
2853
|
query: {
|
|
2454
2854
|
...opts.taskId ? { taskId: opts.taskId } : {},
|
|
2455
2855
|
...opts.streamId ? { streamId: opts.streamId } : {}
|
|
@@ -2476,7 +2876,8 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2476
2876
|
if (opts.signal) {
|
|
2477
2877
|
opts.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
2478
2878
|
}
|
|
2479
|
-
const
|
|
2879
|
+
const fetchImpl = this.config.fetch ?? fetch;
|
|
2880
|
+
const res = await fetchImpl(url, {
|
|
2480
2881
|
headers: {
|
|
2481
2882
|
Authorization: `Basic ${this.config.mailboxToken}`,
|
|
2482
2883
|
Accept: "text/event-stream"
|
|
@@ -2587,12 +2988,19 @@ import { readFileSync as readFileSync2 } from "node:fs";
|
|
|
2587
2988
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2588
2989
|
import { dirname, join } from "node:path";
|
|
2589
2990
|
import { homedir } from "node:os";
|
|
2991
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2590
2992
|
function defaultCredentialsPath() {
|
|
2591
2993
|
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
|
|
2592
2994
|
}
|
|
2593
2995
|
function defaultTaskStatePath() {
|
|
2594
2996
|
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".task-state.json");
|
|
2595
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
|
+
}
|
|
2596
3004
|
function loadCachedIdentity(file) {
|
|
2597
3005
|
const resolved = file ?? defaultCredentialsPath();
|
|
2598
3006
|
if (!existsSync(resolved))
|
|
@@ -2639,6 +3047,66 @@ function saveTaskState(state, file) {
|
|
|
2639
3047
|
terminalTaskIds: state.terminalTaskIds ?? []
|
|
2640
3048
|
}, null, 2), "utf-8");
|
|
2641
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
|
+
}
|
|
2642
3110
|
function ensureDir(dir) {
|
|
2643
3111
|
mkdirSync(dir, { recursive: true });
|
|
2644
3112
|
}
|
|
@@ -2653,7 +3121,7 @@ function writeBinaryFile(path, content) {
|
|
|
2653
3121
|
// src/index.ts
|
|
2654
3122
|
function matchSenderPolicy(task, senderPolicies) {
|
|
2655
3123
|
if (!senderPolicies?.length)
|
|
2656
|
-
return { allowed:
|
|
3124
|
+
return { allowed: false, reason: "no configured senderPolicies" };
|
|
2657
3125
|
const sender = task.from.toLowerCase();
|
|
2658
3126
|
const policy = senderPolicies.find((item) => item.sender.trim().toLowerCase() === sender);
|
|
2659
3127
|
if (!policy) {
|
|
@@ -2682,12 +3150,62 @@ function matchSenderPolicy(task, senderPolicies) {
|
|
|
2682
3150
|
}
|
|
2683
3151
|
return { allowed: true };
|
|
2684
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
|
+
}
|
|
2685
3190
|
function baseUrl(aampHost) {
|
|
2686
3191
|
if (aampHost.startsWith("http://") || aampHost.startsWith("https://")) {
|
|
2687
3192
|
return aampHost.replace(/\/$/, "");
|
|
2688
3193
|
}
|
|
2689
3194
|
return `https://${aampHost}`;
|
|
2690
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
|
+
}
|
|
2691
3209
|
var pendingTasks = /* @__PURE__ */ new Map();
|
|
2692
3210
|
var activeTaskStreams = /* @__PURE__ */ new Map();
|
|
2693
3211
|
var terminalTaskIds = new Set(loadTaskState(defaultTaskStatePath()).terminalTaskIds ?? []);
|
|
@@ -2711,6 +3229,7 @@ var transportMonitorTimer = null;
|
|
|
2711
3229
|
var historicalReconcileCompleted = false;
|
|
2712
3230
|
var channelRuntime = null;
|
|
2713
3231
|
var channelCfg = null;
|
|
3232
|
+
var pairedSenderPolicies = [];
|
|
2714
3233
|
async function ensureTaskStream(task) {
|
|
2715
3234
|
if (!aampClient?.isConnected())
|
|
2716
3235
|
return null;
|
|
@@ -2953,7 +3472,6 @@ function queuePendingTask(task) {
|
|
|
2953
3472
|
threadContextText: task.threadContextText ?? "",
|
|
2954
3473
|
priority: task.priority ?? "normal",
|
|
2955
3474
|
...task.expiresAt ? { expiresAt: task.expiresAt } : {},
|
|
2956
|
-
contextLinks: task.contextLinks ?? [],
|
|
2957
3475
|
messageId: task.messageId ?? "",
|
|
2958
3476
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2959
3477
|
});
|
|
@@ -3016,6 +3534,14 @@ var src_default = {
|
|
|
3016
3534
|
type: "string",
|
|
3017
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."
|
|
3018
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
|
+
},
|
|
3019
3545
|
senderPolicies: {
|
|
3020
3546
|
type: "array",
|
|
3021
3547
|
description: "Per-sender authorization policies. Each sender can optionally require specific X-AAMP-Dispatch-Context key/value pairs before a task is accepted.",
|
|
@@ -3106,6 +3632,70 @@ var src_default = {
|
|
|
3106
3632
|
});
|
|
3107
3633
|
api.logger.info(`[AAMP] Directory profile synced${cardText ? " (card text registered)" : ""}`);
|
|
3108
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
|
+
}
|
|
3109
3699
|
function wakeAgentForPendingTask(task) {
|
|
3110
3700
|
const fallbackSessionKey = buildWakeSessionKeyForPendingTask(task, api.config);
|
|
3111
3701
|
const openClawSessionKey = buildSessionKeyForPendingTask(task, api.config);
|
|
@@ -3189,6 +3779,16 @@ var src_default = {
|
|
|
3189
3779
|
lastTransportMode = "disconnected";
|
|
3190
3780
|
lastLoggedTransportMode = "disconnected";
|
|
3191
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}`);
|
|
3192
3792
|
const base = baseUrl(cfg.aampHost);
|
|
3193
3793
|
aampClient = AampClient.fromMailboxIdentity({
|
|
3194
3794
|
email: identity.email,
|
|
@@ -3207,7 +3807,7 @@ var src_default = {
|
|
|
3207
3807
|
api.logger.info(`[AAMP] Skipping already-terminal task ${task.taskId}`);
|
|
3208
3808
|
return;
|
|
3209
3809
|
}
|
|
3210
|
-
const decision =
|
|
3810
|
+
const decision = matchCombinedSenderPolicy(task, cfg.senderPolicies, pairedSenderPolicies);
|
|
3211
3811
|
if (!decision.allowed) {
|
|
3212
3812
|
api.logger.warn(`[AAMP] \u2717 rejected by senderPolicies: ${task.from} task=${task.taskId} reason=${decision.reason}`);
|
|
3213
3813
|
void aampClient.sendResult({
|
|
@@ -3264,6 +3864,11 @@ var src_default = {
|
|
|
3264
3864
|
api.logger.info(`[AAMP] Cancelled task ${cancel.taskId} \u2014 removed from pending queue`);
|
|
3265
3865
|
}
|
|
3266
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
|
+
});
|
|
3267
3872
|
aampClient.on("task.result", (result) => {
|
|
3268
3873
|
if (result.from.toLowerCase() === agentEmail.toLowerCase())
|
|
3269
3874
|
return;
|
|
@@ -3331,7 +3936,6 @@ ${truncatedOutput}${attachmentInfo}` : `Agent ${result.from} rejected the sub-ta
|
|
|
3331
3936
|
|
|
3332
3937
|
Reason: ${result.errorMsg ?? "unknown"}`,
|
|
3333
3938
|
priority: "urgent",
|
|
3334
|
-
contextLinks: [],
|
|
3335
3939
|
messageId: "",
|
|
3336
3940
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3337
3941
|
});
|
|
@@ -3414,7 +4018,6 @@ Question: ${help.question}
|
|
|
3414
4018
|
Blocked reason: ${help.blockedReason}${help.suggestedOptions?.length ? `
|
|
3415
4019
|
Suggested options: ${help.suggestedOptions.join(", ")}` : ""}`,
|
|
3416
4020
|
priority: "urgent",
|
|
3417
|
-
contextLinks: [],
|
|
3418
4021
|
messageId: "",
|
|
3419
4022
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3420
4023
|
});
|
|
@@ -3716,8 +4319,6 @@ ${Object.entries(task.dispatchContext).map(([key, value]) => ` - ${key}: ${valu
|
|
|
3716
4319
|
task.threadContextText ? `${task.threadContextText}` : "",
|
|
3717
4320
|
task.bodyText ? `Latest user message:
|
|
3718
4321
|
${task.bodyText}` : "",
|
|
3719
|
-
task.contextLinks.length ? `Context Links:
|
|
3720
|
-
${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
3721
4322
|
task.expiresAt ? `Expires: ${task.expiresAt}` : `Expires: none`,
|
|
3722
4323
|
`Received: ${task.receivedAt}`,
|
|
3723
4324
|
otherActionableTasks.length > 0 ? `
|
|
@@ -3758,8 +4359,6 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
|
3758
4359
|
task.threadContextText ? `${task.threadContextText}` : "",
|
|
3759
4360
|
task.bodyText ? `Description:
|
|
3760
4361
|
${task.bodyText}` : "",
|
|
3761
|
-
task.contextLinks.length ? `Context Links:
|
|
3762
|
-
${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
3763
4362
|
task.expiresAt ? `Expires: ${task.expiresAt}` : `Expires: none`,
|
|
3764
4363
|
`Received: ${task.receivedAt}`,
|
|
3765
4364
|
otherActionableTasks.length > 0 ? `
|
|
@@ -4081,6 +4680,14 @@ ${lines.join("\n")}`
|
|
|
4081
4680
|
};
|
|
4082
4681
|
}
|
|
4083
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" });
|
|
4084
4691
|
api.registerTool({
|
|
4085
4692
|
name: "aamp_cancel_task",
|
|
4086
4693
|
description: "Cancel a pending AAMP task and notify the dispatcher.",
|
|
@@ -4128,11 +4735,6 @@ ${lines.join("\n")}`
|
|
|
4128
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." },
|
|
4129
4736
|
priority: { type: "string", enum: ["urgent", "high", "normal"], description: "Task priority (optional)" },
|
|
4130
4737
|
expiresAt: { type: "string", description: "Absolute expiry time in ISO 8601 format (optional)" },
|
|
4131
|
-
contextLinks: {
|
|
4132
|
-
type: "array",
|
|
4133
|
-
items: { type: "string" },
|
|
4134
|
-
description: "URLs providing context (optional)"
|
|
4135
|
-
},
|
|
4136
4738
|
attachments: {
|
|
4137
4739
|
type: "array",
|
|
4138
4740
|
description: "File attachments. Each item: { filename, contentType, path (local file path) }",
|
|
@@ -4167,7 +4769,6 @@ ${lines.join("\n")}`
|
|
|
4167
4769
|
parentTaskId: params.parentTaskId,
|
|
4168
4770
|
priority: params.priority,
|
|
4169
4771
|
expiresAt: params.expiresAt,
|
|
4170
|
-
contextLinks: params.contextLinks,
|
|
4171
4772
|
attachments
|
|
4172
4773
|
});
|
|
4173
4774
|
dispatchedSubtasks.set(result.taskId, {
|
|
@@ -4348,6 +4949,15 @@ Question: ${h.question}`,
|
|
|
4348
4949
|
};
|
|
4349
4950
|
}
|
|
4350
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
|
+
});
|
|
4351
4961
|
}
|
|
4352
4962
|
};
|
|
4353
4963
|
export {
|