mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
feat(gateway): render terminal tool calls as native bash code blocks on markdown platforms (#41215)
Tool-progress now shows a terminal command in a ```bash fenced block — full command, no surrounding quotes, no label, no 40-char truncation — instead of the noisy `terminal: "cmd…"` line, on every platform that renders markdown code blocks (Telegram, Slack, Matrix, WhatsApp, Feishu, Weixin, Discord). Plain-text platforms keep the compact preview line. Gated on a new `BasePlatformAdapter.supports_code_blocks` capability (default False) rather than a hardcoded platform list, so plugin adapters (Discord lives in plugins/platforms/) opt in by setting the flag. Applies to both all/new and verbose progress modes, with a safe fallback when the command arg is missing or blank.
This commit is contained in:
parent
e029b7597b
commit
dde9c0d19d
9 changed files with 44 additions and 3 deletions
|
|
@ -1792,7 +1792,14 @@ class BasePlatformAdapter(ABC):
|
|||
- Sending messages/responses
|
||||
- Handling media
|
||||
"""
|
||||
|
||||
|
||||
# Whether this platform renders triple-backtick fenced code blocks (i.e.
|
||||
# ``format_message`` translates/preserves markdown fences into a real code
|
||||
# block). Drives presentation choices like rendering a ``terminal`` tool
|
||||
# call's command as a ```bash block instead of a flat preview line.
|
||||
# Default False (plain-text platforms); markdown-rendering adapters set True.
|
||||
supports_code_blocks: bool = False
|
||||
|
||||
def __init__(self, config: PlatformConfig, platform: Platform):
|
||||
self.config = config
|
||||
self.platform = platform
|
||||
|
|
|
|||
|
|
@ -1409,6 +1409,8 @@ def check_feishu_requirements() -> bool:
|
|||
class FeishuAdapter(BasePlatformAdapter):
|
||||
"""Feishu/Lark bot adapter."""
|
||||
|
||||
supports_code_blocks = True # Feishu renders fenced code blocks
|
||||
|
||||
MAX_MESSAGE_LENGTH = 8000
|
||||
# Max distinct chat IDs retained in _chat_locks before LRU eviction kicks in.
|
||||
CHAT_LOCK_MAX_SIZE: int = 1000
|
||||
|
|
|
|||
|
|
@ -420,6 +420,8 @@ class _CryptoStateStore:
|
|||
class MatrixAdapter(BasePlatformAdapter):
|
||||
"""Gateway adapter for Matrix (any homeserver)."""
|
||||
|
||||
supports_code_blocks = True # Matrix renders fenced code blocks (HTML/markdown)
|
||||
|
||||
# Threshold for detecting Matrix client-side message splits.
|
||||
# When a chunk is near the ~4000-char practical limit, a continuation
|
||||
# is almost certain.
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
"""
|
||||
|
||||
MAX_MESSAGE_LENGTH = 39000 # Slack API allows 40,000 chars; leave margin
|
||||
supports_code_blocks = True # Slack mrkdwn renders fenced code blocks
|
||||
|
||||
def __init__(self, config: PlatformConfig):
|
||||
super().__init__(config, Platform.SLACK)
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
|
||||
# Telegram message limits
|
||||
MAX_MESSAGE_LENGTH = 4096
|
||||
supports_code_blocks = True # Telegram MarkdownV2 renders fenced code blocks
|
||||
# Threshold for detecting Telegram client-side message splits.
|
||||
# When a chunk is near this limit, a continuation is almost certain.
|
||||
_SPLIT_THRESHOLD = 4000
|
||||
|
|
|
|||
|
|
@ -1138,6 +1138,8 @@ async def qr_login(
|
|||
class WeixinAdapter(BasePlatformAdapter):
|
||||
"""Native Hermes adapter for Weixin personal accounts."""
|
||||
|
||||
supports_code_blocks = True # Weixin renders fenced code blocks
|
||||
|
||||
MAX_MESSAGE_LENGTH = 2000
|
||||
|
||||
# WeChat does not support editing sent messages — streaming must use the
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ class WhatsAppAdapter(BasePlatformAdapter):
|
|||
# WhatsApp message limits — practical UX limit, not protocol max.
|
||||
# WhatsApp allows ~65K but long messages are unreadable on mobile.
|
||||
MAX_MESSAGE_LENGTH = 4096
|
||||
supports_code_blocks = True # WhatsApp renders fenced code blocks (monospace)
|
||||
DEFAULT_REPLY_PREFIX = "⚕ *Hermes Agent*\n────────────\n"
|
||||
|
||||
# Default bridge location relative to the hermes-agent install
|
||||
|
|
|
|||
|
|
@ -17339,10 +17339,32 @@ class GatewayRunner:
|
|||
# Build progress message with primary argument preview
|
||||
from agent.display import get_tool_emoji
|
||||
emoji = get_tool_emoji(tool_name, default="⚙️")
|
||||
|
||||
# Markdown-capable platforms render a terminal command as a native
|
||||
# ```bash fenced block (full command, no quotes, no label, no
|
||||
# truncation) instead of the noisy `terminal: "cmd…"` line. Gated
|
||||
# on the adapter's ``supports_code_blocks`` capability so every
|
||||
# markdown-rendering platform (and plugin adapters that opt in) gets
|
||||
# it, while plain-text platforms keep the compact line.
|
||||
_bash_block = None
|
||||
try:
|
||||
_progress_adapter = self.adapters.get(source.platform)
|
||||
except Exception:
|
||||
_progress_adapter = None
|
||||
if (
|
||||
getattr(_progress_adapter, "supports_code_blocks", False)
|
||||
and tool_name == "terminal"
|
||||
and isinstance(args, dict)
|
||||
and isinstance(args.get("command"), str)
|
||||
and args["command"].strip()
|
||||
):
|
||||
_bash_block = f"```bash\n{args['command'].rstrip()}\n```"
|
||||
|
||||
# Verbose mode: show detailed arguments, respects tool_preview_length
|
||||
if progress_mode == "verbose":
|
||||
if args:
|
||||
if _bash_block is not None:
|
||||
msg = _bash_block
|
||||
elif args:
|
||||
from agent.display import get_tool_preview_max_len
|
||||
_pl = get_tool_preview_max_len()
|
||||
args_str = json.dumps(args, ensure_ascii=False, default=str)
|
||||
|
|
@ -17362,7 +17384,9 @@ class GatewayRunner:
|
|||
# "all" / "new" modes: short preview, respects tool_preview_length
|
||||
# config (defaults to 40 chars when unset to keep gateway messages
|
||||
# compact — unlike CLI spinners, these persist as permanent messages).
|
||||
if preview:
|
||||
if _bash_block is not None:
|
||||
msg = _bash_block
|
||||
elif preview:
|
||||
from agent.display import get_tool_preview_max_len
|
||||
_pl = get_tool_preview_max_len()
|
||||
_cap = _pl if _pl > 0 else 40
|
||||
|
|
|
|||
|
|
@ -573,6 +573,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
# Discord message limits
|
||||
MAX_MESSAGE_LENGTH = 2000
|
||||
_SPLIT_THRESHOLD = 1900 # near the 2000-char split point
|
||||
supports_code_blocks = True # Discord markdown renders fenced code blocks natively
|
||||
|
||||
# Auto-disconnect from voice channel after this many seconds of inactivity
|
||||
VOICE_TIMEOUT = 300
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue