diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index 7386904d..33b33f54 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -216,6 +216,29 @@ async def _print_interactive_progress_line(text: str, thinking: ThinkingSpinner await _print_interactive_line(text) +async def _maybe_print_interactive_progress( + msg: Any, + thinking: ThinkingSpinner | None, + channels_config: Any, +) -> bool: + metadata = msg.metadata or {} + if metadata.get("_retry_wait"): + await _print_interactive_progress_line(msg.content, thinking) + return True + + if not metadata.get("_progress"): + return False + + is_tool_hint = metadata.get("_tool_hint", False) + if channels_config and is_tool_hint and not channels_config.send_tool_hints: + return True + if channels_config and not is_tool_hint and not channels_config.send_progress: + return True + + await _print_interactive_progress_line(msg.content, thinking) + return True + + def _is_exit_command(command: str) -> bool: """Return True when input should end interactive chat.""" return command.lower() in EXIT_COMMANDS @@ -1127,19 +1150,11 @@ def agent( turn_done.set() continue - if msg.metadata.get("_retry_wait"): - await _print_interactive_progress_line(msg.content, _thinking) - continue - - if msg.metadata.get("_progress"): - is_tool_hint = msg.metadata.get("_tool_hint", False) - ch = agent_loop.channels_config - if ch and is_tool_hint and not ch.send_tool_hints: - pass - elif ch and not is_tool_hint and not ch.send_progress: - pass - else: - await _print_interactive_progress_line(msg.content, _thinking) + if await _maybe_print_interactive_progress( + msg, + _thinking, + agent_loop.channels_config, + ): continue if not turn_done.is_set(): diff --git a/tests/cli/test_interactive_retry_wait.py b/tests/cli/test_interactive_retry_wait.py new file mode 100644 index 00000000..5cc217c5 --- /dev/null +++ b/tests/cli/test_interactive_retry_wait.py @@ -0,0 +1,31 @@ +from types import SimpleNamespace +from unittest.mock import patch + +import pytest + +from nanobot.cli import commands + + +@pytest.mark.asyncio +async def test_interactive_retry_wait_is_rendered_as_progress_even_when_progress_disabled(): + """Provider retry waits should not fall through as assistant responses.""" + calls: list[tuple[str, object | None]] = [] + thinking = None + channels_config = SimpleNamespace(send_progress=False, send_tool_hints=False) + msg = SimpleNamespace( + content="Model request failed, retry in 2s (attempt 1).", + metadata={"_retry_wait": True}, + ) + + async def fake_print(text: str, active_thinking: object | None) -> None: + calls.append((text, active_thinking)) + + with patch("nanobot.cli.commands._print_interactive_progress_line", side_effect=fake_print): + handled = await commands._maybe_print_interactive_progress( + msg, + thinking, + channels_config, + ) + + assert handled is True + assert calls == [("Model request failed, retry in 2s (attempt 1).", thinking)]