diff --git a/agent/turn_finalizer.py b/agent/turn_finalizer.py index 3b62028f23c82..79f4397fc6a5c 100644 --- a/agent/turn_finalizer.py +++ b/agent/turn_finalizer.py @@ -547,6 +547,7 @@ def finalize_turn( logger.debug("turn-completion explainer failed: %s", _exp_err) _response_transformed = False + _pre_transform_response = None # Plugin hook: transform_llm_output # Fired once per turn after the tool-calling loop completes. @@ -564,6 +565,7 @@ def finalize_turn( ) for _hook_result in _transform_results: if isinstance(_hook_result, str) and _hook_result: + _pre_transform_response = final_response final_response = _hook_result _response_transformed = True break # First non-empty string wins @@ -648,6 +650,7 @@ def finalize_turn( "partial": False, # True only when stopped due to invalid tool calls "interrupted": interrupted, "response_transformed": _response_transformed, + "pre_transform_response": _pre_transform_response, "response_previewed": getattr(agent, "_response_was_previewed", False), "model": agent.model, "provider": agent.provider, diff --git a/cli.py b/cli.py index 0861fcffe398e..1081c8163ab3a 100644 --- a/cli.py +++ b/cli.py @@ -14445,7 +14445,13 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): elif already_streamed: # Response was already streamed token-by-token with box framing; # _flush_stream() already closed the box. Skip Rich Panel. - pass + # If a plugin appended content (shout, trace) after streaming, + # print only the appended portion now. + if result and result.get("response_transformed"): + _pre = result.get("pre_transform_response") or "" + _appended = response[len(_pre):] + if _appended.strip(): + _cprint(_appended) else: _chat_console = ChatConsole() _chat_console.print(Panel(