aamp-openclaw-plugin 0.1.27 → 0.1.29
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 +14 -0
- package/bin/aamp-openclaw-plugin.mjs +52 -27
- package/dist/file-store.js +37 -3
- package/dist/file-store.js.map +2 -2
- package/dist/index.js +695 -91
- package/dist/index.js.map +3 -3
- package/package.json +5 -3
- package/skills/SKILL.md +36 -32
package/dist/index.js
CHANGED
|
@@ -7,9 +7,10 @@ var AAMP_HEADER = {
|
|
|
7
7
|
VERSION: "X-AAMP-Version",
|
|
8
8
|
INTENT: "X-AAMP-Intent",
|
|
9
9
|
TASK_ID: "X-AAMP-TaskId",
|
|
10
|
-
TIMEOUT: "X-AAMP-Timeout",
|
|
11
10
|
CONTEXT_LINKS: "X-AAMP-ContextLinks",
|
|
12
11
|
DISPATCH_CONTEXT: "X-AAMP-Dispatch-Context",
|
|
12
|
+
PRIORITY: "X-AAMP-Priority",
|
|
13
|
+
EXPIRES_AT: "X-AAMP-Expires-At",
|
|
13
14
|
STATUS: "X-AAMP-Status",
|
|
14
15
|
OUTPUT: "X-AAMP-Output",
|
|
15
16
|
ERROR_MSG: "X-AAMP-ErrorMsg",
|
|
@@ -17,10 +18,59 @@ var AAMP_HEADER = {
|
|
|
17
18
|
QUESTION: "X-AAMP-Question",
|
|
18
19
|
BLOCKED_REASON: "X-AAMP-BlockedReason",
|
|
19
20
|
SUGGESTED_OPTIONS: "X-AAMP-SuggestedOptions",
|
|
20
|
-
PARENT_TASK_ID: "X-AAMP-ParentTaskId"
|
|
21
|
+
PARENT_TASK_ID: "X-AAMP-ParentTaskId",
|
|
22
|
+
CARD_SUMMARY: "X-AAMP-Card-Summary"
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
// ../sdk/src/parser.ts
|
|
26
|
+
function normalizeBodyText(value) {
|
|
27
|
+
return value?.replace(/\r\n/g, "\n").trim() ?? "";
|
|
28
|
+
}
|
|
29
|
+
function escapeRegex(value) {
|
|
30
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31
|
+
}
|
|
32
|
+
function extractBodySection(bodyText, label, nextLabels) {
|
|
33
|
+
if (!bodyText)
|
|
34
|
+
return "";
|
|
35
|
+
const nextPattern = nextLabels.length ? `(?=\\n(?:${nextLabels.map(escapeRegex).join("|")}):|$)` : "$";
|
|
36
|
+
const pattern = new RegExp(
|
|
37
|
+
`(?:^|\\n)${escapeRegex(label)}:\\s*([\\s\\S]*?)${nextPattern}`,
|
|
38
|
+
"i"
|
|
39
|
+
);
|
|
40
|
+
const match = pattern.exec(bodyText);
|
|
41
|
+
return match?.[1]?.trim() ?? "";
|
|
42
|
+
}
|
|
43
|
+
function parseSuggestedOptionsBlock(block) {
|
|
44
|
+
if (!block.trim())
|
|
45
|
+
return [];
|
|
46
|
+
return block.split("\n").map((line) => line.replace(/^\s*(?:[-*]|\d+\.)\s*/, "").trim()).filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
function parseTaskResultBody(bodyText) {
|
|
49
|
+
const normalized = normalizeBodyText(bodyText);
|
|
50
|
+
if (!normalized)
|
|
51
|
+
return { output: "" };
|
|
52
|
+
const output = extractBodySection(normalized, "Output", ["Error"]);
|
|
53
|
+
const errorMsg = extractBodySection(normalized, "Error", []);
|
|
54
|
+
if (output || errorMsg) {
|
|
55
|
+
return { output, ...errorMsg ? { errorMsg } : {} };
|
|
56
|
+
}
|
|
57
|
+
return { output: normalized };
|
|
58
|
+
}
|
|
59
|
+
function parseTaskHelpBody(bodyText) {
|
|
60
|
+
const normalized = normalizeBodyText(bodyText);
|
|
61
|
+
if (!normalized) {
|
|
62
|
+
return { question: "", blockedReason: "", suggestedOptions: [] };
|
|
63
|
+
}
|
|
64
|
+
const question = extractBodySection(normalized, "Question", ["Blocked reason", "Suggested options"]);
|
|
65
|
+
const blockedReason = extractBodySection(normalized, "Blocked reason", ["Suggested options"]);
|
|
66
|
+
const suggestedOptions = parseSuggestedOptionsBlock(
|
|
67
|
+
extractBodySection(normalized, "Suggested options", [])
|
|
68
|
+
);
|
|
69
|
+
if (question || blockedReason || suggestedOptions.length) {
|
|
70
|
+
return { question, blockedReason, suggestedOptions };
|
|
71
|
+
}
|
|
72
|
+
return { question: normalized, blockedReason: "", suggestedOptions: [] };
|
|
73
|
+
}
|
|
24
74
|
function decodeMimeEncodedWordSegment(segment) {
|
|
25
75
|
const match = /^=\?([^?]+)\?([bBqQ])\?([^?]*)\?=$/.exec(segment);
|
|
26
76
|
if (!match)
|
|
@@ -131,18 +181,20 @@ function parseAampHeaders(meta) {
|
|
|
131
181
|
const to = meta.to.replace(/^<|>$/g, "");
|
|
132
182
|
const decodedSubject = decodeMimeEncodedWords(meta.subject);
|
|
133
183
|
if (intent === "task.dispatch") {
|
|
134
|
-
const timeoutStr = getAampHeader(headers, AAMP_HEADER.TIMEOUT) ?? "300";
|
|
135
184
|
const contextLinksStr = getAampHeader(headers, AAMP_HEADER.CONTEXT_LINKS) ?? "";
|
|
136
185
|
const dispatchContext = parseDispatchContextHeader(
|
|
137
186
|
getAampHeader(headers, AAMP_HEADER.DISPATCH_CONTEXT)
|
|
138
187
|
);
|
|
139
188
|
const parentTaskId = getAampHeader(headers, AAMP_HEADER.PARENT_TASK_ID);
|
|
189
|
+
const priority = getAampHeader(headers, AAMP_HEADER.PRIORITY) ?? "normal";
|
|
190
|
+
const expiresAt = getAampHeader(headers, AAMP_HEADER.EXPIRES_AT);
|
|
140
191
|
const dispatch = {
|
|
141
192
|
protocolVersion,
|
|
142
193
|
intent: "task.dispatch",
|
|
143
194
|
taskId,
|
|
144
195
|
title: decodedSubject.replace(/^\[AAMP Task\]\s*/, "").trim() || "Untitled Task",
|
|
145
|
-
|
|
196
|
+
priority: priority === "urgent" || priority === "high" ? priority : "normal",
|
|
197
|
+
...expiresAt ? { expiresAt } : {},
|
|
146
198
|
contextLinks: contextLinksStr ? contextLinksStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
147
199
|
...dispatchContext ? { dispatchContext } : {},
|
|
148
200
|
...parentTaskId ? { parentTaskId } : {},
|
|
@@ -155,10 +207,24 @@ function parseAampHeaders(meta) {
|
|
|
155
207
|
};
|
|
156
208
|
return dispatch;
|
|
157
209
|
}
|
|
210
|
+
if (intent === "task.cancel") {
|
|
211
|
+
const cancel = {
|
|
212
|
+
protocolVersion,
|
|
213
|
+
intent: "task.cancel",
|
|
214
|
+
taskId,
|
|
215
|
+
from,
|
|
216
|
+
to,
|
|
217
|
+
messageId: meta.messageId,
|
|
218
|
+
subject: meta.subject,
|
|
219
|
+
bodyText: ""
|
|
220
|
+
};
|
|
221
|
+
return cancel;
|
|
222
|
+
}
|
|
158
223
|
if (intent === "task.result") {
|
|
224
|
+
const parsedBody = parseTaskResultBody(meta.bodyText);
|
|
159
225
|
const status = getAampHeader(headers, AAMP_HEADER.STATUS) ?? "completed";
|
|
160
|
-
const output = getAampHeader(headers, AAMP_HEADER.OUTPUT) ??
|
|
161
|
-
const errorMsg = getAampHeader(headers, AAMP_HEADER.ERROR_MSG);
|
|
226
|
+
const output = getAampHeader(headers, AAMP_HEADER.OUTPUT) ?? parsedBody.output;
|
|
227
|
+
const errorMsg = getAampHeader(headers, AAMP_HEADER.ERROR_MSG) ?? parsedBody.errorMsg;
|
|
162
228
|
const structuredResult = decodeStructuredResult(
|
|
163
229
|
getAampHeader(headers, AAMP_HEADER.STRUCTURED_RESULT)
|
|
164
230
|
);
|
|
@@ -176,9 +242,10 @@ function parseAampHeaders(meta) {
|
|
|
176
242
|
};
|
|
177
243
|
return result;
|
|
178
244
|
}
|
|
179
|
-
if (intent === "task.help_needed"
|
|
180
|
-
const
|
|
181
|
-
const
|
|
245
|
+
if (intent === "task.help_needed") {
|
|
246
|
+
const parsedBody = parseTaskHelpBody(meta.bodyText);
|
|
247
|
+
const question = getAampHeader(headers, AAMP_HEADER.QUESTION) ?? parsedBody.question;
|
|
248
|
+
const blockedReason = getAampHeader(headers, AAMP_HEADER.BLOCKED_REASON) ?? parsedBody.blockedReason;
|
|
182
249
|
const suggestedOptionsStr = getAampHeader(headers, AAMP_HEADER.SUGGESTED_OPTIONS) ?? "";
|
|
183
250
|
const help = {
|
|
184
251
|
protocolVersion,
|
|
@@ -186,7 +253,7 @@ function parseAampHeaders(meta) {
|
|
|
186
253
|
taskId,
|
|
187
254
|
question: decodeMimeEncodedWords(question),
|
|
188
255
|
blockedReason: decodeMimeEncodedWords(blockedReason),
|
|
189
|
-
suggestedOptions: suggestedOptionsStr ? suggestedOptionsStr.split("|").map((s) => decodeMimeEncodedWords(s.trim())).filter(Boolean) :
|
|
256
|
+
suggestedOptions: suggestedOptionsStr ? suggestedOptionsStr.split("|").map((s) => decodeMimeEncodedWords(s.trim())).filter(Boolean) : parsedBody.suggestedOptions,
|
|
190
257
|
from,
|
|
191
258
|
to,
|
|
192
259
|
messageId: meta.messageId
|
|
@@ -204,16 +271,45 @@ function parseAampHeaders(meta) {
|
|
|
204
271
|
};
|
|
205
272
|
return ack;
|
|
206
273
|
}
|
|
274
|
+
if (intent === "card.query") {
|
|
275
|
+
const cardQuery = {
|
|
276
|
+
protocolVersion,
|
|
277
|
+
intent: "card.query",
|
|
278
|
+
taskId,
|
|
279
|
+
from,
|
|
280
|
+
to,
|
|
281
|
+
messageId: meta.messageId,
|
|
282
|
+
subject: meta.subject,
|
|
283
|
+
bodyText: ""
|
|
284
|
+
};
|
|
285
|
+
return cardQuery;
|
|
286
|
+
}
|
|
287
|
+
if (intent === "card.response") {
|
|
288
|
+
const summary = getAampHeader(headers, AAMP_HEADER.CARD_SUMMARY) ?? "";
|
|
289
|
+
const cardResponse = {
|
|
290
|
+
protocolVersion,
|
|
291
|
+
intent: "card.response",
|
|
292
|
+
taskId,
|
|
293
|
+
summary: decodeMimeEncodedWords(summary) || decodedSubject.replace(/^\[AAMP Card\]\s*/i, "").trim(),
|
|
294
|
+
from,
|
|
295
|
+
to,
|
|
296
|
+
messageId: meta.messageId,
|
|
297
|
+
subject: meta.subject,
|
|
298
|
+
bodyText: ""
|
|
299
|
+
};
|
|
300
|
+
return cardResponse;
|
|
301
|
+
}
|
|
207
302
|
return null;
|
|
208
303
|
}
|
|
209
304
|
function buildDispatchHeaders(params) {
|
|
210
305
|
const headers = {
|
|
211
306
|
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
212
307
|
[AAMP_HEADER.INTENT]: "task.dispatch",
|
|
213
|
-
[AAMP_HEADER.TASK_ID]: params.taskId
|
|
308
|
+
[AAMP_HEADER.TASK_ID]: params.taskId,
|
|
309
|
+
[AAMP_HEADER.PRIORITY]: params.priority ?? "normal"
|
|
214
310
|
};
|
|
215
|
-
if (params.
|
|
216
|
-
headers[AAMP_HEADER.
|
|
311
|
+
if (params.expiresAt) {
|
|
312
|
+
headers[AAMP_HEADER.EXPIRES_AT] = params.expiresAt;
|
|
217
313
|
}
|
|
218
314
|
if (params.contextLinks.length > 0) {
|
|
219
315
|
headers[AAMP_HEADER.CONTEXT_LINKS] = params.contextLinks.join(",");
|
|
@@ -227,6 +323,13 @@ function buildDispatchHeaders(params) {
|
|
|
227
323
|
}
|
|
228
324
|
return headers;
|
|
229
325
|
}
|
|
326
|
+
function buildCancelHeaders(params) {
|
|
327
|
+
return {
|
|
328
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
329
|
+
[AAMP_HEADER.INTENT]: "task.cancel",
|
|
330
|
+
[AAMP_HEADER.TASK_ID]: params.taskId
|
|
331
|
+
};
|
|
332
|
+
}
|
|
230
333
|
function buildAckHeaders(opts) {
|
|
231
334
|
return {
|
|
232
335
|
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
@@ -239,12 +342,8 @@ function buildResultHeaders(params) {
|
|
|
239
342
|
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
240
343
|
[AAMP_HEADER.INTENT]: "task.result",
|
|
241
344
|
[AAMP_HEADER.TASK_ID]: params.taskId,
|
|
242
|
-
[AAMP_HEADER.STATUS]: params.status
|
|
243
|
-
[AAMP_HEADER.OUTPUT]: params.output
|
|
345
|
+
[AAMP_HEADER.STATUS]: params.status
|
|
244
346
|
};
|
|
245
|
-
if (params.errorMsg) {
|
|
246
|
-
headers[AAMP_HEADER.ERROR_MSG] = params.errorMsg;
|
|
247
|
-
}
|
|
248
347
|
const structuredResult = encodeStructuredResult(params.structuredResult);
|
|
249
348
|
if (structuredResult) {
|
|
250
349
|
headers[AAMP_HEADER.STRUCTURED_RESULT] = structuredResult;
|
|
@@ -256,11 +355,24 @@ function buildHelpHeaders(params) {
|
|
|
256
355
|
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
257
356
|
[AAMP_HEADER.INTENT]: "task.help_needed",
|
|
258
357
|
[AAMP_HEADER.TASK_ID]: params.taskId,
|
|
259
|
-
[AAMP_HEADER.QUESTION]: params.question,
|
|
260
|
-
[AAMP_HEADER.BLOCKED_REASON]: params.blockedReason,
|
|
261
358
|
[AAMP_HEADER.SUGGESTED_OPTIONS]: params.suggestedOptions.join("|")
|
|
262
359
|
};
|
|
263
360
|
}
|
|
361
|
+
function buildCardQueryHeaders(params) {
|
|
362
|
+
return {
|
|
363
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
364
|
+
[AAMP_HEADER.INTENT]: "card.query",
|
|
365
|
+
[AAMP_HEADER.TASK_ID]: params.taskId
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function buildCardResponseHeaders(params) {
|
|
369
|
+
return {
|
|
370
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
371
|
+
[AAMP_HEADER.INTENT]: "card.response",
|
|
372
|
+
[AAMP_HEADER.TASK_ID]: params.taskId,
|
|
373
|
+
[AAMP_HEADER.CARD_SUMMARY]: params.summary
|
|
374
|
+
};
|
|
375
|
+
}
|
|
264
376
|
|
|
265
377
|
// ../sdk/src/tiny-emitter.ts
|
|
266
378
|
var TinyEmitter = class {
|
|
@@ -504,7 +616,7 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
504
616
|
* Process a received email.
|
|
505
617
|
*
|
|
506
618
|
* Priority:
|
|
507
|
-
|
|
619
|
+
* 1. If X-AAMP-Intent is present → emit typed AAMP event (task.dispatch / task.cancel / task.result / task.help_needed)
|
|
508
620
|
* 2. If In-Reply-To is present → emit 'reply' event so the application layer can
|
|
509
621
|
* resolve the thread (inReplyTo → taskId via Redis/DB) and handle human replies.
|
|
510
622
|
* 3. Otherwise → ignore (not an AAMP-related email)
|
|
@@ -520,16 +632,18 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
520
632
|
if (this.seenMessageIds.has(messageId))
|
|
521
633
|
return;
|
|
522
634
|
this.seenMessageIds.add(messageId);
|
|
635
|
+
const aampTextPartId = email.textBody?.[0]?.partId;
|
|
636
|
+
const aampBodyText = aampTextPartId ? (email.bodyValues?.[aampTextPartId]?.value ?? "").trim() : "";
|
|
523
637
|
const msg = parseAampHeaders({
|
|
524
638
|
from: fromAddr,
|
|
525
639
|
to: toAddr,
|
|
526
640
|
messageId,
|
|
527
641
|
subject: email.subject ?? "",
|
|
528
|
-
headers: headerMap
|
|
642
|
+
headers: headerMap,
|
|
643
|
+
bodyText: aampBodyText
|
|
529
644
|
});
|
|
530
645
|
if (msg && "intent" in msg) {
|
|
531
|
-
|
|
532
|
-
const aampBodyText = aampTextPartId ? (email.bodyValues?.[aampTextPartId]?.value ?? "").trim() : "";
|
|
646
|
+
;
|
|
533
647
|
msg.bodyText = aampBodyText;
|
|
534
648
|
const receivedAttachments = (email.attachments ?? []).map((a) => ({
|
|
535
649
|
filename: a.name ?? "attachment",
|
|
@@ -549,16 +663,24 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
549
663
|
case "task.dispatch":
|
|
550
664
|
this.emit("task.dispatch", aampMsg);
|
|
551
665
|
break;
|
|
666
|
+
case "task.cancel":
|
|
667
|
+
this.emit("task.cancel", aampMsg);
|
|
668
|
+
break;
|
|
552
669
|
case "task.result":
|
|
553
670
|
this.emit("task.result", aampMsg);
|
|
554
671
|
break;
|
|
555
672
|
case "task.help_needed":
|
|
556
673
|
this.emit("task.help_needed", aampMsg);
|
|
557
|
-
this.emit("task.help", aampMsg);
|
|
558
674
|
break;
|
|
559
675
|
case "task.ack":
|
|
560
676
|
this.emit("task.ack", aampMsg);
|
|
561
677
|
break;
|
|
678
|
+
case "card.query":
|
|
679
|
+
this.emit("card.query", aampMsg);
|
|
680
|
+
break;
|
|
681
|
+
case "card.response":
|
|
682
|
+
this.emit("card.response", aampMsg);
|
|
683
|
+
break;
|
|
562
684
|
}
|
|
563
685
|
return;
|
|
564
686
|
}
|
|
@@ -886,7 +1008,7 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
886
1008
|
* Useful as a safety net when the WebSocket stays "connected"
|
|
887
1009
|
* but a notification is missed by an intermediate layer.
|
|
888
1010
|
*/
|
|
889
|
-
async reconcileRecentEmails(limit = 20) {
|
|
1011
|
+
async reconcileRecentEmails(limit = 20, opts) {
|
|
890
1012
|
if (!this.session) {
|
|
891
1013
|
this.session = await this.fetchSession();
|
|
892
1014
|
}
|
|
@@ -933,7 +1055,7 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
933
1055
|
const bTs = new Date(b.receivedAt).getTime();
|
|
934
1056
|
return aTs - bTs;
|
|
935
1057
|
})) {
|
|
936
|
-
if (!this.shouldProcessBootstrapEmail(email))
|
|
1058
|
+
if (!opts?.includeHistorical && !this.shouldProcessBootstrapEmail(email))
|
|
937
1059
|
continue;
|
|
938
1060
|
this.processEmail(email);
|
|
939
1061
|
}
|
|
@@ -945,7 +1067,16 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
945
1067
|
import { createTransport } from "nodemailer";
|
|
946
1068
|
import { randomUUID } from "crypto";
|
|
947
1069
|
var sanitize = (s) => s.replace(/[\r\n]/g, " ").trim();
|
|
948
|
-
|
|
1070
|
+
function deriveMailboxServiceDefaults(email, baseUrl2) {
|
|
1071
|
+
const domain = email.split("@")[1]?.trim();
|
|
1072
|
+
const resolvedBaseUrl = baseUrl2?.trim() || (domain ? `https://${domain}` : void 0);
|
|
1073
|
+
const smtpHost = domain || (resolvedBaseUrl ? new URL(resolvedBaseUrl).hostname : "localhost");
|
|
1074
|
+
return {
|
|
1075
|
+
smtpHost,
|
|
1076
|
+
httpBaseUrl: resolvedBaseUrl
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
var SmtpSender = class _SmtpSender {
|
|
949
1080
|
constructor(config) {
|
|
950
1081
|
this.config = config;
|
|
951
1082
|
this.transport = createTransport({
|
|
@@ -962,6 +1093,20 @@ var SmtpSender = class {
|
|
|
962
1093
|
});
|
|
963
1094
|
}
|
|
964
1095
|
transport;
|
|
1096
|
+
discoveredApiUrlPromise = null;
|
|
1097
|
+
static fromMailboxIdentity(config) {
|
|
1098
|
+
const derived = deriveMailboxServiceDefaults(config.email, config.baseUrl);
|
|
1099
|
+
return new _SmtpSender({
|
|
1100
|
+
host: derived.smtpHost,
|
|
1101
|
+
port: config.smtpPort ?? 587,
|
|
1102
|
+
user: config.email,
|
|
1103
|
+
password: config.password,
|
|
1104
|
+
httpBaseUrl: derived.httpBaseUrl,
|
|
1105
|
+
authToken: Buffer.from(`${config.email}:${config.password}`).toString("base64"),
|
|
1106
|
+
secure: config.secure,
|
|
1107
|
+
rejectUnauthorized: config.rejectUnauthorized
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
965
1110
|
senderDomain() {
|
|
966
1111
|
return this.config.user.split("@")[1]?.toLowerCase() ?? "";
|
|
967
1112
|
}
|
|
@@ -973,12 +1118,38 @@ var SmtpSender = class {
|
|
|
973
1118
|
this.config.httpBaseUrl && this.config.authToken && this.senderDomain() && this.senderDomain() === this.recipientDomain(to)
|
|
974
1119
|
);
|
|
975
1120
|
}
|
|
976
|
-
async
|
|
1121
|
+
async resolveAampApiUrl() {
|
|
977
1122
|
const base = this.config.httpBaseUrl?.replace(/\/$/, "");
|
|
978
|
-
if (!base
|
|
1123
|
+
if (!base) {
|
|
1124
|
+
throw new Error("HTTP send fallback is not configured");
|
|
1125
|
+
}
|
|
1126
|
+
if (!this.discoveredApiUrlPromise) {
|
|
1127
|
+
this.discoveredApiUrlPromise = (async () => {
|
|
1128
|
+
const discoveryRes = await fetch(`${base}/.well-known/aamp`);
|
|
1129
|
+
if (!discoveryRes.ok) {
|
|
1130
|
+
throw new Error(`AAMP discovery failed: ${discoveryRes.status}`);
|
|
1131
|
+
}
|
|
1132
|
+
const discovery = await discoveryRes.json();
|
|
1133
|
+
if (!discovery.api?.url) {
|
|
1134
|
+
throw new Error("AAMP discovery did not return api.url");
|
|
1135
|
+
}
|
|
1136
|
+
return new URL(discovery.api.url, `${base}/`).toString();
|
|
1137
|
+
})();
|
|
1138
|
+
}
|
|
1139
|
+
try {
|
|
1140
|
+
return await this.discoveredApiUrlPromise;
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
this.discoveredApiUrlPromise = null;
|
|
1143
|
+
throw err;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async sendViaHttp(opts) {
|
|
1147
|
+
if (!this.config.authToken) {
|
|
979
1148
|
throw new Error("HTTP send fallback is not configured");
|
|
980
1149
|
}
|
|
981
|
-
const
|
|
1150
|
+
const apiUrl = new URL(await this.resolveAampApiUrl());
|
|
1151
|
+
apiUrl.searchParams.set("action", "aamp.mailbox.send");
|
|
1152
|
+
const res = await fetch(apiUrl, {
|
|
982
1153
|
method: "POST",
|
|
983
1154
|
headers: {
|
|
984
1155
|
Authorization: `Basic ${this.config.authToken}`,
|
|
@@ -1008,11 +1179,13 @@ var SmtpSender = class {
|
|
|
1008
1179
|
* store a reverse-index (messageId → taskId) for In-Reply-To thread routing.
|
|
1009
1180
|
*/
|
|
1010
1181
|
async sendTask(opts) {
|
|
1011
|
-
const taskId = randomUUID();
|
|
1182
|
+
const taskId = opts.taskId ?? randomUUID();
|
|
1012
1183
|
const aampHeaders = buildDispatchHeaders({
|
|
1013
1184
|
taskId,
|
|
1014
|
-
|
|
1185
|
+
priority: opts.priority,
|
|
1186
|
+
expiresAt: opts.expiresAt,
|
|
1015
1187
|
contextLinks: opts.contextLinks ?? [],
|
|
1188
|
+
dispatchContext: opts.dispatchContext,
|
|
1016
1189
|
parentTaskId: opts.parentTaskId
|
|
1017
1190
|
});
|
|
1018
1191
|
const sendMailOpts = {
|
|
@@ -1022,7 +1195,8 @@ var SmtpSender = class {
|
|
|
1022
1195
|
text: [
|
|
1023
1196
|
`Task: ${opts.title}`,
|
|
1024
1197
|
`Task ID: ${taskId}`,
|
|
1025
|
-
|
|
1198
|
+
`Priority: ${opts.priority ?? "normal"}`,
|
|
1199
|
+
opts.expiresAt ? `Expires At: ${opts.expiresAt}` : `Expires At: none`,
|
|
1026
1200
|
opts.contextLinks?.length ? `Context:
|
|
1027
1201
|
${opts.contextLinks.map((l) => ` ${l}`).join("\n")}` : "",
|
|
1028
1202
|
opts.bodyText ?? "",
|
|
@@ -1111,7 +1285,7 @@ Error: ${opts.errorMsg}` : ""
|
|
|
1111
1285
|
await this.transport.sendMail(mailOpts);
|
|
1112
1286
|
}
|
|
1113
1287
|
/**
|
|
1114
|
-
* Send a task.
|
|
1288
|
+
* Send a task.help_needed email when the agent is blocked
|
|
1115
1289
|
*/
|
|
1116
1290
|
async sendHelp(opts) {
|
|
1117
1291
|
const aampHeaders = buildHelpHeaders({
|
|
@@ -1165,6 +1339,35 @@ ${opts.suggestedOptions.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}` : ""
|
|
|
1165
1339
|
}
|
|
1166
1340
|
await this.transport.sendMail(helpMailOpts);
|
|
1167
1341
|
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Send a task.cancel email to stop a previously dispatched task.
|
|
1344
|
+
*/
|
|
1345
|
+
async sendCancel(opts) {
|
|
1346
|
+
const aampHeaders = buildCancelHeaders({
|
|
1347
|
+
taskId: opts.taskId
|
|
1348
|
+
});
|
|
1349
|
+
const mailOpts = {
|
|
1350
|
+
from: this.config.user,
|
|
1351
|
+
to: opts.to,
|
|
1352
|
+
subject: `[AAMP Cancel] Task ${opts.taskId}`,
|
|
1353
|
+
text: opts.bodyText ?? "The dispatcher cancelled this task.",
|
|
1354
|
+
headers: aampHeaders
|
|
1355
|
+
};
|
|
1356
|
+
if (opts.inReplyTo) {
|
|
1357
|
+
mailOpts.inReplyTo = opts.inReplyTo;
|
|
1358
|
+
mailOpts.references = opts.inReplyTo;
|
|
1359
|
+
}
|
|
1360
|
+
if (this.shouldUseHttpFallback(opts.to)) {
|
|
1361
|
+
await this.sendViaHttp({
|
|
1362
|
+
to: opts.to,
|
|
1363
|
+
subject: mailOpts.subject,
|
|
1364
|
+
text: mailOpts.text,
|
|
1365
|
+
aampHeaders
|
|
1366
|
+
});
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
await this.transport.sendMail(mailOpts);
|
|
1370
|
+
}
|
|
1168
1371
|
/**
|
|
1169
1372
|
* Send a task.ack email to confirm receipt of a dispatch
|
|
1170
1373
|
*/
|
|
@@ -1192,6 +1395,59 @@ ${opts.suggestedOptions.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}` : ""
|
|
|
1192
1395
|
}
|
|
1193
1396
|
await this.transport.sendMail(mailOpts);
|
|
1194
1397
|
}
|
|
1398
|
+
async sendCardQuery(opts) {
|
|
1399
|
+
const taskId = opts.taskId ?? randomUUID();
|
|
1400
|
+
const aampHeaders = buildCardQueryHeaders({ taskId });
|
|
1401
|
+
const mailOpts = {
|
|
1402
|
+
from: this.config.user,
|
|
1403
|
+
to: opts.to,
|
|
1404
|
+
subject: `[AAMP Card Query] ${taskId}`,
|
|
1405
|
+
text: opts.bodyText?.trim() || "Please share your agent card and capability details.",
|
|
1406
|
+
headers: aampHeaders
|
|
1407
|
+
};
|
|
1408
|
+
if (opts.inReplyTo) {
|
|
1409
|
+
mailOpts.inReplyTo = opts.inReplyTo;
|
|
1410
|
+
mailOpts.references = opts.inReplyTo;
|
|
1411
|
+
}
|
|
1412
|
+
if (this.shouldUseHttpFallback(opts.to)) {
|
|
1413
|
+
const info2 = await this.sendViaHttp({
|
|
1414
|
+
to: opts.to,
|
|
1415
|
+
subject: mailOpts.subject,
|
|
1416
|
+
text: mailOpts.text,
|
|
1417
|
+
aampHeaders
|
|
1418
|
+
});
|
|
1419
|
+
return { taskId, messageId: info2.messageId ?? "" };
|
|
1420
|
+
}
|
|
1421
|
+
const info = await this.transport.sendMail(mailOpts);
|
|
1422
|
+
return { taskId, messageId: info.messageId ?? "" };
|
|
1423
|
+
}
|
|
1424
|
+
async sendCardResponse(opts) {
|
|
1425
|
+
const aampHeaders = buildCardResponseHeaders({
|
|
1426
|
+
taskId: opts.taskId,
|
|
1427
|
+
summary: opts.summary
|
|
1428
|
+
});
|
|
1429
|
+
const mailOpts = {
|
|
1430
|
+
from: this.config.user,
|
|
1431
|
+
to: opts.to,
|
|
1432
|
+
subject: `[AAMP Card] ${sanitize(opts.summary)}`,
|
|
1433
|
+
text: opts.bodyText,
|
|
1434
|
+
headers: aampHeaders
|
|
1435
|
+
};
|
|
1436
|
+
if (opts.inReplyTo) {
|
|
1437
|
+
mailOpts.inReplyTo = opts.inReplyTo;
|
|
1438
|
+
mailOpts.references = opts.inReplyTo;
|
|
1439
|
+
}
|
|
1440
|
+
if (this.shouldUseHttpFallback(opts.to)) {
|
|
1441
|
+
await this.sendViaHttp({
|
|
1442
|
+
to: opts.to,
|
|
1443
|
+
subject: mailOpts.subject,
|
|
1444
|
+
text: mailOpts.text,
|
|
1445
|
+
aampHeaders
|
|
1446
|
+
});
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
await this.transport.sendMail(mailOpts);
|
|
1450
|
+
}
|
|
1195
1451
|
/**
|
|
1196
1452
|
* Verify SMTP connection
|
|
1197
1453
|
*/
|
|
@@ -1209,56 +1465,68 @@ ${opts.suggestedOptions.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}` : ""
|
|
|
1209
1465
|
};
|
|
1210
1466
|
|
|
1211
1467
|
// ../sdk/src/client.ts
|
|
1212
|
-
var AampClient = class extends TinyEmitter {
|
|
1468
|
+
var AampClient = class _AampClient extends TinyEmitter {
|
|
1213
1469
|
jmapClient;
|
|
1214
1470
|
smtpSender;
|
|
1215
1471
|
config;
|
|
1216
1472
|
constructor(config) {
|
|
1217
1473
|
super();
|
|
1218
1474
|
this.config = config;
|
|
1475
|
+
const mailboxToken = config.mailboxToken;
|
|
1476
|
+
const resolvedBaseUrl = config.baseUrl;
|
|
1477
|
+
const derived = deriveMailboxServiceDefaults(config.email, resolvedBaseUrl);
|
|
1478
|
+
const smtpHost = config.smtpHost ?? derived.smtpHost;
|
|
1219
1479
|
let password;
|
|
1220
1480
|
try {
|
|
1221
|
-
const decoded = Buffer.from(
|
|
1481
|
+
const decoded = Buffer.from(mailboxToken, "base64").toString("utf-8");
|
|
1222
1482
|
const colonIdx = decoded.indexOf(":");
|
|
1223
1483
|
if (colonIdx < 0)
|
|
1224
|
-
throw new Error("Invalid
|
|
1484
|
+
throw new Error("Invalid mailboxToken format: expected base64(email:password)");
|
|
1225
1485
|
password = decoded.slice(colonIdx + 1);
|
|
1226
1486
|
if (!password)
|
|
1227
|
-
throw new Error("Invalid
|
|
1487
|
+
throw new Error("Invalid mailboxToken: empty password");
|
|
1228
1488
|
} catch (err) {
|
|
1229
|
-
if (err instanceof Error && err.message.startsWith("Invalid
|
|
1489
|
+
if (err instanceof Error && err.message.startsWith("Invalid mailboxToken"))
|
|
1230
1490
|
throw err;
|
|
1231
|
-
throw new Error(`Failed to decode
|
|
1491
|
+
throw new Error(`Failed to decode mailboxToken: ${err.message}`);
|
|
1232
1492
|
}
|
|
1233
1493
|
this.jmapClient = new JmapPushClient({
|
|
1234
1494
|
email: config.email,
|
|
1235
1495
|
password: password ?? config.smtpPassword,
|
|
1236
|
-
jmapUrl:
|
|
1496
|
+
jmapUrl: resolvedBaseUrl,
|
|
1237
1497
|
reconnectInterval: config.reconnectInterval ?? 5e3,
|
|
1238
1498
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1239
1499
|
});
|
|
1240
1500
|
this.smtpSender = new SmtpSender({
|
|
1241
|
-
host:
|
|
1501
|
+
host: smtpHost,
|
|
1242
1502
|
port: config.smtpPort ?? 587,
|
|
1243
1503
|
user: config.email,
|
|
1244
1504
|
password: config.smtpPassword,
|
|
1245
|
-
httpBaseUrl: config.httpSendBaseUrl ??
|
|
1246
|
-
authToken:
|
|
1505
|
+
httpBaseUrl: config.httpSendBaseUrl ?? resolvedBaseUrl,
|
|
1506
|
+
authToken: mailboxToken,
|
|
1247
1507
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1248
1508
|
});
|
|
1249
1509
|
this.jmapClient.on("task.dispatch", (task) => {
|
|
1250
1510
|
this.emit("task.dispatch", task);
|
|
1251
1511
|
});
|
|
1512
|
+
this.jmapClient.on("task.cancel", (task) => {
|
|
1513
|
+
this.emit("task.cancel", task);
|
|
1514
|
+
});
|
|
1252
1515
|
this.jmapClient.on("task.result", (result) => {
|
|
1253
1516
|
this.emit("task.result", result);
|
|
1254
1517
|
});
|
|
1255
1518
|
this.jmapClient.on("task.help_needed", (help) => {
|
|
1256
1519
|
this.emit("task.help_needed", help);
|
|
1257
|
-
this.emit("task.help", help);
|
|
1258
1520
|
});
|
|
1259
1521
|
this.jmapClient.on("task.ack", (ack) => {
|
|
1260
1522
|
this.emit("task.ack", ack);
|
|
1261
1523
|
});
|
|
1524
|
+
this.jmapClient.on("card.query", (query) => {
|
|
1525
|
+
this.emit("card.query", query);
|
|
1526
|
+
});
|
|
1527
|
+
this.jmapClient.on("card.response", (response) => {
|
|
1528
|
+
this.emit("card.response", response);
|
|
1529
|
+
});
|
|
1262
1530
|
this.jmapClient.on("_autoAck", async ({ to, taskId, messageId }) => {
|
|
1263
1531
|
try {
|
|
1264
1532
|
await this.smtpSender.sendAck({ to, taskId, inReplyTo: messageId });
|
|
@@ -1279,6 +1547,86 @@ var AampClient = class extends TinyEmitter {
|
|
|
1279
1547
|
this.emit("error", err);
|
|
1280
1548
|
});
|
|
1281
1549
|
}
|
|
1550
|
+
static fromMailboxIdentity(config) {
|
|
1551
|
+
const derived = deriveMailboxServiceDefaults(config.email, config.baseUrl);
|
|
1552
|
+
return new _AampClient({
|
|
1553
|
+
email: config.email,
|
|
1554
|
+
mailboxToken: Buffer.from(`${config.email}:${config.smtpPassword}`).toString("base64"),
|
|
1555
|
+
baseUrl: derived.httpBaseUrl ?? `https://${config.email.split("@")[1] ?? "localhost"}`,
|
|
1556
|
+
smtpHost: derived.smtpHost,
|
|
1557
|
+
smtpPort: config.smtpPort ?? 587,
|
|
1558
|
+
smtpPassword: config.smtpPassword,
|
|
1559
|
+
reconnectInterval: config.reconnectInterval,
|
|
1560
|
+
rejectUnauthorized: config.rejectUnauthorized
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
static async discoverAampService(aampHost) {
|
|
1564
|
+
const base = aampHost.replace(/\/$/, "");
|
|
1565
|
+
const res = await fetch(`${base}/.well-known/aamp`);
|
|
1566
|
+
if (!res.ok) {
|
|
1567
|
+
throw new Error(`AAMP discovery failed: ${res.status} ${res.statusText}`);
|
|
1568
|
+
}
|
|
1569
|
+
const discovery = await res.json();
|
|
1570
|
+
if (!discovery.api?.url) {
|
|
1571
|
+
throw new Error("AAMP discovery did not return api.url");
|
|
1572
|
+
}
|
|
1573
|
+
return discovery;
|
|
1574
|
+
}
|
|
1575
|
+
static async callDiscoveredApi(base, opts) {
|
|
1576
|
+
const discovery = await _AampClient.discoverAampService(base);
|
|
1577
|
+
const apiUrl = new URL(discovery.api.url, `${base}/`);
|
|
1578
|
+
apiUrl.searchParams.set("action", opts.action);
|
|
1579
|
+
for (const [key, value] of Object.entries(opts.query ?? {})) {
|
|
1580
|
+
if (value == null)
|
|
1581
|
+
continue;
|
|
1582
|
+
apiUrl.searchParams.set(key, String(value));
|
|
1583
|
+
}
|
|
1584
|
+
return fetch(apiUrl, {
|
|
1585
|
+
method: opts.method ?? "GET",
|
|
1586
|
+
headers: {
|
|
1587
|
+
...opts.authToken ? { Authorization: `Basic ${opts.authToken}` } : {},
|
|
1588
|
+
...opts.body ? { "Content-Type": "application/json" } : {}
|
|
1589
|
+
},
|
|
1590
|
+
...opts.body ? { body: JSON.stringify(opts.body) } : {}
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
static async registerMailbox(opts) {
|
|
1594
|
+
const base = opts.aampHost.replace(/\/$/, "");
|
|
1595
|
+
const registerRes = await _AampClient.callDiscoveredApi(base, {
|
|
1596
|
+
action: "aamp.mailbox.register",
|
|
1597
|
+
method: "POST",
|
|
1598
|
+
body: {
|
|
1599
|
+
slug: opts.slug,
|
|
1600
|
+
description: opts.description
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
if (!registerRes.ok) {
|
|
1604
|
+
const body = await registerRes.text().catch(() => "");
|
|
1605
|
+
throw new Error(`Mailbox registration failed: ${registerRes.status} ${body || registerRes.statusText}`);
|
|
1606
|
+
}
|
|
1607
|
+
const registerData = await registerRes.json();
|
|
1608
|
+
if (!registerData.registrationCode) {
|
|
1609
|
+
throw new Error("Mailbox registration succeeded but no registrationCode was returned");
|
|
1610
|
+
}
|
|
1611
|
+
const credsRes = await _AampClient.callDiscoveredApi(base, {
|
|
1612
|
+
action: "aamp.mailbox.credentials",
|
|
1613
|
+
query: { code: registerData.registrationCode }
|
|
1614
|
+
});
|
|
1615
|
+
if (!credsRes.ok) {
|
|
1616
|
+
const body = await credsRes.text().catch(() => "");
|
|
1617
|
+
throw new Error(`Mailbox credential exchange failed: ${credsRes.status} ${body || credsRes.statusText}`);
|
|
1618
|
+
}
|
|
1619
|
+
const creds = await credsRes.json();
|
|
1620
|
+
if (!creds.email || !creds.mailbox?.token || !creds.smtp?.password) {
|
|
1621
|
+
throw new Error("Mailbox credential exchange returned an incomplete identity payload");
|
|
1622
|
+
}
|
|
1623
|
+
return {
|
|
1624
|
+
email: creds.email,
|
|
1625
|
+
mailboxToken: creds.mailbox.token,
|
|
1626
|
+
smtpPassword: creds.smtp.password,
|
|
1627
|
+
baseUrl: base
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1282
1630
|
// =====================================================
|
|
1283
1631
|
// Lifecycle
|
|
1284
1632
|
// =====================================================
|
|
@@ -1316,6 +1664,9 @@ var AampClient = class extends TinyEmitter {
|
|
|
1316
1664
|
async sendTask(opts) {
|
|
1317
1665
|
return this.smtpSender.sendTask(opts);
|
|
1318
1666
|
}
|
|
1667
|
+
async sendCancel(opts) {
|
|
1668
|
+
return this.smtpSender.sendCancel(opts);
|
|
1669
|
+
}
|
|
1319
1670
|
/**
|
|
1320
1671
|
* Send a task.result email (agent → system/dispatcher)
|
|
1321
1672
|
*/
|
|
@@ -1323,11 +1674,72 @@ var AampClient = class extends TinyEmitter {
|
|
|
1323
1674
|
return this.smtpSender.sendResult(opts);
|
|
1324
1675
|
}
|
|
1325
1676
|
/**
|
|
1326
|
-
* Send a task.
|
|
1677
|
+
* Send a task.help_needed email when the agent needs human assistance
|
|
1327
1678
|
*/
|
|
1328
1679
|
async sendHelp(opts) {
|
|
1329
1680
|
return this.smtpSender.sendHelp(opts);
|
|
1330
1681
|
}
|
|
1682
|
+
async sendCardQuery(opts) {
|
|
1683
|
+
return this.smtpSender.sendCardQuery(opts);
|
|
1684
|
+
}
|
|
1685
|
+
async sendCardResponse(opts) {
|
|
1686
|
+
return this.smtpSender.sendCardResponse(opts);
|
|
1687
|
+
}
|
|
1688
|
+
async updateDirectoryProfile(opts) {
|
|
1689
|
+
const base = this.config.baseUrl;
|
|
1690
|
+
const mailboxToken = this.config.mailboxToken;
|
|
1691
|
+
const res = await _AampClient.callDiscoveredApi(base, {
|
|
1692
|
+
action: "aamp.directory.upsert",
|
|
1693
|
+
method: "POST",
|
|
1694
|
+
authToken: mailboxToken,
|
|
1695
|
+
body: opts
|
|
1696
|
+
});
|
|
1697
|
+
if (!res.ok) {
|
|
1698
|
+
const body = await res.text().catch(() => "");
|
|
1699
|
+
throw new Error(`Directory profile update failed: ${res.status} ${body || res.statusText}`);
|
|
1700
|
+
}
|
|
1701
|
+
const data = await res.json();
|
|
1702
|
+
return data.profile;
|
|
1703
|
+
}
|
|
1704
|
+
async listDirectory(opts = {}) {
|
|
1705
|
+
const base = this.config.baseUrl;
|
|
1706
|
+
const mailboxToken = this.config.mailboxToken;
|
|
1707
|
+
const res = await _AampClient.callDiscoveredApi(base, {
|
|
1708
|
+
action: "aamp.directory.list",
|
|
1709
|
+
authToken: mailboxToken,
|
|
1710
|
+
query: {
|
|
1711
|
+
scope: opts.scope,
|
|
1712
|
+
includeSelf: opts.includeSelf,
|
|
1713
|
+
limit: opts.limit
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
if (!res.ok) {
|
|
1717
|
+
const body = await res.text().catch(() => "");
|
|
1718
|
+
throw new Error(`Directory list failed: ${res.status} ${body || res.statusText}`);
|
|
1719
|
+
}
|
|
1720
|
+
const data = await res.json();
|
|
1721
|
+
return data.agents;
|
|
1722
|
+
}
|
|
1723
|
+
async searchDirectory(opts) {
|
|
1724
|
+
const base = this.config.baseUrl;
|
|
1725
|
+
const mailboxToken = this.config.mailboxToken;
|
|
1726
|
+
const res = await _AampClient.callDiscoveredApi(base, {
|
|
1727
|
+
action: "aamp.directory.search",
|
|
1728
|
+
authToken: mailboxToken,
|
|
1729
|
+
query: {
|
|
1730
|
+
q: opts.query,
|
|
1731
|
+
scope: opts.scope,
|
|
1732
|
+
includeSelf: opts.includeSelf,
|
|
1733
|
+
limit: opts.limit
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
if (!res.ok) {
|
|
1737
|
+
const body = await res.text().catch(() => "");
|
|
1738
|
+
throw new Error(`Directory search failed: ${res.status} ${body || res.statusText}`);
|
|
1739
|
+
}
|
|
1740
|
+
const data = await res.json();
|
|
1741
|
+
return data.agents;
|
|
1742
|
+
}
|
|
1331
1743
|
/**
|
|
1332
1744
|
* Download a blob (attachment) by its JMAP blobId.
|
|
1333
1745
|
* Use this to retrieve attachment content from received TaskDispatch or TaskResult messages.
|
|
@@ -1341,8 +1753,8 @@ var AampClient = class extends TinyEmitter {
|
|
|
1341
1753
|
* a flaky WebSocket path. Safe to call periodically; duplicate processing is
|
|
1342
1754
|
* suppressed by the JMAP push client.
|
|
1343
1755
|
*/
|
|
1344
|
-
async reconcileRecentEmails(limit) {
|
|
1345
|
-
return this.jmapClient.reconcileRecentEmails(limit);
|
|
1756
|
+
async reconcileRecentEmails(limit, opts) {
|
|
1757
|
+
return this.jmapClient.reconcileRecentEmails(limit, opts);
|
|
1346
1758
|
}
|
|
1347
1759
|
/**
|
|
1348
1760
|
* Verify SMTP connectivity
|
|
@@ -1362,15 +1774,22 @@ import { homedir } from "node:os";
|
|
|
1362
1774
|
function defaultCredentialsPath() {
|
|
1363
1775
|
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
|
|
1364
1776
|
}
|
|
1777
|
+
function defaultTaskStatePath() {
|
|
1778
|
+
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".task-state.json");
|
|
1779
|
+
}
|
|
1365
1780
|
function loadCachedIdentity(file) {
|
|
1366
1781
|
const resolved = file ?? defaultCredentialsPath();
|
|
1367
1782
|
if (!existsSync(resolved))
|
|
1368
1783
|
return null;
|
|
1369
1784
|
try {
|
|
1370
1785
|
const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
|
|
1371
|
-
if (!parsed.email || !parsed.
|
|
1786
|
+
if (!parsed.email || !parsed.mailboxToken || !parsed.smtpPassword)
|
|
1372
1787
|
return null;
|
|
1373
|
-
return
|
|
1788
|
+
return {
|
|
1789
|
+
email: parsed.email,
|
|
1790
|
+
mailboxToken: parsed.mailboxToken,
|
|
1791
|
+
smtpPassword: parsed.smtpPassword
|
|
1792
|
+
};
|
|
1374
1793
|
} catch {
|
|
1375
1794
|
return null;
|
|
1376
1795
|
}
|
|
@@ -1378,7 +1797,31 @@ function loadCachedIdentity(file) {
|
|
|
1378
1797
|
function saveCachedIdentity(identity, file) {
|
|
1379
1798
|
const resolved = file ?? defaultCredentialsPath();
|
|
1380
1799
|
mkdirSync(dirname(resolved), { recursive: true });
|
|
1381
|
-
writeFileSync(resolved, JSON.stringify(
|
|
1800
|
+
writeFileSync(resolved, JSON.stringify({
|
|
1801
|
+
email: identity.email,
|
|
1802
|
+
mailboxToken: identity.mailboxToken,
|
|
1803
|
+
smtpPassword: identity.smtpPassword
|
|
1804
|
+
}, null, 2), "utf-8");
|
|
1805
|
+
}
|
|
1806
|
+
function loadTaskState(file) {
|
|
1807
|
+
const resolved = file ?? defaultTaskStatePath();
|
|
1808
|
+
if (!existsSync(resolved))
|
|
1809
|
+
return { terminalTaskIds: [] };
|
|
1810
|
+
try {
|
|
1811
|
+
const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
|
|
1812
|
+
return {
|
|
1813
|
+
terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : []
|
|
1814
|
+
};
|
|
1815
|
+
} catch {
|
|
1816
|
+
return { terminalTaskIds: [] };
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
function saveTaskState(state, file) {
|
|
1820
|
+
const resolved = file ?? defaultTaskStatePath();
|
|
1821
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
1822
|
+
writeFileSync(resolved, JSON.stringify({
|
|
1823
|
+
terminalTaskIds: state.terminalTaskIds ?? []
|
|
1824
|
+
}, null, 2), "utf-8");
|
|
1382
1825
|
}
|
|
1383
1826
|
function ensureDir(dir) {
|
|
1384
1827
|
mkdirSync(dir, { recursive: true });
|
|
@@ -1430,6 +1873,7 @@ function baseUrl(aampHost) {
|
|
|
1430
1873
|
return `https://${aampHost}`;
|
|
1431
1874
|
}
|
|
1432
1875
|
var pendingTasks = /* @__PURE__ */ new Map();
|
|
1876
|
+
var terminalTaskIds = new Set(loadTaskState(defaultTaskStatePath()).terminalTaskIds ?? []);
|
|
1433
1877
|
var dispatchedSubtasks = /* @__PURE__ */ new Map();
|
|
1434
1878
|
var waitingDispatches = /* @__PURE__ */ new Map();
|
|
1435
1879
|
var aampClient = null;
|
|
@@ -1456,10 +1900,83 @@ function logTransportState(api, mode, email, previousMode) {
|
|
|
1456
1900
|
}
|
|
1457
1901
|
api.logger.info(`[AAMP] Connected \u2014 listening as ${email}`);
|
|
1458
1902
|
}
|
|
1903
|
+
function isSyntheticPendingKey(taskKey) {
|
|
1904
|
+
return taskKey.startsWith("result:") || taskKey.startsWith("help:");
|
|
1905
|
+
}
|
|
1906
|
+
function saveTerminalTaskIds() {
|
|
1907
|
+
saveTaskState({ terminalTaskIds: [...terminalTaskIds] }, defaultTaskStatePath());
|
|
1908
|
+
}
|
|
1909
|
+
function rememberTerminalTask(taskId) {
|
|
1910
|
+
terminalTaskIds.add(taskId);
|
|
1911
|
+
saveTerminalTaskIds();
|
|
1912
|
+
}
|
|
1913
|
+
function priorityRank(priority) {
|
|
1914
|
+
switch (priority) {
|
|
1915
|
+
case "urgent":
|
|
1916
|
+
return 0;
|
|
1917
|
+
case "high":
|
|
1918
|
+
return 1;
|
|
1919
|
+
default:
|
|
1920
|
+
return 2;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
function hasExpired(task) {
|
|
1924
|
+
if (task.expiresAt) {
|
|
1925
|
+
const expiresAtMs = new Date(task.expiresAt).getTime();
|
|
1926
|
+
if (Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs)
|
|
1927
|
+
return true;
|
|
1928
|
+
}
|
|
1929
|
+
return false;
|
|
1930
|
+
}
|
|
1931
|
+
function nextPendingEntry() {
|
|
1932
|
+
const entries = [...pendingTasks.entries()];
|
|
1933
|
+
const notifications = entries.filter(([key]) => key.startsWith("result:") || key.startsWith("help:"));
|
|
1934
|
+
if (notifications.length > 0) {
|
|
1935
|
+
return notifications.sort((a, b) => new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime())[0];
|
|
1936
|
+
}
|
|
1937
|
+
return entries.filter(([key]) => !key.startsWith("result:") && !key.startsWith("help:")).sort((a, b) => {
|
|
1938
|
+
const rankDiff = priorityRank(a[1].priority) - priorityRank(b[1].priority);
|
|
1939
|
+
if (rankDiff !== 0)
|
|
1940
|
+
return rankDiff;
|
|
1941
|
+
return new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime();
|
|
1942
|
+
})[0];
|
|
1943
|
+
}
|
|
1944
|
+
function queuePendingTask(task) {
|
|
1945
|
+
if (terminalTaskIds.has(task.taskId)) {
|
|
1946
|
+
return false;
|
|
1947
|
+
}
|
|
1948
|
+
pendingTasks.set(task.taskId, {
|
|
1949
|
+
taskId: task.taskId,
|
|
1950
|
+
from: task.from,
|
|
1951
|
+
title: task.title,
|
|
1952
|
+
bodyText: task.bodyText ?? "",
|
|
1953
|
+
priority: task.priority ?? "normal",
|
|
1954
|
+
...task.expiresAt ? { expiresAt: task.expiresAt } : {},
|
|
1955
|
+
contextLinks: task.contextLinks ?? [],
|
|
1956
|
+
messageId: task.messageId ?? "",
|
|
1957
|
+
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1958
|
+
});
|
|
1959
|
+
if (hasExpired(pendingTasks.get(task.taskId))) {
|
|
1960
|
+
pendingTasks.delete(task.taskId);
|
|
1961
|
+
rememberTerminalTask(task.taskId);
|
|
1962
|
+
return false;
|
|
1963
|
+
}
|
|
1964
|
+
return true;
|
|
1965
|
+
}
|
|
1459
1966
|
async function registerNode(cfg) {
|
|
1460
1967
|
const slug = (cfg.slug ?? "openclaw-agent").toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
1461
1968
|
const base = baseUrl(cfg.aampHost);
|
|
1462
|
-
const
|
|
1969
|
+
const discoveryRes = await fetch(`${base}/.well-known/aamp`);
|
|
1970
|
+
if (!discoveryRes.ok) {
|
|
1971
|
+
throw new Error(`AAMP discovery failed (${discoveryRes.status}): ${discoveryRes.statusText}`);
|
|
1972
|
+
}
|
|
1973
|
+
const discovery = await discoveryRes.json();
|
|
1974
|
+
const apiUrl = discovery.api?.url;
|
|
1975
|
+
if (!apiUrl) {
|
|
1976
|
+
throw new Error("AAMP discovery did not return api.url");
|
|
1977
|
+
}
|
|
1978
|
+
const apiBase = new URL(apiUrl, `${base}/`).toString();
|
|
1979
|
+
const res = await fetch(`${apiBase}?action=aamp.mailbox.register`, {
|
|
1463
1980
|
method: "POST",
|
|
1464
1981
|
headers: { "Content-Type": "application/json" },
|
|
1465
1982
|
body: JSON.stringify({ slug, description: "OpenClaw AAMP agent node" })
|
|
@@ -1470,7 +1987,7 @@ async function registerNode(cfg) {
|
|
|
1470
1987
|
}
|
|
1471
1988
|
const regData = await res.json();
|
|
1472
1989
|
const credRes = await fetch(
|
|
1473
|
-
`${
|
|
1990
|
+
`${apiBase}?action=aamp.mailbox.credentials&code=${encodeURIComponent(regData.registrationCode)}`
|
|
1474
1991
|
);
|
|
1475
1992
|
if (!credRes.ok) {
|
|
1476
1993
|
const err = await credRes.json().catch(() => ({}));
|
|
@@ -1479,7 +1996,7 @@ async function registerNode(cfg) {
|
|
|
1479
1996
|
const credData = await credRes.json();
|
|
1480
1997
|
return {
|
|
1481
1998
|
email: credData.email,
|
|
1482
|
-
|
|
1999
|
+
mailboxToken: credData.mailbox.token,
|
|
1483
2000
|
smtpPassword: credData.smtp.password
|
|
1484
2001
|
};
|
|
1485
2002
|
}
|
|
@@ -1644,13 +2161,10 @@ var src_default = {
|
|
|
1644
2161
|
lastLoggedTransportMode = "disconnected";
|
|
1645
2162
|
api.logger.info(`[AAMP] Mailbox identity ready \u2014 ${agentEmail}`);
|
|
1646
2163
|
const base = baseUrl(cfg.aampHost);
|
|
1647
|
-
aampClient =
|
|
2164
|
+
aampClient = AampClient.fromMailboxIdentity({
|
|
1648
2165
|
email: identity.email,
|
|
1649
|
-
jmapToken: identity.jmapToken,
|
|
1650
|
-
jmapUrl: base,
|
|
1651
|
-
smtpHost: new URL(base).hostname,
|
|
1652
|
-
smtpPort: 587,
|
|
1653
2166
|
smtpPassword: identity.smtpPassword,
|
|
2167
|
+
baseUrl: base,
|
|
1654
2168
|
// Local/dev: management-service proxy uses plain HTTP, no TLS cert to verify.
|
|
1655
2169
|
// Production: set to true when using wss:// with valid certs.
|
|
1656
2170
|
rejectUnauthorized: false
|
|
@@ -1658,6 +2172,10 @@ var src_default = {
|
|
|
1658
2172
|
aampClient.on("task.dispatch", (task) => {
|
|
1659
2173
|
api.logger.info(`[AAMP] \u2190 task.dispatch ${task.taskId} "${task.title}" from=${task.from}`);
|
|
1660
2174
|
try {
|
|
2175
|
+
if (terminalTaskIds.has(task.taskId)) {
|
|
2176
|
+
api.logger.info(`[AAMP] Skipping already-terminal task ${task.taskId}`);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
1661
2179
|
const decision = matchSenderPolicy(task, cfg.senderPolicies);
|
|
1662
2180
|
if (!decision.allowed) {
|
|
1663
2181
|
api.logger.warn(`[AAMP] \u2717 rejected by senderPolicies: ${task.from} task=${task.taskId} reason=${decision.reason}`);
|
|
@@ -1672,16 +2190,10 @@ var src_default = {
|
|
|
1672
2190
|
});
|
|
1673
2191
|
return;
|
|
1674
2192
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
bodyText: task.bodyText ?? "",
|
|
1680
|
-
contextLinks: task.contextLinks,
|
|
1681
|
-
timeoutSecs: task.timeoutSecs,
|
|
1682
|
-
messageId: task.messageId ?? "",
|
|
1683
|
-
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1684
|
-
});
|
|
2193
|
+
if (!queuePendingTask(task)) {
|
|
2194
|
+
api.logger.info(`[AAMP] Ignoring already-terminal or expired task ${task.taskId}`);
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
1685
2197
|
wakeAgentForPendingTask(pendingTasks.get(task.taskId));
|
|
1686
2198
|
} catch (err) {
|
|
1687
2199
|
api.logger.error(`[AAMP] task.dispatch handler failed for ${task.taskId}: ${err.message}`);
|
|
@@ -1690,7 +2202,21 @@ var src_default = {
|
|
|
1690
2202
|
}
|
|
1691
2203
|
}
|
|
1692
2204
|
});
|
|
2205
|
+
aampClient.on("task.cancel", (cancel) => {
|
|
2206
|
+
api.logger.info(`[AAMP] \u2190 task.cancel ${cancel.taskId} from=${cancel.from}`);
|
|
2207
|
+
const removed = pendingTasks.delete(cancel.taskId);
|
|
2208
|
+
pendingTasks.delete(`result:${cancel.taskId}`);
|
|
2209
|
+
pendingTasks.delete(`help:${cancel.taskId}`);
|
|
2210
|
+
dispatchedSubtasks.delete(cancel.taskId);
|
|
2211
|
+
waitingDispatches.delete(cancel.taskId);
|
|
2212
|
+
rememberTerminalTask(cancel.taskId);
|
|
2213
|
+
if (removed) {
|
|
2214
|
+
api.logger.info(`[AAMP] Cancelled task ${cancel.taskId} \u2014 removed from pending queue`);
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
1693
2217
|
aampClient.on("task.result", (result) => {
|
|
2218
|
+
if (result.from.toLowerCase() === agentEmail.toLowerCase())
|
|
2219
|
+
return;
|
|
1694
2220
|
api.logger.info(`[AAMP] \u2190 task.result ${result.taskId} status=${result.status} from=${result.from}`);
|
|
1695
2221
|
const sub = dispatchedSubtasks.get(result.taskId);
|
|
1696
2222
|
dispatchedSubtasks.delete(result.taskId);
|
|
@@ -1754,8 +2280,8 @@ Output:
|
|
|
1754
2280
|
${truncatedOutput}${attachmentInfo}` : `Agent ${result.from} rejected the sub-task.
|
|
1755
2281
|
|
|
1756
2282
|
Reason: ${result.errorMsg ?? "unknown"}`,
|
|
2283
|
+
priority: "urgent",
|
|
1757
2284
|
contextLinks: [],
|
|
1758
|
-
timeoutSecs: 0,
|
|
1759
2285
|
messageId: "",
|
|
1760
2286
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1761
2287
|
});
|
|
@@ -1817,6 +2343,8 @@ ${notifyBody?.bodyText ?? "Sub-task completed."}${actionSection}`;
|
|
|
1817
2343
|
});
|
|
1818
2344
|
});
|
|
1819
2345
|
aampClient.on("task.help_needed", (help) => {
|
|
2346
|
+
if (help.from.toLowerCase() === agentEmail.toLowerCase())
|
|
2347
|
+
return;
|
|
1820
2348
|
api.logger.info(`[AAMP] \u2190 task.help_needed ${help.taskId} question="${help.question}" from=${help.from}`);
|
|
1821
2349
|
const waiter = waitingDispatches.get(help.taskId);
|
|
1822
2350
|
if (waiter) {
|
|
@@ -1835,8 +2363,8 @@ ${notifyBody?.bodyText ?? "Sub-task completed."}${actionSection}`;
|
|
|
1835
2363
|
Question: ${help.question}
|
|
1836
2364
|
Blocked reason: ${help.blockedReason}${help.suggestedOptions?.length ? `
|
|
1837
2365
|
Suggested options: ${help.suggestedOptions.join(", ")}` : ""}`,
|
|
2366
|
+
priority: "urgent",
|
|
1838
2367
|
contextLinks: [],
|
|
1839
|
-
timeoutSecs: 0,
|
|
1840
2368
|
messageId: "",
|
|
1841
2369
|
receivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1842
2370
|
});
|
|
@@ -1938,6 +2466,10 @@ ${notifyBody?.bodyText ?? help.question}`;
|
|
|
1938
2466
|
lastTransportMode = mode;
|
|
1939
2467
|
lastLoggedTransportMode = mode;
|
|
1940
2468
|
}, 1e3);
|
|
2469
|
+
void aampClient.reconcileRecentEmails(100, { includeHistorical: true }).catch((err) => {
|
|
2470
|
+
lastConnectionError = err.message;
|
|
2471
|
+
api.logger.warn(`[AAMP] Startup mailbox reconcile failed: ${err.message}`);
|
|
2472
|
+
});
|
|
1941
2473
|
transportMonitorTimer = setInterval(() => {
|
|
1942
2474
|
if (!aampClient)
|
|
1943
2475
|
return;
|
|
@@ -1955,7 +2487,7 @@ ${notifyBody?.bodyText ?? help.question}`;
|
|
|
1955
2487
|
reconcileTimer = setInterval(() => {
|
|
1956
2488
|
if (!aampClient)
|
|
1957
2489
|
return;
|
|
1958
|
-
void aampClient.reconcileRecentEmails(
|
|
2490
|
+
void aampClient.reconcileRecentEmails(100).catch((err) => {
|
|
1959
2491
|
lastConnectionError = err.message;
|
|
1960
2492
|
api.logger.warn(`[AAMP] Mailbox reconcile failed: ${err.message}`);
|
|
1961
2493
|
});
|
|
@@ -2009,24 +2541,45 @@ ${notifyBody?.bodyText ?? help.question}`;
|
|
|
2009
2541
|
if (ctx?.sessionKey && !String(ctx.sessionKey).startsWith("aamp:")) {
|
|
2010
2542
|
currentSessionKey = ctx.sessionKey;
|
|
2011
2543
|
}
|
|
2012
|
-
const now = Date.now();
|
|
2013
2544
|
for (const [id, t] of pendingTasks) {
|
|
2014
|
-
if (
|
|
2015
|
-
|
|
2545
|
+
if (hasExpired(t)) {
|
|
2546
|
+
if (!isSyntheticPendingKey(id) && aampClient?.isConnected()) {
|
|
2547
|
+
void aampClient.sendResult({
|
|
2548
|
+
to: t.from,
|
|
2549
|
+
taskId: t.taskId,
|
|
2550
|
+
status: "rejected",
|
|
2551
|
+
output: "",
|
|
2552
|
+
errorMsg: t.expiresAt ? "Task expired before the agent could complete it." : "Task timed out while waiting for agent completion or follow-up input.",
|
|
2553
|
+
inReplyTo: t.messageId || void 0
|
|
2554
|
+
}).then(() => {
|
|
2555
|
+
rememberTerminalTask(t.taskId);
|
|
2556
|
+
api.logger.warn(`[AAMP] Task ${id} expired \u2014 sent rejected result to dispatcher`);
|
|
2557
|
+
}).catch((err) => {
|
|
2558
|
+
api.logger.error(`[AAMP] Task ${id} expired \u2014 failed to notify dispatcher: ${err.message}`);
|
|
2559
|
+
});
|
|
2560
|
+
} else {
|
|
2561
|
+
rememberTerminalTask(t.taskId);
|
|
2562
|
+
api.logger.warn(`[AAMP] Task ${id} expired \u2014 removing from queue`);
|
|
2563
|
+
}
|
|
2016
2564
|
pendingTasks.delete(id);
|
|
2017
2565
|
}
|
|
2018
2566
|
}
|
|
2019
2567
|
if (pendingTasks.size === 0)
|
|
2020
2568
|
return {};
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
const [taskKey, task] =
|
|
2569
|
+
const nextEntry = nextPendingEntry();
|
|
2570
|
+
if (!nextEntry)
|
|
2571
|
+
return {};
|
|
2572
|
+
const [taskKey, task] = nextEntry;
|
|
2025
2573
|
const isNotification = taskKey.startsWith("result:") || taskKey.startsWith("help:");
|
|
2026
2574
|
if (isNotification && taskKey) {
|
|
2027
2575
|
pendingTasks.delete(taskKey);
|
|
2028
2576
|
}
|
|
2029
|
-
const actionableTasks = [...pendingTasks.entries()].filter(([key]) => !key.startsWith("result:") && !key.startsWith("help:")).map(([, t]) => t)
|
|
2577
|
+
const actionableTasks = [...pendingTasks.entries()].filter(([key]) => !key.startsWith("result:") && !key.startsWith("help:")).map(([, t]) => t).sort((a, b) => {
|
|
2578
|
+
const rankDiff = priorityRank(a.priority) - priorityRank(b.priority);
|
|
2579
|
+
if (rankDiff !== 0)
|
|
2580
|
+
return rankDiff;
|
|
2581
|
+
return new Date(a.receivedAt).getTime() - new Date(b.receivedAt).getTime();
|
|
2582
|
+
});
|
|
2030
2583
|
const hasAttachmentInfo = isNotification && (task.bodyText?.includes("aamp_download_attachment") ?? false);
|
|
2031
2584
|
const actionRequiredSection = isNotification && actionableTasks.length > 0 ? [
|
|
2032
2585
|
``,
|
|
@@ -2036,7 +2589,7 @@ ${notifyBody?.bodyText ?? help.question}`;
|
|
|
2036
2589
|
`Use the sub-task result above to complete them by calling aamp_send_result.`,
|
|
2037
2590
|
``,
|
|
2038
2591
|
...actionableTasks.map(
|
|
2039
|
-
(t) => `- Task ID: ${t.taskId} | From: ${t.from} | Title: "${t.title}"`
|
|
2592
|
+
(t) => `- [${t.priority}] Task ID: ${t.taskId} | From: ${t.from} | Title: "${t.title}"`
|
|
2040
2593
|
),
|
|
2041
2594
|
...hasAttachmentInfo ? [
|
|
2042
2595
|
``,
|
|
@@ -2054,6 +2607,7 @@ ${notifyBody?.bodyText ?? help.question}`;
|
|
|
2054
2607
|
`If the sub-task included attachments, use aamp_download_attachment to fetch them.`,
|
|
2055
2608
|
``,
|
|
2056
2609
|
`Task ID: ${task.taskId}`,
|
|
2610
|
+
`Priority: ${task.priority}`,
|
|
2057
2611
|
`From: ${task.from}`,
|
|
2058
2612
|
`Title: ${task.title}`,
|
|
2059
2613
|
task.bodyText ? `
|
|
@@ -2096,7 +2650,7 @@ ${task.bodyText}` : "",
|
|
|
2096
2650
|
${task.bodyText}` : "",
|
|
2097
2651
|
task.contextLinks.length ? `Context Links:
|
|
2098
2652
|
${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
2099
|
-
task.
|
|
2653
|
+
task.expiresAt ? `Expires: ${task.expiresAt}` : `Expires: none`,
|
|
2100
2654
|
`Received: ${task.receivedAt}`,
|
|
2101
2655
|
pendingTasks.size > 1 ? `
|
|
2102
2656
|
(+${pendingTasks.size - 1} more tasks queued)` : ""
|
|
@@ -2215,6 +2769,7 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
|
2215
2769
|
attachments
|
|
2216
2770
|
});
|
|
2217
2771
|
pendingTasks.delete(task.taskId);
|
|
2772
|
+
rememberTerminalTask(task.taskId);
|
|
2218
2773
|
api.logger.info(`[AAMP] \u2192 task.result ${task.taskId} ${p.status}`);
|
|
2219
2774
|
if (pendingTasks.size > 0) {
|
|
2220
2775
|
try {
|
|
@@ -2296,8 +2851,13 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
|
2296
2851
|
if (pendingTasks.size === 0) {
|
|
2297
2852
|
return { content: [{ type: "text", text: "No pending AAMP tasks." }] };
|
|
2298
2853
|
}
|
|
2299
|
-
const lines = [...pendingTasks.values()].sort((a, b) =>
|
|
2300
|
-
|
|
2854
|
+
const lines = [...pendingTasks.values()].sort((a, b) => {
|
|
2855
|
+
const rankDiff = priorityRank(a.priority) - priorityRank(b.priority);
|
|
2856
|
+
if (rankDiff !== 0)
|
|
2857
|
+
return rankDiff;
|
|
2858
|
+
return new Date(a.receivedAt).getTime() - new Date(b.receivedAt).getTime();
|
|
2859
|
+
}).map(
|
|
2860
|
+
(t, i) => `${i + 1}. [${t.priority}] [${t.taskId}] "${t.title}"${t.bodyText ? `
|
|
2301
2861
|
Description: ${t.bodyText}` : ""} \u2014 from ${t.from} (received ${t.receivedAt})`
|
|
2302
2862
|
);
|
|
2303
2863
|
return {
|
|
@@ -2311,6 +2871,40 @@ ${lines.join("\n")}`
|
|
|
2311
2871
|
};
|
|
2312
2872
|
}
|
|
2313
2873
|
}, { name: "aamp_pending_tasks" });
|
|
2874
|
+
api.registerTool({
|
|
2875
|
+
name: "aamp_cancel_task",
|
|
2876
|
+
description: "Cancel a pending AAMP task and notify the dispatcher.",
|
|
2877
|
+
parameters: {
|
|
2878
|
+
type: "object",
|
|
2879
|
+
required: ["taskId"],
|
|
2880
|
+
properties: {
|
|
2881
|
+
taskId: { type: "string", description: "The AAMP task ID to cancel." },
|
|
2882
|
+
bodyText: { type: "string", description: "Optional cancellation note sent in the email body." }
|
|
2883
|
+
}
|
|
2884
|
+
},
|
|
2885
|
+
execute: async (_id, params) => {
|
|
2886
|
+
const p = params;
|
|
2887
|
+
const task = pendingTasks.get(p.taskId);
|
|
2888
|
+
if (!task) {
|
|
2889
|
+
return { content: [{ type: "text", text: `Error: task ${p.taskId} not found in pending queue.` }] };
|
|
2890
|
+
}
|
|
2891
|
+
if (!aampClient?.isConnected()) {
|
|
2892
|
+
return { content: [{ type: "text", text: "Error: AAMP client is not connected." }] };
|
|
2893
|
+
}
|
|
2894
|
+
await aampClient.sendCancel({
|
|
2895
|
+
to: task.from,
|
|
2896
|
+
taskId: task.taskId,
|
|
2897
|
+
bodyText: p.bodyText,
|
|
2898
|
+
inReplyTo: task.messageId || void 0
|
|
2899
|
+
});
|
|
2900
|
+
pendingTasks.delete(task.taskId);
|
|
2901
|
+
rememberTerminalTask(task.taskId);
|
|
2902
|
+
api.logger.info(`[AAMP] \u2192 task.cancel ${task.taskId}`);
|
|
2903
|
+
return {
|
|
2904
|
+
content: [{ type: "text", text: `Cancellation sent for task ${task.taskId}.` }]
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
}, { name: "aamp_cancel_task" });
|
|
2314
2908
|
api.registerTool({
|
|
2315
2909
|
name: "aamp_dispatch_task",
|
|
2316
2910
|
description: "Send a task to another AAMP agent and WAIT for the result. This tool blocks until the sub-agent replies (typically 5-60s). The sub-agent's output and any attachment file paths are returned directly.",
|
|
@@ -2322,7 +2916,8 @@ ${lines.join("\n")}`
|
|
|
2322
2916
|
title: { type: "string", description: "Task title (concise summary)" },
|
|
2323
2917
|
bodyText: { type: "string", description: "Detailed task description" },
|
|
2324
2918
|
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." },
|
|
2325
|
-
|
|
2919
|
+
priority: { type: "string", enum: ["urgent", "high", "normal"], description: "Task priority (optional)" },
|
|
2920
|
+
expiresAt: { type: "string", description: "Absolute expiry time in ISO 8601 format (optional)" },
|
|
2326
2921
|
contextLinks: {
|
|
2327
2922
|
type: "array",
|
|
2328
2923
|
items: { type: "string" },
|
|
@@ -2360,7 +2955,8 @@ ${lines.join("\n")}`
|
|
|
2360
2955
|
to: params.to,
|
|
2361
2956
|
title: params.title,
|
|
2362
2957
|
parentTaskId: params.parentTaskId,
|
|
2363
|
-
|
|
2958
|
+
priority: params.priority,
|
|
2959
|
+
expiresAt: params.expiresAt,
|
|
2364
2960
|
contextLinks: params.contextLinks,
|
|
2365
2961
|
attachments
|
|
2366
2962
|
});
|
|
@@ -2371,12 +2967,14 @@ ${lines.join("\n")}`
|
|
|
2371
2967
|
parentTaskId: params.parentTaskId
|
|
2372
2968
|
});
|
|
2373
2969
|
api.logger.info(`[AAMP] \u2192 task.dispatch ${result.taskId} to=${params.to} parent=${params.parentTaskId ?? "none"} (waiting for reply\u2026)`);
|
|
2374
|
-
const timeoutMs = (params.
|
|
2970
|
+
const timeoutMs = params.expiresAt ? Math.max(new Date(params.expiresAt).getTime() - Date.now(), 1) : 300 * 1e3;
|
|
2375
2971
|
const reply = await new Promise((resolve, reject) => {
|
|
2376
2972
|
waitingDispatches.set(result.taskId, resolve);
|
|
2377
2973
|
setTimeout(() => {
|
|
2378
2974
|
if (waitingDispatches.delete(result.taskId)) {
|
|
2379
|
-
reject(new Error(
|
|
2975
|
+
reject(new Error(
|
|
2976
|
+
params.expiresAt ? `Sub-task ${result.taskId} expired before a reply was received` : `Sub-task ${result.taskId} timed out after 300s`
|
|
2977
|
+
));
|
|
2380
2978
|
}
|
|
2381
2979
|
}, timeoutMs);
|
|
2382
2980
|
});
|
|
@@ -2478,7 +3076,8 @@ Question: ${h.question}`,
|
|
|
2478
3076
|
const apiUrl = discovery.api?.url;
|
|
2479
3077
|
if (!apiUrl)
|
|
2480
3078
|
throw new Error("AAMP discovery did not return api.url");
|
|
2481
|
-
const
|
|
3079
|
+
const apiBase = new URL(apiUrl, `${base}/`).toString();
|
|
3080
|
+
const res = await fetch(`${apiBase}?action=aamp.mailbox.check&email=${encodeURIComponent(email)}`);
|
|
2482
3081
|
if (!res.ok)
|
|
2483
3082
|
throw new Error(`HTTP ${res.status}`);
|
|
2484
3083
|
const data = await res.json();
|
|
@@ -2558,6 +3157,11 @@ Question: ${h.question}`,
|
|
|
2558
3157
|
}
|
|
2559
3158
|
};
|
|
2560
3159
|
export {
|
|
2561
|
-
|
|
3160
|
+
baseUrl,
|
|
3161
|
+
src_default as default,
|
|
3162
|
+
matchSenderPolicy,
|
|
3163
|
+
queuePendingTask,
|
|
3164
|
+
registerNode,
|
|
3165
|
+
resolveIdentity
|
|
2562
3166
|
};
|
|
2563
3167
|
//# sourceMappingURL=index.js.map
|