mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
feat(gateway): render terminal commands as bare fenced code blocks in chat (#42576)
Terminal tool progress on markdown-capable gateways (Telegram, Slack, Discord, WhatsApp, Matrix, Weixin, Feishu) renders the full command in a fenced code block again, in all/new AND verbose modes — gated on the adapter's supports_code_blocks capability. Plain-text platforms keep the short truncated preview. No language tag is emitted: Slack mrkdwn renders a '```bash' fence with 'bash' as a literal first code line, so a bare '```' fence is used, which renders correctly on every platform that supports blocks. This restores the #41215 feature (removed in #41950 due to the command showing in group chats) as the default. For a personal assistant the command display is desired; the group-chat concern is a preference, not a vulnerability.
This commit is contained in:
parent
3dcfbbfc49
commit
3705625b74
3 changed files with 45 additions and 14 deletions
|
|
@ -1797,9 +1797,10 @@ class BasePlatformAdapter(ABC):
|
|||
# ``format_message`` translates/preserves markdown fences into a real code
|
||||
# block). Capability flag for markdown-aware presentation choices.
|
||||
# Default False (plain-text platforms); markdown-rendering adapters set True.
|
||||
# Note: tool-progress deliberately does NOT use this to render a terminal
|
||||
# command as a ```bash block — that exposed full commands in chat. Progress
|
||||
# shows a short truncated preview only (see gateway/run.py progress_callback).
|
||||
# Tool-progress uses this to render a terminal command as a bare fenced code
|
||||
# block (no language tag — Slack mrkdwn would print the tag as a literal
|
||||
# first code line). Plain-text platforms fall back to the short truncated
|
||||
# preview (see gateway/run.py progress_callback).
|
||||
supports_code_blocks: bool = False
|
||||
|
||||
def __init__(self, config: PlatformConfig, platform: Platform):
|
||||
|
|
|
|||
|
|
@ -12971,9 +12971,33 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
# 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 fenced
|
||||
# code block (full command, no truncation) instead of the compact
|
||||
# `terminal: "cmd…"` preview. Gated on the adapter's
|
||||
# ``supports_code_blocks`` capability so plain-text platforms keep
|
||||
# the short line. No language tag is emitted — Slack mrkdwn renders
|
||||
# the tag as a literal first code line ("bash"), and a bare fence
|
||||
# renders correctly everywhere that supports blocks.
|
||||
_code_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()
|
||||
):
|
||||
_code_block = f"{emoji} {tool_name}\n```\n{args['command'].rstrip()}\n```"
|
||||
|
||||
# Verbose mode: show detailed arguments, respects tool_preview_length
|
||||
if progress_mode == "verbose":
|
||||
if _code_block is not None:
|
||||
progress_queue.put(_code_block)
|
||||
return
|
||||
if args:
|
||||
from agent.display import get_tool_preview_max_len
|
||||
_pl = get_tool_preview_max_len()
|
||||
|
|
@ -12994,7 +13018,11 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
# "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:
|
||||
# Terminal commands on markdown platforms get the full fenced block
|
||||
# built above instead of the truncated preview.
|
||||
if _code_block is not None:
|
||||
msg = _code_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
|
||||
|
|
|
|||
|
|
@ -1295,10 +1295,10 @@ class TerminalCommandAgent:
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_terminal_progress_is_truncated_preview_not_bash_block(monkeypatch, tmp_path):
|
||||
"""Regression for #41215: terminal progress must render as a short truncated
|
||||
preview, never the full command in a fenced ```bash block, even on a
|
||||
markdown-capable (supports_code_blocks) gateway."""
|
||||
async def test_terminal_progress_renders_fenced_code_block(monkeypatch, tmp_path):
|
||||
"""Terminal progress on a markdown-capable (supports_code_blocks) gateway
|
||||
renders the full command in a bare fenced code block — no language tag
|
||||
(Slack mrkdwn would print 'bash' as a literal first code line)."""
|
||||
monkeypatch.setenv("HERMES_TOOL_PROGRESS_MODE", "all")
|
||||
|
||||
fake_dotenv = types.ModuleType("dotenv")
|
||||
|
|
@ -1328,18 +1328,20 @@ async def test_terminal_progress_is_truncated_preview_not_bash_block(monkeypatch
|
|||
context_prompt="",
|
||||
history=[],
|
||||
source=source,
|
||||
session_id="sess-terminal-no-bash-block",
|
||||
session_id="sess-terminal-code-block",
|
||||
session_key="agent:main:telegram:dm:12345",
|
||||
)
|
||||
|
||||
assert result["final_response"] == "done"
|
||||
all_content = " ".join(call["content"] for call in adapter.sent)
|
||||
all_content += " ".join(call["content"] for call in adapter.edits)
|
||||
# Compact truncated preview, not a fenced bash block.
|
||||
# Bare fenced block, no language tag (no '```bash').
|
||||
assert "```" in all_content
|
||||
assert "```bash" not in all_content
|
||||
assert 'terminal: "' in all_content
|
||||
# The full multi-line command body must not reach the chat.
|
||||
assert "npm install -g hyperframes@latest" not in all_content
|
||||
# The full multi-line command body IS present in the block.
|
||||
assert "npm install -g hyperframes@latest" in all_content
|
||||
# No truncated quoted preview for the terminal command.
|
||||
assert 'terminal: "' not in all_content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue