openclaw-quiubo 2.6.7 → 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);
@@ -12599,12 +12611,13 @@ var RealtimeGateway = class {
12599
12611
  */
12600
12612
  async start() {
12601
12613
  this.stopped = false;
12614
+ console.log(`[gw:start] botIdentityId=${this.botIdentityId} pusher=${!!this.pusherConfig} agentId=${this.agentId}`);
12615
+ this.startPolling();
12616
+ if (this.agentId) this.startHeartbeat();
12602
12617
  if (this.pusherConfig) {
12603
12618
  await this.startPusher();
12604
12619
  } else {
12605
- this.log.info?.("No Pusher config \u2014 using polling only");
12606
- this.startPolling();
12607
- if (this.agentId) this.startHeartbeat();
12620
+ console.log("[gw:start] No Pusher config \u2014 polling only");
12608
12621
  }
12609
12622
  }
12610
12623
  /**
@@ -12638,6 +12651,7 @@ var RealtimeGateway = class {
12638
12651
  // ── Pusher ──────────────────────────────────────────────────────
12639
12652
  async startPusher() {
12640
12653
  if (!this.pusherConfig) return;
12654
+ console.log(`[gw:startPusher] botIdentityId=${this.botIdentityId} key=${this.pusherConfig.key} cluster=${this.pusherConfig.cluster}`);
12641
12655
  this.log.info?.(`[gateway] startPusher called for botIdentityId=${this.botIdentityId}`);
12642
12656
  const { key, cluster } = this.pusherConfig;
12643
12657
  const apiClient = this.client;
@@ -12659,12 +12673,13 @@ var RealtimeGateway = class {
12659
12673
  })
12660
12674
  });
12661
12675
  this.pusherClient.connection.bind("state_change", (states) => {
12676
+ console.log(`[gw:pusher] state: ${states.previous} \u2192 ${states.current}`);
12662
12677
  log.info?.(`Pusher state: ${states.previous} \u2192 ${states.current}`);
12663
12678
  });
12664
12679
  this.pusherClient.connection.bind("connected", () => {
12665
12680
  this.pusherConnected = true;
12666
- log.info?.("Pusher connected");
12667
- this.stopPolling();
12681
+ console.log("[gw:pusher] connected");
12682
+ log.info?.("Pusher connected (polling continues as safety net)");
12668
12683
  if (this.agentId) this.startHeartbeat();
12669
12684
  });
12670
12685
  this.pusherClient.connection.bind("disconnected", () => {
@@ -12754,16 +12769,18 @@ var RealtimeGateway = class {
12754
12769
  agentDisplayName: this.agentDisplayName ?? void 0,
12755
12770
  securityMode: data.securityMode
12756
12771
  });
12772
+ this.agentGroupsCache = null;
12757
12773
  });
12758
12774
  channel.bind("agent:group-removed", (data) => {
12759
12775
  log.info?.(`agent:group-removed for group ${data.groupId}`);
12760
12776
  this.botConfigCache.delete(data.groupId);
12777
+ this.agentGroupsCache = null;
12761
12778
  });
12762
12779
  channel.bind_global((eventName, data) => {
12763
- if (eventName.startsWith("pusher:") || eventName.startsWith("pusher_")) return;
12764
- log.info?.(`[pusher:raw] event=${eventName} data=${JSON.stringify(data).slice(0, 300)}`);
12780
+ console.log(`[gw:pusher:raw] event=${eventName} data=${JSON.stringify(data).slice(0, 200)}`);
12765
12781
  });
12766
12782
  channel.bind("new-group-message", (data) => {
12783
+ console.log(`[gw:pusher:msg] messageId=${data.messageId} groupId=${data.groupId} sender=${data.senderUsername} plaintext=${!!data.plaintext} ciphertext=${!!data.ciphertext}`);
12767
12784
  log.info?.(`Pusher event: messageId=${data.messageId} groupId=${data.groupId} sender=${data.senderUsername} plaintext=${!!data.plaintext} ciphertext=${!!data.ciphertext} botId=${this.botIdentityId}`);
12768
12785
  if (data.senderId === this.botIdentityId) return;
12769
12786
  this.shouldProcessMessage(data.groupId, data.senderId, {
@@ -12778,6 +12795,7 @@ var RealtimeGateway = class {
12778
12795
  if (!shouldProcess) return;
12779
12796
  if (data.plaintext) {
12780
12797
  this.cursors.set(data.groupId, data.messageId);
12798
+ this.markDispatched(data.messageId);
12781
12799
  Promise.resolve(this.onMessage({
12782
12800
  messageId: data.messageId,
12783
12801
  groupId: data.groupId,
@@ -12805,6 +12823,7 @@ var RealtimeGateway = class {
12805
12823
  const plaintext = decryptGroupMessage(data.ciphertext, cached.key);
12806
12824
  log.info?.(`[e2ee:pusher] group=${data.groupId} decrypted OK: ${plaintext?.length} chars \u2014 routing to onMessage`);
12807
12825
  this.cursors.set(data.groupId, data.messageId);
12826
+ this.markDispatched(data.messageId);
12808
12827
  return Promise.resolve(this.onMessage({
12809
12828
  messageId: data.messageId,
12810
12829
  groupId: data.groupId,
@@ -12856,6 +12875,8 @@ var RealtimeGateway = class {
12856
12875
  this.log.warn?.(`Message ${messageId} has no plaintext`);
12857
12876
  return;
12858
12877
  }
12878
+ if (this.dispatched.has(msg.id)) return;
12879
+ this.markDispatched(msg.id);
12859
12880
  await this.onMessage({
12860
12881
  messageId: msg.id,
12861
12882
  groupId,
@@ -12877,6 +12898,7 @@ var RealtimeGateway = class {
12877
12898
  // ── Polling (fallback) ──────────────────────────────────────────
12878
12899
  startPolling() {
12879
12900
  if (this.pollTimer) return;
12901
+ console.log(`[gw:startPolling] botIdentityId=${this.botIdentityId} interval=${this.pollIntervalMs}ms`);
12880
12902
  this.log.info?.(`[gateway] startPolling called for botIdentityId=${this.botIdentityId}`);
12881
12903
  this.poll();
12882
12904
  this.pollTimer = setInterval(() => this.poll(), this.pollIntervalMs);
@@ -12887,8 +12909,57 @@ var RealtimeGateway = class {
12887
12909
  this.pollTimer = null;
12888
12910
  }
12889
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
+ }
12890
12961
  async poll() {
12891
- if (this.polling || this.pusherConnected) return;
12962
+ if (this.polling) return;
12892
12963
  this.polling = true;
12893
12964
  try {
12894
12965
  this.clearStaleSuppression();
@@ -12906,11 +12977,9 @@ var RealtimeGateway = class {
12906
12977
  }
12907
12978
  }
12908
12979
  const groupIds = new Set(groups.map((g) => g.id));
12909
- for (const [gid, config] of this.botConfigCache) {
12910
- if (config.source === "directory" && config.enabled) {
12911
- groupIds.add(gid);
12912
- }
12913
- }
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(", ")}]`);
12914
12983
  await Promise.allSettled(
12915
12984
  [...groupIds].map((gid) => this.pollGroup(gid))
12916
12985
  );
@@ -12923,7 +12992,7 @@ var RealtimeGateway = class {
12923
12992
  async pollGroup(groupId) {
12924
12993
  try {
12925
12994
  const cursor = this.cursors.get(groupId);
12926
- this.log.info?.(`[poll] polling group=${groupId} cursor=${cursor ?? "none"}`);
12995
+ console.log(`[gw:pollGroup] group=${groupId} cursor=${cursor ?? "none"}`);
12927
12996
  const { messages } = await this.client.listMessages(groupId, cursor);
12928
12997
  if (messages.length === 0) return;
12929
12998
  const lastMessage = messages[messages.length - 1];
@@ -12947,8 +13016,17 @@ var RealtimeGateway = class {
12947
13016
  }
12948
13017
  if (!plaintext) continue;
12949
13018
  const shouldProcess = await this.shouldProcessMessage(groupId, msg.senderIdentityId, { plaintext });
12950
- if (!shouldProcess) continue;
13019
+ if (!shouldProcess) {
13020
+ console.log(`[gw:pollGroup] skipping msg=${msg.id} in group=${groupId} (shouldProcess=false)`);
13021
+ continue;
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
+ }
12951
13027
  try {
13028
+ console.log(`[gw:pollGroup] dispatching msg=${msg.id} group=${groupId} text=${plaintext?.slice(0, 60)}`);
13029
+ this.markDispatched(msg.id);
12952
13030
  await this.onMessage({
12953
13031
  messageId: msg.id,
12954
13032
  groupId,