From 949e4cb72a78a38bf63d00c3e663b0d069df6f8d Mon Sep 17 00:00:00 2001 From: luxuguang-leo Date: Fri, 26 Jun 2026 14:18:00 +0800 Subject: [PATCH] fix(feishu): add extra_ua_tags=["channel"] to FeishuWSClient for group @mention delivery Without this UA tag the Feishu server does not push group @mention events over the WebSocket transport. The "channel" tag tells the server to use the Channel protocol which enables group-message routing in addition to P2P direct messages. Root cause: FeishuWSClient was created without any UA signaling tag, so the server defaulted to the basic DM-only push mode. Group @mention events were silently dropped before reaching Hermes. Fixes https://github.com/NousResearch/hermes-agent/issues/50656 Also adds a regression test verifying the UA tag is present in the FeishuWSClient constructor call. --- plugins/platforms/feishu/adapter.py | 6 +++ tests/gateway/test_feishu.py | 59 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/plugins/platforms/feishu/adapter.py b/plugins/platforms/feishu/adapter.py index 7dd7e238937..9fecc91ddf4 100644 --- a/plugins/platforms/feishu/adapter.py +++ b/plugins/platforms/feishu/adapter.py @@ -4730,6 +4730,12 @@ class FeishuAdapter(BasePlatformAdapter): log_level=lark.LogLevel.INFO, event_handler=self._event_handler, domain=domain, + # Channel SDK signaling tag: without this UA tag the Feishu + # server does not push group @mention events over the WebSocket + # transport. The tag tells the server to use the Channel protocol + # which enables group-message routing in addition to P2P DM. + # See https://github.com/NousResearch/hermes-agent/issues/50656 + extra_ua_tags=["channel"], ) self._ws_future = loop.run_in_executor( None, diff --git a/tests/gateway/test_feishu.py b/tests/gateway/test_feishu.py index 58d7bb88872..d5d6739773f 100644 --- a/tests/gateway/test_feishu.py +++ b/tests/gateway/test_feishu.py @@ -414,6 +414,65 @@ class TestFeishuAdapterMessaging(unittest.TestCase): self.assertEqual(sleeps, [1]) self.assertEqual(fake_loop.calls, 2) + @patch.dict(os.environ, { + "FEISHU_APP_ID": "cli_app", + "FEISHU_APP_SECRET": "secret_app", + }, clear=True) + def test_connect_websocket_sets_channel_ua_tag(self): + """Verify that FeishuWSClient receives extra_ua_tags=["channel"]. + + Without this UA tag the Feishu server does not push group @mention + events over the WebSocket transport. See + https://github.com/NousResearch/hermes-agent/issues/50656 + """ + from gateway.config import PlatformConfig + from plugins.platforms.feishu.adapter import FeishuAdapter + + adapter = FeishuAdapter(PlatformConfig()) + ws_client = SimpleNamespace() + + with ( + patch("plugins.platforms.feishu.adapter.FEISHU_AVAILABLE", True), + patch("plugins.platforms.feishu.adapter.FEISHU_WEBSOCKET_AVAILABLE", True), + patch("plugins.platforms.feishu.adapter.lark", + SimpleNamespace(LogLevel=SimpleNamespace(INFO="INFO", WARNING="WARNING"))), + patch("plugins.platforms.feishu.adapter.EventDispatcherHandler") as mock_handler_class, + patch("plugins.platforms.feishu.adapter.FeishuWSClient") as mock_ws_client, + patch("plugins.platforms.feishu.adapter._run_official_feishu_ws_client"), + patch("plugins.platforms.feishu.adapter.acquire_scoped_lock", return_value=(True, None)), + patch("plugins.platforms.feishu.adapter.release_scoped_lock"), + patch.object(adapter, "_hydrate_bot_identity", new=AsyncMock()), + patch.object(adapter, "_build_lark_client", return_value=SimpleNamespace()), + ): + _mock_event_dispatcher_builder(mock_handler_class) + + loop = asyncio.new_event_loop() + future = loop.create_future() + future.set_result(None) + + class _Loop: + def run_in_executor(self, *_args, **_kwargs): + return future + def is_closed(self): + return False + + try: + with patch("plugins.platforms.feishu.adapter.asyncio.get_running_loop", + return_value=_Loop()): + connected = asyncio.run(adapter.connect()) + finally: + loop.close() + + self.assertTrue(connected) + # Verify the Channel SDK UA tag is present — this is the fix for + # group @mention message delivery over WebSocket. + mock_ws_client.assert_called_once() + call_kwargs = mock_ws_client.call_args.kwargs + self.assertIn("extra_ua_tags", call_kwargs, + "FeishuWSClient must receive extra_ua_tags for group @mention delivery") + self.assertEqual(call_kwargs["extra_ua_tags"], ["channel"], + "extra_ua_tags must be ['channel'] to enable group event routing") + @patch.dict(os.environ, {}, clear=True) def test_edit_message_updates_existing_feishu_message(self): from gateway.config import PlatformConfig