diff --git a/plugins/platforms/whatsapp/adapter.py b/plugins/platforms/whatsapp/adapter.py index 62689281b50..6c2be63df02 100644 --- a/plugins/platforms/whatsapp/adapter.py +++ b/plugins/platforms/whatsapp/adapter.py @@ -1271,7 +1271,10 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): for msg_data in messages: event = await self._build_message_event(msg_data) if event: - await self._send_read_receipt(msg_data) + # Fire-and-forget: a slow bridge /read must not + # delay message dispatch (matches BlueBubbles + # asyncio.create_task pattern for mark_read). + asyncio.create_task(self._send_read_receipt(msg_data)) if event.message_type == MessageType.TEXT: self._enqueue_text_event(event) else: diff --git a/tests/gateway/test_whatsapp_reply_prefix.py b/tests/gateway/test_whatsapp_reply_prefix.py index ba6084096ba..6ba12a06ca4 100644 --- a/tests/gateway/test_whatsapp_reply_prefix.py +++ b/tests/gateway/test_whatsapp_reply_prefix.py @@ -200,10 +200,10 @@ class TestReadReceiptPolicyOrdering: await adapter._poll_messages() - adapter._send_read_receipt.assert_not_awaited() + adapter._send_read_receipt.assert_not_called() @pytest.mark.asyncio - async def test_policy_accepted_message_is_marked_read_before_dispatch(self, monkeypatch): + async def test_policy_accepted_message_is_marked_read_fire_and_forget(self, monkeypatch): from plugins.platforms.whatsapp.adapter import WhatsAppAdapter adapter = WhatsAppAdapter( @@ -230,7 +230,7 @@ class TestReadReceiptPolicyOrdering: await adapter._poll_messages() - adapter._send_read_receipt.assert_awaited_once_with(raw) + adapter._send_read_receipt.assert_called_once_with(raw) adapter.handle_message.assert_awaited_once_with(event) diff --git a/website/docs/user-guide/messaging/whatsapp.md b/website/docs/user-guide/messaging/whatsapp.md index b447b534707..a80333d3967 100644 --- a/website/docs/user-guide/messaging/whatsapp.md +++ b/website/docs/user-guide/messaging/whatsapp.md @@ -180,8 +180,11 @@ Hermes supports voice on WhatsApp: whatsapp: reply_prefix: "" # Empty string disables the header # reply_prefix: "šŸ¤– *My Bot*\n──────\n" # Custom prefix (supports \n for newlines) + send_read_receipts: false # Mark accepted inbound messages as read (blue ticks) ``` +When `send_read_receipts` is `true`, the adapter marks policy-accepted inbound messages as read after DM/group/mention filtering passes. Rejected messages (e.g., from non-allowlisted senders) are not marked read. Disabled by default for privacy. Changing this setting automatically restarts the bridge subprocess on the next connection. + --- ## Message Formatting & Delivery