diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index 432e028467a..b9729924104 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -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 # --------------------------------------------------------------------------- diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 35edf8ab12a..e822855db37 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -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"}: