mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-11 18:20:30 +00:00
2fa67ad699
* Release PL (v0.51.451): show named-profile external sessions in the all-profiles list (#4067, #4065) All-profiles session enumeration now scans across profiles only when the request explicitly asks (all-profiles view); cross-profile import resolves to the active profile's state.db for unqualified requests (foreign id -> 404), mirroring the #3982/#3991 detail/export profile-scoping gate. Co-authored-by: rodboev * gate fix (Codex CORE + Opus): close cross-profile leak on the import_cli refresh/existing-session branch The already-imported branch of _handle_session_import_cli forced allow_all_profiles=True whenever the globally-stored session carried a profile, with no active-profile visibility gate — so an unqualified request from profile A could read/refresh a session imported under profile B's live state.db (both advisors reproduced the foreign-transcript leak). Now gated exactly like the /api/session detail + export endpoints: unqualified requests require _session_visible_to_active_profile; explicit all_profiles requires a matching profile. + 3 regression tests. * gate fix (Codex SILENT, re-gate): thread source_filter into the all-profiles CLI scan (models.py:4113) The all-profiles branch keys its cache on source_filter but called _load_cli_sessions_uncached without it, so /api/sessions?all_profiles=1 with a source filter returned every-source sessions under a filtered key. + regression test. --------- Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
127 lines
5.5 KiB
Python
127 lines
5.5 KiB
Python
from urllib.parse import urlparse
|
|
|
|
|
|
def test_webui_session_metadata_load_skips_cli_metadata_scan(monkeypatch):
|
|
"""Opening a normal WebUI session should not scan imported CLI sessions."""
|
|
import api.routes as routes
|
|
from api.models import Session
|
|
|
|
session = Session(
|
|
session_id="webui_normal",
|
|
title="Normal WebUI chat",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
)
|
|
|
|
monkeypatch.setattr(routes, "get_session", lambda sid, metadata_only=False: session)
|
|
monkeypatch.setattr(routes, "_clear_stale_stream_state", lambda _session: None)
|
|
monkeypatch.setattr(routes, "redact_session_data", lambda payload: payload)
|
|
monkeypatch.setattr(routes, "j", lambda _handler, payload, status=200, extra_headers=None: payload)
|
|
monkeypatch.setattr(
|
|
routes,
|
|
"_lookup_cli_session_metadata",
|
|
lambda _sid: (_ for _ in ()).throw(AssertionError("normal WebUI loads should not scan CLI sessions")),
|
|
)
|
|
|
|
response = routes.handle_get(
|
|
object(),
|
|
urlparse("/api/session?session_id=webui_normal&messages=0&resolve_model=0"),
|
|
)
|
|
|
|
assert response["session"]["session_id"] == "webui_normal"
|
|
assert response["session"]["messages"] == []
|
|
|
|
|
|
def test_read_only_session_metadata_load_preserves_cli_metadata_lookup(monkeypatch):
|
|
"""Read-only imported sidecars still need CLI metadata for source identity."""
|
|
import api.routes as routes
|
|
from api.models import Session
|
|
|
|
session = Session(
|
|
session_id="readonly_sidecar",
|
|
title="Imported chat",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
read_only=True,
|
|
)
|
|
looked_up = []
|
|
|
|
monkeypatch.setattr(routes, "get_session", lambda sid, metadata_only=False: session)
|
|
monkeypatch.setattr(routes, "_clear_stale_stream_state", lambda _session: None)
|
|
monkeypatch.setattr(routes, "get_cli_session_messages", lambda _sid, profile=None: [])
|
|
monkeypatch.setattr(routes, "redact_session_data", lambda payload: payload)
|
|
monkeypatch.setattr(routes, "j", lambda _handler, payload, status=200, extra_headers=None: payload)
|
|
|
|
def fake_lookup(sid):
|
|
looked_up.append(sid)
|
|
return {
|
|
"session_id": sid,
|
|
"read_only": True,
|
|
"source_label": "External Agent",
|
|
}
|
|
|
|
monkeypatch.setattr(routes, "_lookup_cli_session_metadata", fake_lookup)
|
|
|
|
response = routes.handle_get(
|
|
object(),
|
|
urlparse("/api/session?session_id=readonly_sidecar&messages=0&resolve_model=0"),
|
|
)
|
|
|
|
assert looked_up == ["readonly_sidecar"]
|
|
assert response["session"]["read_only"] is True
|
|
|
|
|
|
def test_messaging_session_metadata_load_preserves_cli_metadata_lookup(monkeypatch):
|
|
"""Messaging/imported sidecars still need CLI metadata for source identity."""
|
|
import api.routes as routes
|
|
from api.models import Session
|
|
|
|
session = Session(
|
|
session_id="messaging_sidecar",
|
|
title="Telegram chat",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
session_source="messaging",
|
|
raw_source="telegram",
|
|
)
|
|
looked_up = []
|
|
|
|
monkeypatch.setattr(routes, "get_session", lambda sid, metadata_only=False: session)
|
|
monkeypatch.setattr(routes, "_clear_stale_stream_state", lambda _session: None)
|
|
monkeypatch.setattr(routes, "get_cli_session_messages", lambda _sid, profile=None: [])
|
|
monkeypatch.setattr(routes, "redact_session_data", lambda payload: payload)
|
|
monkeypatch.setattr(routes, "j", lambda _handler, payload, status=200, extra_headers=None: payload)
|
|
|
|
def fake_lookup(sid):
|
|
looked_up.append(sid)
|
|
return {
|
|
"session_id": sid,
|
|
"session_source": "messaging",
|
|
"raw_source": "telegram",
|
|
"source_label": "Telegram",
|
|
}
|
|
|
|
monkeypatch.setattr(routes, "_lookup_cli_session_metadata", fake_lookup)
|
|
|
|
response = routes.handle_get(
|
|
object(),
|
|
urlparse("/api/session?session_id=messaging_sidecar&messages=0&resolve_model=0"),
|
|
)
|
|
|
|
assert looked_up == ["messaging_sidecar"]
|
|
assert response["session"]["source_label"] == "Telegram"
|
|
|
|
|
|
def test_messaging_session_metadata_matches_full_display_merge(monkeypatch):
|
|
import api.routes as routes
|
|
from api.models import Session
|
|
sidecar = [{"role": "user", "content": "hi", "timestamp": 1000}, {"role": "assistant", "content": "ok", "timestamp": 1001}]
|
|
cli = sidecar + [{"role": "assistant", "content": "ok", "timestamp": 1001.7}]
|
|
session = Session(session_id="telegram_resume", title="Telegram", messages=sidecar, session_source="messaging", raw_source="telegram")
|
|
monkeypatch.setattr(routes, "get_session", lambda sid, metadata_only=False: session)
|
|
monkeypatch.setattr(routes, "_clear_stale_stream_state", lambda _session: None)
|
|
monkeypatch.setattr(routes, "_lookup_cli_session_metadata", lambda sid: {"session_id": sid, "session_source": "messaging", "raw_source": "telegram"})
|
|
monkeypatch.setattr(routes, "get_cli_session_messages", lambda _sid, profile=None: cli)
|
|
monkeypatch.setattr(routes, "redact_session_data", lambda payload: payload)
|
|
monkeypatch.setattr(routes, "j", lambda _handler, payload, status=200, extra_headers=None: payload)
|
|
full = routes.handle_get(object(), urlparse("/api/session?session_id=telegram_resume&messages=1&resolve_model=0"))["session"]
|
|
meta = routes.handle_get(object(), urlparse("/api/session?session_id=telegram_resume&messages=0&resolve_model=0"))["session"]
|
|
assert (meta["message_count"], meta["last_message_at"]) == (full["message_count"], full["last_message_at"])
|