diff --git a/cli.py b/cli.py index af8ac4efc247..171c4d43c69a 100755 --- a/cli.py +++ b/cli.py @@ -1511,10 +1511,22 @@ class HermesCLI: partial lines and emits complete lines via _cprint to work reliably with prompt_toolkit's patch_stdout. + A ``None`` sentinel signals that an intermediate turn (with tool + calls) just completed — close any open response/reasoning box + and reset so the final response gets a clean frame. + Reasoning/thinking blocks (, , etc.) are suppressed during streaming since they'd display raw XML tags. The agent strips them from the final response anyway. """ + if text is None: + # Intermediate turn ended — discard any open box and reset + self._close_reasoning_box() + if self._stream_box_opened: + w = shutil.get_terminal_size().columns + _cprint(f"{_GOLD}╰{'─' * (w - 2)}╯{_RST}") + self._reset_stream_state() + return if not text: return diff --git a/run_agent.py b/run_agent.py index 78948a78270a..d3b74b7e2d3e 100644 --- a/run_agent.py +++ b/run_agent.py @@ -4837,7 +4837,7 @@ class AIAgent: spinner.stop(cute_msg) elif self.quiet_mode: self._vprint(f" {cute_msg}") - elif self.quiet_mode and not self._has_stream_consumers(): + elif self.quiet_mode: face = random.choice(KawaiiSpinner.KAWAII_WAITING) emoji = _get_tool_emoji(function_name) preview = _build_tool_preview(function_name, function_args) or function_name @@ -6552,7 +6552,13 @@ class AIAgent: ) assistant_msg = self._build_assistant_message(assistant_message, finish_reason) - + + # Signal streaming display that this was an intermediate turn. + # Content may have been streamed (opening the response box) before + # tool_calls were detected. The None sentinel tells the CLI to + # close any open box and reset so the final response gets a clean frame. + self._fire_stream_delta(None) + # If this turn has both content AND tool_calls, capture the content # as a fallback final response. Common pattern: model delivers its # answer and calls memory/skill tools as a side-effect in the same