diff --git a/nanobot/channels/feishu.py b/nanobot/channels/feishu.py index f8868dbaf..060ba2bb5 100644 --- a/nanobot/channels/feishu.py +++ b/nanobot/channels/feishu.py @@ -485,7 +485,9 @@ class FeishuChannel(BaseChannel): key = mention.key or None if not key: continue - pattern = rf"{re.escape(key)}(?=\s|$)" + # Feishu placeholders are numbered keys like @_user_1. Keep + # punctuation-adjacent mentions valid without matching @_user_10. + pattern = rf"{re.escape(key)}(?![A-Za-z0-9_])" if not re.search(pattern, text): continue @@ -532,7 +534,7 @@ class FeishuChannel(BaseChannel): candidate = text.lstrip() for mention in mentions: key = getattr(mention, "key", None) or "" - if not key or not re.match(rf"{re.escape(key)}(?=\s|$)", candidate): + if not key or not re.match(rf"{re.escape(key)}(?![A-Za-z0-9_])", candidate): continue if not self._is_bot_mention_event(mention): continue diff --git a/tests/channels/test_feishu_mentions.py b/tests/channels/test_feishu_mentions.py index a49e76ee6..0404e87ec 100644 --- a/tests/channels/test_feishu_mentions.py +++ b/tests/channels/test_feishu_mentions.py @@ -44,6 +44,12 @@ class TestResolveMentions: assert "@_user_1" not in result assert "@_user_2" not in result + def test_mention_before_punctuation_replaced(self): + text = "hello @_user_1, are you there?" + mentions = [_mention("@_user_1", "Alice", open_id="ou_a")] + result = FeishuChannel._resolve_mentions(text, mentions) + assert result == "hello @Alice (ou_a), are you there?" + def test_no_mentions_returns_text(self): assert FeishuChannel._resolve_mentions("hello world", None) == "hello world" assert FeishuChannel._resolve_mentions("hello world", []) == "hello world"