From 836ac8d6f8e7d516fb238bd9c4f567021d344afe Mon Sep 17 00:00:00 2001 From: worldmozara Date: Tue, 22 Sep 2026 21:15:07 +0800 Subject: [PATCH] cli/history: never collapse a turn's ends The mixed policy previewed the user row, so a long prompt came back truncated even though it is half of what the turn is about. Both ends - the human's message and the model's answer - now print in full at every verbosity level; --preview remains the explicit one-line skimmer that collapses them too. Tests cover the ends staying full in the default and -v views (and the rounds between them staying one-liners) plus a long-prompt regression. --- src/plyngent/cli/slash.py | 5 +++-- src/plyngent/cli/transcript.py | 14 +++++++++++--- tests/test_cli/test_history.py | 29 +++++++++++++++++++++++------ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/plyngent/cli/slash.py b/src/plyngent/cli/slash.py index 51c1419..b608085 100644 --- a/src/plyngent/cli/slash.py +++ b/src/plyngent/cli/slash.py @@ -1440,8 +1440,9 @@ def history_cmd( ``/history N`` — turn N (numbered from the first turn of this session) ``/history last [N]`` — the last N turns (default 1) ``/history --message N`` — one message (display number, not a DB row) - ``-v`` adds every intermediate row (tool calls/results, notices); ``-vv`` - prints full bodies for all of them; ``--preview`` collapses to one line each. + The turn's ends are printed in full; ``-v`` adds every intermediate row (tool + calls/results, notices) as a one-liner, ``-vv`` prints those in full too, and + ``--preview`` collapses every row (ends included) to one line. """ from plyngent.cli import transcript as transcript_mod diff --git a/src/plyngent/cli/transcript.py b/src/plyngent/cli/transcript.py index 78cfc94..be6acdc 100644 --- a/src/plyngent/cli/transcript.py +++ b/src/plyngent/cli/transcript.py @@ -39,8 +39,9 @@ PREVIEW_CHARS = 200 # ``-v`` expands a turn's rows; ``-vv`` also prints their full bodies. MAX_VERBOSE = 2 -# Detail policy: ``mixed`` = a turn's final assistant answer in full, everything -# else on one line; ``full``/``preview`` force the extremes. +# Detail policy: ``mixed`` = a turn's two ends (the user message and the final +# answer) in full, the rounds between them on one line; ``full``/``preview`` +# force the extremes (``--preview`` also collapses the ends). type Detail = Literal["mixed", "full", "preview"] # ``/history N`` addresses a turn by number; ``last`` counts from the end. type TurnTarget = int | Literal["last"] @@ -281,9 +282,16 @@ def echo_turn(turn: Turn, *, verbose: bool, detail: Detail) -> None: def _row_detail(row: Row, *, turn: Turn, detail: Detail) -> Detail: - """Effective detail for *row*: mixed prints the turn's answer in full.""" + """Effective detail for *row*. + + Under ``mixed`` the two ends of a turn — the human's message and the model's + answer — are the rows a reader actually wants, so they are never collapsed; + the rounds between them stay one-liners. + """ if detail != "mixed": return detail + if row is turn.user: + return "full" return "full" if row is turn.last and isinstance(row.message, AssistantChatMessage) else "preview" diff --git a/tests/test_cli/test_history.py b/tests/test_cli/test_history.py index 9563e17..68aeb79 100644 --- a/tests/test_cli/test_history.py +++ b/tests/test_cli/test_history.py @@ -96,8 +96,8 @@ async def test_default_shows_the_last_turns_two_ends( out = capsys.readouterr().out assert "turns=2 messages=11 showing=last 1 turn(s) mode=mixed" in out assert "turn 2 (msgs 8-11, rounds=1)" in out - assert "8. user: now run the tests" in out - # The answer is printed in full; the round's tool rows are not. + # Both ends are printed in full, not as one-line previews. + assert "8. user:\nnow run the tests" in out assert "11. assistant:" in out assert "Tests pass: **12 passed** in 3.2s." in out assert "tool(c3)" not in out @@ -112,13 +112,16 @@ async def test_verbose_expands_every_row_of_the_turn( assert await handle_slash(state, "/history -v 1") is True out = capsys.readouterr().out assert "turn 1 (msgs 1-7, rounds=2)" in out + # The ends stay full (the human's own words, the model's answer) … + assert "1. user:\nread src/a.py and fix the typo" in out + assert "7. assistant:" in out + assert "- line 12 changed" in out + # … while the rounds in between are one-liners. assert "2. assistant: tool_calls=[read_file]" in out assert "3. tool(c1): L1-30 (+26 lines)" in out assert "4. developer: [DIRECTIVE CHECKPOINT band=1" in out assert "6. tool(c2): replaced 1 occurrence" in out - # The turn's final answer stays full even though the row detail is mixed. - assert "7. assistant:" in out - assert "- line 12 changed" in out + assert "print('bye')" not in out async def test_double_verbose_prints_full_bodies( @@ -146,7 +149,21 @@ async def test_verbose_keeps_local_rows_out_of_the_way( out = capsys.readouterr().out assert "local. system:" not in out # Numbering is over conversation rows only, so the user row is #1. - assert "1. user: read src/a.py and fix the typo" in out + assert "1. user:\nread src/a.py and fix the typo" in out + + +async def test_long_user_message_is_not_collapsed( + state: ReplState, + capsys: pytest.CaptureFixture[str], +) -> None: + """Regression: both ends of a turn print in full, however long they are.""" + body = "please review this: " + "x" * 400 + state.agent.messages = [UserChatMessage(content=body), AssistantChatMessage(content="ok")] + assert await handle_slash(state, "/history") is True + out = capsys.readouterr().out + assert body in out + assert "1. user:\n" in out + assert "…" not in out.split("2. assistant:")[0] async def test_preview_collapses_every_row(