aamp-openclaw-plugin 0.1.35 → 0.1.37

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 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
 
@@ -0,0 +1,82 @@
1
+ import { createRequire as __aampCreateRequire } from "module"; const require = __aampCreateRequire(import.meta.url);
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined")
12
+ return require.apply(this, arguments);
13
+ throw Error('Dynamic require of "' + x + '" is not supported');
14
+ });
15
+ var __commonJS = (cb, mod) => function __require2() {
16
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+
35
+ // src/file-store.ts
36
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
37
+ import { dirname, join } from "node:path";
38
+ import { homedir } from "node:os";
39
+ function defaultCredentialsPath() {
40
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
41
+ }
42
+ function loadCachedIdentity(file) {
43
+ const resolved = file ?? defaultCredentialsPath();
44
+ if (!existsSync(resolved))
45
+ return null;
46
+ try {
47
+ const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
48
+ if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword)
49
+ return null;
50
+ return parsed;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function saveCachedIdentity(identity, file) {
56
+ const resolved = file ?? defaultCredentialsPath();
57
+ mkdirSync(dirname(resolved), { recursive: true });
58
+ writeFileSync(resolved, JSON.stringify(identity, null, 2), "utf-8");
59
+ }
60
+ function ensureDir(dir) {
61
+ mkdirSync(dir, { recursive: true });
62
+ }
63
+ function readBinaryFile(path) {
64
+ return readFileSync(path);
65
+ }
66
+ function writeBinaryFile(path, content) {
67
+ mkdirSync(dirname(path), { recursive: true });
68
+ writeFileSync(path, content);
69
+ }
70
+
71
+ export {
72
+ __require,
73
+ __commonJS,
74
+ __toESM,
75
+ defaultCredentialsPath,
76
+ loadCachedIdentity,
77
+ saveCachedIdentity,
78
+ ensureDir,
79
+ readBinaryFile,
80
+ writeBinaryFile
81
+ };
82
+ //# sourceMappingURL=chunk-ORTVVAMV.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/file-store.ts"],
4
+ "sourcesContent": ["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { homedir } from 'node:os'\n\nexport interface Identity {\n email: string\n jmapToken: string\n smtpPassword: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function loadCachedIdentity(file?: string): Identity | null {\n const resolved = file ?? defaultCredentialsPath()\n if (!existsSync(resolved)) return null\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as Partial<Identity>\n if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword) return null\n return parsed as Identity\n } catch {\n return null\n }\n}\n\nexport function saveCachedIdentity(identity: Identity, file?: string): void {\n const resolved = file ?? defaultCredentialsPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(identity, null, 2), 'utf-8')\n}\n\nexport function ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true })\n}\n\nexport function readBinaryFile(path: string): Buffer {\n return readFileSync(path)\n}\n\nexport function writeBinaryFile(path: string, content: Uint8Array | Buffer): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AAQjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;AAEO,SAAS,mBAAmB,MAAgC;AACjE,QAAM,WAAW,QAAQ,uBAAuB;AAChD,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO;AAClC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,QAAI,CAAC,OAAO,SAAS,CAAC,OAAO,aAAa,CAAC,OAAO;AAAc,aAAO;AACvE,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,UAAoB,MAAqB;AAC1E,QAAM,WAAW,QAAQ,uBAAuB;AAChD,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AACpE;AAEO,SAAS,UAAU,KAAmB;AAC3C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC;AAEO,SAAS,eAAe,MAAsB;AACnD,SAAO,aAAa,IAAI;AAC1B;AAEO,SAAS,gBAAgB,MAAc,SAAoC;AAChF,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,OAAO;AAC7B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,81 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined")
11
+ return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+
34
+ // src/file-store.ts
35
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
36
+ import { dirname, join } from "node:path";
37
+ import { homedir } from "node:os";
38
+ function defaultCredentialsPath() {
39
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
40
+ }
41
+ function loadCachedIdentity(file) {
42
+ const resolved = file ?? defaultCredentialsPath();
43
+ if (!existsSync(resolved))
44
+ return null;
45
+ try {
46
+ const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
47
+ if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword)
48
+ return null;
49
+ return parsed;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ function saveCachedIdentity(identity, file) {
55
+ const resolved = file ?? defaultCredentialsPath();
56
+ mkdirSync(dirname(resolved), { recursive: true });
57
+ writeFileSync(resolved, JSON.stringify(identity, null, 2), "utf-8");
58
+ }
59
+ function ensureDir(dir) {
60
+ mkdirSync(dir, { recursive: true });
61
+ }
62
+ function readBinaryFile(path) {
63
+ return readFileSync(path);
64
+ }
65
+ function writeBinaryFile(path, content) {
66
+ mkdirSync(dirname(path), { recursive: true });
67
+ writeFileSync(path, content);
68
+ }
69
+
70
+ export {
71
+ __require,
72
+ __commonJS,
73
+ __toESM,
74
+ defaultCredentialsPath,
75
+ loadCachedIdentity,
76
+ saveCachedIdentity,
77
+ ensureDir,
78
+ readBinaryFile,
79
+ writeBinaryFile
80
+ };
81
+ //# sourceMappingURL=chunk-W4C7IUCH.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/file-store.ts"],
4
+ "sourcesContent": ["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { homedir } from 'node:os'\n\nexport interface Identity {\n email: string\n jmapToken: string\n smtpPassword: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function loadCachedIdentity(file?: string): Identity | null {\n const resolved = file ?? defaultCredentialsPath()\n if (!existsSync(resolved)) return null\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as Partial<Identity>\n if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword) return null\n return parsed as Identity\n } catch {\n return null\n }\n}\n\nexport function saveCachedIdentity(identity: Identity, file?: string): void {\n const resolved = file ?? defaultCredentialsPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(identity, null, 2), 'utf-8')\n}\n\nexport function ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true })\n}\n\nexport function readBinaryFile(path: string): Buffer {\n return readFileSync(path)\n}\n\nexport function writeBinaryFile(path: string, content: Uint8Array | Buffer): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AAQjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;AAEO,SAAS,mBAAmB,MAAgC;AACjE,QAAM,WAAW,QAAQ,uBAAuB;AAChD,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO;AAClC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,QAAI,CAAC,OAAO,SAAS,CAAC,OAAO,aAAa,CAAC,OAAO;AAAc,aAAO;AACvE,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,UAAoB,MAAqB;AAC1E,QAAM,WAAW,QAAQ,uBAAuB;AAChD,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AACpE;AAEO,SAAS,UAAU,KAAmB;AAC3C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC;AAEO,SAAS,eAAe,MAAsB;AACnD,SAAO,aAAa,IAAI;AAC1B;AAEO,SAAS,gBAAgB,MAAc,SAAoC;AAChF,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,OAAO;AAC7B;",
6
+ "names": []
7
+ }
package/dist/index.js CHANGED
@@ -421,6 +421,17 @@ var TinyEmitter = class {
421
421
  }
422
422
  return true;
423
423
  }
424
+ async emitAsync(event, ...args) {
425
+ const bucket = this.listeners.get(event);
426
+ if (!bucket || bucket.size === 0)
427
+ return false;
428
+ const settled = await Promise.allSettled([...bucket].map((listener) => Promise.resolve(listener(...args))));
429
+ const rejected = settled.find((result) => result.status === "rejected");
430
+ if (rejected) {
431
+ throw rejected.reason;
432
+ }
433
+ return true;
434
+ }
424
435
  };
425
436
 
426
437
  // ../sdk/src/jmap-push.js
@@ -1897,14 +1908,27 @@ function buildRegisteredCommandDispatchPayload(opts) {
1897
1908
  stream: { mode: opts.streamMode ?? "full" }
1898
1909
  };
1899
1910
  }
