diff --git a/plugins/platforms/slack/adapter.py b/plugins/platforms/slack/adapter.py index 56ea96491a..46ae570309 100644 --- a/plugins/platforms/slack/adapter.py +++ b/plugins/platforms/slack/adapter.py @@ -2870,6 +2870,11 @@ class SlackAdapter(BasePlatformAdapter): logger.debug("[Slack] block render failed; using plain text", exc_info=True) return None + def format_tool_preview(self, preview) -> str: + """Keep compact tool arguments out of mrkdwn emphasis conversion.""" + # Substitute embedded delimiters in the display text only. + return f"`{preview.text.replace('`', 'ˋ')}`" + def format_message(self, content: str) -> str: """Convert standard markdown to Slack mrkdwn. Tables are fenced first; code is protected from later passes; broadcast mentions are escaped diff --git a/tests/gateway/test_slack.py b/tests/gateway/test_slack.py index 885213726f..76f9515e17 100644 --- a/tests/gateway/test_slack.py +++ b/tests/gateway/test_slack.py @@ -2593,6 +2593,35 @@ class TestSendTyping: class TestFormatMessage: """Test markdown to Slack mrkdwn conversion.""" + @pytest.mark.asyncio + @pytest.mark.parametrize("target,pattern", [ + ("files", "*test*.py"), + ("content", r"test_.*_.*\.py"), + ]) + async def test_tool_progress_preserves_search_pattern(self, adapter, target, pattern): + from gateway.run_turn_runner import TurnRunner + + args = {"target": target, "pattern": pattern} + ctx = SimpleNamespace(source=None, progress_mode="all", last_was_terminal_block=[False]) + runner = SimpleNamespace(_adapter_for_source=lambda source: adapter) + message = TurnRunner(runner, ctx)._progress_build_message("search_files", pattern, args) + client = adapter._app.client + client.chat_postMessage.return_value = {"ok": True, "ts": "123.456"} + client.chat_update.return_value = {"ok": True, "ts": "123.456"} + + assert (await adapter.send("C123", message)).success + assert (await adapter.edit_message("C123", "123.456", message)).success + for method in (client.chat_postMessage, client.chat_update): + assert method.call_args.kwargs["text"].endswith(f"`{pattern}`") + assert args == {"target": target, "pattern": pattern} + + def test_tool_preview_backticks_do_not_break_code_span(self, adapter): + from agent.display import ToolPreview + + preview = ToolPreview(text="`*test*`.py") + rendered = adapter.format_message(adapter.format_tool_preview(preview)) + assert rendered == "`ˋ*test*ˋ.py`" + assert preview.text == "`*test*`.py" def test_italic_asterisk_conversion(self, adapter): assert adapter.format_message("*hello*") == "_hello_"