mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-06-07 17:30:21 +00:00
fix: keep webui mirrored sessions out of cli filter
This commit is contained in:
+15
-4
@@ -142,14 +142,16 @@ def is_cli_session_row(row: dict) -> bool:
|
||||
if not isinstance(row, dict):
|
||||
return False
|
||||
source = _safe_lower(row.get("session_source"))
|
||||
if source == "messaging":
|
||||
return False
|
||||
if source == "cli":
|
||||
return True
|
||||
source_tag = _safe_lower(row.get("source_tag"))
|
||||
raw_source = _safe_lower(row.get("raw_source"))
|
||||
source_name = _safe_lower(row.get("source"))
|
||||
source_label = _safe_lower(row.get("source_label"))
|
||||
if "webui" in {source, source_tag, raw_source, source_name, source_label}:
|
||||
return False
|
||||
if source == "messaging":
|
||||
return False
|
||||
if source == "cli":
|
||||
return True
|
||||
if source_tag == "cli" or raw_source == "cli" or source_name == "cli" or source_label == "cli":
|
||||
return True
|
||||
|
||||
@@ -727,6 +729,15 @@ def read_session_lineage_metadata(db_path: Path, session_ids: list[str] | set[st
|
||||
state_title = str(row.get('title') or '').strip()
|
||||
if state_title:
|
||||
metadata.setdefault(sid, {})['_state_db_title'] = state_title
|
||||
state_source = str(row.get('source') or '').strip().lower()
|
||||
if state_source:
|
||||
entry = metadata.setdefault(sid, {})
|
||||
entry['_state_db_source'] = state_source
|
||||
source_meta = normalize_agent_session_source(state_source)
|
||||
entry['_state_db_source_tag'] = state_source
|
||||
entry['_state_db_raw_source'] = source_meta.get('raw_source')
|
||||
entry['_state_db_session_source'] = source_meta.get('session_source')
|
||||
entry['_state_db_source_label'] = source_meta.get('source_label')
|
||||
|
||||
parent_id = row.get('parent_session_id')
|
||||
parent_row = rows.get(parent_id) if parent_id else None
|
||||
|
||||
@@ -2295,7 +2295,18 @@ def _enrich_sidebar_lineage_metadata(sessions: list[dict]) -> None:
|
||||
if sid in metadata:
|
||||
entry = dict(metadata[sid])
|
||||
state_db_title = entry.pop('_state_db_title', None)
|
||||
state_db_source = entry.pop('_state_db_source', None)
|
||||
state_db_source_tag = entry.pop('_state_db_source_tag', None)
|
||||
state_db_raw_source = entry.pop('_state_db_raw_source', None)
|
||||
state_db_session_source = entry.pop('_state_db_session_source', None)
|
||||
state_db_source_label = entry.pop('_state_db_source_label', None)
|
||||
session.update(entry)
|
||||
if state_db_source == 'webui':
|
||||
session['source_tag'] = state_db_source_tag
|
||||
session['raw_source'] = state_db_raw_source
|
||||
session['session_source'] = state_db_session_source
|
||||
session['source_label'] = state_db_source_label
|
||||
session['is_cli_session'] = False
|
||||
title = session.get('title')
|
||||
if (
|
||||
state_db_title
|
||||
|
||||
+16
-1
@@ -2385,6 +2385,15 @@ def _is_cli_session_for_settings(session: dict) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _normalize_sidebar_source_flags(session: dict) -> dict:
|
||||
"""Return a sidebar row with the frontend CLI flag matching source metadata."""
|
||||
if not isinstance(session, dict):
|
||||
return session
|
||||
normalized = dict(session)
|
||||
normalized["is_cli_session"] = is_cli_session_row(normalized)
|
||||
return normalized
|
||||
|
||||
|
||||
CLI_VISIBLE_SESSION_CAP = 20
|
||||
|
||||
|
||||
@@ -2414,7 +2423,11 @@ def _merge_cli_sidebar_metadata(ui_session: dict, cli_meta: dict) -> dict:
|
||||
if not cli_meta:
|
||||
return dict(ui_session)
|
||||
merged = dict(ui_session)
|
||||
merged["is_cli_session"] = True
|
||||
# Only preserve the CLI flag when the imported metadata is actually a CLI
|
||||
# row. WebUI sessions are also mirrored into state.db; treating every
|
||||
# matching state row as CLI hides long WebUI continuations from the default
|
||||
# sidebar source tab.
|
||||
merged["is_cli_session"] = is_cli_session_row(cli_meta)
|
||||
for key in (
|
||||
"source_tag",
|
||||
"raw_source",
|
||||
@@ -4415,6 +4428,7 @@ def handle_get(handler, parsed) -> bool:
|
||||
diag.stage("load_settings")
|
||||
settings = load_settings()
|
||||
show_cli_sessions = bool(settings.get("show_cli_sessions"))
|
||||
webui_sessions = [_normalize_sidebar_source_flags(s) for s in webui_sessions]
|
||||
if show_cli_sessions:
|
||||
diag.stage("get_cli_sessions")
|
||||
cli = get_cli_sessions()
|
||||
@@ -4432,6 +4446,7 @@ def handle_get(handler, parsed) -> bool:
|
||||
for key in ("source_tag", "raw_source", "session_source", "source_label"):
|
||||
if not s.get(key) and meta.get(key):
|
||||
s[key] = meta[key]
|
||||
webui_sessions = [_normalize_sidebar_source_flags(s) for s in webui_sessions]
|
||||
# Apply the same CLI visibility semantics to imported local copies so
|
||||
# low-value imported artifacts do not leak into the sidebar.
|
||||
webui_sessions = [s for s in webui_sessions if is_cli_session_row_visible(s)]
|
||||
|
||||
@@ -29,3 +29,94 @@ def test_session_source_tabs_have_dedicated_sidebar_styles():
|
||||
assert ".session-source-tabs" in css
|
||||
assert ".session-source-tab.active" in css
|
||||
assert ".session-empty-note" in css
|
||||
|
||||
|
||||
def test_webui_state_db_mirror_does_not_become_cli_sidebar_row():
|
||||
from api.routes import _merge_cli_sidebar_metadata
|
||||
|
||||
merged = _merge_cli_sidebar_metadata(
|
||||
{"session_id": "webui-tip", "title": "Long WebUI session", "source_tag": "webui"},
|
||||
{
|
||||
"session_id": "webui-tip",
|
||||
"source_tag": "webui",
|
||||
"session_source": "webui",
|
||||
"message_count": 1740,
|
||||
},
|
||||
)
|
||||
|
||||
assert merged["is_cli_session"] is False
|
||||
assert merged["source_tag"] == "webui"
|
||||
assert merged["session_source"] == "webui"
|
||||
assert merged["message_count"] == 1740
|
||||
|
||||
|
||||
def test_real_cli_state_db_mirror_stays_cli_sidebar_row():
|
||||
from api.routes import _merge_cli_sidebar_metadata
|
||||
|
||||
merged = _merge_cli_sidebar_metadata(
|
||||
{"session_id": "cli-tip", "title": "CLI session", "source_tag": "cli"},
|
||||
{
|
||||
"session_id": "cli-tip",
|
||||
"source_tag": "cli",
|
||||
"session_source": "cli",
|
||||
"message_count": 12,
|
||||
},
|
||||
)
|
||||
|
||||
assert merged["is_cli_session"] is True
|
||||
assert merged["session_source"] == "cli"
|
||||
|
||||
|
||||
def test_stale_webui_sidebar_cli_flag_is_cleared_before_frontend_response():
|
||||
from api.routes import _normalize_sidebar_source_flags
|
||||
|
||||
normalized = _normalize_sidebar_source_flags(
|
||||
{
|
||||
"session_id": "webui-tip",
|
||||
"title": "Long WebUI session",
|
||||
"source_tag": "webui",
|
||||
"session_source": "webui",
|
||||
"is_cli_session": True,
|
||||
"message_count": 1740,
|
||||
}
|
||||
)
|
||||
|
||||
assert normalized["is_cli_session"] is False
|
||||
assert normalized["source_tag"] == "webui"
|
||||
assert normalized["session_source"] == "webui"
|
||||
|
||||
|
||||
|
||||
def test_webui_source_overrides_stale_cli_flag_even_with_default_title():
|
||||
from api.agent_sessions import is_cli_session_row
|
||||
from api.routes import _normalize_sidebar_source_flags
|
||||
|
||||
stale_webui = {
|
||||
"session_id": "webui-default-title",
|
||||
"title": "CLI Session",
|
||||
"source_tag": "webui",
|
||||
"session_source": "webui",
|
||||
"source_label": "WebUI",
|
||||
"is_cli_session": True,
|
||||
"message_count": 23,
|
||||
}
|
||||
|
||||
assert is_cli_session_row(stale_webui) is False
|
||||
assert _normalize_sidebar_source_flags(stale_webui)["is_cli_session"] is False
|
||||
|
||||
|
||||
def test_real_cli_sidebar_cli_flag_is_preserved_before_frontend_response():
|
||||
from api.routes import _normalize_sidebar_source_flags
|
||||
|
||||
normalized = _normalize_sidebar_source_flags(
|
||||
{
|
||||
"session_id": "cli-tip",
|
||||
"title": "CLI session",
|
||||
"source_tag": "cli",
|
||||
"session_source": "cli",
|
||||
"is_cli_session": True,
|
||||
"message_count": 12,
|
||||
}
|
||||
)
|
||||
|
||||
assert normalized["is_cli_session"] is True
|
||||
|
||||
@@ -271,6 +271,41 @@ def test_cross_surface_child_session_metadata_marks_orphan_top_level_candidate(_
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_state_db_webui_source_overrides_stale_cli_json_metadata(_isolate):
|
||||
"""State-db WebUI mirrors should clear stale CLI source fields in sidebar rows."""
|
||||
conn = _ensure_state_db(_isolate)
|
||||
t0 = time.time() - 100
|
||||
try:
|
||||
session = Session(
|
||||
session_id="lineage_api_stale_cli_source",
|
||||
title="WebUI Chatnachrichten verschwinden nach Neustart #9",
|
||||
messages=[{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}],
|
||||
updated_at=t0,
|
||||
is_cli_session=True,
|
||||
source_tag="cli",
|
||||
raw_source="cli",
|
||||
session_source="cli",
|
||||
source_label="CLI",
|
||||
)
|
||||
session.save(touch_updated_at=False)
|
||||
_insert_state_row(
|
||||
conn,
|
||||
"lineage_api_stale_cli_source",
|
||||
source="webui",
|
||||
started_at=t0,
|
||||
)
|
||||
|
||||
row = {row["session_id"]: row for row in all_sessions()}["lineage_api_stale_cli_source"]
|
||||
|
||||
assert row["source_tag"] == "webui"
|
||||
assert row["raw_source"] == "webui"
|
||||
assert row["session_source"] == "webui"
|
||||
assert row["source_label"] == "WebUI"
|
||||
assert row["is_cli_session"] is False
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_generic_webui_title_gets_read_only_state_db_display_title(_isolate):
|
||||
"""Sidebar rows can display the fresher state.db title without mutating JSON."""
|
||||
conn = _ensure_state_db(_isolate)
|
||||
|
||||
Reference in New Issue
Block a user