mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-21 07:00:23 +00:00
* 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:
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user