1911
+ var DEFAULT_TASK_DISPATCH_CONCURRENCY = 10;
1912
+ function normalizeTaskDispatchConcurrency(value) {
1913
+ if (value == null)
1914
+ return DEFAULT_TASK_DISPATCH_CONCURRENCY;
1915
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
1916
+ throw new Error("taskDispatchConcurrency must be a positive integer");
1917
+ }
1918
+ return value;
1919
+ }
1900
1920
  var AampClient = class _AampClient extends TinyEmitter {
1901
1921
  jmapClient;
1902
1922
  smtpSender;
1903
1923
  config;
1924
+ taskDispatchConcurrency;
1925
+ pendingTaskDispatches = [];
1926
+ activeTaskDispatchCount = 0;
1904
1927
  streamAppendQueues = /* @__PURE__ */ new Map();
1905
1928
  constructor(config) {
1906
1929
  super();
1907
1930
  this.config = config;
1931
+ this.taskDispatchConcurrency = normalizeTaskDispatchConcurrency(config.taskDispatchConcurrency);
1908
1932
  const mailboxToken = config.mailboxToken;
1909
1933
  const resolvedBaseUrl = config.baseUrl;
1910
1934
  const derived = deriveMailboxServiceDefaults(config.email, resolvedBaseUrl);
@@ -1940,7 +1964,7 @@ var AampClient = class _AampClient extends TinyEmitter {
1940
1964
  rejectUnauthorized: config.rejectUnauthorized
1941
1965
  });
1942
1966
  this.jmapClient.on("task.dispatch", (task) => {
1943
- this.emit("task.dispatch", task);
1967
+ this.enqueueTaskDispatch(task);
1944
1968
  });
1945
1969
  this.jmapClient.on("task.cancel", (task) => {
1946
1970
  this.emit("task.cancel", task);
@@ -1993,6 +2017,7 @@ var AampClient = class _AampClient extends TinyEmitter {
1993
2017
  smtpPort: config.smtpPort ?? 587,
1994
2018
  smtpPassword: config.smtpPassword,
1995
2019
  reconnectInterval: config.reconnectInterval,
2020
+ taskDispatchConcurrency: config.taskDispatchConcurrency,
1996
2021
  rejectUnauthorized: config.rejectUnauthorized
1997
2022
  });
1998
2023
  }
@@ -2246,6 +2271,30 @@ var AampClient = class _AampClient extends TinyEmitter {
2246
2271
  }
2247
2272
  return res.json();
2248
2273
  }
2274
+ enqueueTaskDispatch(task) {
2275
+ this.pendingTaskDispatches.push(task);
2276
+ this.drainTaskDispatchQueue();
2277
+ }
2278
+ drainTaskDispatchQueue() {
2279
+ while (this.activeTaskDispatchCount < this.taskDispatchConcurrency && this.pendingTaskDispatches.length > 0) {
2280
+ const nextTask = this.pendingTaskDispatches.shift();
2281
+ if (!nextTask)
2282
+ return;
2283
+ this.activeTaskDispatchCount += 1;
2284
+ void this.runTaskDispatch(nextTask);
2285
+ }
2286
+ }
2287
+ async runTaskDispatch(task) {
2288
+ try {
2289
+ await this.emitAsync("task.dispatch", task);
2290
+ } catch (err) {
2291
+ const error = err instanceof Error ? err : new Error(String(err));
2292
+ this.emit("error", error);
2293
+ } finally {
2294
+ this.activeTaskDispatchCount = Math.max(0, this.activeTaskDispatchCount - 1);
2295
+ this.drainTaskDispatchQueue();
2296
+ }
2297
+ }
2249
2298
  getStreamAppendQueue(streamId) {
2250
2299
  let queue = this.streamAppendQueues.get(streamId);
2251
2300
  if (!queue) {
@@ -2706,6 +2755,24 @@ function isSyntheticPendingKey(taskKey) {
2706
2755
  function isTaskAwaitingHelpReply(task) {
2707
2756
  return task.awaitingHelpReply === true;
2708
2757
  }
2758
+ function isConversationalTask(task) {
2759
+ return task.dispatchContext?.source === "feishu";
2760
+ }
2761
+ function firstDispatchContextValue(context, keys) {
2762
+ if (!context)
2763
+ return void 0;
2764
+ for (const key of keys) {
2765
+ const value = context[key]?.trim();
2766
+ if (value)
2767
+ return value;
2768
+ }
2769
+ return void 0;
2770
+ }
2771
+ function threadAlreadyTerminal(events) {
2772
+ return (events ?? []).some(
2773
+ (event) => event.intent === "task.result" || event.intent === "task.cancel"
2774
+ );
2775
+ }
2709
2776
  function isActionablePendingTask(taskKey, task) {
2710
2777
  return !isSyntheticPendingKey(taskKey) && !isTaskAwaitingHelpReply(task);
2711
2778
  }
@@ -2747,12 +2814,36 @@ function buildOpenClawMainSessionKey(mainKey, config) {
2747
2814
  function buildAampConversationSessionKey(value, config) {
2748
2815
  return buildOpenClawMainSessionKey(`${AAMP_SESSION_PREFIX}default:${value}`, config);
2749
2816
  }
2817
+ function buildAampStickySessionKey(dispatchContext, config) {
2818
+ const stickyValue = firstDispatchContextValue(dispatchContext, ["session_key", "conversation_key", "thread_key"]);
2819
+ if (!stickyValue)
2820
+ return void 0;
2821
+ return buildAampConversationSessionKey(`session:${stickyValue}`, config);
2822
+ }
2750
2823
  function buildAampTaskSessionKey(taskId, config) {
2751
2824
  return buildAampConversationSessionKey(`task:${taskId}`, config);
2752
2825
  }
2753
2826
  function buildAampWakeSessionKey(kind, id) {
2754
2827
  return `${AAMP_SESSION_PREFIX}wake:${kind}:${id}`;
2755
2828
  }
2829
+ function buildSessionKeyForPendingTask(task, config) {
2830
+ return buildAampStickySessionKey(task.dispatchContext, config) ?? buildAampTaskSessionKey(task.taskId, config);
2831
+ }
2832
+ function buildWakeSessionKeyForPendingTask(task, config) {
2833
+ return buildAampStickySessionKey(task.dispatchContext, config) ?? buildAampWakeSessionKey("task", task.taskId);
2834
+ }
2835
+ function findPendingEntryForSession(sessionKey, config) {
2836
+ if (typeof sessionKey !== "string" || !isAampSessionKey(sessionKey))
2837
+ return void 0;
2838
+ const requested = buildOpenClawMainSessionKey(stripOpenClawAgentScope(sessionKey), config);
2839
+ const entries = [...pendingTasks.entries()].filter(([key, task]) => isActionablePendingTask(key, task)).filter(([, task]) => buildSessionKeyForPendingTask(task, config) === requested).sort((a, b) => {
2840
+ const rankDiff = priorityRank(a[1].priority) - priorityRank(b[1].priority);
2841
+ if (rankDiff !== 0)
2842
+ return rankDiff;
2843
+ return new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime();
2844
+ });
2845
+ return entries[0];
2846
+ }
2756
2847
  function resolvePendingKeyFromSessionKey(sessionKey) {
2757
2848
  if (typeof sessionKey !== "string")
2758
2849
  return void 0;
@@ -2843,6 +2934,7 @@ function queuePendingTask(task) {
2843
2934
  from: task.from,
2844
2935
  title: task.title,
2845
2936
  bodyText: task.bodyText ?? "",
2937
+ dispatchContext: task.dispatchContext,
2846
2938
  threadHistory: task.threadHistory ?? [],
2847
2939
  threadContextText: task.threadContextText ?? "",
2848
2940
  priority: task.priority ?? "normal",
@@ -3025,8 +3117,8 @@ var src_default = {
3025
3117
  api.logger.info(`[AAMP] Directory profile synced${cardText ? " (card text registered)" : ""}`);
3026
3118
  }
3027
3119
  function wakeAgentForPendingTask(task) {
3028
- const fallbackSessionKey = buildAampWakeSessionKey("task", task.taskId);
3029
- const openClawSessionKey = buildAampTaskSessionKey(task.taskId, api.config);
3120
+ const fallbackSessionKey = buildWakeSessionKeyForPendingTask(task, api.config);
3121
+ const openClawSessionKey = buildSessionKeyForPendingTask(task, api.config);
3030
3122
  const fallback = () => triggerHeartbeatWake(fallbackSessionKey, `task ${task.taskId}`);
3031
3123
  const dispatcher = channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher;
3032
3124
  api.logger.info(
@@ -3112,13 +3204,14 @@ var src_default = {
3112
3204
  email: identity.email,
3113
3205
  smtpPassword: identity.smtpPassword,
3114
3206
  baseUrl: base,
3207
+ taskDispatchConcurrency: cfg.taskDispatchConcurrency,
3115
3208
  // Local/dev: management-service proxy uses plain HTTP, no TLS cert to verify.
3116
3209
  // Production: set to true when using wss:// with valid certs.
3117
3210
  rejectUnauthorized: false
3118
3211
  });
3119
3212
  aampClient.on("task.dispatch", (task) => {
3120
3213
  api.logger.info(`[AAMP] \u2190 task.dispatch ${task.taskId} "${task.title}" from=${task.from}`);
3121
- void (async () => {
3214
+ return (async () => {
3122
3215
  try {
3123
3216
  if (terminalTaskIds.has(task.taskId)) {
3124
3217
  api.logger.info(`[AAMP] Skipping already-terminal task ${task.taskId}`);
@@ -3146,6 +3239,11 @@ var src_default = {
3146
3239
  threadContextText: ""
3147
3240
  };
3148
3241
  });
3242
+ if (threadAlreadyTerminal(hydratedTask.threadHistory)) {
3243
+ rememberTerminalTask(task.taskId);
3244
+ api.logger.info(`[AAMP] Skipping historical task ${task.taskId} because the thread already reached a terminal state`);
3245
+ return;
3246
+ }
3149
3247
  if (!queuePendingTask(hydratedTask)) {
3150
3248
  api.logger.info(`[AAMP] Ignoring already-terminal or expired task ${task.taskId}`);
3151
3249
  return;
@@ -3157,7 +3255,7 @@ var src_default = {
3157
3255
  } catch (err) {
3158
3256
  api.logger.error(`[AAMP] task.dispatch handler failed for ${task.taskId}: ${err.message}`);
3159
3257
  if (pendingTasks.has(task.taskId)) {
3160
- triggerHeartbeatWake(buildAampWakeSessionKey("task", task.taskId), `task ${task.taskId}`);
3258
+ triggerHeartbeatWake(buildWakeSessionKeyForPendingTask(pendingTasks.get(task.taskId), api.config), `task ${task.taskId}`);
3161
3259
  }
3162
3260
  }
3163
3261
  })();
@@ -3557,7 +3655,8 @@ ${notifyBody?.bodyText ?? help.question}`;
3557
3655
  }
3558
3656
  return [targetedPendingKey, targetedTask];
3559
3657
  })() : void 0;
3560
- const nextEntry = targetedPendingKey ? targetedEntry : nextPendingEntry();
3658
+ const sessionScopedEntry = targetedPendingKey ? void 0 : findPendingEntryForSession(ctx?.sessionKey, api.config);
3659
+ const nextEntry = targetedPendingKey ? targetedEntry : sessionScopedEntry ?? nextPendingEntry();
3561
3660
  if (!nextEntry)
3562
3661
  return {};
3563
3662
  const [taskKey, task] = nextEntry;
@@ -3592,19 +3691,45 @@ ${notifyBody?.bodyText ?? help.question}`;
3592
3691
  ` Example: attachments: [{ filename: "file.html", path: "/tmp/aamp-files/file.html" }]`
3593
3692
  ] : []
3594
3693
  ].join("\n") : "";
3595
- const lines = isNotification ? [
3596
- `## Sub-task Update`,
3694
+ const dispatchContextLines = task.dispatchContext && Object.keys(task.dispatchContext).length > 0 ? `Dispatch Context:
3695
+ ${Object.entries(task.dispatchContext).map(([key, value]) => ` - ${key}: ${value}`).join("\n")}` : "";
3696
+ const taskPromptLines = isConversationalTask(task) ? [
3697
+ `## Pending AAMP Conversation Turn`,
3597
3698
  ``,
3598
- `A sub-task you dispatched has returned a result. Review the information below.`,
3599
- `If the sub-task included attachments, use aamp_download_attachment to fetch them.`,
3699
+ `This AAMP task came from a chat surface (${task.dispatchContext?.source ?? "unknown"}).`,
3700
+ `Treat it as an ongoing conversation turn, not a one-off work order.`,
3701
+ `Your job is to reply naturally to the user's latest message and keep the conversation moving.`,
3702
+ ``,
3703
+ `### Tool selection rules for chat turns:`,
3704
+ ``,
3705
+ `Use aamp_send_result for normal conversation replies, including:`,
3706
+ ` - greetings, acknowledgements, and small talk ("hi", "hello", "thanks", "got it")`,
3707
+ ` - short follow-up questions that help narrow the user's intent`,
3708
+ ` - direct answers, suggestions, or next-step guidance`,
3709
+ ``,
3710
+ `Use aamp_send_help ONLY when you are truly blocked and cannot produce a meaningful`,
3711
+ `reply without waiting for specific missing information from the human.`,
3712
+ `Do NOT use aamp_send_help just because the message is brief or casual.`,
3713
+ ``,
3714
+ `IMPORTANT: For conversational traffic, replying to "hi" with a natural greeting and an`,
3715
+ `offer to help is CORRECT. Do not reject greetings as invalid tasks.`,
3716
+ ``,
3717
+ `### Sub-task dispatch rules:`,
3718
+ `If you delegate work to another agent via aamp_dispatch_task, you MUST pass`,
3719
+ `parentTaskId: "${task.taskId}" to establish the parent-child relationship.`,
3720
+ `If you need to find a suitable agent first, call aamp_directory_search.`,
3600
3721
  ``,
3601
3722
  `Task ID: ${task.taskId}`,
3602
- `Priority: ${task.priority}`,
3603
3723
  `From: ${task.from}`,
3604
3724
  `Title: ${task.title}`,
3605
- task.bodyText ? `
3725
+ dispatchContextLines,
3726
+ task.threadContextText ? `${task.threadContextText}` : "",
3727
+ task.bodyText ? `Latest user message:
3606
3728
  ${task.bodyText}` : "",
3607
- actionRequiredSection,
3729
+ task.contextLinks.length ? `Context Links:
3730
+ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
3731
+ task.expiresAt ? `Expires: ${task.expiresAt}` : `Expires: none`,
3732
+ `Received: ${task.receivedAt}`,
3608
3733
  otherActionableTasks.length > 0 ? `
3609
3734
  (+${otherActionableTasks.length} more tasks queued)` : ""
3610
3735
  ] : [
@@ -3639,6 +3764,7 @@ ${task.bodyText}` : "",
3639
3764
  `Task ID: ${task.taskId}`,
3640
3765
  `From: ${task.from}`,
3641
3766
  `Title: ${task.title}`,
3767
+ dispatchContextLines,
3642
3768
  task.threadContextText ? `${task.threadContextText}` : "",
3643
3769
  task.bodyText ? `Description:
3644
3770
  ${task.bodyText}` : "",
@@ -3648,7 +3774,23 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
3648
3774
  `Received: ${task.receivedAt}`,
3649
3775
  otherActionableTasks.length > 0 ? `
3650
3776
  (+${otherActionableTasks.length} more tasks queued)` : ""
3651
- ].filter(Boolean).join("\n");
3777
+ ];
3778
+ const lines = isNotification ? [
3779
+ `## Sub-task Update`,
3780
+ ``,
3781
+ `A sub-task you dispatched has returned a result. Review the information below.`,
3782
+ `If the sub-task included attachments, use aamp_download_attachment to fetch them.`,
3783
+ ``,
3784
+ `Task ID: ${task.taskId}`,
3785
+ `Priority: ${task.priority}`,
3786
+ `From: ${task.from}`,
3787
+ `Title: ${task.title}`,
3788
+ task.bodyText ? `
3789
+ ${task.bodyText}` : "",
3790
+ actionRequiredSection,
3791
+ otherActionableTasks.length > 0 ? `
3792
+ (+${otherActionableTasks.length} more tasks queued)` : ""
3793
+ ] : taskPromptLines.filter(Boolean).join("\n");
3652
3794
  return { prependContext: lines };
3653
3795
  },
3654
3796
  { priority: 5 }