fix(cli): hold agent status lines until the streamed response box closes
Subagent completion notices ("✓ [set 7 · 2/2] … (2175.14s)"), background
process notices and spinner print_above text are emitted from worker threads
through agent._print_fn. In the interactive CLI that was bound straight to
_cprint, so a notice landing while a reply streamed was printed between two
paragraphs inside the response box.
_print_fn is now HermesCLI._agent_status_print: while a response or reasoning
box is open the line is parked in _held_status_lines and released right after
the box footer; outside a box it prints immediately as before. Gateway, ACP
and TUI printers are untouched (they never bound _cprint).
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""Agent status lines (subagent ``✓ [set n · i/N]`` completions, background-process notices) arrive on other
|
||||
threads mid-stream. The CLI's ``agent._print_fn`` must park them while a response box is open and release them
|
||||
at the footer, never between two paragraphs of the reply."""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
|
||||
def _plain(s: str) -> str:
|
||||
return re.sub(r"\x1b\[[0-9;]*m", "", s)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cli_stub(monkeypatch):
|
||||
from cli import HermesCLI
|
||||
import cli as climod
|
||||
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.show_reasoning = False
|
||||
cli.final_response_markdown = "raw"
|
||||
cli.show_timestamps = False
|
||||
cli._reset_stream_state()
|
||||
emitted = []
|
||||
monkeypatch.setattr(climod, "_cprint", lambda s: emitted.append(s))
|
||||
monkeypatch.setattr(climod, "_terminal_width_for_streaming", lambda: 74)
|
||||
monkeypatch.setattr(HermesCLI, "_scrollback_box_width", lambda self: 74)
|
||||
return cli, emitted
|
||||
|
||||
|
||||
def test_status_line_waits_for_box_footer(cli_stub):
|
||||
cli, emitted = cli_stub
|
||||
cli._stream_delta("First paragraph.\n")
|
||||
cli._agent_status_print(" ✓ [set 7 · 2/2] worker (2175.14s)")
|
||||
cli._stream_delta("Second paragraph.\n")
|
||||
cli._flush_stream()
|
||||
lines = [_plain(e) for e in emitted]
|
||||
notice = next(i for i, l in enumerate(lines) if "set 7" in l)
|
||||
footer = next(i for i, l in enumerate(lines) if l.startswith("╰"))
|
||||
second = next(i for i, l in enumerate(lines) if "Second paragraph" in l)
|
||||
assert second < footer < notice, lines
|
||||
|
||||
|
||||
def test_status_line_prints_immediately_outside_a_box(cli_stub):
|
||||
cli, emitted = cli_stub
|
||||
cli._agent_status_print(" ✓ [set 1 · 1/1] worker (3.0s)")
|
||||
assert [_plain(e) for e in emitted] == [" ✓ [set 1 · 1/1] worker (3.0s)"]
|
||||
assert not getattr(cli, "_held_status_lines", [])
|
||||
Reference in New Issue
Block a user