fix(feishu): stop co-mentioned humans being learned as bot aliases

The mention alias-learning fed every identifier of every mention in a
delivery into the per-chat alias cache. A single delivery of "@bot @alice"
matched the bot by its global id and then learned alice's openId as a bot
alias, so every later "@alice" message was misdetected as @bot and the agent
replied to messages never addressed to it.

Only single-mention deliveries are unambiguous bot identities, so restrict
alias learning to them — a multi-mention delivery mixes the bot with
co-mentioned humans, and Feishu's dual-delivery alias form is itself a single
mention, so this is safe and keeps the learning feature working. Also cap the
per-chat alias set size. Adds a [bot, human] co-mention regression test.
This commit is contained in:
matevip 2026-06-09 09:35:35 +08:00
parent 74b2607e40
commit fcc2dd5ccf
2 changed files with 32 additions and 3 deletions

View File

@ -94,11 +94,17 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre
*/
private final ConcurrentHashMap<String, Set<String>> chatBotAliases = new ConcurrentHashMap<>();
/** Max learned aliases retained per chat, to bound memory on busy groups. */
private static final int CHAT_ALIAS_MAX = 64;
/**
* Per-messageId mention tracker TTL
* <p>飞书 SDK 经常对同一条消息双投递一份 mentions bot <em>全局身份</em>来自 /bot/v3/info
* 另一份含 bot <em>群内别名</em>我们累积同一 messageId 下所有投递看到的 mention 标识
* 一旦其中任何一份被识别为 @bot就把累积的全部标识写入 {@link #chatBotAliases}
* 另一份含 bot <em>群内别名</em>我们累积同一 messageId <em> mention</em>投递看到的标识
* 一旦其中任何一份被识别为 @bot就把累积的标识写入 {@link #chatBotAliases}
* <p>只累积单 mention 投递是有意为之 mention 投递 {@code @bot @某人}会把 bot
* 被同时 @ 的人混在一起无法区分若整体学习会把人误学成 bot 别名导致之后 @ 该人的消息
* 被误判为 @bot而飞书双投递里 bot 别名那一份本身就是单 mention所以这样既安全又不丢功能
*/
private final ConcurrentHashMap<String, MentionTrack> mentionTracker = new ConcurrentHashMap<>();
@ -720,7 +726,13 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre
MentionTrack track = null;
if (messageId != null) {
track = mentionTracker.computeIfAbsent(messageId, k -> new MentionTrack());
collectMentionIdentifiers(mentions, track.seenIds);
// Only single-mention deliveries are unambiguous bot identities. A
// multi-mention delivery (e.g. @bot @alice) mixes the bot with
// co-mentioned humans that must NOT be learned as aliases; Feishu's
// dual-delivery alias form is itself a single mention, so this is safe.
if (mentions.length == 1) {
collectMentionIdentifiers(mentions, track.seenIds);
}
}
// 1. 直接匹配 bot 的全局身份
@ -754,6 +766,7 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre
private void learnFromTrack(String chatId, MentionTrack track) {
if (chatId == null || track == null || track.seenIds.isEmpty()) return;
Set<String> aliases = chatBotAliases.computeIfAbsent(chatId, k -> ConcurrentHashMap.newKeySet());
if (aliases.size() >= CHAT_ALIAS_MAX) return;
int before = aliases.size();
aliases.addAll(track.seenIds);
int added = aliases.size() - before;

View File

@ -132,6 +132,22 @@ class FeishuMentionTest {
new MentionEvent[]{mentionEvent(alias)}, chatB, "m3", BOT_ID, null));
}
@Test
void learning_coMentionedHumanNotLearnedAsAlias() {
FeishuChannelAdapter adapter = newAdapter();
String human = "ou_human_alice";
String chatA = "oc_chatA";
// @bot @alice 在同一次投递bot 用全局身份命中返回 true
// 但被同时 @ 的人不能被学成 bot 别名
assertTrue(adapter.detectBotMentionWithLearning(
new MentionEvent[]{mentionEvent(BOT_ID), mentionEvent(human)}, chatA, "m_co", BOT_ID, null));
// 之后只 @ 那个人的消息绝不能被误判为 @bot
assertFalse(adapter.detectBotMentionWithLearning(
new MentionEvent[]{mentionEvent(human)}, chatA, "m_human", BOT_ID, null));
}
// ==================== mention tracker TTL ====================
@Test