openclaw-quiubo 2.0.3 → 2.2.0

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
@@ -34,6 +34,7 @@ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__
34
34
  // ../../node_modules/.pnpm/pusher-js@8.4.0/node_modules/pusher-js/dist/node/pusher.js
35
35
  var require_pusher = __commonJS({
36
36
  "../../node_modules/.pnpm/pusher-js@8.4.0/node_modules/pusher-js/dist/node/pusher.js"(exports, module) {
37
+ "use strict";
37
38
  module.exports = /******/
38
39
  (function(modules) {
39
40
  var installedModules = {};
@@ -9191,6 +9192,25 @@ var QuiuboApiClient = class {
9191
9192
  return this.request("GET", `/agents/${agentId}/groups/${groupId}/encryption/epochs${query}`);
9192
9193
  }
9193
9194
  // ========================================================================
9195
+ // Group Agents
9196
+ // ========================================================================
9197
+ /**
9198
+ * List all groups for an agent (partner + directory)
9199
+ */
9200
+ async listAgentGroups(agentId) {
9201
+ return this.request("GET", `/agents/${agentId}/groups`);
9202
+ }
9203
+ /**
9204
+ * Get group agent status (cache-miss fallback for directory groups)
9205
+ */
9206
+ async getGroupAgent(agentId, groupId) {
9207
+ try {
9208
+ return await this.request("GET", `/agents/${agentId}/groups/${groupId}/agent-status`);
9209
+ } catch {
9210
+ return null;
9211
+ }
9212
+ }
9213
+ // ========================================================================
9194
9214
  // Pusher Auth
9195
9215
  // ========================================================================
9196
9216
  /**
@@ -12459,6 +12479,9 @@ var RealtimeGateway = class {
12459
12479
  // E2EE state
12460
12480
  keyManager;
12461
12481
  e2eeGrantedGroups;
12482
+ // Agent identity for mention matching
12483
+ agentName;
12484
+ agentDisplayName;
12462
12485
  stopped = false;
12463
12486
  constructor(opts) {
12464
12487
  this.client = opts.client;
@@ -12472,11 +12495,17 @@ var RealtimeGateway = class {
12472
12495
  this.agentId = opts.agentId ?? null;
12473
12496
  this.keyManager = opts.keyManager ?? null;
12474
12497
  this.e2eeGrantedGroups = opts.e2eeGrantedGroups ?? /* @__PURE__ */ new Set();
12498
+ this.agentName = opts.agentName ?? null;
12499
+ this.agentDisplayName = opts.agentDisplayName ?? null;
12475
12500
  }
12476
12501
  /** Set bot config for a group */
12477
12502
  setBotConfig(groupId, config) {
12478
12503
  this.botConfigCache.set(groupId, config);
12479
12504
  }
12505
+ /** Get bot config for a group (for outbound scope checks) */
12506
+ getBotConfig(groupId) {
12507
+ return this.botConfigCache.get(groupId);
12508
+ }
12480
12509
  /** Set the cache-miss callback */
12481
12510
  setOnCacheMiss(fn) {
12482
12511
  this.onCacheMiss = fn;
@@ -12491,6 +12520,20 @@ var RealtimeGateway = class {
12491
12520
  }
12492
12521
  }
12493
12522
  }
12523
+ /**
12524
+ * Check if text contains a @mention for this agent.
12525
+ */
12526
+ matchesMention(text, config) {
12527
+ const lower = text.toLowerCase();
12528
+ const names = [];
12529
+ if (config?.agentDisplayName) names.push(config.agentDisplayName);
12530
+ if (this.agentDisplayName) names.push(this.agentDisplayName);
12531
+ if (this.agentName) names.push(this.agentName);
12532
+ for (const name of names) {
12533
+ if (lower.includes(`@${name.toLowerCase()}`)) return true;
12534
+ }
12535
+ return false;
12536
+ }
12494
12537
  /**
12495
12538
  * Gate a message through bot-enabled filtering + owner suppression.
12496
12539
  * Returns true if the message should be processed, false to skip.
@@ -12500,11 +12543,6 @@ var RealtimeGateway = class {
12500
12543
  let config = this.botConfigCache.get(groupId);
12501
12544
  const groupType = eventData?.groupType ?? config?.groupType;
12502
12545
  if (groupType === "agent_channel") return true;
12503
- const securityMode = eventData?.groupSecurityMode ?? "PLAINTEXT_SDK";
12504
- if (securityMode !== "PLAINTEXT_SDK" && !this.e2eeGrantedGroups.has(groupId)) {
12505
- this.log.debug?.(`Skipping message in ${groupId}: security mode ${securityMode} and no E2EE grant`);
12506
- return false;
12507
- }
12508
12546
  if (!config) {
12509
12547
  if (this.onCacheMiss) {
12510
12548
  try {
@@ -12528,6 +12566,41 @@ var RealtimeGateway = class {
12528
12566
  this.log.debug?.(`Skipping message in ${groupId}: bot disabled`);
12529
12567
  return false;
12530
12568
  }
12569
+ if (config.source === "directory" && config.triggerMode) {
12570
+ if (config.triggerMode === "all_messages") {
12571
+ const secMode = config.securityMode ?? eventData?.groupSecurityMode ?? "PLAINTEXT_SDK";
12572
+ if (secMode !== "PLAINTEXT_SDK" && !this.e2eeGrantedGroups.has(groupId)) {
12573
+ this.log.debug?.(`Skipping directory all_messages in ${groupId}: E2EE but no grant`);
12574
+ return false;
12575
+ }
12576
+ return true;
12577
+ }
12578
+ const plaintext = eventData?.plaintext;
12579
+ if (plaintext) {
12580
+ return this.matchesMention(plaintext, config);
12581
+ }
12582
+ if (eventData?.ciphertext && this.keyManager && this.e2eeGrantedGroups.has(groupId)) {
12583
+ try {
12584
+ const epoch = parseGroupEnvelopeEpoch(eventData.ciphertext);
12585
+ if (epoch !== null) {
12586
+ const cached = await this.keyManager.getEpochKey(groupId, epoch);
12587
+ if (cached) {
12588
+ const decrypted = decryptGroupMessage(eventData.ciphertext, cached.key);
12589
+ return this.matchesMention(decrypted, config);
12590
+ }
12591
+ }
12592
+ } catch (err) {
12593
+ this.log.warn?.(`Mention check decrypt failed for group ${groupId}: ${err}`);
12594
+ }
12595
+ }
12596
+ this.log.debug?.(`Skipping mention-mode message in ${groupId}: no readable text`);
12597
+ return false;
12598
+ }
12599
+ const securityMode = config.securityMode ?? eventData?.groupSecurityMode ?? "PLAINTEXT_SDK";
12600
+ if (securityMode !== "PLAINTEXT_SDK" && !this.e2eeGrantedGroups.has(groupId)) {
12601
+ this.log.debug?.(`Skipping message in ${groupId}: security mode ${securityMode} and no E2EE grant`);
12602
+ return false;
12603
+ }
12531
12604
  const ownerIdentityId = eventData?.groupOwnerIdentityId ?? config.ownerIdentityId;
12532
12605
  if (ownerIdentityId && senderId === ownerIdentityId) {
12533
12606
  const suppressMinutes = config.suppressionMinutes ?? 10;
@@ -12674,13 +12747,33 @@ var RealtimeGateway = class {
12674
12747
  this.keyManager.invalidate(data.groupId);
12675
12748
  }
12676
12749
  });
12750
+ channel.bind("agent:group-added", (data) => {
12751
+ log.info?.(`agent:group-added for group ${data.groupId} (${data.groupName}) triggerMode=${data.triggerMode}`);
12752
+ this.botConfigCache.set(data.groupId, {
12753
+ enabled: true,
12754
+ suppressionMinutes: 10,
12755
+ ownerIdentityId: "",
12756
+ groupType: data.groupType,
12757
+ grantedScopes: data.grantedScopes,
12758
+ triggerMode: data.triggerMode,
12759
+ source: "directory",
12760
+ agentDisplayName: this.agentDisplayName ?? void 0,
12761
+ securityMode: data.securityMode
12762
+ });
12763
+ });
12764
+ channel.bind("agent:group-removed", (data) => {
12765
+ log.info?.(`agent:group-removed for group ${data.groupId}`);
12766
+ this.botConfigCache.delete(data.groupId);
12767
+ });
12677
12768
  channel.bind("new-group-message", (data) => {
12678
12769
  log.info?.(`Pusher event: messageId=${data.messageId} groupId=${data.groupId} sender=${data.senderUsername} plaintext=${!!data.plaintext}`);
12679
12770
  if (data.senderId === this.botIdentityId) return;
12680
12771
  this.shouldProcessMessage(data.groupId, data.senderId, {
12681
12772
  groupSecurityMode: data.groupSecurityMode,
12682
12773
  groupOwnerIdentityId: data.groupOwnerIdentityId,
12683
- groupType: data.groupType
12774
+ groupType: data.groupType,
12775
+ plaintext: data.plaintext,
12776
+ ciphertext: data.ciphertext
12684
12777
  }).then((shouldProcess) => {
12685
12778
  if (!shouldProcess) return;
12686
12779
  if (data.plaintext) {
@@ -13454,6 +13547,8 @@ var quiuboPlugin = {
13454
13547
  error: (...args) => log.error?.(`[${accountId}] Quiubo:`, ...args)
13455
13548
  } : void 0;
13456
13549
  let resolvedAgentId;
13550
+ let resolvedAgentDisplayName;
13551
+ let resolvedAgentName;
13457
13552
  let agentKeys;
13458
13553
  try {
13459
13554
  const { agents: agentList } = await client.listAgents();
@@ -13468,7 +13563,10 @@ var quiuboPlugin = {
13468
13563
  try {
13469
13564
  const created = await client.createAgent({
13470
13565
  identityId: botIdentityId,
13471
- name: `bot-${accountId}`
13566
+ name: `bot-${accountId}`,
13567
+ directoryEnabled: true,
13568
+ offeredScopes: ["send_messages", "read_messages"],
13569
+ directoryDisplayName: auth?.appName ?? accountId
13472
13570
  });
13473
13571
  match = { ...created, scopes: created.scopes ?? ["send_messages", "read_messages"] };
13474
13572
  log?.info?.(`[${accountId}] Quiubo: auto-registered agent ${created.id}`);
@@ -13479,7 +13577,9 @@ var quiuboPlugin = {
13479
13577
  }
13480
13578
  if (match) {
13481
13579
  resolvedAgentId = match.id;
13482
- log?.info?.(`[${accountId}] Quiubo: resolved agent ${match.name} (${match.id}) for heartbeat`);
13580
+ resolvedAgentDisplayName = match.directoryDisplayName ?? match.name ?? auth.appName;
13581
+ resolvedAgentName = match.name;
13582
+ log?.info?.(`[${accountId}] Quiubo: resolved agent ${match.name} (${match.id}) for heartbeat, displayName=${resolvedAgentDisplayName}`);
13483
13583
  try {
13484
13584
  const keys = loadOrGenerateKeys(quiuboConfig);
13485
13585
  if (!match.e2eeConfigured) {
@@ -13522,6 +13622,8 @@ var quiuboPlugin = {
13522
13622
  agentId: resolvedAgentId,
13523
13623
  keyManager,
13524
13624
  e2eeGrantedGroups,
13625
+ agentName: resolvedAgentName,
13626
+ agentDisplayName: resolvedAgentDisplayName,
13525
13627
  onCacheMiss: async (groupId) => {
13526
13628
  try {
13527
13629
  const [group, { members }] = await Promise.all([
@@ -13536,8 +13638,26 @@ var quiuboPlugin = {
13536
13638
  ownerIdentityId: owner?.identityId ?? "",
13537
13639
  groupType: group.groupType ?? "standard"
13538
13640
  };
13539
- } catch (err) {
13540
- log?.warn?.(`[${accountId}] Quiubo: cache-miss fetch failed for group ${groupId}: ${err}`);
13641
+ } catch {
13642
+ if (resolvedAgentId) {
13643
+ try {
13644
+ const status = await client.getGroupAgent(resolvedAgentId, groupId);
13645
+ if (status) {
13646
+ return {
13647
+ enabled: status.enabled,
13648
+ suppressionMinutes: 10,
13649
+ ownerIdentityId: "",
13650
+ groupType: "standard",
13651
+ triggerMode: status.triggerMode,
13652
+ source: "directory",
13653
+ grantedScopes: status.grantedScopes,
13654
+ agentDisplayName: resolvedAgentDisplayName
13655
+ };
13656
+ }
13657
+ } catch (dirErr) {
13658
+ log?.warn?.(`[${accountId}] Quiubo: directory agent status fetch failed for group ${groupId}: ${dirErr}`);
13659
+ }
13660
+ }
13541
13661
  return null;
13542
13662
  }
13543
13663
  },
@@ -13587,37 +13707,73 @@ var quiuboPlugin = {
13587
13707
  gateways.set(accountId, gateway);
13588
13708
  await gateway.start();
13589
13709
  try {
13590
- const { groups: allGroups } = await client.listGroups(botIdentityId);
13591
- for (const group of allGroups) {
13592
- const botSettings = group.settings?.bot ?? {};
13593
- let ownerIdentityId = "";
13594
- try {
13595
- const { members } = await client.listGroupMembers(group.id);
13596
- const owner = members.find((m) => m.role === "owner");
13597
- ownerIdentityId = owner?.identityId ?? "";
13598
- } catch {
13710
+ if (resolvedAgentId) {
13711
+ const { groups: agentGroups } = await client.listAgentGroups(resolvedAgentId);
13712
+ for (const ag of agentGroups) {
13713
+ if (ag.source === "directory") {
13714
+ gateway.setBotConfig(ag.groupId, {
13715
+ enabled: ag.enabled ?? true,
13716
+ suppressionMinutes: 10,
13717
+ ownerIdentityId: "",
13718
+ groupType: ag.groupType ?? "standard",
13719
+ triggerMode: ag.triggerMode,
13720
+ source: "directory",
13721
+ grantedScopes: ag.grantedScopes,
13722
+ agentDisplayName: ag.agentDisplayName ?? resolvedAgentDisplayName,
13723
+ securityMode: ag.securityMode
13724
+ });
13725
+ } else {
13726
+ const botSettings = ag.settings?.bot ?? {};
13727
+ let ownerIdentityId = "";
13728
+ try {
13729
+ const { members } = await client.listGroupMembers(ag.groupId);
13730
+ const owner = members.find((m) => m.role === "owner");
13731
+ ownerIdentityId = owner?.identityId ?? "";
13732
+ } catch {
13733
+ }
13734
+ gateway.setBotConfig(ag.groupId, {
13735
+ enabled: botSettings.enabled ?? false,
13736
+ suppressionMinutes: botSettings.suppressionMinutes ?? 10,
13737
+ ownerIdentityId,
13738
+ groupType: ag.groupType ?? "standard",
13739
+ securityMode: ag.securityMode
13740
+ });
13741
+ }
13599
13742
  }
13600
- gateway.setBotConfig(group.id, {
13601
- enabled: botSettings.enabled ?? false,
13602
- suppressionMinutes: botSettings.suppressionMinutes ?? 10,
13603
- ownerIdentityId,
13604
- groupType: group.groupType ?? "standard"
13605
- });
13606
- }
13607
- log?.info?.(`[${accountId}] Quiubo: hydrated bot config for ${allGroups.length} group(s)`);
13608
- if (keyManager && resolvedAgentId) {
13743
+ log?.info?.(`[${accountId}] Quiubo: hydrated bot config for ${agentGroups.length} group(s) via listAgentGroups`);
13744
+ if (keyManager) {
13745
+ for (const ag of agentGroups) {
13746
+ try {
13747
+ const cached = await keyManager.getActiveEpochKey(ag.groupId);
13748
+ if (cached) {
13749
+ e2eeGrantedGroups.add(ag.groupId);
13750
+ }
13751
+ } catch {
13752
+ }
13753
+ }
13754
+ if (e2eeGrantedGroups.size > 0) {
13755
+ log?.info?.(`[${accountId}] Quiubo: E2EE grants hydrated for ${e2eeGrantedGroups.size} group(s)`);
13756
+ }
13757
+ }
13758
+ } else {
13759
+ const { groups: allGroups } = await client.listGroups(botIdentityId);
13609
13760
  for (const group of allGroups) {
13761
+ const botSettings = group.settings?.bot ?? {};
13762
+ let ownerIdentityId = "";
13610
13763
  try {
13611
- const cached = await keyManager.getActiveEpochKey(group.id);
13612
- if (cached) {
13613
- e2eeGrantedGroups.add(group.id);
13614
- }
13764
+ const { members } = await client.listGroupMembers(group.id);
13765
+ const owner = members.find((m) => m.role === "owner");
13766
+ ownerIdentityId = owner?.identityId ?? "";
13615
13767
  } catch {
13616
13768
  }
13769
+ gateway.setBotConfig(group.id, {
13770
+ enabled: botSettings.enabled ?? false,
13771
+ suppressionMinutes: botSettings.suppressionMinutes ?? 10,
13772
+ ownerIdentityId,
13773
+ groupType: group.groupType ?? "standard"
13774
+ });
13617
13775
  }
13618
- if (e2eeGrantedGroups.size > 0) {
13619
- log?.info?.(`[${accountId}] Quiubo: E2EE grants hydrated for ${e2eeGrantedGroups.size} group(s)`);
13620
- }
13776
+ log?.info?.(`[${accountId}] Quiubo: hydrated bot config for ${allGroups.length} group(s) via listGroups`);
13621
13777
  }
13622
13778
  } catch (err) {
13623
13779
  log?.warn?.(`[${accountId}] Quiubo: failed to hydrate bot config (non-fatal): ${err}`);
@@ -13770,6 +13926,12 @@ async function routeInboundMessage(opts) {
13770
13926
  log?.debug?.(`[${accountId}] Quiubo: skipping empty ${info.kind} reply`);
13771
13927
  return;
13772
13928
  }
13929
+ const gw = gateways.get(accountId);
13930
+ const cachedConfig = gw?.getBotConfig(groupId);
13931
+ if (cachedConfig?.grantedScopes && !cachedConfig.grantedScopes.includes("send_messages")) {
13932
+ log?.warn?.(`[${accountId}] Quiubo: skipping outbound \u2014 agent lacks send_messages scope for group ${groupId}`);
13933
+ return;
13934
+ }
13773
13935
  log?.info?.(`[${accountId}] Quiubo: delivering ${info.kind} reply [${payload.text?.length ?? 0} chars, ${mdAttachments.length} attachments]`);
13774
13936
  try {
13775
13937
  let ciphertextOut;