fix(tui): /compress shows a before/after summary (#46686)

The TUI /compress slash side-effect compressed the session, synced the
key, and emitted session.info — but returned an empty string, so the
user saw no 'Compressed: N → M messages / ~X → ~Y tokens' feedback. The
CLI (_manual_compress) and gateway (slash_commands) paths both already
call summarize_manual_compression; the TUI slash path was the lone gap.

Snapshot history + rough token estimate before and after compaction and
return the formatted summarize_manual_compression() feedback, mirroring
the session.compress RPC handler. The estimate uses the same
estimate_request_tokens_rough(system_prompt, tools) inputs as the RPC
path, re-reading the system prompt after compaction (it may be rebuilt).

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
This commit is contained in:
teknium1 2026-06-21 11:21:52 -07:00 committed by Teknium
parent 9e4fe32d36
commit d0de4601d2
2 changed files with 53 additions and 4 deletions

View file

@ -4968,7 +4968,8 @@ def test_mirror_slash_side_effects_allowed_when_idle(monkeypatch):
def test_mirror_slash_compress_does_not_prelock_history(monkeypatch):
"""Regression guard: /compress side effect must not hold history_lock
when calling _compress_session_history (the helper snapshots under
the same non-reentrant lock internally)."""
the same non-reentrant lock internally). It also returns a before/after
summary string (#46686)."""
import types
seen = {"compress": False, "sync": False}
@ -4977,7 +4978,9 @@ def test_mirror_slash_compress_does_not_prelock_history(monkeypatch):
def _fake_compress(session, focus_topic=None, **_kw):
seen["compress"] = True
assert not session["history_lock"].locked()
return (0, {"total": 0})
# Simulate a real compaction shrinking the transcript.
session["history"] = [{"role": "user", "content": "summary"}]
return (1, {"total": 0})
def _fake_sync(_sid, _session):
seen["sync"] = True
@ -4988,14 +4991,20 @@ def test_mirror_slash_compress_does_not_prelock_history(monkeypatch):
monkeypatch.setattr(server, "_emit", lambda *args: emitted.append(args))
session = _session(running=False)
session["agent"] = types.SimpleNamespace(model="x")
session["history"] = [
{"role": "user", "content": f"m{i}"} for i in range(6)
]
session["agent"] = types.SimpleNamespace(model="x", _cached_system_prompt="", tools=None)
warning = server._mirror_slash_side_effects("sid", session, "/compress")
assert warning == ""
# Now returns a before/after summary (was "" before #46686).
assert seen["compress"]
assert seen["sync"]
assert ("session.info", "sid", {"model": "x"}) in emitted
assert "Compressed:" in warning
assert "6 → 1 messages" in warning
assert "tokens" in warning
# ---------------------------------------------------------------------------

View file

@ -9806,9 +9806,49 @@ def _mirror_slash_side_effects(sid: str, session: dict, command: str) -> str:
agent.ephemeral_system_prompt = new_prompt or None
agent._cached_system_prompt = None
elif name == "compress" and agent:
# Mirror the session.compress RPC: build a before/after summary so
# the user gets feedback (#46686). The slash path previously just
# compressed + emitted session.info and returned "", so the TUI
# showed no "compressed N → M messages / ~X → ~Y tokens" stats
# while CLI and gateway both did.
from agent.manual_compression_feedback import summarize_manual_compression
from agent.model_metadata import estimate_request_tokens_rough
with session["history_lock"]:
_before_messages = list(session.get("history", []))
_before_count = len(_before_messages)
_sys_prompt = getattr(agent, "_cached_system_prompt", "") or ""
_tools = getattr(agent, "tools", None) or None
_before_tokens = (
estimate_request_tokens_rough(
_before_messages, system_prompt=_sys_prompt, tools=_tools
)
if _before_count
else 0
)
_compress_session_history(session, arg)
_sync_session_key_after_compress(sid, session)
with session["history_lock"]:
_after_messages = list(session.get("history", []))
_sys_prompt_after = getattr(agent, "_cached_system_prompt", "") or _sys_prompt
_tools_after = getattr(agent, "tools", None) or _tools
_after_tokens = (
estimate_request_tokens_rough(
_after_messages, system_prompt=_sys_prompt_after, tools=_tools_after
)
if _after_messages
else 0
)
_emit("session.info", sid, _session_info(agent, session))
_fb = summarize_manual_compression(
_before_messages, _after_messages, _before_tokens, _after_tokens
)
_lines = [_fb["headline"], _fb["token_line"]]
if _fb.get("note"):
_lines.append(_fb["note"])
return "\n".join(_lines)
elif name == "fast" and agent:
mode = arg.lower()
if mode in {"fast", "on"}: