fix: keep webui mirrored sessions out of cli filter

This commit is contained in:
ai-ag2026
2026-05-27 22:36:32 +02:00
parent 329debcd33
commit 9650b387fd
5 changed files with 168 additions and 5 deletions
+15 -4
View File
@@ -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
+11
View File
@@ -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
View File
@@ -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)