mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Two related root causes for the '?[33mTool progress: NEW?[0m' garbling reported on kitty, alacritty, ghostty and gnome-console: 1. /verbose label printing used self.console.print() with Rich markup ([yellow]...[/]). self.console is a plain Rich Console() whose output goes directly to sys.stdout, which patch_stdout's StdoutProxy intercepts and mangles raw ANSI sequences. 2. Context pressure status lines (e.g. 'approaching compaction') from AIAgent._safe_print() had the same problem -- _safe_print() was a @staticmethod that always called builtin print(), bypassing the prompt_toolkit renderer entirely. Fix: - Convert AIAgent._safe_print() from @staticmethod to an instance method that delegates to self._print_fn (defaults to builtin print, preserving all non-CLI behaviour). - After the CLI creates its AIAgent instance, wire self.agent._print_fn to the existing _cprint() helper which routes through prompt_toolkit.print_formatted_text(ANSI(text)). - Rewrite the /verbose feedback labels to use hermes_cli.colors.Colors ANSI constants in f-strings and emit them via _cprint() directly, removing the Rich-markup-inside-patch_stdout anti-pattern. Fixes #2262 Co-authored-by: Animesh Mishra <animesh.m.7523@gmail.com>
This commit is contained in:
parent
2c2334d4db
commit
8cb7864110
2 changed files with 26 additions and 8 deletions
18
cli.py
Executable file → Normal file
18
cli.py
Executable file → Normal file
|
|
@ -1915,6 +1915,9 @@ class HermesCLI:
|
|||
tool_progress_callback=self._on_tool_progress,
|
||||
stream_delta_callback=self._stream_delta if self.streaming_enabled else None,
|
||||
)
|
||||
# Route agent status output through prompt_toolkit so ANSI escape
|
||||
# sequences aren't garbled by patch_stdout's StdoutProxy (#2262).
|
||||
self.agent._print_fn = _cprint
|
||||
self._active_agent_route_signature = (
|
||||
effective_model,
|
||||
runtime.get("provider"),
|
||||
|
|
@ -4238,13 +4241,18 @@ class HermesCLI:
|
|||
elif not self.show_reasoning:
|
||||
self.agent.reasoning_callback = None
|
||||
|
||||
# Use raw ANSI codes via _cprint so the output is routed through
|
||||
# prompt_toolkit's renderer. self.console.print() with Rich markup
|
||||
# writes directly to stdout which patch_stdout's StdoutProxy mangles
|
||||
# into garbled sequences like '?[33mTool progress: NEW?[0m' (#2262).
|
||||
from hermes_cli.colors import Colors as _Colors
|
||||
labels = {
|
||||
"off": "[dim]Tool progress: OFF[/] — silent mode, just the final response.",
|
||||
"new": "[yellow]Tool progress: NEW[/] — show each new tool (skip repeats).",
|
||||
"all": "[green]Tool progress: ALL[/] — show every tool call.",
|
||||
"verbose": "[bold green]Tool progress: VERBOSE[/] — full args, results, think blocks, and debug logs.",
|
||||
"off": f"{_Colors.DIM}Tool progress: OFF{_Colors.RESET} — silent mode, just the final response.",
|
||||
"new": f"{_Colors.YELLOW}Tool progress: NEW{_Colors.RESET} — show each new tool (skip repeats).",
|
||||
"all": f"{_Colors.GREEN}Tool progress: ALL{_Colors.RESET} — show every tool call.",
|
||||
"verbose": f"{_Colors.BOLD}{_Colors.GREEN}Tool progress: VERBOSE{_Colors.RESET} — full args, results, think blocks, and debug logs.",
|
||||
}
|
||||
self.console.print(labels.get(self.tool_progress_mode, ""))
|
||||
_cprint(labels.get(self.tool_progress_mode, ""))
|
||||
|
||||
def _handle_reasoning_command(self, cmd: str):
|
||||
"""Handle /reasoning — manage effort level and display toggle.
|
||||
|
|
|
|||
16
run_agent.py
16
run_agent.py
|
|
@ -473,6 +473,11 @@ class AIAgent:
|
|||
self.quiet_mode = quiet_mode
|
||||
self.ephemeral_system_prompt = ephemeral_system_prompt
|
||||
self.platform = platform # "cli", "telegram", "discord", "whatsapp", etc.
|
||||
# Pluggable print function — CLI replaces this with _cprint so that
|
||||
# raw ANSI status lines are routed through prompt_toolkit's renderer
|
||||
# instead of going directly to stdout where patch_stdout's StdoutProxy
|
||||
# would mangle the escape sequences. None = use builtins.print.
|
||||
self._print_fn = None
|
||||
self.skip_context_files = skip_context_files
|
||||
self.pass_session_id = pass_session_id
|
||||
self.log_prefix_chars = log_prefix_chars
|
||||
|
|
@ -1111,16 +1116,21 @@ class AIAgent:
|
|||
self.context_compressor.compression_count = 0
|
||||
self.context_compressor._context_probed = False
|
||||
|
||||
@staticmethod
|
||||
def _safe_print(*args, **kwargs):
|
||||
def _safe_print(self, *args, **kwargs):
|
||||
"""Print that silently handles broken pipes / closed stdout.
|
||||
|
||||
In headless environments (systemd, Docker, nohup) stdout may become
|
||||
unavailable mid-session. A raw ``print()`` raises ``OSError`` which
|
||||
can crash cron jobs and lose completed work.
|
||||
|
||||
Internally routes through ``self._print_fn`` (default: builtin
|
||||
``print``) so callers such as the CLI can inject a renderer that
|
||||
handles ANSI escape sequences properly (e.g. prompt_toolkit's
|
||||
``print_formatted_text(ANSI(...))``) without touching this method.
|
||||
"""
|
||||
try:
|
||||
print(*args, **kwargs)
|
||||
fn = self._print_fn or print
|
||||
fn(*args, **kwargs)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue