fix(conversation): clear _mute_post_response on substantive tool-only turn

Salvage of #63888. The original fix clears stale _last_content_with_tools
on substantive tool-only turns but doesn't clear _mute_post_response, which
a prior housekeeping turn may have set. This suppresses tool progress
output via _vprint until the no-tool-call branch resets it at line ~4834
— after all tools have finished executing.

Fix: also reset _mute_post_response = False when clearing stale fallback.

Added test: verify pure housekeeping turns (content + only housekeeping
tools) still set the fallback correctly — the original use case the
fallback was designed for.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
This commit is contained in:
kshitijk4poor 2026-07-14 16:27:29 +05:30 committed by kshitij
parent 8a7d32d4e4
commit 3c2886f599
2 changed files with 61 additions and 0 deletions

View file

@ -4695,6 +4695,13 @@ def run_conversation(
if assistant_message.tool_calls and not _all_housekeeping:
agent._last_content_with_tools = None
agent._last_content_tools_all_housekeeping = False
# Also clear the mute flag: a prior housekeeping turn may
# have set _mute_post_response (line ~4667), and the
# substantive tools in THIS turn should produce visible
# progress output. Without this reset, _vprint suppresses
# tool progress until the no-tool-call branch clears it at
# line ~4834 — after all tools have finished.
agent._mute_post_response = False
# If this turn has both content AND tool_calls, capture the content
# as a fallback final response. Common pattern: model delivers its

View file

@ -123,4 +123,58 @@ def test_substantive_tool_only_turn_invalidates_older_housekeeping_fallback():
assert result["turn_exit_reason"].startswith("text_response"), (
f"Expected text_response exit, got: {result['turn_exit_reason']}. "
f"This indicates the wrong fallback path was taken."
)
def test_housekeeping_only_turn_still_sets_fallback():
"""Regression: pure housekeeping turns (content + only housekeeping tools)
must still set the fallback so the post-response mute path works. This
verifies the fix doesn't break the original use case the fallback was
designed for.
"""
with (
patch("run_agent.get_tool_definitions", return_value=_tool_defs("memory")),
patch("run_agent.check_toolset_requirements", return_value={}),
patch("run_agent.OpenAI"),
):
agent = AIAgent(
api_key="test-key",
base_url="https://openrouter.ai/api/v1/",
quiet_mode=True,
skip_context_files=True,
skip_memory=True,
)
agent._cached_system_prompt = "You are helpful."
agent._use_prompt_caching = False
agent.tool_delay = 0
agent.compression_enabled = False
agent.save_trajectories = False
agent.valid_tool_names = {"memory"}
agent.client = MagicMock()
agent.client.chat.completions.create.side_effect = [
# Turn 1: Content + housekeeping tool (should set fallback)
_response(
content="You're welcome!",
finish_reason="tool_calls",
tool_calls=[_tool_call("memory", "mem1")],
),
# Turn 2: Empty response (should use the housekeeping fallback)
_response(content="", finish_reason="stop"),
]
with (
patch("run_agent.handle_function_call", return_value="ok"),
patch.object(agent, "_persist_session"),
patch.object(agent, "_save_trajectory"),
patch.object(agent, "_cleanup_task_resources"),
):
result = agent.run_conversation("save this")
assert result["final_response"] == "You're welcome!", (
f"Expected housekeeping fallback content, got: {result['final_response']}. "
f"Pure housekeeping turns should still set the fallback."
)
assert "fallback_prior_turn_content" in result.get("turn_exit_reason", ""), (
f"Expected fallback_prior_turn_content exit, got: {result['turn_exit_reason']}."
)