openclaw-quiubo 2.6.9 → 2.6.11

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);
@@ -12757,10 +12769,12 @@ var RealtimeGateway = class {
12757
12769
  agentDisplayName: this.agentDisplayName ?? void 0,
12758
12770
  securityMode: data.securityMode
12759
12771
  });
12772
+ this.agentGroupsCache = null;
12760
12773
  });
12761
12774
  channel.bind("agent:group-removed", (data) => {
12762
12775
  log.info?.(`agent:group-removed for group ${data.groupId}`);
12763
12776
  this.botConfigCache.delete(data.groupId);
12777
+ this.agentGroupsCache = null;
12764
12778
  });
12765
12779
  channel.bind_global((eventName, data) => {
12766
12780
  console.log(`[gw:pusher:raw] event=${eventName} data=${JSON.stringify(data).slice(0, 200)}`);
@@ -12781,6 +12795,7 @@ var RealtimeGateway = class {
12781
12795
  if (!shouldProcess) return;
12782
12796
  if (data.plaintext) {
12783
12797
  this.cursors.set(data.groupId, data.messageId);
12798
+ this.markDispatched(data.messageId);
12784
12799
  Promise.resolve(this.onMessage({
12785
12800
  messageId: data.messageId,
12786
12801
  groupId: data.groupId,
@@ -12808,6 +12823,7 @@ var RealtimeGateway = class {
12808
12823
  const plaintext = decryptGroupMessage(data.ciphertext, cached.key);
12809
12824
  log.info?.(`[e2ee:pusher] group=${data.groupId} decrypted OK: ${plaintext?.length} chars \u2014 routing to onMessage`);
12810
12825
  this.cursors.set(data.groupId, data.messageId);
12826
+ this.markDispatched(data.messageId);
12811
12827
  return Promise.resolve(this.onMessage({
12812
12828
  messageId: data.messageId,
12813
12829
  groupId: data.groupId,
@@ -12859,6 +12875,8 @@ var RealtimeGateway = class {
12859
12875
  this.log.warn?.(`Message ${messageId} has no plaintext`);
12860
12876
  return;
12861
12877
  }
12878
+ if (this.dispatched.has(msg.id)) return;
12879
+ this.markDispatched(msg.id);
12862
12880
  await this.onMessage({
12863
12881
  messageId: msg.id,
12864
12882
  groupId,
@@ -12891,13 +12909,61 @@ var RealtimeGateway = class {
12891
12909
  this.pollTimer = null;
12892
12910
  }
12893
12911
  }
12912
+ /**
12913
+ * Get directory group IDs, refreshing from API every AGENT_GROUPS_TTL ms.
12914
+ * Falls back to botConfigCache if the API call fails.
12915
+ * Pusher agent:group-added/removed events also invalidate the cache.
12916
+ */
12917
+ async getDirectoryGroupIds() {
12918
+ if (!this.agentId) {
12919
+ const ids = /* @__PURE__ */ new Set();
12920
+ for (const [gid, config] of this.botConfigCache) {
12921
+ if (config.source === "directory" && config.enabled) ids.add(gid);
12922
+ }
12923
+ return ids;
12924
+ }
12925
+ const now = Date.now();
12926
+ if (this.agentGroupsCache && now - this.agentGroupsCache.fetchedAt < this.AGENT_GROUPS_TTL) {
12927
+ return this.agentGroupsCache.groupIds;
12928
+ }
12929
+ try {
12930
+ const { groups: agentGroups } = await this.client.listAgentGroups(this.agentId);
12931
+ const ids = /* @__PURE__ */ new Set();
12932
+ for (const ag of agentGroups) {
12933
+ if (ag.source === "directory" && (ag.enabled ?? true)) {
12934
+ ids.add(ag.groupId);
12935
+ if (!this.botConfigCache.has(ag.groupId)) {
12936
+ this.botConfigCache.set(ag.groupId, {
12937
+ enabled: ag.enabled ?? true,
12938
+ suppressionMinutes: 10,
12939
+ ownerIdentityId: "",
12940
+ groupType: ag.groupType ?? "standard",
12941
+ source: "directory",
12942
+ grantedScopes: ag.grantedScopes,
12943
+ agentDisplayName: ag.agentDisplayName ?? this.agentDisplayName ?? void 0,
12944
+ securityMode: ag.securityMode
12945
+ });
12946
+ }
12947
+ }
12948
+ }
12949
+ this.agentGroupsCache = { groupIds: ids, fetchedAt: now };
12950
+ return ids;
12951
+ } catch (err) {
12952
+ this.log.warn?.(`listAgentGroups failed: ${err}, using cache`);
12953
+ if (this.agentGroupsCache) return this.agentGroupsCache.groupIds;
12954
+ const ids = /* @__PURE__ */ new Set();
12955
+ for (const [gid, config] of this.botConfigCache) {
12956
+ if (config.source === "directory" && config.enabled) ids.add(gid);
12957
+ }
12958
+ return ids;
12959
+ }
12960
+ }
12894
12961
  async poll() {
12895
12962
  if (this.polling) return;
12896
12963
  this.polling = true;
12897
12964
  try {
12898
12965
  this.clearStaleSuppression();
12899
12966
  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
12967
  for (const group of groups) {
12902
12968
  if (!this.botConfigCache.has(group.id) && group.settings) {
12903
12969
  const botSettings = group.settings?.bot ?? {};
@@ -12911,11 +12977,9 @@ var RealtimeGateway = class {
12911
12977
  }
12912
12978
  }
12913
12979
  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
- }
12980
+ const directoryIds = await this.getDirectoryGroupIds();
12981
+ for (const gid of directoryIds) groupIds.add(gid);
12982
+ console.log(`[gw:poll] partner=${groups.length} directory=${directoryIds.size} total=${groupIds.size} groups=[${[...groupIds].join(", ")}]`);
12919
12983
  await Promise.allSettled(
12920
12984
  [...groupIds].map((gid) => this.pollGroup(gid))
12921
12985
  );
@@ -12956,8 +13020,13 @@ var RealtimeGateway = class {
12956
13020
  console.log(`[gw:pollGroup] skipping msg=${msg.id} in group=${groupId} (shouldProcess=false)`);
12957
13021
  continue;
12958
13022
  }
13023
+ if (this.dispatched.has(msg.id)) {
13024
+ console.log(`[gw:pollGroup] skipping msg=${msg.id} in group=${groupId} (already dispatched)`);
13025
+ continue;
13026
+ }
12959
13027
  try {
12960
13028
  console.log(`[gw:pollGroup] dispatching msg=${msg.id} group=${groupId} text=${plaintext?.slice(0, 60)}`);
13029
+ this.markDispatched(msg.id);
12961
13030
  await this.onMessage({
12962
13031
  messageId: msg.id,
12963
13032
  groupId,