fix(cli): announce "preparing <tool>…" once per tool per batch
The tool-gen callback fires once per tool CALL, so a model issuing three parallel terminal calls printed three identical status lines before the spinner took over (#10478). Coalesce repeats of the same tool name until a tool actually starts (tool.started), which marks the next generation batch. The line itself stays: it is the only feedback while a large argument payload (e.g. a 45 KB write_file) streams, before the spinner exists. PR #10598 removed it entirely; this keeps the first line and drops only the duplicates. Live A/B (in-process HTTP mock streaming 3 parallel terminal calls into a real HermesCLI with display.streaming on): before 3 lines, after 1. Fixes #10478
This commit is contained in:
@@ -621,12 +621,20 @@ class CLIStreamMixin:
|
||||
|
||||
def _on_tool_gen_start(self, tool_name: str) -> None:
|
||||
"""Model began generating tool-call arguments: close open boxes once, then print a status
|
||||
line so a large payload (e.g. 45 KB write_file) doesn't look like a frozen screen."""
|
||||
line so a large payload (e.g. 45 KB write_file) doesn't look like a frozen screen.
|
||||
|
||||
Fires once per tool CALL, so a batch of parallel calls to the same tool printed the same
|
||||
line N times (#10478); repeats within one generation batch are coalesced. The set is
|
||||
cleared when a tool actually starts (``tool.started``), i.e. on the next batch."""
|
||||
from cli import _cprint
|
||||
if getattr(self, "_stream_box_opened", False):
|
||||
if getattr(self, '_stream_box_opened', False):
|
||||
self._flush_stream()
|
||||
self._stream_box_opened = False
|
||||
self._close_reasoning_box()
|
||||
announced = self.__dict__.setdefault("_tool_gen_announced", set())
|
||||
if tool_name in announced:
|
||||
return
|
||||
announced.add(tool_name)
|
||||
from agent.display import get_tool_emoji
|
||||
_cprint(f" ┊ {get_tool_emoji(tool_name, default='⚡')} preparing {tool_name}…")
|
||||
|
||||
@@ -666,6 +674,7 @@ class CLIStreamMixin:
|
||||
# Feed the pet: tools mean "running"; a failed tool latches the turn to end on a sulk.
|
||||
if event_type == "tool.started":
|
||||
self._pet_reasoning = False
|
||||
self.__dict__.pop("_tool_gen_announced", None)
|
||||
elif event_type == "tool.completed" and kwargs.get("is_error"):
|
||||
self._pet_turn_error = True
|
||||
elif event_type and event_type.startswith("reasoning"):
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"""A batch of parallel tool calls announces "preparing <tool>…" once per tool, not once per call (#10478)."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from tests.cli.test_tool_progress_scrollback import _make_cli
|
||||
import tests.cli.test_tool_progress_scrollback as _scrollback
|
||||
|
||||
|
||||
def _announce(cli, names):
|
||||
printed = []
|
||||
with patch.object(_scrollback._cli_mod, "_cprint", lambda line: printed.append(line)):
|
||||
for n in names:
|
||||
cli._on_tool_gen_start(n)
|
||||
return printed
|
||||
|
||||
|
||||
def test_repeated_tool_in_one_batch_prints_once():
|
||||
cli = _make_cli(tool_progress="off")
|
||||
printed = _announce(cli, ["terminal", "terminal", "terminal", "read_file"])
|
||||
assert sum("preparing terminal" in p for p in printed) == 1
|
||||
assert sum("preparing read_file" in p for p in printed) == 1
|
||||
# A tool actually starting closes the batch; the next generation announces again.
|
||||
with patch.object(_scrollback._cli_mod, "_cprint", lambda line: None):
|
||||
cli._on_tool_progress("tool.started", "terminal", "ls", {"command": "ls"})
|
||||
assert sum("preparing terminal" in p for p in _announce(cli, ["terminal"])) == 1
|
||||
Reference in New Issue
Block a user