diff --git a/api/models.py b/api/models.py index 652bde3f..1fde1b51 100644 --- a/api/models.py +++ b/api/models.py @@ -2815,21 +2815,28 @@ def _json_loads_if_string(value): return value -def get_state_db_session_messages(sid, *, stitch_continuations: bool = False) -> list: - """Read messages for a Hermes session from the active profile's state.db. +def get_state_db_session_messages(sid, *, stitch_continuations: bool = False, profile=None) -> list: + """Read messages for a Hermes session from state.db. - This generic reader intentionally works for any session source, including - WebUI-origin sessions that were later updated through another Hermes surface - such as the Gateway API Server. When ``stitch_continuations`` is true it - preserves the historical CLI/external-agent behavior of walking compatible - compression/close parent segments before reading messages. + When *profile* is supplied, reads from that profile's state.db; otherwise + falls back to the active profile's state.db. This generic reader works for + any session source, including WebUI-origin sessions that were later updated + through another Hermes surface such as the Gateway API Server. When + ``stitch_continuations`` is true it preserves the historical CLI/external-agent + behavior of walking compatible compression/close parent segments before reading + messages. """ try: import sqlite3 except ImportError: return [] - db_path = _active_state_db_path() + if isinstance(profile, str) and profile: + db_path = _get_profile_home(profile) / 'state.db' + if not db_path.exists(): + db_path = _active_state_db_path() + else: + db_path = _active_state_db_path() if not db_path.exists(): return [] @@ -2852,7 +2859,8 @@ def get_state_db_session_messages(sid, *, stitch_continuations: bool = False) -> 'reasoning_content', 'codex_message_items', ] - selected = ['role', 'content', 'timestamp'] + [c for c in optional if c in available] + id_col = ['id'] if 'id' in available else [] + selected = id_col + ['role', 'content', 'timestamp'] + [c for c in optional if c in available] session_chain = [str(sid)] if stitch_continuations: diff --git a/api/routes.py b/api/routes.py index efc76b06..e6d0ffda 100644 --- a/api/routes.py +++ b/api/routes.py @@ -3752,17 +3752,18 @@ def handle_get(handler, parsed) -> bool: cli_messages = [] state_db_messages = [] sidecar_metadata_messages = None + _session_profile = getattr(s, 'profile', None) or None if is_messaging_session: cli_messages = get_cli_session_messages(sid) elif load_messages: - state_db_messages = get_state_db_session_messages(sid) + state_db_messages = get_state_db_session_messages(sid, profile=_session_profile) elif not is_messaging_session: # Metadata-only callers still need the same append-only # reconciliation contract as full loads. A raw state.db summary # can count stale rows that the merge intentionally filters out, # which makes sidebar polling think the transcript is always # newer than the loaded conversation. - state_db_messages = get_state_db_session_messages(sid) + state_db_messages = get_state_db_session_messages(sid, profile=_session_profile) sidecar_metadata_session = Session.load(sid) sidecar_metadata_messages = ( getattr(sidecar_metadata_session, "messages", []) or []