fix(display): rate-limit spinner flushes to prevent line spam under patch_stdout
The KawaiiSpinner animation would occasionally spam dozens of duplicate lines instead of overwriting in-place with \r. This happened because prompt_toolkit's StdoutProxy processes each flush() as a separate run_in_terminal() call — when the write thread is slow (busy event loop during long tool executions), each \r frame gets its own call, and the terminal layout save/restore between calls breaks the \r overwrite semantics. Fix: rate-limit flush() calls to at most every 0.4s. Between flushes, \r-frame writes accumulate in StdoutProxy's buffer. When flushed, they concatenate into one string (e.g. \r frame1 \r frame2 \r frame3) and are written in a single run_in_terminal() call where \r works correctly. The spinner still animates (flush ~2.5x/sec) but each flush batches ~3 frames, guaranteeing the \r collapse always works. Most visible with execute_code and terminal tools (3+ second executions).
This commit is contained in:
@@ -206,6 +206,7 @@ class KawaiiSpinner:
|
||||
self.frame_idx = 0
|
||||
self.start_time = None
|
||||
self.last_line_len = 0
|
||||
self._last_flush_time = 0.0 # Rate-limit flushes for patch_stdout compat
|
||||
# Capture stdout NOW, before any redirect_stdout(devnull) from
|
||||
# child agents can replace sys.stdout with a black hole.
|
||||
self._out = sys.stdout
|
||||
@@ -236,7 +237,18 @@ class KawaiiSpinner:
|
||||
else:
|
||||
line = f" {frame} {self.message} ({elapsed:.1f}s)"
|
||||
pad = max(self.last_line_len - len(line), 0)
|
||||
self._write(f"\r{line}{' ' * pad}", end='', flush=True)
|
||||
# Rate-limit flush() calls to avoid spinner spam under
|
||||
# prompt_toolkit's patch_stdout. Each flush() pushes a queue
|
||||
# item that may trigger a separate run_in_terminal() call; if
|
||||
# items are processed one-at-a-time the \r overwrite is lost
|
||||
# and every frame appears on its own line. By flushing at
|
||||
# most every 0.4s we guarantee multiple \r-frames are batched
|
||||
# into a single write, so the terminal collapses them correctly.
|
||||
now = time.time()
|
||||
should_flush = (now - self._last_flush_time) >= 0.4
|
||||
self._write(f"\r{line}{' ' * pad}", end='', flush=should_flush)
|
||||
if should_flush:
|
||||
self._last_flush_time = now
|
||||
self.last_line_len = len(line)
|
||||
self.frame_idx += 1
|
||||
time.sleep(0.12)
|
||||
|
||||
Reference in New Issue
Block a user