diff --git a/hermes_cli/cli_stream_mixin.py b/hermes_cli/cli_stream_mixin.py index cf8705f3fc..30b41d6fc7 100644 --- a/hermes_cli/cli_stream_mixin.py +++ b/hermes_cli/cli_stream_mixin.py @@ -285,7 +285,7 @@ class CLIStreamMixin: _cprint(line) def _close_reasoning_box(self) -> None: - """Close the live reasoning box if it's open, then flush deferred content.""" + """Close the live reasoning box if it's open (renders the buffered reasoning tail).""" from cli import _DIM, _RST, _cprint if not getattr(self, "_reasoning_box_opened", False): return @@ -298,10 +298,6 @@ class CLIStreamMixin: self._reasoning_box_opened = False if not getattr(self, "_stream_box_live", False): self._release_held_status_lines() - deferred = getattr(self, "_deferred_content", "") - if deferred: - self._deferred_content = "" - self._emit_stream_text(deferred) def _stream_delta(self, text) -> None: """Line-buffered streaming callback for real-time token rendering. @@ -531,7 +527,6 @@ class CLIStreamMixin: self._reasoning_box_opened = False self._reasoning_buf = "" self._reasoning_preview_buf = "" - self._deferred_content = "" # A batch cancelled/errored before any tool.started would otherwise mute the next turn's line. self.__dict__.pop("_tool_gen_announced", None) self._stream_table_buf = [] diff --git a/tests/hermes_cli/test_status_lines_held_during_stream_box.py b/tests/hermes_cli/test_status_lines_held_during_stream_box.py index 08c9078ce4..a9ac24c32a 100644 --- a/tests/hermes_cli/test_status_lines_held_during_stream_box.py +++ b/tests/hermes_cli/test_status_lines_held_during_stream_box.py @@ -31,6 +31,31 @@ def cli_stub(monkeypatch): return cli, emitted +def test_answer_streams_before_turn_end_after_reasoning(cli_stub): + """#47116: with show_reasoning on, the reasoning box closes on the first content token so the + answer streams mid-turn instead of being held until end of turn.""" + cli, emitted = cli_stub + cli.show_reasoning = True + cli._stream_reasoning_delta("thinking about it\n") + cli._stream_delta("First answer line.\n") + # No _flush_stream(): the line must already be on screen mid-turn. + lines = [_plain(e) for e in emitted] + answer = [i for i, l in enumerate(lines) if "First answer line." in l] + assert answer, lines + reasoning = next(i for i, l in enumerate(lines) if "thinking about it" in l) + assert reasoning < answer[0] + + +def test_late_reasoning_does_not_reopen_box_inside_answer(cli_stub): + cli, emitted = cli_stub + cli.show_reasoning = True + cli._stream_reasoning_delta("early thought\n") + cli._stream_delta("Answer.\n") + cli._stream_reasoning_delta("late thought\n") + cli._flush_stream() + assert not any("late thought" in _plain(e) for e in emitted), emitted + + def test_status_line_waits_for_box_footer(cli_stub): cli, emitted = cli_stub cli._stream_delta("First paragraph.\n") diff --git a/tests/hermes_cli/test_stream_delta_think_tag.py b/tests/hermes_cli/test_stream_delta_think_tag.py index 3ffafad9fe..911e5d4bae 100644 --- a/tests/hermes_cli/test_stream_delta_think_tag.py +++ b/tests/hermes_cli/test_stream_delta_think_tag.py @@ -23,7 +23,6 @@ def _make_cli_stub(): cli._reasoning_box_opened = False cli._reasoning_buf = "" cli._reasoning_preview_buf = "" - cli._deferred_content = "" cli._stream_text_ansi = "" cli._stream_needs_break = False cli._emitted = []