diff --git a/langstage_cli/cli.py b/langstage_cli/cli.py index a43b1d5..2a58ee8 100644 --- a/langstage_cli/cli.py +++ b/langstage_cli/cli.py @@ -253,6 +253,8 @@ def start(self): def stop(self): """Stop the spinner and clear the line.""" + if not self.running: + return self.running = False if self.thread: self.thread.join(timeout=0.2) diff --git a/tests/test_turn_exit_and_render.py b/tests/test_turn_exit_and_render.py index d61de2b..eb7c476 100644 --- a/tests/test_turn_exit_and_render.py +++ b/tests/test_turn_exit_and_render.py @@ -18,7 +18,7 @@ pytest.importorskip("fastapi") from langstage_cli.agui_stream import build_session_agent # noqa: E402 -from langstage_cli.cli import print_chunk, run_single_turn_agui # noqa: E402 +from langstage_cli.cli import Spinner, print_chunk, run_single_turn_agui # noqa: E402 async def test_turn_reports_error_frame_for_nonzero_exit(monkeypatch): @@ -46,6 +46,23 @@ async def test_successful_turn_reports_no_error(): assert had_error is False +async def test_turn_does_not_clear_output_after_first_chunk(monkeypatch): + async def fake_stream(agent, message, thread_id, resume=None): + yield {"status": "streaming", "chunk": "visible reply", "node": "agent"} + + import langstage_cli.agui_stream as agui_mod + + monkeypatch.setattr(agui_mod, "agui_stream_updates", fake_stream) + monkeypatch.setattr(Spinner, "_spin", lambda self: None) + buf = io.StringIO() + with redirect_stdout(buf): + await run_single_turn_agui(object(), "hi", "t-visible", interactive=False) + + out = buf.getvalue() + assert out.count("\r\033[2K") == 1 + assert out.endswith("visible reply") + + def test_print_chunk_breaks_on_node_change_nonverbose(): # gh #43: two nodes' output must not render as one run-on line. print_chunk._streaming_text = False