{"repo":"NousResearch/hermes-agent","public_id":"46686","display_label":"TUI /compress command missing before/after summary","summary":"These issues and fixes address the TUI not displaying a before/after compression summary after running /compress. Changes aim to bring TUI behavior to parity with CLI feedback.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":50303,"merged_at":"2026-06-21T18:36:09+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-06-21T18:36:09+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":46686,"pr_number":50303,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-21T18:36:09+00:00"}],"members":[{"kind":"issue","number":46686,"title":"TUI/Desktop: /compress doesn't show the before/after summary","url":null,"state":"closed"},{"kind":"pr","number":46702,"title":"fix(tui): show compression summary in _mirror_slash_side_effects","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#50303","number":50303}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":50303,"title":"fix(tui): /compress shows a before/after summary (#46686)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/test_tui_gateway_server.py","tui_gateway/server.py"],"fix_hunks":[{"file":"tests/test_tui_gateway_server.py","hunks":["@@ -4968,7 +4968,8 @@ def _fake_apply_model(sid, session, arg):\n def test_mirror_slash_compress_does_not_prelock_history(monkeypatch):\n     \"\"\"Regression guard: /compress side effect must not hold history_lock\n     when calling _compress_session_history (the helper snapshots under\n-    the same non-reentrant lock internally).\"\"\"\n+    the same non-reentrant lock internally). It also returns a before/after\n+    summary string (#46686).\"\"\"\n     import types\n \n     seen = {\"compress\": False, \"sync\": False}","@@ -4977,7 +4978,9 @@ def test_mirror_slash_compress_does_not_prelock_history(monkeypatch):\n     def _fake_compress(session, focus_topic=None, **_kw):\n         seen[\"compress\"] = True\n         assert not session[\"history_lock\"].locked()\n-        return (0, {\"total\": 0})\n+        # Simulate a real compaction shrinking the transcript.\n+        session[\"history\"] = [{\"role\": \"user\", \"content\": \"summary\"}]\n+        return (1, {\"total\": 0})\n \n     def _fake_sync(_sid, _session):\n         seen[\"sync\"] = True","@@ -4988,14 +4991,20 @@ def _fake_sync(_sid, _session):\n     monkeypatch.setattr(server, \"_emit\", lambda *args: emitted.append(args))\n \n     session = _session(running=False)\n-    session[\"agent\"] = types.SimpleNamespace(model=\"x\")\n+    session[\"history\"] = [\n+        {\"role\": \"user\", \"content\": f\"m{i}\"} for i in range(6)\n+    ]\n+    session[\"agent\"] = types.SimpleNamespace(model=\"x\", _cached_system_prompt=\"\", tools=None)\n \n     warning = server._mirror_slash_side_effects(\"sid\", session, \"/compress\")\n \n-    assert warning == \"\"\n+    # Now returns a before/after summary (was \"\" before #46686).\n     assert seen[\"compress\"]\n     assert seen[\"sync\"]\n     assert (\"session.info\", \"sid\", {\"model\": \"x\"}) in emitted\n+    assert \"Compressed:\" in warning\n+    assert \"6 → 1 messages\" in warning\n+    assert \"tokens\" in warning\n \n \n # ---------------------------------------------------------------------------"]},{"file":"tui_gateway/server.py","hunks":["@@ -9806,9 +9806,49 @@ def _mirror_slash_side_effects(sid: str, session: dict, command: str) -> str:\n             agent.ephemeral_system_prompt = new_prompt or None\n             agent._cached_system_prompt = None\n         elif name == \"compress\" and agent:\n+            # Mirror the session.compress RPC: build a before/after summary so\n+            # the user gets feedback (#46686). The slash path previously just\n+            # compressed + emitted session.info and returned \"\", so the TUI\n+            # showed no \"compressed N → M messages / ~X → ~Y tokens\" stats\n+            # while CLI and gateway both did.\n+            from agent.manual_compression_feedback import summarize_manual_compression\n+            from agent.model_metadata import estimate_request_tokens_rough\n+\n+            with session[\"history_lock\"]:\n+                _before_messages = list(session.get(\"history\", []))\n+            _before_count = len(_before_messages)\n+            _sys_prompt = getattr(agent, \"_cached_system_prompt\", \"\") or \"\"\n+            _tools = getattr(agent, \"tools\", None) or None\n+            _before_tokens = (\n+                estimate_request_tokens_rough(\n+                    _before_messages, system_prompt=_sys_prompt, tools=_tools\n+                )\n+                if _before_count\n+                else 0\n+            )\n+\n             _compress_session_history(session, arg)\n             _sync_session_key_after_compress(sid, session)\n+\n+            with session[\"history_lock\"]:\n+                _after_messages = list(session.get(\"history\", []))\n+            _sys_prompt_after = getattr(agent, \"_cached_system_prompt\", \"\") or _sys_prompt\n+            _tools_after = getattr(agent, \"tools\", None) or _tools\n+            _after_tokens = (\n+                estimate_request_tokens_rough(\n+                    _after_messages, system_prompt=_sys_prompt_after, tools=_tools_after\n+                )\n+                if _after_messages\n+                else 0\n+            )\n             _emit(\"session.info\", sid, _session_info(agent, session))\n+            _fb = summarize_manual_compression(\n+                _before_messages, _after_messages, _before_tokens, _after_tokens\n+            )\n+            _lines = [_fb[\"headline\"], _fb[\"token_line\"]]\n+            if _fb.get(\"note\"):\n+                _lines.append(_fb[\"note\"])\n+            return \"\\n\".join(_lines)\n         elif name == \"fast\" and agent:\n             mode = arg.lower()\n             if mode in {\"fast\", \"on\"}:\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}