fix(sidebar): treat TUI sessions like CLI for discoverability (#4213) (#4237)

* Fix TUI session sidebar discoverability

* chore(release): stamp v0.51.432 (Release OS) for #4213

---------

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
This commit is contained in:
nesquena-hermes
2026-06-15 00:48:56 -07:00
committed by GitHub
parent 4c5e0af4d2
commit f927fc3a42
5 changed files with 109 additions and 7 deletions
+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.432] — 2026-06-15 — Release OS (TUI sessions discoverable in the sidebar)
### Fixed
- **TUI sessions now show up in the sidebar like CLI sessions do.** The session source-classification only recognized `cli` (and friends), so `tui`-sourced sessions weren't tagged as CLI-origin and could be filtered out or mislabeled. `tui` is now treated alongside `cli` everywhere the source is classified, and a TUI continuation tip keeps the navigation pointed at the latest tip with its visible TUI title (rather than an opaque compression-snapshot title), so the newest TUI conversation is findable by name. (#4213)
## [v0.51.431] — 2026-06-15 — Release OR (make request logging non-fatal)
### Fixed
+30 -6
View File
@@ -31,6 +31,7 @@ SOURCE_LABELS = {
'slack': 'Slack',
'telegram': 'Telegram',
'tool': 'Tool',
'tui': 'TUI',
'webui': 'WebUI',
'weixin': 'Weixin',
}
@@ -47,7 +48,7 @@ def normalize_agent_session_source(raw_source: str | None) -> dict:
if raw == 'webui':
session_source = 'webui'
elif raw == 'cli':
elif raw in {'cli', 'tui'}:
session_source = 'cli'
elif raw in MESSAGING_SOURCES:
session_source = 'messaging'
@@ -176,7 +177,12 @@ def is_cli_session_row(row: dict) -> bool:
return False
if source == "cli":
return True
if source_tag == "cli" or raw_source == "cli" or source_name == "cli" or source_label == "cli":
if (
source_tag in {"cli", "tui"}
or raw_source in {"cli", "tui"}
or source_name in {"cli", "tui"}
or source_label in {"cli", "tui"}
):
return True
# Legacy imported CLI rows may only be marked as CLI in sidebar metadata.
@@ -202,6 +208,14 @@ def is_cli_session_row_visible(row: dict) -> bool:
if message_count <= 0:
return False
if "tui" in {
_normalize_source_name(row.get("source")),
_normalize_source_name(row.get("source_tag")),
_normalize_source_name(row.get("raw_source")),
_normalize_source_name(row.get("source_label")),
}:
return True
if _has_cli_lineage(row):
return True
@@ -375,10 +389,20 @@ def _project_agent_session_rows(rows: list[dict]) -> list[dict]:
):
if key in tip:
merged[key] = tip[key]
if not merged.get('title'):
merged['title'] = tip.get('title')
if not merged.get('source'):
merged['source'] = tip.get('source')
if str(tip.get('source') or '').strip().lower() == 'tui':
# TUI continuation rows are user-visible session segments (#6, #17,
# ...), not opaque compression snapshots. Keep navigation pointed at
# the latest tip and show that tip's title so the newest conversation
# can be found by its visible TUI name.
if tip.get('title'):
merged['title'] = tip.get('title')
if tip.get('source'):
merged['source'] = tip.get('source')
else:
if not merged.get('title'):
merged['title'] = tip.get('title')
if not merged.get('source'):
merged['source'] = tip.get('source')
merged['_lineage_root_id'] = row['id']
merged['_lineage_tip_id'] = tip['id']
merged['_compression_segment_count'] = segment_count
+1 -1
View File
@@ -1418,7 +1418,7 @@ function _isCliSession(session) {
|| session.source_label
|| ''
).toLowerCase();
if (raw === 'cli') return true;
if (raw === 'cli' || raw === 'tui') return true;
// If messaging-like, don't classify as legacy CLI even when is_cli_session is true.
if (_isMessagingSession(session)) return false;
return session.is_cli_session === true;
@@ -71,6 +71,7 @@ class TestSidebarCancelAction:
assert "session.source" in body
assert "session.source_label" in body
assert "if (_isMessagingSession(session)) return false;" in body
assert "raw === 'tui'" in body
assert "return session.is_cli_session === true;" in body
def test_cli_sessions_hide_duplicate_and_delete_in_action_menu(self):
@@ -125,3 +125,74 @@ def test_real_cli_sidebar_cli_flag_is_preserved_before_frontend_response():
)
assert normalized["is_cli_session"] is True
def test_tui_state_db_rows_are_cli_sidebar_rows():
"""Hermes TUI state.db rows belong in the CLI/agent sidebar bucket.
TUI sessions are projected from state.db with raw/source_tag='tui'. If they
stay session_source='other' and is_cli_session=false, the two-tab sidebar
partition can make active TUI continuations disappear from both the WebUI
and CLI views.
"""
from api.agent_sessions import is_cli_session_row, normalize_agent_session_source
from api.routes import _normalize_sidebar_source_flags
normalized_source = normalize_agent_session_source("tui")
assert normalized_source["session_source"] == "cli"
assert normalized_source["source_label"] == "TUI"
tui_row = {
"session_id": "tui-tip",
"title": "Podcast work #17",
"source_tag": "tui",
"raw_source": "tui",
"session_source": "other",
"source_label": "Tui",
"message_count": 281,
}
assert is_cli_session_row(tui_row) is True
assert _normalize_sidebar_source_flags(tui_row)["is_cli_session"] is True
def test_tui_continuation_projection_uses_latest_tip_title():
"""TUI continuation rows should surface under the latest segment title."""
from api.agent_sessions import _project_agent_session_rows
rows = [
{
"id": "tui_parent",
"source": "tui",
"title": "Podcast work #6",
"started_at": 100.0,
"last_activity": 150.0,
"message_count": 10,
"actual_message_count": 10,
"actual_user_message_count": 5,
"parent_session_id": None,
"ended_at": 199.0,
"end_reason": "cli_close",
},
{
"id": "tui_tip",
"source": "tui",
"title": "Podcast work #17",
"started_at": 200.0,
"last_activity": 250.0,
"message_count": 8,
"actual_message_count": 8,
"actual_user_message_count": 4,
"parent_session_id": "tui_parent",
"ended_at": None,
"end_reason": None,
},
]
projected = _project_agent_session_rows(rows)
assert len(projected) == 1
assert projected[0]["id"] == "tui_tip"
assert projected[0]["title"] == "Podcast work #17"
assert projected[0]["_lineage_root_id"] == "tui_parent"
assert projected[0]["_lineage_tip_id"] == "tui_tip"