diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index c86d1b12425..56aebf848b3 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -4031,6 +4031,8 @@ def run_conversation( except Exception as _ver_err: logger.debug("file-mutation verifier footer failed: %s", _ver_err) + _response_transformed = False + # Plugin hook: transform_llm_output # Fired once per turn after the tool-calling loop completes. # Plugins can transform the LLM's output text before it's returned. @@ -4045,9 +4047,11 @@ def run_conversation( model=agent.model, platform=getattr(agent, "platform", None) or "", ) + _response_transformed = False for _hook_result in _transform_results: if isinstance(_hook_result, str) and _hook_result: final_response = _hook_result + _response_transformed = True break # First non-empty string wins except Exception as exc: logger.warning("transform_llm_output hook failed: %s", exc) @@ -4099,6 +4103,7 @@ def run_conversation( "failed": failed, "partial": False, # True only when stopped due to invalid tool calls "interrupted": interrupted, + "response_transformed": _response_transformed, "response_previewed": getattr(agent, "_response_was_previewed", False), "model": agent.model, "provider": agent.provider, diff --git a/gateway/run.py b/gateway/run.py index 52fccb83364..d62fbdc0035 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -17678,7 +17678,11 @@ class GatewayRunner: _content_delivered = bool( _sc and getattr(_sc, "final_content_delivered", False) ) - if not _is_empty_sentinel and (_streamed or _previewed or _content_delivered): + # Plugin hooks (e.g. transform_llm_output) may have appended content + # after streaming finished — when the response was transformed, always + # send the final version so the appended content reaches the client. + _transformed = bool(response.get("response_transformed")) + if not _is_empty_sentinel and not _transformed and (_streamed or _previewed or _content_delivered): logger.info( "Suppressing normal final send for session %s: final delivery already confirmed (streamed=%s previewed=%s content_delivered=%s).", session_key or "?",