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.
This commit is contained in:
luxuguang-leo 2026-06-26 14:18:00 +08:00 committed by Teknium
parent 07271a6f62
commit 949e4cb72a
2 changed files with 65 additions and 0 deletions

View file

@ -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,

View file

@ -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