Compare commits

...

1 Commits

Author SHA1 Message Date
Dutch Dim
74ae07b612 fix(gateway): normalize empty agent responses in inner runner early-return
When _run_agent_inner returns via its "not final_response" early path
(e.g. an output-length truncation with no visible assistant text), it
returned the raw "⚠️ Response truncated due to output length limit"
string as final text. Messaging gateways then echoed that to users.

Route this path through _normalize_empty_agent_response +
_sanitize_gateway_final_response — the same treatment the outer
_handle_message_with_agent path already applies — so users see the
friendly "⚠️ Processing stopped: … Try again." instead, falling back to
the raw error only when normalization yields nothing.

Salvaged from #48034 by @djimit (gateway normalization half only; the
retry-count/token-boost tuning in conversation_loop.py was dropped as
symptom-tuning of an already-working recovery loop).
2026-07-01 04:32:02 -07:00
3 changed files with 44 additions and 2 deletions

View File

@@ -17611,9 +17611,14 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
)
if not final_response:
error_msg = f"⚠️ {result['error']}" if result.get("error") else ""
final_response = _normalize_empty_agent_response(
result, final_response or "", history_len=len(agent_history),
)
final_response = _sanitize_gateway_final_response(source.platform, final_response)
if not final_response:
final_response = f"⚠️ {result['error']}" if result.get("error") else ""
return {
"final_response": error_msg,
"final_response": final_response,
"messages": result.get("messages", []),
"api_calls": result.get("api_calls", 0),
"failed": result.get("failed", False),

View File

@@ -46,6 +46,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
# Auto-extracted from noreply emails + manual overrides
AUTHOR_MAP = {
"mac-studio@Fabios-Mac-Studio.local": "valenteff", # PR #53277 salvage (macOS launchd reload: retry bootstrap via _launchctl_bootstrap until launchctl-list confirms registration or the restart-drain window elapses; retry TimeoutExpired not just CalledProcessError; log persistent orphans)
"info@djimit.nl": "djimit", # PR #48034 salvage (normalize empty agent responses in the proxy/inner runner's not-final_response early return so gateways surface "⚠️ Processing stopped: … Try again." instead of a raw truncation error)
"gary@bitcryptic.com": "bitcryptic-gw", # PR #53997 salvage (Matrix E2EE: resolve device_id via query_keys({mxid: []}) when whoami returns none; guard verification call sites so query_keys is never sent [null]; reset _device_id_unverified at connect() start; disconnect before reconnect)
"7698789+abchiaravalle@users.noreply.github.com": "abchiaravalle", # PR #46997 salvage (recover resume_pending sessions: dual freshness signal + empty-turn safety net so restart auto-resume never sends a blank user turn)
"swissly@users.noreply.github.com": "swissly", # PR #47167 salvage (wrap cron delivery thread-pool fallback in its own try/except so a per-target failure can't escape the except-RuntimeError block and crash the multi-target delivery loop; #47163)

View File

@@ -106,6 +106,29 @@ class InterruptedAgent:
return {"final_response": "interrupted", "messages": [], "api_calls": 1}
class PartialTruncationAgent:
"""Returns an incomplete turn with no visible assistant text."""
def __init__(self, **kwargs):
self.tool_progress_callback = kwargs.get("tool_progress_callback")
self.tools = []
self._interrupt_requested = False
@property
def is_interrupted(self) -> bool:
return self._interrupt_requested
def run_conversation(self, message, conversation_history=None, task_id=None):
return {
"final_response": None,
"messages": [],
"api_calls": 2,
"completed": False,
"partial": True,
"error": "Response truncated due to output length limit",
}
def _make_runner(adapter):
gateway_run = importlib.import_module("gateway.run")
GatewayRunner = gateway_run.GatewayRunner
@@ -181,6 +204,19 @@ async def test_baseline_non_interrupted_agent_renders_progress(monkeypatch, tmp_
)
@pytest.mark.asyncio
async def test_partial_empty_agent_response_is_normalized(monkeypatch, tmp_path):
"""Messaging gateways should not echo raw truncation errors as final text."""
adapter, result = await _run_once(
monkeypatch, tmp_path, PartialTruncationAgent, "sess-partial-empty"
)
assert result["final_response"].startswith("⚠️ Processing stopped:")
assert "Response truncated due to output length limit" in result["final_response"]
assert result["final_response"] != "⚠️ Response truncated due to output length limit"
assert result["partial"] is True
@pytest.mark.asyncio
async def test_progress_suppressed_when_agent_is_interrupted(monkeypatch, tmp_path):
"""Post-interrupt tool.started events must not render as bubbles.