Merge pull request #4403 from nesquena/stage-4399
Release v0.51.486 — QV (archived cron runs stay hidden on state.db re-projection #4397)
This commit is contained in:
commit
5ced430628
3 changed files with 90 additions and 17 deletions
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.51.486] — 2026-06-18 — Release QV (archived cron runs stay hidden on state.db re-projection)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Archived cron runs no longer reappear in the Sessions panel after a refresh (#4397).** A follow-up to #4385/#4388: that fix stopped stale cron *sidecars* from being treated as CLI rows, but a remaining path let `get_cli_sessions()` re-project the same cron run straight from Hermes `state.db` with `archived: false` (after `all_sessions()` omitted the hidden sidecar). The state.db projection now reads the UI-owned sidecar metadata (title **and** archived flag) via a shared helper and carries the archived flag onto the projected row, so an archived cron/tool/API run stays hidden no matter which path surfaces it. Thanks @TomBanksAU.
|
||||
|
||||
## [v0.51.485] — 2026-06-18 — Release QU (mobile: bigger hamburger tap target + browser edge-swipe)
|
||||
|
||||
### Improved
|
||||
|
|
|
|||
|
|
@ -3877,6 +3877,22 @@ def _all_profiles_cli_contexts() -> tuple[list[tuple[Path, Path, str | None]], t
|
|||
return contexts, tuple(cache_entries)
|
||||
|
||||
|
||||
def _state_projection_sidecar_metadata(sid: str) -> dict:
|
||||
"""Return UI-owned metadata for a state.db-projected sidebar row."""
|
||||
metadata = {"title": None, "archived": False}
|
||||
try:
|
||||
webui_meta = Session.load_metadata_only(sid)
|
||||
except Exception:
|
||||
return metadata
|
||||
if not webui_meta:
|
||||
return metadata
|
||||
title = getattr(webui_meta, 'title', None)
|
||||
if title:
|
||||
metadata["title"] = title
|
||||
metadata["archived"] = bool(getattr(webui_meta, 'archived', False))
|
||||
return metadata
|
||||
|
||||
|
||||
def _load_cli_sessions_uncached(
|
||||
hermes_home: Path,
|
||||
db_path: Path,
|
||||
|
|
@ -3944,15 +3960,14 @@ def _load_cli_sessions_uncached(
|
|||
except Exception:
|
||||
pass # degrade gracefully
|
||||
# If a WebUI JSON file exists for this session (e.g. previously
|
||||
# imported or renamed in the sidebar), prefer its title over the
|
||||
# state.db title. This fixes rename-not-persisting for CLI sessions
|
||||
# after compression chain extension (#1486).
|
||||
try:
|
||||
_webui_meta = Session.load_metadata_only(sid)
|
||||
if _webui_meta and getattr(_webui_meta, 'title', None):
|
||||
_title = _webui_meta.title
|
||||
except Exception:
|
||||
pass
|
||||
# imported or renamed in the sidebar), prefer its UI-owned metadata over
|
||||
# the state.db projection. This keeps archived cron/tool/API runs hidden
|
||||
# even when all_sessions() omits the hidden sidecar and the state row is
|
||||
# re-injected from Hermes state.db (#4397).
|
||||
_sidecar_meta = _state_projection_sidecar_metadata(sid)
|
||||
if _sidecar_meta.get('title'):
|
||||
_title = _sidecar_meta['title']
|
||||
_archived = bool(_sidecar_meta.get('archived'))
|
||||
_display_title = _title or f'{_source.title()} Session'
|
||||
cli_sessions.append({
|
||||
'session_id': sid,
|
||||
|
|
@ -3963,7 +3978,7 @@ def _load_cli_sessions_uncached(
|
|||
'created_at': row['started_at'],
|
||||
'updated_at': raw_ts,
|
||||
'pinned': False,
|
||||
'archived': False,
|
||||
'archived': _archived,
|
||||
'project_id': _cron_pid() if is_cron_session(sid, _source) else None,
|
||||
'profile': profile,
|
||||
'source_tag': _source,
|
||||
|
|
@ -4033,12 +4048,10 @@ def _load_cli_sessions_uncached(
|
|||
break
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
_webui_meta = Session.load_metadata_only(sid)
|
||||
if _webui_meta and getattr(_webui_meta, 'title', None):
|
||||
_title = _webui_meta.title
|
||||
except Exception:
|
||||
pass
|
||||
_sidecar_meta = _state_projection_sidecar_metadata(sid)
|
||||
if _sidecar_meta.get('title'):
|
||||
_title = _sidecar_meta['title']
|
||||
_archived = bool(_sidecar_meta.get('archived'))
|
||||
_display_title = _title or 'Cron Session'
|
||||
cli_sessions.append({
|
||||
'session_id': sid,
|
||||
|
|
@ -4049,7 +4062,7 @@ def _load_cli_sessions_uncached(
|
|||
'created_at': row['started_at'],
|
||||
'updated_at': raw_ts,
|
||||
'pinned': False,
|
||||
'archived': False,
|
||||
'archived': _archived,
|
||||
'project_id': _cron_pid(),
|
||||
'profile': profile_value,
|
||||
'source_tag': 'cron',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
|
|
@ -79,6 +80,59 @@ def test_materializing_cron_session_preserves_non_cli_identity(monkeypatch):
|
|||
assert session.is_cli_session is False
|
||||
|
||||
|
||||
def test_cron_state_projection_preserves_archived_sidecar(monkeypatch, tmp_path):
|
||||
"""A hidden archived sidecar must still mark the state.db cron projection archived."""
|
||||
import api.models as models
|
||||
|
||||
sid = "cron_job123_20260618"
|
||||
db_path = tmp_path / "state.db"
|
||||
with sqlite3.connect(db_path) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT,
|
||||
model TEXT,
|
||||
message_count INTEGER,
|
||||
started_at REAL,
|
||||
source TEXT,
|
||||
session_source TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO sessions (
|
||||
id, title, model, message_count, started_at, source, session_source
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(sid, "Cron Session", "test-model", 1, 20, "cron", "cron"),
|
||||
)
|
||||
|
||||
class ArchivedSidecar:
|
||||
title = "Cron Session"
|
||||
archived = True
|
||||
|
||||
monkeypatch.setattr(
|
||||
models.Session,
|
||||
"load_metadata_only",
|
||||
staticmethod(lambda candidate: ArchivedSidecar() if candidate == sid else None),
|
||||
)
|
||||
monkeypatch.setattr(models, "ensure_cron_project", lambda: "cron-project")
|
||||
|
||||
rows = models._load_cli_sessions_uncached(
|
||||
tmp_path,
|
||||
db_path,
|
||||
"default",
|
||||
source_filter="cron",
|
||||
include_claude_code=False,
|
||||
)
|
||||
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["session_id"] == sid
|
||||
assert rows[0]["archived"] is True
|
||||
|
||||
|
||||
def test_archived_cron_sidecar_suppresses_raw_unarchived_cron_row(monkeypatch):
|
||||
"""An archived cron sidecar should keep the raw state.db cron row hidden."""
|
||||
import api.routes as routes
|
||||
|
|
|
|||
Loading…
Reference in a new issue