From 7e36468511c80bf75416490e88355890fa4fb4ed Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sat, 7 Mar 2026 16:09:23 -0800 Subject: [PATCH] fix: /clear command broken inside TUI (patch_stdout interference) The /clear command was using Rich's console.clear() and console.print() which write directly to stdout. Inside the TUI, prompt_toolkit's patch_stdout intercepts stdout via StdoutProxy, which doesn't interpret screen-clearing escape sequences and mangles Rich's ANSI output, resulting in raw escape codes dumped to the terminal. Fix: - Use prompt_toolkit's output.erase_screen() + cursor_goto() to clear the terminal directly (bypasses patch_stdout's StdoutProxy) - Render the banner through ChatConsole (which routes Rich output through prompt_toolkit's native print_formatted_text/ANSI renderer) - Use _cprint for the status message (prompt_toolkit-compatible) - Fall back to the old behavior when not inside the TUI (e.g. startup) --- cli.py | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/cli.py b/cli.py index 7dd74b0b2..68787e9e2 100755 --- a/cli.py +++ b/cli.py @@ -1811,13 +1811,46 @@ class HermesCLI: self.agent.flush_memories(self.conversation_history) except Exception: pass - # Clear terminal screen using Rich (portable, no shell needed) - self.console.clear() + # Clear terminal screen. Inside the TUI, Rich's console.clear() + # goes through patch_stdout's StdoutProxy which swallows the + # screen-clear escape sequences. Use prompt_toolkit's output + # object directly to actually clear the terminal. + if self._app: + out = self._app.output + out.erase_screen() + out.cursor_goto(0, 0) + out.flush() + else: + self.console.clear() # Reset conversation self.conversation_history = [] - # Show fresh banner - self.show_banner() - print(" ✨ (◕‿◕)✨ Fresh start! Screen cleared and conversation reset.\n") + # Show fresh banner. Inside the TUI we must route Rich output + # through ChatConsole (which uses prompt_toolkit's native ANSI + # renderer) instead of self.console (which writes raw to stdout + # and gets mangled by patch_stdout). + if self._app: + cc = ChatConsole() + if self.compact: + cc.print(COMPACT_BANNER) + else: + tools = get_tool_definitions(enabled_toolsets=self.enabled_toolsets, quiet_mode=True) + cwd = os.getenv("TERMINAL_CWD", os.getcwd()) + ctx_len = None + if hasattr(self, 'agent') and self.agent and hasattr(self.agent, 'context_compressor'): + ctx_len = self.agent.context_compressor.context_length + build_welcome_banner( + console=cc, + model=self.model, + cwd=cwd, + tools=tools, + enabled_toolsets=self.enabled_toolsets, + session_id=self.session_id, + context_length=ctx_len, + ) + _cprint(" ✨ (◕‿◕)✨ Fresh start! Screen cleared and conversation reset.\n") + else: + self.show_banner() + print(" ✨ (◕‿◕)✨ Fresh start! Screen cleared and conversation reset.\n") elif cmd_lower == "/history": self.show_history() elif cmd_lower in ("/reset", "/new"):