aamp-openclaw-plugin 0.1.30 → 0.1.32

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/dist/index.js CHANGED
@@ -1833,6 +1833,7 @@ var AampClient = class _AampClient extends TinyEmitter {
1833
1833
  jmapClient;
1834
1834
  smtpSender;
1835
1835
  config;
1836
+ streamAppendQueues = /* @__PURE__ */ new Map();
1836
1837
  constructor(config) {
1837
1838
  super();
1838
1839
  this.config = config;
@@ -2162,7 +2163,15 @@ var AampClient = class _AampClient extends TinyEmitter {
2162
2163
  }
2163
2164
  return res.json();
2164
2165
  }
2165
- async appendStreamEvent(opts) {
2166
+ getStreamAppendQueue(streamId) {
2167
+ let queue = this.streamAppendQueues.get(streamId);
2168
+ if (!queue) {
2169
+ queue = { running: false, operations: [] };
2170
+ this.streamAppendQueues.set(streamId, queue);
2171
+ }
2172
+ return queue;
2173
+ }
2174
+ async dispatchStreamAppend(opts) {
2166
2175
  const stream = await this.resolveStreamCapability();
2167
2176
  const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2168
2177
  action: stream.appendAction ?? "aamp.stream.append",
@@ -2176,7 +2185,98 @@ var AampClient = class _AampClient extends TinyEmitter {
2176
2185
  }
2177
2186
  return res.json();
2178
2187
  }
2188
+ enqueueStreamAppend(streamId, operation) {
2189
+ const queue = this.getStreamAppendQueue(streamId);
2190
+ queue.operations.push(operation);
2191
+ void this.drainStreamAppendQueue(streamId);
2192
+ }
2193
+ async drainStreamAppendQueue(streamId) {
2194
+ const queue = this.streamAppendQueues.get(streamId);
2195
+ if (!queue || queue.running)
2196
+ return;
2197
+ queue.running = true;
2198
+ try {
2199
+ while (queue.operations.length) {
2200
+ const operation = queue.operations.shift();
2201
+ if (!operation)
2202
+ continue;
2203
+ if (operation.kind === "text-delta-batch") {
2204
+ try {
2205
+ const event = await this.dispatchStreamAppend({
2206
+ streamId,
2207
+ type: "text.delta",
2208
+ payload: {
2209
+ ...operation.payload,
2210
+ text: operation.text
2211
+ }
2212
+ });
2213
+ for (const resolve of operation.resolvers)
2214
+ resolve(event);
2215
+ } catch (error) {
2216
+ for (const reject of operation.rejecters)
2217
+ reject(error);
2218
+ }
2219
+ continue;
2220
+ }
2221
+ try {
2222
+ const event = await this.dispatchStreamAppend(operation.opts);
2223
+ operation.resolve(event);
2224
+ } catch (error) {
2225
+ operation.reject(error);
2226
+ }
2227
+ }
2228
+ } finally {
2229
+ queue.running = false;
2230
+ if (queue.operations.length === 0) {
2231
+ this.streamAppendQueues.delete(streamId);
2232
+ }
2233
+ }
2234
+ }
2235
+ async flushStreamAppendQueue(streamId) {
2236
+ while (true) {
2237
+ const queue = this.streamAppendQueues.get(streamId);
2238
+ if (!queue)
2239
+ return;
2240
+ if (!queue.running && queue.operations.length === 0) {
2241
+ this.streamAppendQueues.delete(streamId);
2242
+ return;
2243
+ }
2244
+ await new Promise((resolve) => setTimeout(resolve, 0));
2245
+ }
2246
+ }
2247
+ async appendStreamEvent(opts) {
2248
+ if (opts.type === "text.delta" && typeof opts.payload.text === "string") {
2249
+ return await new Promise((resolve, reject) => {
2250
+ const queue = this.getStreamAppendQueue(opts.streamId);
2251
+ const lastOperation = queue.operations.at(-1);
2252
+ if (lastOperation?.kind === "text-delta-batch") {
2253
+ lastOperation.text += String(opts.payload.text ?? "");
2254
+ lastOperation.resolvers.push(resolve);
2255
+ lastOperation.rejecters.push(reject);
2256
+ return;
2257
+ }
2258
+ this.enqueueStreamAppend(opts.streamId, {
2259
+ kind: "text-delta-batch",
2260
+ text: String(opts.payload.text ?? ""),
2261
+ payload: {
2262
+ ...opts.payload
2263
+ },
2264
+ resolvers: [resolve],
2265
+ rejecters: [reject]
2266
+ });
2267
+ });
2268
+ }
2269
+ return await new Promise((resolve, reject) => {
2270
+ this.enqueueStreamAppend(opts.streamId, {
2271
+ kind: "single-event",
2272
+ opts,
2273
+ resolve,
2274
+ reject
2275
+ });
2276
+ });
2277
+ }
2179
2278
  async closeStream(opts) {
2279
+ await this.flushStreamAppendQueue(opts.streamId);
2180
2280
  const stream = await this.resolveStreamCapability();
2181
2281
  const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2182
2282
  action: stream.closeAction ?? "aamp.stream.close",
@@ -2436,6 +2536,13 @@ function baseUrl(aampHost) {
2436
2536
  var pendingTasks = /* @__PURE__ */ new Map();
2437
2537
  var activeTaskStreams = /* @__PURE__ */ new Map();
2438
2538
  var terminalTaskIds = new Set(loadTaskState(defaultTaskStatePath()).terminalTaskIds ?? []);
2539
+ var AAMP_SESSION_PREFIX = "aamp:";
2540
+ var DEFAULT_OPENCLAW_AGENT_ID = "main";
2541
+ var OPENCLAW_AGENT_SESSION_PREFIX = "agent:";
2542
+ var VALID_OPENCLAW_AGENT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
2543
+ var INVALID_OPENCLAW_AGENT_ID_RE = /[^a-z0-9_-]+/g;
2544
+ var LEADING_DASH_RE = /^-+/;
2545
+ var TRAILING_DASH_RE = /-+$/;
2439
2546
  var dispatchedSubtasks = /* @__PURE__ */ new Map();
2440
2547
  var waitingDispatches = /* @__PURE__ */ new Map();
2441
2548
  var aampClient = null;
@@ -2447,7 +2554,6 @@ var lastLoggedTransportMode = "disconnected";
2447
2554
  var reconcileTimer = null;
2448
2555
  var transportMonitorTimer = null;
2449
2556
  var historicalReconcileCompleted = false;
2450
- var currentSessionKey = "agent:main:main";
2451
2557
  var channelRuntime = null;
2452
2558
  var channelCfg = null;
2453
2559
  async function ensureTaskStream(task) {
@@ -2514,6 +2620,50 @@ function logTransportState(api, mode, email, previousMode) {
2514
2620
  function isSyntheticPendingKey(taskKey) {
2515
2621
  return taskKey.startsWith("result:") || taskKey.startsWith("help:");
2516
2622
  }
2623
+ function normalizeOpenClawAgentId(value) {
2624
+ const trimmed = typeof value === "string" ? value.trim() : "";
2625
+ if (!trimmed)
2626
+ return DEFAULT_OPENCLAW_AGENT_ID;
2627
+ if (VALID_OPENCLAW_AGENT_ID_RE.test(trimmed))
2628
+ return trimmed.toLowerCase();
2629
+ return trimmed.toLowerCase().replace(INVALID_OPENCLAW_AGENT_ID_RE, "-").replace(LEADING_DASH_RE, "").replace(TRAILING_DASH_RE, "").slice(0, 64) || DEFAULT_OPENCLAW_AGENT_ID;
2630
+ }
2631
+ function resolveDefaultOpenClawAgentId(config) {
2632
+ const agents = config?.agents?.list;
2633
+ if (!Array.isArray(agents) || agents.length === 0)
2634
+ return DEFAULT_OPENCLAW_AGENT_ID;
2635
+ const defaults = agents.filter((agent) => agent?.default);
2636
+ return normalizeOpenClawAgentId((defaults[0] ?? agents[0])?.id);
2637
+ }
2638
+ function stripOpenClawAgentScope(sessionKey) {
2639
+ const trimmed = sessionKey.trim();
2640
+ if (!trimmed.toLowerCase().startsWith(OPENCLAW_AGENT_SESSION_PREFIX))
2641
+ return trimmed;
2642
+ const parts = trimmed.split(":");
2643
+ if (parts.length < 3 || parts[0]?.toLowerCase() !== "agent")
2644
+ return trimmed;
2645
+ return parts.slice(2).join(":");
2646
+ }
2647
+ function isAampSessionKey(sessionKey) {
2648
+ return typeof sessionKey === "string" && stripOpenClawAgentScope(sessionKey).toLowerCase().startsWith(AAMP_SESSION_PREFIX);
2649
+ }
2650
+ function buildOpenClawMainSessionKey(mainKey, config) {
2651
+ const trimmed = mainKey.trim();
2652
+ if (!trimmed)
2653
+ return `${OPENCLAW_AGENT_SESSION_PREFIX}${resolveDefaultOpenClawAgentId(config)}:main`;
2654
+ if (trimmed.toLowerCase().startsWith(OPENCLAW_AGENT_SESSION_PREFIX))
2655
+ return trimmed;
2656
+ return `${OPENCLAW_AGENT_SESSION_PREFIX}${resolveDefaultOpenClawAgentId(config)}:${trimmed}`;
2657
+ }
2658
+ function buildAampConversationSessionKey(value, config) {
2659
+ return buildOpenClawMainSessionKey(`${AAMP_SESSION_PREFIX}default:${value}`, config);
2660
+ }
2661
+ function buildAampTaskSessionKey(taskId, config) {
2662
+ return buildAampConversationSessionKey(`task:${taskId}`, config);
2663
+ }
2664
+ function buildAampWakeSessionKey(kind, id) {
2665
+ return `${AAMP_SESSION_PREFIX}wake:${kind}:${id}`;
2666
+ }
2517
2667
  function saveTerminalTaskIds() {
2518
2668
  saveTaskState({ terminalTaskIds: [...terminalTaskIds] }, defaultTaskStatePath());
2519
2669
  }
@@ -2749,10 +2899,12 @@ var src_default = {
2749
2899
  api.logger.info(`[AAMP] Directory profile synced${cardText ? " (card text registered)" : ""}`);
2750
2900
  }
2751
2901
  function wakeAgentForPendingTask(task) {
2752
- const fallback = () => triggerHeartbeatWake(currentSessionKey, `task ${task.taskId}`);
2902
+ const fallbackSessionKey = buildAampWakeSessionKey("task", task.taskId);
2903
+ const openClawSessionKey = buildAampTaskSessionKey(task.taskId, api.config);
2904
+ const fallback = () => triggerHeartbeatWake(fallbackSessionKey, `task ${task.taskId}`);
2753
2905
  const dispatcher = channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher;
2754
2906
  api.logger.info(
2755
- `[AAMP] Wake requested for task ${task.taskId} \u2014 channelRuntime=${channelRuntime ? "yes" : "no"} channelCfg=${channelCfg ? "yes" : "no"} dispatcher=${typeof dispatcher === "function" ? "yes" : "no"} session=${currentSessionKey}`
2907
+ `[AAMP] Wake requested for task ${task.taskId} \u2014 channelRuntime=${channelRuntime ? "yes" : "no"} channelCfg=${channelCfg ? "yes" : "no"} dispatcher=${typeof dispatcher === "function" ? "yes" : "no"} session=${openClawSessionKey} fallbackSession=${fallbackSessionKey}`
2756
2908
  );
2757
2909
  if (!channelRuntime || !channelCfg || typeof dispatcher !== "function") {
2758
2910
  fallback();
@@ -2772,7 +2924,7 @@ var src_default = {
2772
2924
  BodyForAgent: prompt,
2773
2925
  From: task.from,
2774
2926
  To: agentEmail,
2775
- SessionKey: `aamp:default:task:${task.taskId}`,
2927
+ SessionKey: openClawSessionKey,
2776
2928
  AccountId: "default",
2777
2929
  ChatType: "dm",
2778
2930
  Provider: "aamp",
@@ -2879,7 +3031,7 @@ var src_default = {
2879
3031
  } catch (err) {
2880
3032
  api.logger.error(`[AAMP] task.dispatch handler failed for ${task.taskId}: ${err.message}`);
2881
3033
  if (pendingTasks.has(task.taskId)) {
2882
- triggerHeartbeatWake(currentSessionKey, `task ${task.taskId}`);
3034
+ triggerHeartbeatWake(buildAampWakeSessionKey("task", task.taskId), `task ${task.taskId}`);
2883
3035
  }
2884
3036
  }
2885
3037
  })();
@@ -2986,7 +3138,7 @@ ${notifyBody?.bodyText ?? "Sub-task completed."}${actionSection}`;
2986
3138
  BodyForAgent: prompt,
2987
3139
  From: result.from,
2988
3140
  To: agentEmail,
2989
- SessionKey: `aamp:default:${result.from}`,
3141
+ SessionKey: buildAampConversationSessionKey(result.from, api.config),
2990
3142
  AccountId: "default",
2991
3143
  ChatType: "dm",
2992
3144
  Provider: "aamp",
@@ -3014,7 +3166,7 @@ ${notifyBody?.bodyText ?? "Sub-task completed."}${actionSection}`;
3014
3166
  api.logger.error(`[AAMP] Channel dispatch failed: ${err.message}`);
3015
3167
  });
3016
3168
  } else {
3017
- const notifySessionKey = `agent:main:aamp-notify-${Date.now()}`;
3169
+ const notifySessionKey = buildAampWakeSessionKey("result", result.taskId);
3018
3170
  try {
3019
3171
  api.runtime.system.requestHeartbeatNow({ reason: "wake", sessionKey: notifySessionKey });
3020
3172
  api.logger.info(`[AAMP] Heartbeat for sub-task result ${result.taskId}`);
@@ -3063,7 +3215,7 @@ ${notifyBody?.bodyText ?? help.question}`;
3063
3215
  BodyForAgent: prompt,
3064
3216
  From: help.from,
3065
3217
  To: agentEmail,
3066
- SessionKey: `aamp:default:${help.from}`,
3218
+ SessionKey: buildAampConversationSessionKey(help.from, api.config),
3067
3219
  AccountId: "default",
3068
3220
  ChatType: "dm",
3069
3221
  Provider: "aamp",
@@ -3090,7 +3242,7 @@ ${notifyBody?.bodyText ?? help.question}`;
3090
3242
  api.logger.error(`[AAMP] Channel dispatch failed for help: ${err.message}`);
3091
3243
  });
3092
3244
  } else {
3093
- const helpSessionKey = `agent:main:aamp-notify-${Date.now()}`;
3245
+ const helpSessionKey = buildAampWakeSessionKey("help", help.taskId);
3094
3246
  try {
3095
3247
  api.runtime.system.requestHeartbeatNow({ reason: "wake", sessionKey: helpSessionKey });
3096
3248
  api.logger.info(`[AAMP] Heartbeat fallback for sub-task help ${help.taskId}`);
@@ -3230,7 +3382,10 @@ ${notifyBody?.bodyText ?? help.question}`;
3230
3382
  return;
3231
3383
  api.logger.info(`[AAMP] gateway_start: re-triggering heartbeat for ${pendingTasks.size} pending task(s)`);
3232
3384
  try {
3233
- api.runtime.system.requestHeartbeatNow({ reason: "wake", sessionKey: currentSessionKey });
3385
+ api.runtime.system.requestHeartbeatNow({
3386
+ reason: "wake",
3387
+ sessionKey: buildAampWakeSessionKey("queue", "gateway-start")
3388
+ });
3234
3389
  } catch (err) {
3235
3390
  api.logger.warn(`[AAMP] gateway_start heartbeat failed: ${err.message}`);
3236
3391
  }
@@ -3238,8 +3393,8 @@ ${notifyBody?.bodyText ?? help.question}`;
3238
3393
  api.on(
3239
3394
  "before_prompt_build",
3240
3395
  (_event, ctx) => {
3241
- if (ctx?.sessionKey && !String(ctx.sessionKey).startsWith("aamp:")) {
3242
- currentSessionKey = ctx.sessionKey;
3396
+ if (!isAampSessionKey(ctx?.sessionKey)) {
3397
+ return {};
3243
3398
  }
3244
3399
  for (const [id, t] of pendingTasks) {
3245
3400
  if (hasExpired(t)) {
@@ -3525,7 +3680,10 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
3525
3680
  api.logger.info(`[AAMP] \u2192 task.result ${task.taskId} ${p.status}`);
3526
3681
  if (pendingTasks.size > 0) {
3527
3682
  try {
3528
- api.runtime.system.requestHeartbeatNow({ reason: "wake", sessionKey: currentSessionKey });
3683
+ api.runtime.system.requestHeartbeatNow({
3684
+ reason: "wake",
3685
+ sessionKey: buildAampWakeSessionKey("queue", "follow-up")
3686
+ });
3529
3687
  } catch {
3530
3688
  }
3531
3689
  }