diff --git a/agent/prompt_builder.py b/agent/prompt_builder.py index b5b2b58c3..957b3f5fa 100644 --- a/agent/prompt_builder.py +++ b/agent/prompt_builder.py @@ -659,19 +659,7 @@ PLATFORM_HINTS = { "Standard Markdown is automatically converted to Telegram formatting. " "Supported: **bold**, *italic*, ~~strikethrough~~, ||spoiler||, " "`inline code`, ```code blocks```, [links](url), and ## headers. " - "Telegram now supports rich Markdown, so lean into it: whenever it " - "makes the answer clearer or easier to scan, actively reach for real " - "Markdown tables (pipe `| col | col |` syntax), bullet and numbered " - "lists, task lists (`- [ ]` / `- [x]`), headings, nested blockquotes, " - "collapsible details, footnotes/references, math/formulas (`$...$`, " - "`$$...$$`), underline, subscript/superscript, marked (highlighted) " - "text, and anchors. Default to structured formatting over dense " - "paragraphs for any comparison, set of steps, key/value summary, or " - "tabular data. Prefer real Markdown tables and task lists over " - "hand-built bullet substitutes when presenting structured data; these " - "degrade gracefully (tables become readable bullet groups) when rich " - "rendering is unavailable, but advanced constructs like math and " - "collapsible details may render as plain source text in that case. " + "Prefer bullet lists and labeled key:value pairs for structured data. " "You can send media files natively: to deliver a file to the user, " "include MEDIA:/absolute/path/to/file in your response. Images " "(.png, .jpg, .webp) appear as photos, audio (.ogg) sends as voice " @@ -865,6 +853,27 @@ PLATFORM_HINTS = { ), } +# Telegram rich-messages extension — only injected when the user has opted in +# to ``platforms.telegram.extra.rich_messages: true``. The base +# PLATFORM_HINTS["telegram"] covers MarkdownV2-compatible constructs; this +# extension adds the Bot API 10.1 rich-Markdown guidance (tables, task lists, +# collapsible details, math, etc.). +TELEGRAM_RICH_MESSAGES_HINT = ( + "Telegram now supports rich Markdown, so lean into it: whenever it " + "makes the answer clearer or easier to scan, actively reach for real " + "Markdown tables (pipe `| col | col |` syntax), bullet and numbered " + "lists, task lists (`- [ ]` / `- [x]`), headings, nested blockquotes, " + "collapsible details, footnotes/references, math/formulas (`$...$`, " + "`$$...$$`), underline, subscript/superscript, marked (highlighted) " + "text, and anchors. Default to structured formatting over dense " + "paragraphs for any comparison, set of steps, key/value summary, or " + "tabular data. Prefer real Markdown tables and task lists over " + "hand-built bullet substitutes when presenting structured data; these " + "degrade gracefully (tables become readable bullet groups) when rich " + "rendering is unavailable, but advanced constructs like math and " + "collapsible details may render as plain source text in that case. " +) + # --------------------------------------------------------------------------- # Environment hints — execution-environment awareness for the agent. # Unlike PLATFORM_HINTS (which describe the messaging channel), these describe diff --git a/agent/system_prompt.py b/agent/system_prompt.py index ea33df54a..17959af3c 100644 --- a/agent/system_prompt.py +++ b/agent/system_prompt.py @@ -40,6 +40,7 @@ from agent.prompt_builder import ( SKILLS_GUIDANCE, STEER_CHANNEL_NOTE, TASK_COMPLETION_GUIDANCE, + TELEGRAM_RICH_MESSAGES_HINT, TOOL_USE_ENFORCEMENT_GUIDANCE, TOOL_USE_ENFORCEMENT_MODELS, drain_truncation_warnings, @@ -429,6 +430,20 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) except Exception: pass + # For Telegram: append the rich-messages extension only when the user has + # opted in to ``platforms.telegram.extra.rich_messages: true``. The base + # hint covers MarkdownV2-compatible constructs; the extension adds Bot API + # 10.1 guidance (tables, task lists, math, collapsible details, etc.). + if platform_key == "telegram" and _default_hint: + try: + from hermes_cli.config import load_config_readonly + _cfg = load_config_readonly() + _tg_extra = ((_cfg.get("platforms") or {}).get("telegram") or {}).get("extra") or {} + if _tg_extra.get("rich_messages"): + _default_hint = _default_hint.rstrip() + " " + TELEGRAM_RICH_MESSAGES_HINT + except Exception: + pass # Config read failure — fall back to base hint only + _effective_hint = _resolve_platform_hint(agent, platform_key, _default_hint) if platform_key == "tui" and _effective_hint: _effective_hint = _tui_embedded_pane_clarifier(_effective_hint) diff --git a/tests/agent/test_prompt_builder.py b/tests/agent/test_prompt_builder.py index 858c880ec..481f68eae 100644 --- a/tests/agent/test_prompt_builder.py +++ b/tests/agent/test_prompt_builder.py @@ -1092,15 +1092,22 @@ class TestPromptBuilderConstants: hint = PLATFORM_HINTS["telegram"] lowered = hint.lower() assert "Telegram has NO table syntax" not in hint - assert "rich markdown" in lowered - assert "table" in lowered - assert "task list" in lowered - assert "math" in lowered + # Base hint covers MarkdownV2-compatible constructs. + assert "MEDIA:" in hint + # Rich-messages extension (TELEGRAM_RICH_MESSAGES_HINT) covers the + # Bot API 10.1 guidance; it is injected conditionally in + # system_prompt.py when rich_messages: true. + from agent.prompt_builder import TELEGRAM_RICH_MESSAGES_HINT + rich_lowered = TELEGRAM_RICH_MESSAGES_HINT.lower() + assert "rich markdown" in rich_lowered + assert "table" in rich_lowered + assert "task list" in rich_lowered + assert "math" in rich_lowered # Hint should proactively steer toward structured formatting, not just # permit it: bullet + numbered lists for scannable, structured output. - assert "bullet" in lowered - assert "numbered" in lowered - # Local media delivery guidance must remain intact. + assert "bullet" in rich_lowered + assert "numbered" in rich_lowered + # Local media delivery guidance must remain intact in the base hint. assert "include MEDIA:" in hint def test_platform_hints_mattermost(self): diff --git a/tests/agent/test_system_prompt.py b/tests/agent/test_system_prompt.py index 6ebf2a619..0536de1dc 100644 --- a/tests/agent/test_system_prompt.py +++ b/tests/agent/test_system_prompt.py @@ -99,3 +99,46 @@ class TestCodingContextBlock: monkeypatch.setenv("TERMINAL_CWD", str(tmp_path)) agent = _make_agent(valid_tool_names=[], platform="cli") assert "coding agent" not in _stable_prompt(agent) + + +class TestTelegramRichMessagesHint: + """Verify that TELEGRAM_RICH_MESSAGES_HINT is conditionally included.""" + + def test_base_hint_without_rich_messages(self, monkeypatch): + """When rich_messages is False (default), only the base hint is used.""" + agent = _make_agent(platform="telegram") + # Mock config to return rich_messages: false (default) + with patch("hermes_cli.config.load_config_readonly") as mock_cfg: + mock_cfg.return_value = { + "platforms": {"telegram": {"extra": {"rich_messages": False}}} + } + stable = _stable_prompt(agent) + # Base hint should be present + assert "Standard Markdown is automatically converted" in stable + # Rich-messages extension should NOT be present + assert "lean into it" not in stable + assert "task lists" not in stable + + def test_rich_hint_with_rich_messages_enabled(self, monkeypatch): + """When rich_messages is True, the rich-messages extension is appended.""" + agent = _make_agent(platform="telegram") + with patch("hermes_cli.config.load_config_readonly") as mock_cfg: + mock_cfg.return_value = { + "platforms": {"telegram": {"extra": {"rich_messages": True}}} + } + stable = _stable_prompt(agent) + # Base hint should be present + assert "Standard Markdown is automatically converted" in stable + # Rich-messages extension should be present + assert "lean into it" in stable + assert "task lists" in stable + assert "math/formulas" in stable + + def test_base_hint_without_config(self, monkeypatch): + """When config has no telegram section, only base hint is used.""" + agent = _make_agent(platform="telegram") + with patch("hermes_cli.config.load_config_readonly") as mock_cfg: + mock_cfg.return_value = {} + stable = _stable_prompt(agent) + assert "Standard Markdown is automatically converted" in stable + assert "lean into it" not in stable