diff --git a/plugins/platforms/feishu/adapter.py b/plugins/platforms/feishu/adapter.py index 85636135c6..9c02560c0c 100644 --- a/plugins/platforms/feishu/adapter.py +++ b/plugins/platforms/feishu/adapter.py @@ -2592,6 +2592,7 @@ class FeishuAdapter(BasePlatformAdapter): existing.timestamp = event.timestamp if event.message_id: existing.message_id = event.message_id + existing.source.message_id = event.message_id self._schedule_media_batch_flush(key) def _schedule_media_batch_flush(self, key: str) -> None: @@ -2850,6 +2851,7 @@ class FeishuAdapter(BasePlatformAdapter): existing.timestamp = event.timestamp if event.message_id: existing.message_id = event.message_id + existing.source.message_id = event.message_id self._pending_text_batch_counts[key] = next_count self._schedule_text_batch_flush(key) diff --git a/tests/gateway/test_feishu.py b/tests/gateway/test_feishu.py index 2954e638dc..39bc2b8749 100644 --- a/tests/gateway/test_feishu.py +++ b/tests/gateway/test_feishu.py @@ -971,14 +971,18 @@ class TestAdapterBehavior(unittest.TestCase): adapter = FeishuAdapter(PlatformConfig()) adapter.handle_message = AsyncMock() - source = SessionSource( - platform=adapter.platform, - chat_id="oc_chat", - chat_name="Feishu DM", - chat_type="dm", - user_id="ou_user", - user_name="张三", - ) + + def _source(message_id: str) -> SessionSource: + # Each inbound message carries its own source, pinned to that message's id. + return SessionSource( + platform=adapter.platform, + chat_id="oc_chat", + chat_name="Feishu DM", + chat_type="dm", + user_id="ou_user", + user_name="张三", + message_id=message_id, + ) async def _sleep(_delay): return None @@ -986,13 +990,13 @@ class TestAdapterBehavior(unittest.TestCase): async def _run() -> None: with patch("plugins.platforms.feishu.adapter.asyncio.sleep", side_effect=_sleep): await adapter._dispatch_inbound_event( - MessageEvent(text="A", message_type=MessageType.TEXT, source=source, message_id="om_1") + MessageEvent(text="A", message_type=MessageType.TEXT, source=_source("om_1"), message_id="om_1") ) await adapter._dispatch_inbound_event( - MessageEvent(text="B", message_type=MessageType.TEXT, source=source, message_id="om_2") + MessageEvent(text="B", message_type=MessageType.TEXT, source=_source("om_2"), message_id="om_2") ) await adapter._dispatch_inbound_event( - MessageEvent(text="C", message_type=MessageType.TEXT, source=source, message_id="om_3") + MessageEvent(text="C", message_type=MessageType.TEXT, source=_source("om_3"), message_id="om_3") ) pending = list(adapter._pending_text_batch_tasks.values()) self.assertEqual(len(pending), 1) @@ -1005,6 +1009,10 @@ class TestAdapterBehavior(unittest.TestCase): second = adapter.handle_message.await_args_list[1].args[0] self.assertEqual(first.text, "A\nB") self.assertEqual(second.text, "C") + # Coalescing advances the event id to the latest message; the reply anchor + # (source.message_id) must move with it or replies/session tools disagree. + self.assertEqual(first.message_id, "om_2") + self.assertEqual(first.source.message_id, first.message_id) @patch.dict(os.environ, {}, clear=True) def test_media_batch_merges_rapid_photo_messages(self):