openclaw-lark-multi-agent 0.1.9 → 0.1.10

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.
@@ -68,6 +68,7 @@ export declare class FeishuBot {
68
68
  private shouldRespond;
69
69
  private isMentioned;
70
70
  private isAllMention;
71
+ private isAllMentionItem;
71
72
  private mentionedBotName;
72
73
  private resolveBotName;
73
74
  private resolveHumanName;
@@ -264,6 +264,12 @@ export class FeishuBot {
264
264
  if (messageType === "text") {
265
265
  const rawText = content.text || "";
266
266
  cleanText = this.cleanMentions(rawText);
267
+ // A mention-only text message is still a valid routing trigger. Feishu may
268
+ // expose mentions as display text like "@万万(Claude)" rather than @_user_xxx,
269
+ // so decide emptiness after stripping leading routing mentions.
270
+ if ((this.isMentioned(message.mentions || []) || this.isAllMention(rawText, message.mentions || [])) && !this.stripLeadingCommandMentions(cleanText).trim()) {
271
+ cleanText = "请回复上面最近一条用户消息。";
272
+ }
267
273
  }
268
274
  else if (messageType === "image") {
269
275
  // Download image and pass local path
@@ -495,7 +501,7 @@ export class FeishuBot {
495
501
  // Track this message for reaction status updates
496
502
  const pending = this.pendingAckMessages.get(chatId) || [];
497
503
  // Anti-loop
498
- const streak = this.store.getBotStreak(chatId);
504
+ const streak = this.store.getBotStreak(chatId, this.config.name);
499
505
  if (streak >= MAX_BOT_STREAK) {
500
506
  console.log(`[${this.config.name}] Anti-loop: ${streak} consecutive bot msgs`);
501
507
  return;
@@ -694,9 +700,11 @@ export class FeishuBot {
694
700
  // Check if this bot is explicitly mentioned
695
701
  if (this.isMentioned(mentions))
696
702
  return true;
697
- // Check if any other bot is mentioned (not us) don't respond
698
- const anyBotMentioned = mentions.some((m) => this.mentionedBotName(m) !== null);
699
- if (anyBotMentioned && !this.isMentioned(mentions))
703
+ // Targeted mentions are exclusive. If a human mentions another person or
704
+ // another bot, free-mode bots must not steal that message. Free mode only
705
+ // applies to plain human messages with no targeted mentions.
706
+ const hasTargetedMention = mentions.some((m) => !this.isAllMentionItem(m));
707
+ if (hasTargetedMention)
700
708
  return false;
701
709
  // No bot mentioned: check current per-bot mode
702
710
  if (chatId) {
@@ -712,10 +720,13 @@ export class FeishuBot {
712
720
  isAllMention(rawText, mentions = []) {
713
721
  if (rawText && (rawText.includes("@_all") || rawText.includes("@all") || rawText.includes("@所有人")))
714
722
  return true;
715
- return mentions.some((m) => m.key === "all" || m.key === "@_all" || m.id?.user_id === "all" || m.id?.open_id === "all" || m.name === "所有人");
723
+ return mentions.some((m) => this.isAllMentionItem(m));
724
+ }
725
+ isAllMentionItem(mention) {
726
+ return mention.key === "all" || mention.key === "@_all" || mention.id?.user_id === "all" || mention.id?.open_id === "all" || mention.name === "所有人";
716
727
  }
717
728
  mentionedBotName(mention) {
718
- if (mention.key === "all" || mention.key === "@_all" || mention.id?.user_id === "all" || mention.id?.open_id === "all" || mention.name === "所有人")
729
+ if (this.isAllMentionItem(mention))
719
730
  return null;
720
731
  const candidates = [this, ...Array.from(FeishuBot.allBots.values()).filter((bot) => bot !== this)];
721
732
  for (const bot of candidates) {
@@ -51,9 +51,13 @@ export declare class MessageStore {
51
51
  */
52
52
  getRecent(chatId: string, maxCount?: number): ChatMessage[];
53
53
  /**
54
- * Count consecutive bot messages at the tail of a chat.
54
+ * Count consecutive messages from one bot at the tail of a chat.
55
+ *
56
+ * Other bots do not consume this bot's anti-loop budget. Human messages reset
57
+ * the streak. This lets multiple bots free-discuss without a global bot-streak
58
+ * guard shutting everyone down after N total bot messages.
55
59
  */
56
- getBotStreak(chatId: string): number;
60
+ getBotStreak(chatId: string, botName: string): number;
57
61
  upsertChatInfo(info: ChatInfo): void;
58
62
  setFreeDiscussion(chatId: string, on: boolean): void;
59
63
  setVerbose(chatId: string, verbose: boolean): void;
@@ -233,21 +233,25 @@ export class MessageStore {
233
233
  }));
234
234
  }
235
235
  /**
236
- * Count consecutive bot messages at the tail of a chat.
236
+ * Count consecutive messages from one bot at the tail of a chat.
237
+ *
238
+ * Other bots do not consume this bot's anti-loop budget. Human messages reset
239
+ * the streak. This lets multiple bots free-discuss without a global bot-streak
240
+ * guard shutting everyone down after N total bot messages.
237
241
  */
238
- getBotStreak(chatId) {
242
+ getBotStreak(chatId, botName) {
239
243
  const rows = this.db.prepare(`
240
- SELECT sender_type FROM messages
244
+ SELECT sender_type, sender_name FROM messages
241
245
  WHERE chat_id = ?
242
246
  ORDER BY timestamp DESC
243
- LIMIT 20
247
+ LIMIT 50
244
248
  `).all(chatId);
245
249
  let count = 0;
246
250
  for (const r of rows) {
247
- if (r.sender_type === "bot")
248
- count++;
249
- else
251
+ if (r.sender_type === "human")
250
252
  break;
253
+ if (r.sender_type === "bot" && r.sender_name === botName)
254
+ count++;
251
255
  }
252
256
  return count;
253
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-lark-multi-agent",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Multi-bot Lark/Feishu bridge for OpenClaw, with per-bot model routing and isolated sessions",
5
5
  "type": "module",
6
6
  "scripts": {