fix(cli): stop spinner before printing tool progress lines
The Rich console.status() spinner ('nanobot is thinking...') was not
cleared when tool call progress lines were printed during processing,
causing overlapping/garbled terminal output.
Replace the context-manager approach with explicit start/stop lifecycle:
- _pause_spinner() stops the spinner before any progress line is printed
- _resume_spinner() restarts it after printing
- Applied to both single-message mode (_cli_progress) and interactive
mode (_consume_outbound)
Closes #1956
This commit is contained in:
@@ -635,26 +635,56 @@ def agent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Show spinner when logs are off (no output to miss); skip when logs are on
|
# Show spinner when logs are off (no output to miss); skip when logs are on
|
||||||
def _thinking_ctx():
|
def _make_spinner():
|
||||||
if logs:
|
if logs:
|
||||||
from contextlib import nullcontext
|
return None
|
||||||
return nullcontext()
|
|
||||||
# Animated spinner is safe to use with prompt_toolkit input handling
|
|
||||||
return console.status("[dim]nanobot is thinking...[/dim]", spinner="dots")
|
return console.status("[dim]nanobot is thinking...[/dim]", spinner="dots")
|
||||||
|
|
||||||
|
# Shared reference so progress callbacks can pause/resume the spinner
|
||||||
|
_active_spinner = None
|
||||||
|
|
||||||
|
def _pause_spinner() -> None:
|
||||||
|
"""Temporarily stop the spinner before printing progress."""
|
||||||
|
if _active_spinner is not None:
|
||||||
|
try:
|
||||||
|
_active_spinner.stop()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _resume_spinner() -> None:
|
||||||
|
"""Restart the spinner after printing progress."""
|
||||||
|
if _active_spinner is not None:
|
||||||
|
try:
|
||||||
|
_active_spinner.start()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
async def _cli_progress(content: str, *, tool_hint: bool = False) -> None:
|
async def _cli_progress(content: str, *, tool_hint: bool = False) -> None:
|
||||||
ch = agent_loop.channels_config
|
ch = agent_loop.channels_config
|
||||||
if ch and tool_hint and not ch.send_tool_hints:
|
if ch and tool_hint and not ch.send_tool_hints:
|
||||||
return
|
return
|
||||||
if ch and not tool_hint and not ch.send_progress:
|
if ch and not tool_hint and not ch.send_progress:
|
||||||
return
|
return
|
||||||
console.print(f" [dim]↳ {content}[/dim]")
|
_pause_spinner()
|
||||||
|
try:
|
||||||
|
console.print(f" [dim]↳ {content}[/dim]")
|
||||||
|
finally:
|
||||||
|
_resume_spinner()
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
# Single message mode — direct call, no bus needed
|
# Single message mode — direct call, no bus needed
|
||||||
async def run_once():
|
async def run_once():
|
||||||
with _thinking_ctx():
|
nonlocal _active_spinner
|
||||||
|
spinner = _make_spinner()
|
||||||
|
_active_spinner = spinner
|
||||||
|
if spinner:
|
||||||
|
spinner.start()
|
||||||
|
try:
|
||||||
response = await agent_loop.process_direct(message, session_id, on_progress=_cli_progress)
|
response = await agent_loop.process_direct(message, session_id, on_progress=_cli_progress)
|
||||||
|
finally:
|
||||||
|
if spinner:
|
||||||
|
spinner.stop()
|
||||||
|
_active_spinner = None
|
||||||
_print_agent_response(response, render_markdown=markdown)
|
_print_agent_response(response, render_markdown=markdown)
|
||||||
await agent_loop.close_mcp()
|
await agent_loop.close_mcp()
|
||||||
|
|
||||||
@@ -704,7 +734,11 @@ def agent(
|
|||||||
elif ch and not is_tool_hint and not ch.send_progress:
|
elif ch and not is_tool_hint and not ch.send_progress:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
await _print_interactive_line(msg.content)
|
_pause_spinner()
|
||||||
|
try:
|
||||||
|
await _print_interactive_line(msg.content)
|
||||||
|
finally:
|
||||||
|
_resume_spinner()
|
||||||
|
|
||||||
elif not turn_done.is_set():
|
elif not turn_done.is_set():
|
||||||
if msg.content:
|
if msg.content:
|
||||||
@@ -744,8 +778,17 @@ def agent(
|
|||||||
content=user_input,
|
content=user_input,
|
||||||
))
|
))
|
||||||
|
|
||||||
with _thinking_ctx():
|
nonlocal _active_spinner
|
||||||
|
spinner = _make_spinner()
|
||||||
|
_active_spinner = spinner
|
||||||
|
if spinner:
|
||||||
|
spinner.start()
|
||||||
|
try:
|
||||||
await turn_done.wait()
|
await turn_done.wait()
|
||||||
|
finally:
|
||||||
|
if spinner:
|
||||||
|
spinner.stop()
|
||||||
|
_active_spinner = None
|
||||||
|
|
||||||
if turn_response:
|
if turn_response:
|
||||||
_print_agent_response(turn_response[0], render_markdown=markdown)
|
_print_agent_response(turn_response[0], render_markdown=markdown)
|
||||||
|
|||||||
Reference in New Issue
Block a user