diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 28286d48c0..660ed46dd8 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -1081,6 +1081,8 @@ class DiscordAdapter(BasePlatformAdapter): chat_id: str, message_id: str, content: str, + *, + finalize: bool = False, ) -> SendResult: """Edit a previously sent Discord message.""" if not self._client: diff --git a/gateway/platforms/feishu.py b/gateway/platforms/feishu.py index 0531bff487..4b4fa0da4e 100644 --- a/gateway/platforms/feishu.py +++ b/gateway/platforms/feishu.py @@ -1468,6 +1468,8 @@ class FeishuAdapter(BasePlatformAdapter): chat_id: str, message_id: str, content: str, + *, + finalize: bool = False, ) -> SendResult: """Edit a previously sent Feishu text/post message.""" if not self._client: diff --git a/gateway/platforms/matrix.py b/gateway/platforms/matrix.py index cdd67b337d..a5f9352b55 100644 --- a/gateway/platforms/matrix.py +++ b/gateway/platforms/matrix.py @@ -825,7 +825,7 @@ class MatrixAdapter(BasePlatformAdapter): async def edit_message( - self, chat_id: str, message_id: str, content: str + self, chat_id: str, message_id: str, content: str, *, finalize: bool = False ) -> SendResult: """Edit an existing message (via m.replace).""" diff --git a/gateway/platforms/mattermost.py b/gateway/platforms/mattermost.py index 18367a8e44..10539bf646 100644 --- a/gateway/platforms/mattermost.py +++ b/gateway/platforms/mattermost.py @@ -304,7 +304,7 @@ class MattermostAdapter(BasePlatformAdapter): ) async def edit_message( - self, chat_id: str, message_id: str, content: str + self, chat_id: str, message_id: str, content: str, *, finalize: bool = False ) -> SendResult: """Edit an existing post.""" formatted = self.format_message(content) diff --git a/gateway/platforms/slack.py b/gateway/platforms/slack.py index ba444c53e8..5455c0fa56 100644 --- a/gateway/platforms/slack.py +++ b/gateway/platforms/slack.py @@ -316,6 +316,8 @@ class SlackAdapter(BasePlatformAdapter): chat_id: str, message_id: str, content: str, + *, + finalize: bool = False, ) -> SendResult: """Edit a previously sent Slack message.""" if not self._app: diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index 0b74c4e15f..1bc4ec2b10 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -1081,6 +1081,8 @@ class TelegramAdapter(BasePlatformAdapter): chat_id: str, message_id: str, content: str, + *, + finalize: bool = False, ) -> SendResult: """Edit a previously sent Telegram message.""" if not self._bot: diff --git a/gateway/platforms/whatsapp.py b/gateway/platforms/whatsapp.py index d1de5b8568..78b1b92f77 100644 --- a/gateway/platforms/whatsapp.py +++ b/gateway/platforms/whatsapp.py @@ -655,6 +655,8 @@ class WhatsAppAdapter(BasePlatformAdapter): chat_id: str, message_id: str, content: str, + *, + finalize: bool = False, ) -> SendResult: """Edit a previously sent message via the WhatsApp bridge.""" if not self._running or not self._http_session: diff --git a/tests/gateway/test_stream_consumer.py b/tests/gateway/test_stream_consumer.py index 0a0e0631db..7ae587dadd 100644 --- a/tests/gateway/test_stream_consumer.py +++ b/tests/gateway/test_stream_consumer.py @@ -133,6 +133,43 @@ class TestFinalizeCapabilityGate: assert picky.edit_message.call_args[1]["finalize"] is True +class TestEditMessageFinalizeSignature: + """Every concrete platform adapter must accept the ``finalize`` kwarg. + + stream_consumer._send_or_edit always passes ``finalize=`` to + ``adapter.edit_message(...)`` (see gateway/stream_consumer.py). An + adapter that overrides edit_message without accepting finalize raises + TypeError the first time streaming hits a segment break or final edit. + Guard the contract with an explicit signature check so it cannot + silently regress — existing tests use MagicMock which swallows any + kwarg and cannot catch this. + """ + + @pytest.mark.parametrize( + "module_path,class_name", + [ + ("gateway.platforms.telegram", "TelegramAdapter"), + ("gateway.platforms.discord", "DiscordAdapter"), + ("gateway.platforms.slack", "SlackAdapter"), + ("gateway.platforms.matrix", "MatrixAdapter"), + ("gateway.platforms.mattermost", "MattermostAdapter"), + ("gateway.platforms.feishu", "FeishuAdapter"), + ("gateway.platforms.whatsapp", "WhatsAppAdapter"), + ("gateway.platforms.dingtalk", "DingTalkAdapter"), + ], + ) + def test_edit_message_accepts_finalize(self, module_path, class_name): + import inspect + + module = pytest.importorskip(module_path) + cls = getattr(module, class_name) + params = inspect.signature(cls.edit_message).parameters + assert "finalize" in params, ( + f"{class_name}.edit_message must accept 'finalize' kwarg; " + f"stream_consumer._send_or_edit passes it unconditionally" + ) + + class TestSendOrEditMediaStripping: """Verify _send_or_edit strips MEDIA: before sending to the platform."""