openclaw-quiubo 2.6.9 → 2.6.13

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
@@ -12470,6 +12470,11 @@ var RealtimeGateway = class {
12470
12470
  pollTimer = null;
12471
12471
  polling = false;
12472
12472
  cursors = /* @__PURE__ */ new Map();
12473
+ /** Message IDs already dispatched (Pusher + poll dedup). Capped to prevent unbounded growth. */
12474
+ dispatched = /* @__PURE__ */ new Set();
12475
+ // Cached directory group IDs from listAgentGroups (refreshed every 60s)
12476
+ agentGroupsCache = null;
12477
+ AGENT_GROUPS_TTL = 6e4;
12473
12478
  // Bot config cache (per group)
12474
12479
  botConfigCache = /* @__PURE__ */ new Map();
12475
12480
  // Owner takeover suppression: groupId → suppressedUntil timestamp
@@ -12503,6 +12508,13 @@ var RealtimeGateway = class {
12503
12508
  setBotConfig(groupId, config) {
12504
12509
  this.botConfigCache.set(groupId, config);
12505
12510
  }
12511
+ /** Track a dispatched message ID (capped at 500 to prevent unbounded growth) */
12512
+ markDispatched(messageId) {
12513
+ this.dispatched.add(messageId);
12514
+ if (this.dispatched.size > 500) {
12515
+ this.dispatched.clear();
12516
+ }
12517
+ }
12506
12518
  /** Get bot config for a group (for outbound scope checks) */
12507
12519
  getBotConfig(groupId) {
12508
12520
  return this.botConfigCache.get(groupId);
@@ -12599,13 +12611,13 @@ var RealtimeGateway = class {
12599
12611
  */
12600
12612
  async start() {
12601
12613
  this.stopped = false;
12602
- console.log(`[gw:start] botIdentityId=${this.botIdentityId} pusher=${!!this.pusherConfig} agentId=${this.agentId}`);
12614
+ this.log.info?.(`Gateway starting: botIdentityId=${this.botIdentityId} pusher=${!!this.pusherConfig} agentId=${this.agentId}`);
12603
12615
  this.startPolling();
12604
12616
  if (this.agentId) this.startHeartbeat();
12605
12617
  if (this.pusherConfig) {
12606
12618
  await this.startPusher();
12607
12619
  } else {
12608
- console.log("[gw:start] No Pusher config \u2014 polling only");
12620
+ this.log.info?.("No Pusher config \u2014 polling only");
12609
12621
  }
12610
12622
  }
12611
12623
  /**
@@ -12639,8 +12651,7 @@ var RealtimeGateway = class {
12639
12651
  // ── Pusher ──────────────────────────────────────────────────────
12640
12652
  async startPusher() {
12641
12653
  if (!this.pusherConfig) return;
12642
- console.log(`[gw:startPusher] botIdentityId=${this.botIdentityId} key=${this.pusherConfig.key} cluster=${this.pusherConfig.cluster}`);
12643
- this.log.info?.(`[gateway] startPusher called for botIdentityId=${this.botIdentityId}`);
12654
+ this.log.info?.(`Pusher connecting: key=${this.pusherConfig.key} cluster=${this.pusherConfig.cluster}`);
12644
12655
  const { key, cluster } = this.pusherConfig;
12645
12656
  const apiClient = this.client;
12646
12657
  const botIdentityId = this.botIdentityId;
@@ -12661,12 +12672,10 @@ var RealtimeGateway = class {
12661
12672
  })
12662
12673
  });
12663
12674
  this.pusherClient.connection.bind("state_change", (states) => {
12664
- console.log(`[gw:pusher] state: ${states.previous} \u2192 ${states.current}`);
12665
12675
  log.info?.(`Pusher state: ${states.previous} \u2192 ${states.current}`);
12666
12676
  });
12667
12677
  this.pusherClient.connection.bind("connected", () => {
12668
12678
  this.pusherConnected = true;
12669
- console.log("[gw:pusher] connected");
12670
12679
  log.info?.("Pusher connected (polling continues as safety net)");
12671
12680
  if (this.agentId) this.startHeartbeat();
12672
12681
  });
@@ -12757,17 +12766,19 @@ var RealtimeGateway = class {
12757
12766
  agentDisplayName: this.agentDisplayName ?? void 0,
12758
12767
  securityMode: data.securityMode
12759
12768
  });
12769
+ this.agentGroupsCache = null;
12760
12770
  });
12761
12771
  channel.bind("agent:group-removed", (data) => {
12762
12772
  log.info?.(`agent:group-removed for group ${data.groupId}`);
12763
12773
  this.botConfigCache.delete(data.groupId);
12774
+ this.agentGroupsCache = null;
12764
12775
  });
12765
12776
  channel.bind_global((eventName, data) => {
12766
- console.log(`[gw:pusher:raw] event=${eventName} data=${JSON.stringify(data).slice(0, 200)}`);
12777
+ if (eventName.startsWith("pusher:") || eventName.startsWith("pusher_")) return;
12778
+ log.debug?.(`Pusher event: ${eventName} data=${JSON.stringify(data).slice(0, 200)}`);
12767
12779
  });
12768
12780
  channel.bind("new-group-message", (data) => {
12769
- console.log(`[gw:pusher:msg] messageId=${data.messageId} groupId=${data.groupId} sender=${data.senderUsername} plaintext=${!!data.plaintext} ciphertext=${!!data.ciphertext}`);
12770
- log.info?.(`Pusher event: messageId=${data.messageId} groupId=${data.groupId} sender=${data.senderUsername} plaintext=${!!data.plaintext} ciphertext=${!!data.ciphertext} botId=${this.botIdentityId}`);
12781
+ log.info?.(`new-group-message: msg=${data.messageId} group=${data.groupId} sender=${data.senderUsername} plaintext=${!!data.plaintext} ciphertext=${!!data.ciphertext}`);
12771
12782
  if (data.senderId === this.botIdentityId) return;
12772
12783
  this.shouldProcessMessage(data.groupId, data.senderId, {
12773
12784
  fromPusher: true,
@@ -12781,6 +12792,7 @@ var RealtimeGateway = class {
12781
12792
  if (!shouldProcess) return;
12782
12793
  if (data.plaintext) {
12783
12794
  this.cursors.set(data.groupId, data.messageId);
12795
+ this.markDispatched(data.messageId);
12784
12796
  Promise.resolve(this.onMessage({
12785
12797
  messageId: data.messageId,
12786
12798
  groupId: data.groupId,
@@ -12808,6 +12820,7 @@ var RealtimeGateway = class {
12808
12820
  const plaintext = decryptGroupMessage(data.ciphertext, cached.key);
12809
12821
  log.info?.(`[e2ee:pusher] group=${data.groupId} decrypted OK: ${plaintext?.length} chars \u2014 routing to onMessage`);
12810
12822
  this.cursors.set(data.groupId, data.messageId);
12823
+ this.markDispatched(data.messageId);
12811
12824
  return Promise.resolve(this.onMessage({
12812
12825
  messageId: data.messageId,
12813
12826
  groupId: data.groupId,
@@ -12859,6 +12872,8 @@ var RealtimeGateway = class {
12859
12872
  this.log.warn?.(`Message ${messageId} has no plaintext`);
12860
12873
  return;
12861
12874
  }
12875
+ if (this.dispatched.has(msg.id)) return;
12876
+ this.markDispatched(msg.id);
12862
12877
  await this.onMessage({
12863
12878
  messageId: msg.id,
12864
12879
  groupId,
@@ -12880,8 +12895,7 @@ var RealtimeGateway = class {
12880
12895
  // ── Polling (fallback) ──────────────────────────────────────────
12881
12896
  startPolling() {
12882
12897
  if (this.pollTimer) return;
12883
- console.log(`[gw:startPolling] botIdentityId=${this.botIdentityId} interval=${this.pollIntervalMs}ms`);
12884
- this.log.info?.(`[gateway] startPolling called for botIdentityId=${this.botIdentityId}`);
12898
+ this.log.info?.(`Polling started: interval=${this.pollIntervalMs}ms`);
12885
12899
  this.poll();
12886
12900
  this.pollTimer = setInterval(() => this.poll(), this.pollIntervalMs);
12887
12901
  }
@@ -12891,13 +12905,61 @@ var RealtimeGateway = class {
12891
12905
  this.pollTimer = null;
12892
12906
  }
12893
12907
  }
12908
+ /**
12909
+ * Get directory group IDs, refreshing from API every AGENT_GROUPS_TTL ms.
12910
+ * Falls back to botConfigCache if the API call fails.
12911
+ * Pusher agent:group-added/removed events also invalidate the cache.
12912
+ */
12913
+ async getDirectoryGroupIds() {
12914
+ if (!this.agentId) {
12915
+ const ids = /* @__PURE__ */ new Set();
12916
+ for (const [gid, config] of this.botConfigCache) {
12917
+ if (config.source === "directory" && config.enabled) ids.add(gid);
12918
+ }
12919
+ return ids;
12920
+ }
12921
+ const now = Date.now();
12922
+ if (this.agentGroupsCache && now - this.agentGroupsCache.fetchedAt < this.AGENT_GROUPS_TTL) {
12923
+ return this.agentGroupsCache.groupIds;
12924
+ }
12925
+ try {
12926
+ const { groups: agentGroups } = await this.client.listAgentGroups(this.agentId);
12927
+ const ids = /* @__PURE__ */ new Set();
12928
+ for (const ag of agentGroups) {
12929
+ if (ag.source === "directory" && (ag.enabled ?? true)) {
12930
+ ids.add(ag.groupId);
12931
+ if (!this.botConfigCache.has(ag.groupId)) {
12932
+ this.botConfigCache.set(ag.groupId, {
12933
+ enabled: ag.enabled ?? true,
12934
+ suppressionMinutes: 10,
12935
+ ownerIdentityId: "",
12936
+ groupType: ag.groupType ?? "standard",
12937
+ source: "directory",
12938
+ grantedScopes: ag.grantedScopes,
12939
+ agentDisplayName: ag.agentDisplayName ?? this.agentDisplayName ?? void 0,
12940
+ securityMode: ag.securityMode
12941
+ });
12942
+ }
12943
+ }
12944
+ }
12945
+ this.agentGroupsCache = { groupIds: ids, fetchedAt: now };
12946
+ return ids;
12947
+ } catch (err) {
12948
+ this.log.warn?.(`listAgentGroups failed: ${err}, using cache`);
12949
+ if (this.agentGroupsCache) return this.agentGroupsCache.groupIds;
12950
+ const ids = /* @__PURE__ */ new Set();
12951
+ for (const [gid, config] of this.botConfigCache) {
12952
+ if (config.source === "directory" && config.enabled) ids.add(gid);
12953
+ }
12954
+ return ids;
12955
+ }
12956
+ }
12894
12957
  async poll() {
12895
12958
  if (this.polling) return;
12896
12959
  this.polling = true;
12897
12960
  try {
12898
12961
  this.clearStaleSuppression();
12899
12962
  const { groups } = await this.client.listGroups(this.botIdentityId);
12900
- console.log(`[gw:poll] listGroups returned ${groups.length} groups, botConfigCache has ${this.botConfigCache.size} entries`);
12901
12963
  for (const group of groups) {
12902
12964
  if (!this.botConfigCache.has(group.id) && group.settings) {
12903
12965
  const botSettings = group.settings?.bot ?? {};
@@ -12911,11 +12973,9 @@ var RealtimeGateway = class {
12911
12973
  }
12912
12974
  }
12913
12975
  const groupIds = new Set(groups.map((g) => g.id));
12914
- for (const [gid, config] of this.botConfigCache) {
12915
- if (config.source === "directory" && config.enabled) {
12916
- groupIds.add(gid);
12917
- }
12918
- }
12976
+ const directoryIds = await this.getDirectoryGroupIds();
12977
+ for (const gid of directoryIds) groupIds.add(gid);
12978
+ this.log.debug?.(`Poll cycle: partner=${groups.length} directory=${directoryIds.size} total=${groupIds.size}`);
12919
12979
  await Promise.allSettled(
12920
12980
  [...groupIds].map((gid) => this.pollGroup(gid))
12921
12981
  );
@@ -12928,7 +12988,6 @@ var RealtimeGateway = class {
12928
12988
  async pollGroup(groupId) {
12929
12989
  try {
12930
12990
  const cursor = this.cursors.get(groupId);
12931
- console.log(`[gw:pollGroup] group=${groupId} cursor=${cursor ?? "none"}`);
12932
12991
  const { messages } = await this.client.listMessages(groupId, cursor);
12933
12992
  if (messages.length === 0) return;
12934
12993
  const lastMessage = messages[messages.length - 1];
@@ -12952,12 +13011,10 @@ var RealtimeGateway = class {
12952
13011
  }
12953
13012
  if (!plaintext) continue;
12954
13013
  const shouldProcess = await this.shouldProcessMessage(groupId, msg.senderIdentityId, { plaintext });
12955
- if (!shouldProcess) {
12956
- console.log(`[gw:pollGroup] skipping msg=${msg.id} in group=${groupId} (shouldProcess=false)`);
12957
- continue;
12958
- }
13014
+ if (!shouldProcess) continue;
13015
+ if (this.dispatched.has(msg.id)) continue;
12959
13016
  try {
12960
- console.log(`[gw:pollGroup] dispatching msg=${msg.id} group=${groupId} text=${plaintext?.slice(0, 60)}`);
13017
+ this.markDispatched(msg.id);
12961
13018
  await this.onMessage({
12962
13019
  messageId: msg.id,
12963
13020
  groupId,
@@ -13415,7 +13472,6 @@ var quiuboPlugin = {
13415
13472
  accounts.set(accountId, account);
13416
13473
  }
13417
13474
  const groupId = resolveOutboundGroupId(ctx);
13418
- console.log(`[delivery:sendText] ctx.to=${ctx.to}, sessionKey=${ctx.SessionKey ?? ctx.target?.raw?.SessionKey}, conversationLabel=${ctx.target?.raw?.ConversationLabel}, resolved groupId=${groupId}, text=${text?.length ?? 0} chars`);
13419
13475
  if (!groupId) {
13420
13476
  return { ok: false, error: "No groupId in outbound context" };
13421
13477
  }
@@ -13424,7 +13480,6 @@ var quiuboPlugin = {
13424
13480
  return { ok: false, error: "No botIdentityId configured" };
13425
13481
  }
13426
13482
  try {
13427
- console.log(`[delivery:sendText] calling client.sendMessage(${groupId}, sender=${senderId})`);
13428
13483
  await client.sendMessage(groupId, {
13429
13484
  senderIdentityId: senderId,
13430
13485
  plaintext: text,
@@ -13443,7 +13498,6 @@ var quiuboPlugin = {
13443
13498
  if (ctx.mediaUrl) urls.push(ctx.mediaUrl);
13444
13499
  if (Array.isArray(ctx.mediaUrls)) urls.push(...ctx.mediaUrls);
13445
13500
  const mdAttachments = await readMdAttachments(urls, "agent", void 0, "sendMedia");
13446
- console.log(`[delivery:sendMedia] urls=${urls.length}, mdAttachments=${mdAttachments.length} [${mdAttachments.map((a) => a.filename).join(", ")}], media=${Array.isArray(ctx.media) ? ctx.media.length : "no"}`);
13447
13501
  const plaintext = text || (mdAttachments.length === 0 ? "[Media attachment]" : "");
13448
13502
  const accountId = ctx.accountId ?? DEFAULT_ACCOUNT_ID;
13449
13503
  let client = clients.get(accountId);
@@ -13460,7 +13514,6 @@ var quiuboPlugin = {
13460
13514
  accounts.set(accountId, account);
13461
13515
  }
13462
13516
  const groupId = resolveOutboundGroupId(ctx);
13463
- console.log(`[delivery:sendMedia] ctx.to=${ctx.to}, sessionKey=${ctx.SessionKey ?? ctx.target?.raw?.SessionKey}, conversationLabel=${ctx.target?.raw?.ConversationLabel}, resolved groupId=${groupId}`);
13464
13517
  if (!groupId) {
13465
13518
  return { ok: false, error: "No groupId in outbound context" };
13466
13519
  }
@@ -13469,7 +13522,6 @@ var quiuboPlugin = {
13469
13522
  return { ok: false, error: "No botIdentityId configured" };
13470
13523
  }
13471
13524
  try {
13472
- console.log(`[delivery:sendMedia] calling client.sendMessage(${groupId}, sender=${senderId})`);
13473
13525
  await client.sendMessage(groupId, {
13474
13526
  senderIdentityId: senderId,
13475
13527
  plaintext,
@@ -13569,10 +13621,10 @@ var quiuboPlugin = {
13569
13621
  log?.warn?.(`[${accountId}] Quiubo: auto-provision failed (non-fatal): ${provisionError}`);
13570
13622
  }
13571
13623
  const gatewayLog = log ? {
13572
- debug: (...args) => log.debug?.(`[${accountId}] Quiubo:`, ...args),
13573
- info: (...args) => log.info?.(`[${accountId}] Quiubo:`, ...args),
13574
- warn: (...args) => log.warn?.(`[${accountId}] Quiubo:`, ...args),
13575
- error: (...args) => log.error?.(`[${accountId}] Quiubo:`, ...args)
13624
+ debug: (...args) => log.debug?.(`${args.join(" ")}`),
13625
+ info: (...args) => log.info?.(`${args.join(" ")}`),
13626
+ warn: (...args) => log.warn?.(`${args.join(" ")}`),
13627
+ error: (...args) => log.error?.(`${args.join(" ")}`)
13576
13628
  } : void 0;
13577
13629
  let resolvedAgentId;
13578
13630
  let resolvedAgentDisplayName;
@@ -13860,7 +13912,6 @@ function resolveOutboundGroupId(ctx) {
13860
13912
  }
13861
13913
  const targetGroupId = ctx.target?.groupId ?? raw.groupId;
13862
13914
  if (targetGroupId) return targetGroupId;
13863
- console.log(`[delivery:resolveOutboundGroupId] no ConversationLabel/SessionKey/groupId found, ctx.to=${ctx.to} (not used \u2014 likely botIdentityId)`);
13864
13915
  return void 0;
13865
13916
  }
13866
13917
  async function getActivityData(runtime2, log, agentId) {