perf(sidebar): trim hidden and empty sidebar payload rows (#4775)
This commit is contained in:
parent
8a98626967
commit
e27b7cf042
4 changed files with 353 additions and 3 deletions
|
|
@ -1597,6 +1597,8 @@ def _session_list_cache_key(
|
|||
show_previous_messaging_sessions: bool,
|
||||
show_cron_sessions: bool,
|
||||
include_archived: bool = False,
|
||||
exclude_hidden: bool = False,
|
||||
visible_only: bool = False,
|
||||
source_filter: str | None = None,
|
||||
sidebar_source: str | None = None,
|
||||
) -> tuple:
|
||||
|
|
@ -1607,6 +1609,8 @@ def _session_list_cache_key(
|
|||
bool(show_previous_messaging_sessions),
|
||||
bool(show_cron_sessions),
|
||||
bool(include_archived),
|
||||
bool(exclude_hidden),
|
||||
bool(visible_only),
|
||||
source_filter,
|
||||
sidebar_source,
|
||||
)
|
||||
|
|
@ -1926,12 +1930,37 @@ def _build_session_list_cache_payload(
|
|||
show_previous_messaging_sessions: bool,
|
||||
show_cron_sessions: bool,
|
||||
include_archived: bool = False,
|
||||
exclude_hidden: bool = False,
|
||||
visible_only: bool = False,
|
||||
source_filter: str | None = None,
|
||||
sidebar_source: str | None = None,
|
||||
diag=None,
|
||||
) -> dict:
|
||||
diag_stage = diag.stage if diag is not None else lambda *_a, **_k: None
|
||||
|
||||
def _session_has_server_visible_messages(session: dict) -> bool:
|
||||
"""Return True when a non-active sidebar row has a visibility signal.
|
||||
|
||||
Keep this mirror of the non-active server filter narrow and local to
|
||||
route behavior so model-layer behavior remains unchanged.
|
||||
"""
|
||||
if not isinstance(session, dict):
|
||||
return False
|
||||
if _numeric_count(session.get("message_count")) > 0:
|
||||
return True
|
||||
|
||||
attention = session.get("attention")
|
||||
if isinstance(attention, dict) and attention.get("kind"):
|
||||
if _numeric_count(attention.get("count")) > 0:
|
||||
return True
|
||||
|
||||
return bool(
|
||||
session.get("is_streaming")
|
||||
or session.get("active_stream_id")
|
||||
or session.get("pending_user_message")
|
||||
or session.get("has_pending_user_message")
|
||||
)
|
||||
|
||||
def _all_sessions_for_sidebar():
|
||||
try:
|
||||
return all_sessions(diag=diag, include_lineage_metadata=False)
|
||||
|
|
@ -2090,6 +2119,16 @@ def _build_session_list_cache_payload(
|
|||
diag_stage("cli_cap")
|
||||
archived_scoped = _cap_recent_cli_sessions(archived_scoped, cli_cap=CLI_VISIBLE_SESSION_CAP)
|
||||
visible_scoped = _cap_recent_cli_sessions(visible_scoped, cli_cap=CLI_VISIBLE_SESSION_CAP)
|
||||
if visible_only:
|
||||
archived_scoped = [
|
||||
s for s in archived_scoped if _session_has_server_visible_messages(s)
|
||||
]
|
||||
visible_scoped = [
|
||||
s for s in visible_scoped if _session_has_server_visible_messages(s)
|
||||
]
|
||||
if exclude_hidden:
|
||||
archived_scoped = [s for s in archived_scoped if not s.get("default_hidden")]
|
||||
visible_scoped = [s for s in visible_scoped if not s.get("default_hidden")]
|
||||
archived_webui_count = sum(
|
||||
1 for s in archived_scoped
|
||||
if s.get("archived") and not _is_cli_session_for_settings(s)
|
||||
|
|
@ -9794,6 +9833,7 @@ def handle_get(handler, parsed) -> bool:
|
|||
active_profile = get_active_profile_name()
|
||||
all_profiles = _all_profiles_enabled(parsed)
|
||||
include_archived = _query_flag(parsed, "include_archived")
|
||||
exclude_hidden = _query_flag(parsed, "exclude_hidden")
|
||||
sidebar_source = parse_qs(parsed.query).get("sidebar_source", [""])[0].strip().lower() or None
|
||||
if sidebar_source not in ("webui", "cli"):
|
||||
sidebar_source = None
|
||||
|
|
@ -9804,6 +9844,8 @@ def handle_get(handler, parsed) -> bool:
|
|||
show_previous_messaging_sessions=show_previous_messaging_sessions,
|
||||
show_cron_sessions=show_cron_sessions,
|
||||
include_archived=include_archived,
|
||||
exclude_hidden=exclude_hidden,
|
||||
visible_only=True,
|
||||
source_filter=agent_session_source_filter,
|
||||
sidebar_source=sidebar_source,
|
||||
)
|
||||
|
|
@ -9820,6 +9862,8 @@ def handle_get(handler, parsed) -> bool:
|
|||
show_previous_messaging_sessions=show_previous_messaging_sessions,
|
||||
show_cron_sessions=show_cron_sessions,
|
||||
include_archived=include_archived,
|
||||
exclude_hidden=exclude_hidden,
|
||||
visible_only=True,
|
||||
source_filter=agent_session_source_filter,
|
||||
sidebar_source=sidebar_source,
|
||||
diag=diag,
|
||||
|
|
|
|||
|
|
@ -1640,6 +1640,7 @@ function _requestedSessionSidebarSource() {
|
|||
function _sessionListQueryString() {
|
||||
const qs = new URLSearchParams();
|
||||
qs.set('sidebar_source', _requestedSessionSidebarSource());
|
||||
if(_activeProject===null || _activeProject===NO_PROJECT_FILTER) qs.set('exclude_hidden','1');
|
||||
if(_showAllProfiles) qs.set('all_profiles','1');
|
||||
if(_showArchived) qs.set('include_archived','1');
|
||||
return `?${qs.toString()}`;
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ def test_session_list_query_string_respects_sidebar_source_and_flags():
|
|||
query_fn = _extract_function(src, "_sessionListQueryString")
|
||||
script = f"""
|
||||
global.window = {{ _showCliSessions: true }};
|
||||
global._activeProject = null;
|
||||
global._sessionSourceFilter = 'cli';
|
||||
global._showAllProfiles = true;
|
||||
global._showArchived = false;
|
||||
|
|
@ -262,12 +263,18 @@ const first = _sessionListQueryString();
|
|||
window._showCliSessions = false;
|
||||
global._showArchived = true;
|
||||
const second = _sessionListQueryString();
|
||||
console.log(JSON.stringify({{ first, second }}));
|
||||
global._activeProject = '__none__';
|
||||
global.NO_PROJECT_FILTER = '__none__';
|
||||
global._showAllProfiles = false;
|
||||
global._showArchived = false;
|
||||
const third = _sessionListQueryString();
|
||||
console.log(JSON.stringify({{ first, second, third }}));
|
||||
"""
|
||||
body = _run_node(script)
|
||||
|
||||
assert body["first"] == "?sidebar_source=cli&all_profiles=1"
|
||||
assert body["second"] == "?sidebar_source=webui&all_profiles=1&include_archived=1"
|
||||
assert body["first"] == "?sidebar_source=cli&exclude_hidden=1&all_profiles=1"
|
||||
assert body["second"] == "?sidebar_source=webui&exclude_hidden=1&all_profiles=1&include_archived=1"
|
||||
assert body["third"] == "?sidebar_source=webui&exclude_hidden=1"
|
||||
|
||||
|
||||
@pytest.mark.skipif(NODE is None, reason="node not on PATH")
|
||||
|
|
|
|||
298
tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py
Normal file
298
tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
"""Regression coverage for issue #4775: hide zero-message and default_hidden rows for default sidebar loads."""
|
||||
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import api.profiles as profiles
|
||||
import api.routes as routes
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SESSIONS_JS = ROOT / "static" / "sessions.js"
|
||||
NODE = shutil.which("node")
|
||||
|
||||
|
||||
class _FakeHandler:
|
||||
def __init__(self):
|
||||
self.status = None
|
||||
self.headers = {}
|
||||
self.wfile = io.BytesIO()
|
||||
|
||||
def send_response(self, status):
|
||||
self.status = status
|
||||
|
||||
def send_header(self, key, value):
|
||||
self.headers[key] = value
|
||||
|
||||
def end_headers(self):
|
||||
pass
|
||||
|
||||
def json_body(self):
|
||||
return json.loads(self.wfile.getvalue().decode("utf-8"))
|
||||
|
||||
|
||||
def _session_rows():
|
||||
return [
|
||||
{
|
||||
"session_id": "visible-with-message",
|
||||
"title": "Visible message",
|
||||
"profile": "default",
|
||||
"archived": False,
|
||||
"message_count": 3,
|
||||
"updated_at": 1000,
|
||||
"last_message_at": 1000,
|
||||
"source": "webui",
|
||||
"raw_source": "webui",
|
||||
"session_source": "webui",
|
||||
"source_tag": "webui",
|
||||
"default_hidden": False,
|
||||
},
|
||||
{
|
||||
"session_id": "hidden-by-default",
|
||||
"title": "Default hidden",
|
||||
"profile": "default",
|
||||
"archived": False,
|
||||
"message_count": 1,
|
||||
"updated_at": 900,
|
||||
"last_message_at": 900,
|
||||
"source": "webui",
|
||||
"raw_source": "webui",
|
||||
"session_source": "webui",
|
||||
"source_tag": "webui",
|
||||
"default_hidden": True,
|
||||
},
|
||||
{
|
||||
"session_id": "zero-message",
|
||||
"title": "Plain zero message",
|
||||
"profile": "default",
|
||||
"archived": False,
|
||||
"message_count": 0,
|
||||
"updated_at": 800,
|
||||
"last_message_at": 800,
|
||||
"source": "webui",
|
||||
"raw_source": "webui",
|
||||
"session_source": "webui",
|
||||
"source_tag": "webui",
|
||||
"default_hidden": False,
|
||||
},
|
||||
{
|
||||
"session_id": "active-stream-row",
|
||||
"title": "Streaming row",
|
||||
"profile": "default",
|
||||
"archived": False,
|
||||
"message_count": 0,
|
||||
"active_stream_id": "stream-active",
|
||||
"updated_at": 700,
|
||||
"last_message_at": 700,
|
||||
"source": "webui",
|
||||
"raw_source": "webui",
|
||||
"session_source": "webui",
|
||||
"source_tag": "webui",
|
||||
"default_hidden": False,
|
||||
},
|
||||
{
|
||||
"session_id": "attention-row",
|
||||
"title": "Attention row",
|
||||
"profile": "default",
|
||||
"archived": False,
|
||||
"message_count": 0,
|
||||
"attention": {"kind": "clarify", "count": 1},
|
||||
"updated_at": 600,
|
||||
"last_message_at": 600,
|
||||
"source": "webui",
|
||||
"raw_source": "webui",
|
||||
"session_source": "webui",
|
||||
"source_tag": "webui",
|
||||
"default_hidden": False,
|
||||
},
|
||||
{
|
||||
"session_id": "pending-user-row",
|
||||
"title": "Pending user row",
|
||||
"profile": "default",
|
||||
"archived": False,
|
||||
"message_count": 0,
|
||||
"pending_user_message": "user typing",
|
||||
"updated_at": 500,
|
||||
"last_message_at": 500,
|
||||
"source": "webui",
|
||||
"raw_source": "webui",
|
||||
"session_source": "webui",
|
||||
"source_tag": "webui",
|
||||
"default_hidden": False,
|
||||
},
|
||||
{
|
||||
"session_id": "pending-flag-row",
|
||||
"title": "Pending flag row",
|
||||
"profile": "default",
|
||||
"archived": False,
|
||||
"message_count": 0,
|
||||
"has_pending_user_message": True,
|
||||
"updated_at": 550,
|
||||
"last_message_at": 550,
|
||||
"source": "webui",
|
||||
"raw_source": "webui",
|
||||
"session_source": "webui",
|
||||
"source_tag": "webui",
|
||||
"default_hidden": False,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _handle_sessions(url):
|
||||
handler = _FakeHandler()
|
||||
routes.handle_get(handler, urlparse(url))
|
||||
return handler
|
||||
|
||||
|
||||
def _extract_function(source_text, function_name):
|
||||
marker = f"function {function_name}("
|
||||
start = source_text.index(marker)
|
||||
brace_start = source_text.index("{", start)
|
||||
depth = 0
|
||||
for index in range(brace_start, len(source_text)):
|
||||
char = source_text[index]
|
||||
if char == "{":
|
||||
depth += 1
|
||||
elif char == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source_text[start : index + 1]
|
||||
raise AssertionError(f"Could not extract {function_name}")
|
||||
|
||||
|
||||
def _run_node(script):
|
||||
proc = subprocess.run([NODE, "-e", script], capture_output=True, text=True, check=True)
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_cache():
|
||||
routes._session_list_cache_clear()
|
||||
yield
|
||||
routes._session_list_cache_clear()
|
||||
|
||||
|
||||
def _install_common_monkeypatches(monkeypatch, rows):
|
||||
session_ids = {str(row["session_id"]) for row in rows if row.get("session_id")}
|
||||
monkeypatch.setattr(routes, "all_sessions", lambda diag=None: list(rows))
|
||||
monkeypatch.setattr(routes, "_reconcile_stale_stream_state_for_session_rows", lambda _rows: False)
|
||||
monkeypatch.setattr(routes, "_enrich_sidebar_lineage_metadata", lambda _rows: None)
|
||||
monkeypatch.setattr(routes, "get_cli_sessions", lambda source_filter=None, all_profiles=False: [])
|
||||
monkeypatch.setattr(routes, "agent_session_rows_existing", lambda ids, profile=None: set(ids) & session_ids)
|
||||
monkeypatch.setattr(routes, "load_settings", lambda: {"show_cli_sessions": True})
|
||||
monkeypatch.setattr(profiles, "get_active_profile_name", lambda: "default")
|
||||
return rows
|
||||
|
||||
|
||||
def test_default_sidebar_excludes_hidden_and_plain_zero_message_rows(monkeypatch):
|
||||
rows = _session_rows()
|
||||
_install_common_monkeypatches(monkeypatch, rows)
|
||||
|
||||
handler = _handle_sessions("http://example.com/api/sessions?sidebar_source=webui&exclude_hidden=1")
|
||||
body = handler.json_body()
|
||||
|
||||
assert handler.status == 200
|
||||
assert [r["session_id"] for r in body["sessions"]] == [
|
||||
"visible-with-message",
|
||||
"active-stream-row",
|
||||
"attention-row",
|
||||
"pending-flag-row",
|
||||
"pending-user-row",
|
||||
]
|
||||
|
||||
|
||||
def test_zero_message_rows_with_visibility_signals_survive(monkeypatch):
|
||||
rows = _session_rows()
|
||||
_install_common_monkeypatches(monkeypatch, rows)
|
||||
|
||||
handler = _handle_sessions("http://example.com/api/sessions?sidebar_source=webui&exclude_hidden=1")
|
||||
body = handler.json_body()
|
||||
|
||||
assert handler.status == 200
|
||||
assert [r["session_id"] for r in body["sessions"]] == [
|
||||
"visible-with-message",
|
||||
"active-stream-row",
|
||||
"attention-row",
|
||||
"pending-flag-row",
|
||||
"pending-user-row",
|
||||
]
|
||||
|
||||
|
||||
def test_omit_exclude_hidden_still_returns_default_hidden_rows(monkeypatch):
|
||||
rows = _session_rows()
|
||||
_install_common_monkeypatches(monkeypatch, rows)
|
||||
|
||||
handler = _handle_sessions("http://example.com/api/sessions?sidebar_source=webui")
|
||||
body = handler.json_body()
|
||||
|
||||
assert handler.status == 200
|
||||
session_ids = {row["session_id"] for row in body["sessions"]}
|
||||
assert "hidden-by-default" in session_ids
|
||||
|
||||
|
||||
def test_default_and_unassigned_queries_send_exclude_hidden(monkeypatch):
|
||||
src = SESSIONS_JS.read_text(encoding="utf-8")
|
||||
requested_source_fn = _extract_function(src, "_requestedSessionSidebarSource")
|
||||
query_fn = _extract_function(src, "_sessionListQueryString")
|
||||
script = f"""
|
||||
global.window = {{ _showCliSessions: false }};
|
||||
global._showCliSessions = false;
|
||||
global._showAllProfiles = false;
|
||||
global._showArchived = false;
|
||||
global._activeProject = null;
|
||||
global.NO_PROJECT_FILTER = '__none__';
|
||||
{requested_source_fn}
|
||||
{query_fn}
|
||||
const default_query = _sessionListQueryString();
|
||||
global._activeProject = '__none__';
|
||||
const unassigned_query = _sessionListQueryString();
|
||||
global._activeProject = 'demo-project';
|
||||
const named_project_query = _sessionListQueryString();
|
||||
console.log(JSON.stringify({{ default_query, unassigned_query, named_project_query }}));
|
||||
"""
|
||||
body = _run_node(script)
|
||||
|
||||
assert body["default_query"] == "?sidebar_source=webui&exclude_hidden=1"
|
||||
assert body["unassigned_query"] == "?sidebar_source=webui&exclude_hidden=1"
|
||||
assert body["named_project_query"] == "?sidebar_source=webui"
|
||||
|
||||
|
||||
def test_cache_key_varies_for_exclude_hidden_and_visible_only():
|
||||
key_without_filters = routes._session_list_cache_key(
|
||||
active_profile="default",
|
||||
all_profiles=False,
|
||||
show_cli_sessions=True,
|
||||
show_previous_messaging_sessions=False,
|
||||
show_cron_sessions=False,
|
||||
include_archived=False,
|
||||
sidebar_source="webui",
|
||||
)
|
||||
key_exclude_hidden = routes._session_list_cache_key(
|
||||
active_profile="default",
|
||||
all_profiles=False,
|
||||
show_cli_sessions=True,
|
||||
show_previous_messaging_sessions=False,
|
||||
show_cron_sessions=False,
|
||||
include_archived=False,
|
||||
exclude_hidden=True,
|
||||
sidebar_source="webui",
|
||||
)
|
||||
key_visible_only = routes._session_list_cache_key(
|
||||
active_profile="default",
|
||||
all_profiles=False,
|
||||
show_cli_sessions=True,
|
||||
show_previous_messaging_sessions=False,
|
||||
show_cron_sessions=False,
|
||||
include_archived=False,
|
||||
visible_only=True,
|
||||
sidebar_source="webui",
|
||||
)
|
||||
|
||||
assert key_without_filters != key_exclude_hidden
|
||||
assert key_without_filters != key_visible_only
|
||||
assert key_exclude_hidden != key_visible_only
|
||||
Loading…
Reference in a new issue