mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-10 08:32:09 +00:00
fix(gateway): cap terminal code-block preview in non-verbose mode (#42729)
The markdown code-block change rendered args['command'] in full in both verbose AND non-verbose (all/new) modes, so a long or multi-line terminal command bypassed the tool_preview_length cap (default 40) and rendered as a huge block. Non-verbose now collapses to a single line capped at the preview length while keeping the fence; verbose keeps the full command.
This commit is contained in:
parent
a38cc69bcc
commit
8d99b5bc4f
2 changed files with 91 additions and 18 deletions
|
|
@ -13002,13 +13002,20 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
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
|
||||
# code block 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.
|
||||
#
|
||||
# Verbose mode shows the FULL command. Non-verbose ("all"/"new")
|
||||
# modes still wrap in a fence but truncate to a single line capped
|
||||
# at ``tool_preview_length`` (default 40) so a long or multi-line
|
||||
# command doesn't render as a huge block — matching the budget the
|
||||
# non-terminal preview path already applies (#42634).
|
||||
_code_block_full = None
|
||||
_code_block_short = None
|
||||
try:
|
||||
_progress_adapter = self.adapters.get(source.platform)
|
||||
except Exception:
|
||||
|
|
@ -13020,12 +13027,25 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
and isinstance(args.get("command"), str)
|
||||
and args["command"].strip()
|
||||
):
|
||||
_code_block = f"{emoji} {tool_name}\n```\n{args['command'].rstrip()}\n```"
|
||||
from agent.display import get_tool_preview_max_len
|
||||
_cmd_full = args["command"].rstrip()
|
||||
_code_block_full = f"{emoji} {tool_name}\n```\n{_cmd_full}\n```"
|
||||
# Single-line, capped preview for non-verbose modes.
|
||||
_pl = get_tool_preview_max_len()
|
||||
_cap = _pl if _pl > 0 else 40
|
||||
_lines = _cmd_full.splitlines()
|
||||
_cmd_short = _lines[0] if _lines else _cmd_full
|
||||
_multiline = len(_lines) > 1
|
||||
if len(_cmd_short) > _cap:
|
||||
_cmd_short = _cmd_short[:_cap - 3] + "..."
|
||||
elif _multiline:
|
||||
_cmd_short = _cmd_short + " ..."
|
||||
_code_block_short = f"{emoji} {tool_name}\n```\n{_cmd_short}\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)
|
||||
if _code_block_full is not None:
|
||||
progress_queue.put(_code_block_full)
|
||||
return
|
||||
if args:
|
||||
from agent.display import get_tool_preview_max_len
|
||||
|
|
@ -13047,10 +13067,10 @@ 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).
|
||||
# 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
|
||||
# Terminal commands on markdown platforms get a single-line capped
|
||||
# fenced block (built above) instead of the truncated preview.
|
||||
if _code_block_short is not None:
|
||||
msg = _code_block_short
|
||||
elif preview:
|
||||
from agent.display import get_tool_preview_max_len
|
||||
_pl = get_tool_preview_max_len()
|
||||
|
|
|
|||
|
|
@ -1297,8 +1297,10 @@ class TerminalCommandAgent:
|
|||
@pytest.mark.asyncio
|
||||
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)."""
|
||||
renders a bare fenced code block — no language tag (Slack mrkdwn would print
|
||||
'bash' as a literal first code line). In non-verbose ("all"/"new") mode the
|
||||
command is collapsed to a single line capped at tool_preview_length so a long
|
||||
or multi-line command doesn't render as a huge block (#42634)."""
|
||||
monkeypatch.setenv("HERMES_TOOL_PROGRESS_MODE", "all")
|
||||
|
||||
fake_dotenv = types.ModuleType("dotenv")
|
||||
|
|
@ -1338,12 +1340,63 @@ async def test_terminal_progress_renders_fenced_code_block(monkeypatch, tmp_path
|
|||
# Bare fenced block, no language tag (no '```bash').
|
||||
assert "```" in all_content
|
||||
assert "```bash" not in all_content
|
||||
# The full multi-line command body IS present in the block.
|
||||
assert "npm install -g hyperframes@latest" in all_content
|
||||
# Non-verbose collapses to the first line + truncation marker — the later
|
||||
# command lines must NOT appear (this was the "huge block" regression).
|
||||
assert "set -euo pipefail" in all_content
|
||||
assert "npm install -g hyperframes@latest" not in all_content
|
||||
assert "node --version" not in all_content
|
||||
# No truncated quoted preview for the terminal command.
|
||||
assert 'terminal: "' not in all_content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_terminal_progress_verbose_shows_full_command(monkeypatch, tmp_path):
|
||||
"""Verbose mode on a markdown-capable gateway renders the FULL multi-line
|
||||
command in a bare fenced block (no truncation, no 'bash' tag). This is the
|
||||
parity guarantee for #42634: verbose keeps full detail, non-verbose caps."""
|
||||
monkeypatch.setenv("HERMES_TOOL_PROGRESS_MODE", "verbose")
|
||||
|
||||
fake_dotenv = types.ModuleType("dotenv")
|
||||
fake_dotenv.load_dotenv = lambda *args, **kwargs: None
|
||||
monkeypatch.setitem(sys.modules, "dotenv", fake_dotenv)
|
||||
|
||||
fake_run_agent = types.ModuleType("run_agent")
|
||||
fake_run_agent.AIAgent = TerminalCommandAgent
|
||||
monkeypatch.setitem(sys.modules, "run_agent", fake_run_agent)
|
||||
import tools.terminal_tool # noqa: F401 - register terminal emoji
|
||||
|
||||
adapter = CodeBlockProgressAdapter(platform=Platform.TELEGRAM)
|
||||
runner = _make_runner(adapter)
|
||||
gateway_run = importlib.import_module("gateway.run")
|
||||
monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
|
||||
monkeypatch.setattr(gateway_run, "_resolve_runtime_agent_kwargs", lambda: {"api_key": "***"})
|
||||
|
||||
source = SessionSource(
|
||||
platform=Platform.TELEGRAM,
|
||||
chat_id="12345",
|
||||
chat_type="dm",
|
||||
thread_id=None,
|
||||
)
|
||||
|
||||
result = await runner._run_agent(
|
||||
message="hello",
|
||||
context_prompt="",
|
||||
history=[],
|
||||
source=source,
|
||||
session_id="sess-terminal-code-block-verbose",
|
||||
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)
|
||||
assert "```" in all_content
|
||||
assert "```bash" not in all_content
|
||||
# Full command body present — verbose is uncapped.
|
||||
assert "npm install -g hyperframes@latest" in all_content
|
||||
assert "node --version" in all_content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_terminal_progress_no_bash_block_in_verbose_mode(monkeypatch, tmp_path):
|
||||
"""#41215 also rendered the bash block in verbose mode. The revert removed it
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue