aamp-openclaw-plugin 0.1.36 → 0.1.38
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 +2 -0
- package/bin/aamp-openclaw-plugin.mjs +7 -41
- package/dist/index.js +182 -76
- package/dist/index.js.map +3 -3
- package/package.json +24 -1
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ npm run build
|
|
|
38
38
|
"enabled": true,
|
|
39
39
|
"config": {
|
|
40
40
|
"aampHost": "https://meshmail.ai",
|
|
41
|
+
"taskDispatchConcurrency": 10,
|
|
41
42
|
"slug": "openclaw-agent",
|
|
42
43
|
"credentialsFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json",
|
|
43
44
|
"senderPolicies": [
|
|
@@ -57,6 +58,7 @@ npm run build
|
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
If `senderPolicies` is omitted, all senders are accepted. If set, the dispatch sender must match one policy and all configured dispatch-context rules for that sender must pass.
|
|
61
|
+
`taskDispatchConcurrency` is optional and defaults to `10`.
|
|
60
62
|
|
|
61
63
|
The plugin also understands:
|
|
62
64
|
|
|
@@ -8,6 +8,7 @@ import { stdin as input, stdout as output, stderr } from 'node:process'
|
|
|
8
8
|
import { spawnSync } from 'node:child_process'
|
|
9
9
|
import { createRequire } from 'node:module'
|
|
10
10
|
import { fileURLToPath } from 'node:url'
|
|
11
|
+
import { AampClient } from 'aamp-sdk'
|
|
11
12
|
|
|
12
13
|
const PLUGIN_ID = 'aamp-openclaw-plugin'
|
|
13
14
|
const DEFAULT_AAMP_HOST = 'https://meshmail.ai'
|
|
@@ -444,50 +445,15 @@ export async function ensureMailboxIdentity({ aampHost, slug, credentialsFile })
|
|
|
444
445
|
}
|
|
445
446
|
}
|
|
446
447
|
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
throw new Error(`AAMP discovery failed (${discoveryRes.status}): ${text || discoveryRes.statusText}`)
|
|
452
|
-
}
|
|
453
|
-
const discovery = await discoveryRes.json()
|
|
454
|
-
const apiUrl = discovery?.api?.url
|
|
455
|
-
if (!apiUrl) {
|
|
456
|
-
throw new Error('AAMP discovery did not return api.url')
|
|
457
|
-
}
|
|
458
|
-
const apiBase = new URL(apiUrl, `${base}/`).toString()
|
|
459
|
-
|
|
460
|
-
const registerRes = await fetch(`${apiBase}?action=aamp.mailbox.register`, {
|
|
461
|
-
method: 'POST',
|
|
462
|
-
headers: { 'Content-Type': 'application/json' },
|
|
463
|
-
body: JSON.stringify({
|
|
464
|
-
slug,
|
|
465
|
-
description: 'OpenClaw AAMP agent node',
|
|
466
|
-
}),
|
|
448
|
+
const credData = await AampClient.registerMailbox({
|
|
449
|
+
aampHost: normalizeBaseUrl(aampHost),
|
|
450
|
+
slug,
|
|
451
|
+
description: 'OpenClaw AAMP agent node',
|
|
467
452
|
})
|
|
468
|
-
|
|
469
|
-
if (!registerRes.ok) {
|
|
470
|
-
const text = await registerRes.text().catch(() => '')
|
|
471
|
-
throw new Error(`AAMP self-register failed (${registerRes.status}): ${text || registerRes.statusText}`)
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const registerData = await registerRes.json()
|
|
475
|
-
const code = registerData?.registrationCode
|
|
476
|
-
if (!code) {
|
|
477
|
-
throw new Error('AAMP self-register succeeded but no registrationCode was returned')
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const credRes = await fetch(`${apiBase}?action=aamp.mailbox.credentials&code=${encodeURIComponent(code)}`)
|
|
481
|
-
if (!credRes.ok) {
|
|
482
|
-
const text = await credRes.text().catch(() => '')
|
|
483
|
-
throw new Error(`AAMP credential exchange failed (${credRes.status}): ${text || credRes.statusText}`)
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const credData = await credRes.json()
|
|
487
453
|
const identity = {
|
|
488
454
|
email: credData?.email,
|
|
489
|
-
mailboxToken: credData?.
|
|
490
|
-
smtpPassword: credData?.
|
|
455
|
+
mailboxToken: credData?.mailboxToken,
|
|
456
|
+
smtpPassword: credData?.smtpPassword,
|
|
491
457
|
}
|
|
492
458
|
|
|
493
459
|
if (!identity.email || !identity.mailboxToken || !identity.smtpPassword) {
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
// ../sdk/
|
|
1
|
+
// ../sdk/dist/jmap-push.js
|
|
2
2
|
import WebSocket from "ws";
|
|
3
3
|
|
|
4
|
-
// ../sdk/
|
|
4
|
+
// ../sdk/dist/types.js
|
|
5
5
|
var AAMP_PROTOCOL_VERSION = "1.1";
|
|
6
6
|
var AAMP_HEADER = {
|
|
7
7
|
VERSION: "X-AAMP-Version",
|
|
8
8
|
INTENT: "X-AAMP-Intent",
|
|
9
9
|
TASK_ID: "X-AAMP-TaskId",
|
|
10
|
+
SESSION_KEY: "X-AAMP-Session-Key",
|
|
10
11
|
CONTEXT_LINKS: "X-AAMP-ContextLinks",
|
|
11
12
|
DISPATCH_CONTEXT: "X-AAMP-Dispatch-Context",
|
|
12
13
|
PRIORITY: "X-AAMP-Priority",
|
|
@@ -23,7 +24,7 @@ var AAMP_HEADER = {
|
|
|
23
24
|
CARD_SUMMARY: "X-AAMP-Card-Summary"
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
// ../sdk/
|
|
27
|
+
// ../sdk/dist/parser.js
|
|
27
28
|
function normalizeBodyText(value) {
|
|
28
29
|
return value?.replace(/\r\n/g, "\n").trim() ?? "";
|
|
29
30
|
}
|
|
@@ -171,6 +172,7 @@ function parseAampHeaders(meta) {
|
|
|
171
172
|
if (intent === "task.dispatch") {
|
|
172
173
|
const contextLinksStr = getAampHeader(headers, AAMP_HEADER.CONTEXT_LINKS) ?? "";
|
|
173
174
|
const dispatchContext = parseDispatchContextHeader(getAampHeader(headers, AAMP_HEADER.DISPATCH_CONTEXT));
|
|
175
|
+
const sessionKey = getAampHeader(headers, AAMP_HEADER.SESSION_KEY);
|
|
174
176
|
const parentTaskId = getAampHeader(headers, AAMP_HEADER.PARENT_TASK_ID);
|
|
175
177
|
const priority = getAampHeader(headers, AAMP_HEADER.PRIORITY) ?? "normal";
|
|
176
178
|
const expiresAt = getAampHeader(headers, AAMP_HEADER.EXPIRES_AT);
|
|
@@ -178,6 +180,7 @@ function parseAampHeaders(meta) {
|
|
|
178
180
|
protocolVersion,
|
|
179
181
|
intent: "task.dispatch",
|
|
180
182
|
taskId,
|
|
183
|
+
...sessionKey ? { sessionKey } : {},
|
|
181
184
|
title: decodedSubject.replace(/^\[AAMP Task\]\s*/, "").trim() || "Untitled Task",
|
|
182
185
|
priority: priority === "urgent" || priority === "high" ? priority : "normal",
|
|
183
186
|
...expiresAt ? { expiresAt } : {},
|
|
@@ -310,6 +313,9 @@ function buildDispatchHeaders(params) {
|
|
|
310
313
|
if (params.expiresAt) {
|
|
311
314
|
headers[AAMP_HEADER.EXPIRES_AT] = params.expiresAt;
|
|
312
315
|
}
|
|
316
|
+
if (params.sessionKey?.trim()) {
|
|
317
|
+
headers[AAMP_HEADER.SESSION_KEY] = params.sessionKey.trim();
|
|
318
|
+
}
|
|
313
319
|
if (params.contextLinks.length > 0) {
|
|
314
320
|
headers[AAMP_HEADER.CONTEXT_LINKS] = params.contextLinks.join(",");
|
|
315
321
|
}
|
|
@@ -381,7 +387,7 @@ function buildCardResponseHeaders(params) {
|
|
|
381
387
|
};
|
|
382
388
|
}
|
|
383
389
|
|
|
384
|
-
// ../sdk/
|
|
390
|
+
// ../sdk/dist/tiny-emitter.js
|
|
385
391
|
var TinyEmitter = class {
|
|
386
392
|
listeners = /* @__PURE__ */ new Map();
|
|
387
393
|
onceWrappers = /* @__PURE__ */ new WeakMap();
|
|
@@ -421,9 +427,20 @@ var TinyEmitter = class {
|
|
|
421
427
|
}
|
|
422
428
|
return true;
|
|
423
429
|
}
|
|
430
|
+
async emitAsync(event, ...args) {
|
|
431
|
+
const bucket = this.listeners.get(event);
|
|
432
|
+
if (!bucket || bucket.size === 0)
|
|
433
|
+
return false;
|
|
434
|
+
const settled = await Promise.allSettled([...bucket].map((listener) => Promise.resolve(listener(...args))));
|
|
435
|
+
const rejected = settled.find((result) => result.status === "rejected");
|
|
436
|
+
if (rejected) {
|
|
437
|
+
throw rejected.reason;
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
424
441
|
};
|
|
425
442
|
|
|
426
|
-
// ../sdk/
|
|
443
|
+
// ../sdk/dist/jmap-push.js
|
|
427
444
|
function describeError(err) {
|
|
428
445
|
if (!(err instanceof Error))
|
|
429
446
|
return String(err);
|
|
@@ -1103,7 +1120,7 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
1103
1120
|
}
|
|
1104
1121
|
};
|
|
1105
1122
|
|
|
1106
|
-
// ../sdk/
|
|
1123
|
+
// ../sdk/dist/smtp-sender.js
|
|
1107
1124
|
import { createTransport } from "nodemailer";
|
|
1108
1125
|
import { randomUUID } from "crypto";
|
|
1109
1126
|
var sanitize = (s) => s.replace(/[\r\n]/g, " ").trim();
|
|
@@ -1361,7 +1378,7 @@ var SmtpSender = class _SmtpSender {
|
|
|
1361
1378
|
from: this.config.user,
|
|
1362
1379
|
to: opts.to,
|
|
1363
1380
|
subject: `[AAMP Task] ${sanitize(opts.title)}`,
|
|
1364
|
-
text: [
|
|
1381
|
+
text: opts.rawBodyText ?? [
|
|
1365
1382
|
`Task: ${opts.title}`,
|
|
1366
1383
|
`Task ID: ${taskId}`,
|
|
1367
1384
|
`Priority: ${opts.priority ?? "normal"}`,
|
|
@@ -1429,7 +1446,7 @@ ${opts.contextLinks.map((l) => ` ${l}`).join("\n")}` : "",
|
|
|
1429
1446
|
from: this.config.user,
|
|
1430
1447
|
to: opts.to,
|
|
1431
1448
|
subject: `[AAMP Result] Task ${opts.taskId} \u2014 ${opts.status}`,
|
|
1432
|
-
text: [
|
|
1449
|
+
text: opts.rawBodyText ?? [
|
|
1433
1450
|
`AAMP Task Result`,
|
|
1434
1451
|
``,
|
|
1435
1452
|
`Task ID: ${opts.taskId}`,
|
|
@@ -1503,7 +1520,7 @@ Error: ${opts.errorMsg}` : ""
|
|
|
1503
1520
|
from: this.config.user,
|
|
1504
1521
|
to: opts.to,
|
|
1505
1522
|
subject: `[AAMP Help] Task ${opts.taskId} needs assistance`,
|
|
1506
|
-
text: [
|
|
1523
|
+
text: opts.rawBodyText ?? [
|
|
1507
1524
|
`AAMP Task Help Request`,
|
|
1508
1525
|
``,
|
|
1509
1526
|
`Task ID: ${opts.taskId}`,
|
|
@@ -1819,7 +1836,7 @@ Stream ID: ${opts.streamId}`,
|
|
|
1819
1836
|
}
|
|
1820
1837
|
};
|
|
1821
1838
|
|
|
1822
|
-
// ../sdk/
|
|
1839
|
+
// ../sdk/dist/thread.js
|
|
1823
1840
|
function singleLine(value, maxLength = 220) {
|
|
1824
1841
|
const normalized = (value ?? "").replace(/\s+/g, " ").trim();
|
|
1825
1842
|
if (!normalized)
|
|
@@ -1873,7 +1890,7 @@ function renderThreadHistoryForAgent(events, options = {}) {
|
|
|
1873
1890
|
].join("\n");
|
|
1874
1891
|
}
|
|
1875
1892
|
|
|
1876
|
-
// ../sdk/
|
|
1893
|
+
// ../sdk/dist/client.js
|
|
1877
1894
|
function buildRegisteredCommandDispatchPayload(opts) {
|
|
1878
1895
|
const command = opts.command.trim();
|
|
1879
1896
|
if (!command) {
|
|
@@ -1897,14 +1914,27 @@ function buildRegisteredCommandDispatchPayload(opts) {
|
|
|
1897
1914
|
stream: { mode: opts.streamMode ?? "full" }
|
|
1898
1915
|
};
|
|
1899
1916
|
}
|
|
1917
|
+
var DEFAULT_TASK_DISPATCH_CONCURRENCY = 10;
|
|
1918
|
+
function normalizeTaskDispatchConcurrency(value) {
|
|
1919
|
+
if (value == null)
|
|
1920
|
+
return DEFAULT_TASK_DISPATCH_CONCURRENCY;
|
|
1921
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
|
|
1922
|
+
throw new Error("taskDispatchConcurrency must be a positive integer");
|
|
1923
|
+
}
|
|
1924
|
+
return value;
|
|
1925
|
+
}
|
|
1900
1926
|
var AampClient = class _AampClient extends TinyEmitter {
|
|
1901
1927
|
jmapClient;
|
|
1902
1928
|
smtpSender;
|
|
1903
1929
|
config;
|
|
1930
|
+
taskDispatchConcurrency;
|
|
1931
|
+
pendingTaskDispatches = [];
|
|
1932
|
+
activeTaskDispatchCount = 0;
|
|
1904
1933
|
streamAppendQueues = /* @__PURE__ */ new Map();
|
|
1905
1934
|
constructor(config) {
|
|
1906
1935
|
super();
|
|
1907
1936
|
this.config = config;
|
|
1937
|
+
this.taskDispatchConcurrency = normalizeTaskDispatchConcurrency(config.taskDispatchConcurrency);
|
|
1908
1938
|
const mailboxToken = config.mailboxToken;
|
|
1909
1939
|
const resolvedBaseUrl = config.baseUrl;
|
|
1910
1940
|
const derived = deriveMailboxServiceDefaults(config.email, resolvedBaseUrl);
|
|
@@ -1940,7 +1970,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
1940
1970
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1941
1971
|
});
|
|
1942
1972
|
this.jmapClient.on("task.dispatch", (task) => {
|
|
1943
|
-
this.
|
|
1973
|
+
this.enqueueTaskDispatch(task);
|
|
1944
1974
|
});
|
|
1945
1975
|
this.jmapClient.on("task.cancel", (task) => {
|
|
1946
1976
|
this.emit("task.cancel", task);
|
|
@@ -1993,6 +2023,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
1993
2023
|
smtpPort: config.smtpPort ?? 587,
|
|
1994
2024
|
smtpPassword: config.smtpPassword,
|
|
1995
2025
|
reconnectInterval: config.reconnectInterval,
|
|
2026
|
+
taskDispatchConcurrency: config.taskDispatchConcurrency,
|
|
1996
2027
|
rejectUnauthorized: config.rejectUnauthorized
|
|
1997
2028
|
});
|
|
1998
2029
|
}
|
|
@@ -2063,6 +2094,22 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2063
2094
|
baseUrl: base
|
|
2064
2095
|
};
|
|
2065
2096
|
}
|
|
2097
|
+
static async checkMailbox(opts) {
|
|
2098
|
+
const base = opts.aampHost.replace(/\/$/, "");
|
|
2099
|
+
const res = await _AampClient.callDiscoveredApi(base, {
|
|
2100
|
+
action: "aamp.mailbox.check",
|
|
2101
|
+
query: { email: opts.email }
|
|
2102
|
+
});
|
|
2103
|
+
if (!res.ok) {
|
|
2104
|
+
const body = await res.text().catch(() => "");
|
|
2105
|
+
throw new Error(`Mailbox check failed: ${res.status} ${body || res.statusText}`);
|
|
2106
|
+
}
|
|
2107
|
+
const payload = await res.json();
|
|
2108
|
+
return {
|
|
2109
|
+
aamp: Boolean(payload.aamp),
|
|
2110
|
+
...payload.domain ? { domain: payload.domain } : {}
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2066
2113
|
// =====================================================
|
|
2067
2114
|
// Lifecycle
|
|
2068
2115
|
// =====================================================
|
|
@@ -2109,6 +2156,7 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2109
2156
|
rawBodyText: JSON.stringify(payload, null, 2),
|
|
2110
2157
|
priority: opts.priority,
|
|
2111
2158
|
expiresAt: opts.expiresAt,
|
|
2159
|
+
sessionKey: opts.sessionKey,
|
|
2112
2160
|
contextLinks: opts.contextLinks,
|
|
2113
2161
|
dispatchContext: opts.dispatchContext,
|
|
2114
2162
|
parentTaskId: opts.parentTaskId,
|
|
@@ -2246,6 +2294,30 @@ var AampClient = class _AampClient extends TinyEmitter {
|
|
|
2246
2294
|
}
|
|
2247
2295
|
return res.json();
|
|
2248
2296
|
}
|
|
2297
|
+
enqueueTaskDispatch(task) {
|
|
2298
|
+
this.pendingTaskDispatches.push(task);
|
|
2299
|
+
this.drainTaskDispatchQueue();
|
|
2300
|
+
}
|
|
2301
|
+
drainTaskDispatchQueue() {
|
|
2302
|
+
while (this.activeTaskDispatchCount < this.taskDispatchConcurrency && this.pendingTaskDispatches.length > 0) {
|
|
2303
|
+
const nextTask = this.pendingTaskDispatches.shift();
|
|
2304
|
+
if (!nextTask)
|
|
2305
|
+
return;
|
|
2306
|
+
this.activeTaskDispatchCount += 1;
|
|
2307
|
+
void this.runTaskDispatch(nextTask);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
async runTaskDispatch(task) {
|
|
2311
|
+
try {
|
|
2312
|
+
await this.emitAsync("task.dispatch", task);
|
|
2313
|
+
} catch (err) {
|
|
2314
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2315
|
+
this.emit("error", error);
|
|
2316
|
+
} finally {
|
|
2317
|
+
this.activeTaskDispatchCount = Math.max(0, this.activeTaskDispatchCount - 1);
|
|
2318
|
+
this.drainTaskDispatchQueue();
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2249
2321
|
getStreamAppendQueue(streamId) {
|
|
2250
2322
|
let queue = this.streamAppendQueues.get(streamId);
|
|
2251
2323
|
if (!queue) {
|
|
@@ -2706,6 +2778,10 @@ function isSyntheticPendingKey(taskKey) {
|
|
|
2706
2778
|
function isTaskAwaitingHelpReply(task) {
|
|
2707
2779
|
return task.awaitingHelpReply === true;
|
|
2708
2780
|
}
|
|
2781
|
+
function isConversationalTask(task) {
|
|
2782
|
+
const source = task.dispatchContext?.source?.trim().toLowerCase();
|
|
2783
|
+
return source === "feishu" || source === "wechat";
|
|
2784
|
+
}
|
|
2709
2785
|
function threadAlreadyTerminal(events) {
|
|
2710
2786
|
return (events ?? []).some(
|
|
2711
2787
|
(event) => event.intent === "task.result" || event.intent === "task.cancel"
|
|
@@ -2752,12 +2828,36 @@ function buildOpenClawMainSessionKey(mainKey, config) {
|
|
|
2752
2828
|
function buildAampConversationSessionKey(value, config) {
|
|
2753
2829
|
return buildOpenClawMainSessionKey(`${AAMP_SESSION_PREFIX}default:${value}`, config);
|
|
2754
2830
|
}
|
|
2831
|
+
function buildAampStickySessionKey(sessionKey, config) {
|
|
2832
|
+
const stickyValue = sessionKey?.trim();
|
|
2833
|
+
if (!stickyValue)
|
|
2834
|
+
return void 0;
|
|
2835
|
+
return buildAampConversationSessionKey(`session:${stickyValue}`, config);
|
|
2836
|
+
}
|
|
2755
2837
|
function buildAampTaskSessionKey(taskId, config) {
|
|
2756
2838
|
return buildAampConversationSessionKey(`task:${taskId}`, config);
|
|
2757
2839
|
}
|
|
2758
2840
|
function buildAampWakeSessionKey(kind, id) {
|
|
2759
2841
|
return `${AAMP_SESSION_PREFIX}wake:${kind}:${id}`;
|
|
2760
2842
|
}
|
|
2843
|
+
function buildSessionKeyForPendingTask(task, config) {
|
|
2844
|
+
return buildAampStickySessionKey(task.sessionKey, config) ?? buildAampTaskSessionKey(task.taskId, config);
|
|
2845
|
+
}
|
|
2846
|
+
function buildWakeSessionKeyForPendingTask(task, config) {
|
|
2847
|
+
return buildAampStickySessionKey(task.sessionKey, config) ?? buildAampWakeSessionKey("task", task.taskId);
|
|
2848
|
+
}
|
|
2849
|
+
function findPendingEntryForSession(sessionKey, config) {
|
|
2850
|
+
if (typeof sessionKey !== "string" || !isAampSessionKey(sessionKey))
|
|
2851
|
+
return void 0;
|
|
2852
|
+
const requested = buildOpenClawMainSessionKey(stripOpenClawAgentScope(sessionKey), config);
|
|
2853
|
+
const entries = [...pendingTasks.entries()].filter(([key, task]) => isActionablePendingTask(key, task)).filter(([, task]) => buildSessionKeyForPendingTask(task, config) === requested).sort((a, b) => {
|
|
2854
|
+
const rankDiff = priorityRank(a[1].priority) - priorityRank(b[1].priority);
|
|
2855
|
+
if (rankDiff !== 0)
|
|
2856
|
+
return rankDiff;
|
|
2857
|
+
return new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime();
|
|
2858
|
+
});
|
|
2859
|
+
return entries[0];
|
|
2860
|
+
}
|
|
2761
2861
|
function resolvePendingKeyFromSessionKey(sessionKey) {
|
|
2762
2862
|
if (typeof sessionKey !== "string")
|
|
2763
2863
|
return void 0;
|
|
@@ -2848,6 +2948,7 @@ function queuePendingTask(task) {
|
|
|
2848
2948
|
from: task.from,
|
|
2849
2949
|
title: task.title,
|
|
2850
2950
|
bodyText: task.bodyText ?? "",
|
|
2951
|
+
dispatchContext: task.dispatchContext,
|
|
2851
2952
|
threadHistory: task.threadHistory ?? [],
|
|
2852
2953
|
threadContextText: task.threadContextText ?? "",
|
|
2853
2954
|
priority: task.priority ?? "normal",
|
|
@@ -2865,39 +2966,15 @@ function queuePendingTask(task) {
|
|
|
2865
2966
|
}
|
|
2866
2967
|
async function registerNode(cfg) {
|
|
2867
2968
|
const slug = (cfg.slug ?? "openclaw-agent").toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
}
|
|
2873
|
-
const discovery = await discoveryRes.json();
|
|
2874
|
-
const apiUrl = discovery.api?.url;
|
|
2875
|
-
if (!apiUrl) {
|
|
2876
|
-
throw new Error("AAMP discovery did not return api.url");
|
|
2877
|
-
}
|
|
2878
|
-
const apiBase = new URL(apiUrl, `${base}/`).toString();
|
|
2879
|
-
const res = await fetch(`${apiBase}?action=aamp.mailbox.register`, {
|
|
2880
|
-
method: "POST",
|
|
2881
|
-
headers: { "Content-Type": "application/json" },
|
|
2882
|
-
body: JSON.stringify({ slug, description: "OpenClaw AAMP agent node" })
|
|
2969
|
+
const credData = await AampClient.registerMailbox({
|
|
2970
|
+
aampHost: cfg.aampHost,
|
|
2971
|
+
slug,
|
|
2972
|
+
description: "OpenClaw AAMP agent node"
|
|
2883
2973
|
});
|
|
2884
|
-
if (!res.ok) {
|
|
2885
|
-
const err = await res.json().catch(() => ({}));
|
|
2886
|
-
throw new Error(`AAMP registration failed (${res.status}): ${err.error ?? res.statusText}`);
|
|
2887
|
-
}
|
|
2888
|
-
const regData = await res.json();
|
|
2889
|
-
const credRes = await fetch(
|
|
2890
|
-
`${apiBase}?action=aamp.mailbox.credentials&code=${encodeURIComponent(regData.registrationCode)}`
|
|
2891
|
-
);
|
|
2892
|
-
if (!credRes.ok) {
|
|
2893
|
-
const err = await credRes.json().catch(() => ({}));
|
|
2894
|
-
throw new Error(`AAMP credential exchange failed (${credRes.status}): ${err.error ?? credRes.statusText}`);
|
|
2895
|
-
}
|
|
2896
|
-
const credData = await credRes.json();
|
|
2897
2974
|
return {
|
|
2898
2975
|
email: credData.email,
|
|
2899
|
-
mailboxToken: credData.
|
|
2900
|
-
smtpPassword: credData.
|
|
2976
|
+
mailboxToken: credData.mailboxToken,
|
|
2977
|
+
smtpPassword: credData.smtpPassword
|
|
2901
2978
|
};
|
|
2902
2979
|
}
|
|
2903
2980
|
async function resolveIdentity(cfg) {
|
|
@@ -3030,8 +3107,8 @@ var src_default = {
|
|
|
3030
3107
|
api.logger.info(`[AAMP] Directory profile synced${cardText ? " (card text registered)" : ""}`);
|
|
3031
3108
|
}
|
|
3032
3109
|
function wakeAgentForPendingTask(task) {
|
|
3033
|
-
const fallbackSessionKey =
|
|
3034
|
-
const openClawSessionKey =
|
|
3110
|
+
const fallbackSessionKey = buildWakeSessionKeyForPendingTask(task, api.config);
|
|
3111
|
+
const openClawSessionKey = buildSessionKeyForPendingTask(task, api.config);
|
|
3035
3112
|
const fallback = () => triggerHeartbeatWake(fallbackSessionKey, `task ${task.taskId}`);
|
|
3036
3113
|
const dispatcher = channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher;
|
|
3037
3114
|
api.logger.info(
|
|
@@ -3117,13 +3194,14 @@ var src_default = {
|
|
|
3117
3194
|
email: identity.email,
|
|
3118
3195
|
smtpPassword: identity.smtpPassword,
|
|
3119
3196
|
baseUrl: base,
|
|
3197
|
+
taskDispatchConcurrency: cfg.taskDispatchConcurrency,
|
|
3120
3198
|
// Local/dev: management-service proxy uses plain HTTP, no TLS cert to verify.
|
|
3121
3199
|
// Production: set to true when using wss:// with valid certs.
|
|
3122
3200
|
rejectUnauthorized: false
|
|
3123
3201
|
});
|
|
3124
3202
|
aampClient.on("task.dispatch", (task) => {
|
|
3125
3203
|
api.logger.info(`[AAMP] \u2190 task.dispatch ${task.taskId} "${task.title}" from=${task.from}`);
|
|
3126
|
-
|
|
3204
|
+
return (async () => {
|
|
3127
3205
|
try {
|
|
3128
3206
|
if (terminalTaskIds.has(task.taskId)) {
|
|
3129
3207
|
api.logger.info(`[AAMP] Skipping already-terminal task ${task.taskId}`);
|
|
@@ -3167,7 +3245,7 @@ var src_default = {
|
|
|
3167
3245
|
} catch (err) {
|
|
3168
3246
|
api.logger.error(`[AAMP] task.dispatch handler failed for ${task.taskId}: ${err.message}`);
|
|
3169
3247
|
if (pendingTasks.has(task.taskId)) {
|
|
3170
|
-
triggerHeartbeatWake(
|
|
3248
|
+
triggerHeartbeatWake(buildWakeSessionKeyForPendingTask(pendingTasks.get(task.taskId), api.config), `task ${task.taskId}`);
|
|
3171
3249
|
}
|
|
3172
3250
|
}
|
|
3173
3251
|
})();
|
|
@@ -3567,7 +3645,8 @@ ${notifyBody?.bodyText ?? help.question}`;
|
|
|
3567
3645
|
}
|
|
3568
3646
|
return [targetedPendingKey, targetedTask];
|
|
3569
3647
|
})() : void 0;
|
|
3570
|
-
const
|
|
3648
|
+
const sessionScopedEntry = targetedPendingKey ? void 0 : findPendingEntryForSession(ctx?.sessionKey, api.config);
|
|
3649
|
+
const nextEntry = targetedPendingKey ? targetedEntry : sessionScopedEntry ?? nextPendingEntry();
|
|
3571
3650
|
if (!nextEntry)
|
|
3572
3651
|
return {};
|
|
3573
3652
|
const [taskKey, task] = nextEntry;
|
|
@@ -3602,19 +3681,45 @@ ${notifyBody?.bodyText ?? help.question}`;
|
|
|
3602
3681
|
` Example: attachments: [{ filename: "file.html", path: "/tmp/aamp-files/file.html" }]`
|
|
3603
3682
|
] : []
|
|
3604
3683
|
].join("\n") : "";
|
|
3605
|
-
const
|
|
3606
|
-
|
|
3684
|
+
const dispatchContextLines = task.dispatchContext && Object.keys(task.dispatchContext).length > 0 ? `Dispatch Context:
|
|
3685
|
+
${Object.entries(task.dispatchContext).map(([key, value]) => ` - ${key}: ${value}`).join("\n")}` : "";
|
|
3686
|
+
const taskPromptLines = isConversationalTask(task) ? [
|
|
3687
|
+
`## Pending AAMP Conversation Turn`,
|
|
3607
3688
|
``,
|
|
3608
|
-
`
|
|
3609
|
-
`
|
|
3689
|
+
`This AAMP task came from a chat surface (${task.dispatchContext?.source ?? "unknown"}).`,
|
|
3690
|
+
`Treat it as an ongoing conversation turn, not a one-off work order.`,
|
|
3691
|
+
`Your job is to reply naturally to the user's latest message and keep the conversation moving.`,
|
|
3692
|
+
``,
|
|
3693
|
+
`### Tool selection rules for chat turns:`,
|
|
3694
|
+
``,
|
|
3695
|
+
`Use aamp_send_result for normal conversation replies, including:`,
|
|
3696
|
+
` - greetings, acknowledgements, and small talk ("hi", "hello", "thanks", "got it")`,
|
|
3697
|
+
` - short follow-up questions that help narrow the user's intent`,
|
|
3698
|
+
` - direct answers, suggestions, or next-step guidance`,
|
|
3699
|
+
``,
|
|
3700
|
+
`Use aamp_send_help ONLY when you are truly blocked and cannot produce a meaningful`,
|
|
3701
|
+
`reply without waiting for specific missing information from the human.`,
|
|
3702
|
+
`Do NOT use aamp_send_help just because the message is brief or casual.`,
|
|
3703
|
+
``,
|
|
3704
|
+
`IMPORTANT: For conversational traffic, replying to "hi" with a natural greeting and an`,
|
|
3705
|
+
`offer to help is CORRECT. Do not reject greetings as invalid tasks.`,
|
|
3706
|
+
``,
|
|
3707
|
+
`### Sub-task dispatch rules:`,
|
|
3708
|
+
`If you delegate work to another agent via aamp_dispatch_task, you MUST pass`,
|
|
3709
|
+
`parentTaskId: "${task.taskId}" to establish the parent-child relationship.`,
|
|
3710
|
+
`If you need to find a suitable agent first, call aamp_directory_search.`,
|
|
3610
3711
|
``,
|
|
3611
3712
|
`Task ID: ${task.taskId}`,
|
|
3612
|
-
`Priority: ${task.priority}`,
|
|
3613
3713
|
`From: ${task.from}`,
|
|
3614
3714
|
`Title: ${task.title}`,
|
|
3615
|
-
|
|
3715
|
+
dispatchContextLines,
|
|
3716
|
+
task.threadContextText ? `${task.threadContextText}` : "",
|
|
3717
|
+
task.bodyText ? `Latest user message:
|
|
3616
3718
|
${task.bodyText}` : "",
|
|
3617
|
-
|
|
3719
|
+
task.contextLinks.length ? `Context Links:
|
|
3720
|
+
${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
3721
|
+
task.expiresAt ? `Expires: ${task.expiresAt}` : `Expires: none`,
|
|
3722
|
+
`Received: ${task.receivedAt}`,
|
|
3618
3723
|
otherActionableTasks.length > 0 ? `
|
|
3619
3724
|
(+${otherActionableTasks.length} more tasks queued)` : ""
|
|
3620
3725
|
] : [
|
|
@@ -3649,6 +3754,7 @@ ${task.bodyText}` : "",
|
|
|
3649
3754
|
`Task ID: ${task.taskId}`,
|
|
3650
3755
|
`From: ${task.from}`,
|
|
3651
3756
|
`Title: ${task.title}`,
|
|
3757
|
+
dispatchContextLines,
|
|
3652
3758
|
task.threadContextText ? `${task.threadContextText}` : "",
|
|
3653
3759
|
task.bodyText ? `Description:
|
|
3654
3760
|
${task.bodyText}` : "",
|
|
@@ -3658,7 +3764,23 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
|
3658
3764
|
`Received: ${task.receivedAt}`,
|
|
3659
3765
|
otherActionableTasks.length > 0 ? `
|
|
3660
3766
|
(+${otherActionableTasks.length} more tasks queued)` : ""
|
|
3661
|
-
]
|
|
3767
|
+
];
|
|
3768
|
+
const lines = isNotification ? [
|
|
3769
|
+
`## Sub-task Update`,
|
|
3770
|
+
``,
|
|
3771
|
+
`A sub-task you dispatched has returned a result. Review the information below.`,
|
|
3772
|
+
`If the sub-task included attachments, use aamp_download_attachment to fetch them.`,
|
|
3773
|
+
``,
|
|
3774
|
+
`Task ID: ${task.taskId}`,
|
|
3775
|
+
`Priority: ${task.priority}`,
|
|
3776
|
+
`From: ${task.from}`,
|
|
3777
|
+
`Title: ${task.title}`,
|
|
3778
|
+
task.bodyText ? `
|
|
3779
|
+
${task.bodyText}` : "",
|
|
3780
|
+
actionRequiredSection,
|
|
3781
|
+
otherActionableTasks.length > 0 ? `
|
|
3782
|
+
(+${otherActionableTasks.length} more tasks queued)` : ""
|
|
3783
|
+
] : taskPromptLines.filter(Boolean).join("\n");
|
|
3662
3784
|
return { prependContext: lines };
|
|
3663
3785
|
},
|
|
3664
3786
|
{ priority: 5 }
|
|
@@ -4075,17 +4197,9 @@ ${lines.join("\n")}`
|
|
|
4075
4197
|
const dir = "/tmp/aamp-files";
|
|
4076
4198
|
ensureDir(dir);
|
|
4077
4199
|
const downloaded = [];
|
|
4078
|
-
const base = baseUrl(cfg.aampHost);
|
|
4079
|
-
const identity = loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath());
|
|
4080
|
-
const authHeader = identity ? `Basic ${Buffer.from(identity.email + ":" + identity.smtpPassword).toString("base64")}` : "";
|
|
4081
4200
|
for (const att of r.attachments) {
|
|
4082
4201
|
try {
|
|
4083
|
-
const
|
|
4084
|
-
api.logger.info(`[AAMP] Fetching ${dlUrl}`);
|
|
4085
|
-
const dlRes = await fetch(dlUrl, { headers: { Authorization: authHeader } });
|
|
4086
|
-
if (!dlRes.ok)
|
|
4087
|
-
throw new Error(`HTTP ${dlRes.status}`);
|
|
4088
|
-
const buffer = Buffer.from(await dlRes.arrayBuffer());
|
|
4202
|
+
const buffer = await aampClient.downloadBlob(att.blobId, att.filename);
|
|
4089
4203
|
const filepath = `${dir}/${att.filename}`;
|
|
4090
4204
|
writeBinaryFile(filepath, buffer);
|
|
4091
4205
|
downloaded.push(`${att.filename} (${(buffer.length / 1024).toFixed(1)} KB) \u2192 ${filepath}`);
|
|
@@ -4157,18 +4271,10 @@ Question: ${h.question}`,
|
|
|
4157
4271
|
return { content: [{ type: "text", text: "Error: email parameter is required" }] };
|
|
4158
4272
|
}
|
|
4159
4273
|
try {
|
|
4160
|
-
const
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
const apiUrl = discovery.api?.url;
|
|
4165
|
-
if (!apiUrl)
|
|
4166
|
-
throw new Error("AAMP discovery did not return api.url");
|
|
4167
|
-
const apiBase = new URL(apiUrl, `${base}/`).toString();
|
|
4168
|
-
const res = await fetch(`${apiBase}?action=aamp.mailbox.check&email=${encodeURIComponent(email)}`);
|
|
4169
|
-
if (!res.ok)
|
|
4170
|
-
throw new Error(`HTTP ${res.status}`);
|
|
4171
|
-
const data = await res.json();
|
|
4274
|
+
const data = await AampClient.checkMailbox({
|
|
4275
|
+
aampHost: base,
|
|
4276
|
+
email
|
|
4277
|
+
});
|
|
4172
4278
|
return {
|
|
4173
4279
|
content: [{
|
|
4174
4280
|
type: "text",
|