Release v0.51.626 — keep sidebar in sync after Desktop app continues a session (#4834) - #4841
nesquena-hermes wants to merge 4 commits into
Conversation
# Conflicts: # CHANGELOG.md
…iation) + tighten CHANGELOG wording per Opus (sidebar-overlay is the new bit; dedup/writeback are tested-not-introduced)
|
| Filename | Overview |
|---|---|
| api/models.py | Adds message-count/timestamp enrichment to _read_state_db_sidebar_overrides and guard logic to _apply_sidebar_state_db_override_metadata; two known issues (updated_at in guard, isdigit() float coercion) were already flagged in prior review threads. |
| api/routes.py | Removes the all-zeros short-circuit in _session_list_cache_source_stamp that blocked state.db cache invalidation when show_cli_sessions=False; intentional correctness fix with clear explanatory comment. |
| tests/test_webui_state_db_reconciliation.py | Adds two well-scoped regression tests for the Desktop-continuation sidebar overlay and no-duplicate-prefix full-load scenario, plus a helper for appending rows to state.db in tests. |
| tests/test_webui_state_db_context_reconciliation.py | Adds saved-content assertions to verify no deduplication occurs after a WebUI+external-messages reconciliation; drops an unused json import. |
| CHANGELOG.md | Release entry for v0.51.626 added by the release process, as expected for this repo. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant DA as Desktop App
participant SDB as state.db
participant CS as Cache Stamp
participant SO as _read_state_db_sidebar_overrides
participant AM as _apply_sidebar_state_db_override_metadata
participant SB as WebUI Sidebar
Note over DA,SDB: Desktop App continues a WebUI-origin session
DA->>SDB: Appends settled rows (messages table)
Note over CS: Previously: all-zeros stamp when show_cli_sessions=False
Note over CS: Now: state.db mtime/fingerprint always watched
SB->>CS: Poll /api/sessions
CS->>SDB: Check mtime + WAL stat + content fingerprint
SDB-->>CS: Stamp changed to cache miss
CS->>SO: Rebuild session list
SO->>SDB: SELECT sessions (id, source, message_count)
SO->>SDB: "SELECT COUNT(*), MAX(timestamp) FROM messages GROUP BY session_id"
SDB-->>SO: "state_count=4, state_last=1003.0"
SO-->>AM: "metadata with _state_db_message_count=4 and _state_db_last_message_at=1003.0"
AM->>AM: "Guard: state_count(4) > current_count(2) AND state_last > current_last"
AM->>SB: "session.message_count=4, last_message_at=1003.0"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant DA as Desktop App
participant SDB as state.db
participant CS as Cache Stamp
participant SO as _read_state_db_sidebar_overrides
participant AM as _apply_sidebar_state_db_override_metadata
participant SB as WebUI Sidebar
Note over DA,SDB: Desktop App continues a WebUI-origin session
DA->>SDB: Appends settled rows (messages table)
Note over CS: Previously: all-zeros stamp when show_cli_sessions=False
Note over CS: Now: state.db mtime/fingerprint always watched
SB->>CS: Poll /api/sessions
CS->>SDB: Check mtime + WAL stat + content fingerprint
SDB-->>CS: Stamp changed to cache miss
CS->>SO: Rebuild session list
SO->>SDB: SELECT sessions (id, source, message_count)
SO->>SDB: "SELECT COUNT(*), MAX(timestamp) FROM messages GROUP BY session_id"
SDB-->>SO: "state_count=4, state_last=1003.0"
SO-->>AM: "metadata with _state_db_message_count=4 and _state_db_last_message_at=1003.0"
AM->>AM: "Guard: state_count(4) > current_count(2) AND state_last > current_last"
AM->>SB: "session.message_count=4, last_message_at=1003.0"
Reviews (2): Last reviewed commit: "ci: re-trigger (prior run wedged ~25min ..." | Re-trigger Greptile
| current_last = max( | ||
| float(session.get('last_message_at') or 0), | ||
| float(session.get('updated_at') or 0), | ||
| ) | ||
| except (TypeError, ValueError): | ||
| current_last = 0.0 | ||
| try: | ||
| state_last = float(state_db_last_message_at or 0) | ||
| except (TypeError, ValueError): | ||
| state_last = 0.0 | ||
| if state_count > current_count and (state_last <= 0 or state_last > current_last): |
There was a problem hiding this comment.
updated_at in guard can block valid count refreshes
current_last is the max of last_message_at and updated_at, and the gate state_last > current_last must clear both. If the session's updated_at was bumped by a non-message event (e.g., title rename, settings flush) after the last WebUI message, a Desktop-app turn that lands before the next WebUI message may produce a state_last that exceeds last_message_at but still falls below updated_at, silently suppressing the sidebar refresh. Using only last_message_at as the reference point would be a tighter semantic fit for "is the Desktop turn newer than the last message the WebUI knows about?"
| session['actual_message_count'] = max( | ||
| state_count, | ||
| int(session.get('actual_message_count') or 0) if str(session.get('actual_message_count') or '').isdigit() else 0, | ||
| ) |
There was a problem hiding this comment.
isdigit() guard silently zeroes float-valued actual_message_count
str(4.0).isdigit() is False, so if actual_message_count is a float (e.g., came through JSON deserialization without an explicit cast), the guard evaluates to else 0 and the field is written as max(state_count, 0) instead of max(state_count, 4). A plain try: int(...) except: 0 pattern (matching every other integer-coercion in this function) would handle floats correctly and be consistent with the surrounding style.
…ilure; local full suite 10366 + 31 reconciliation tests green)
Release v0.51.626 — keep the sidebar in sync after the Desktop app continues a session (#4834)
Ships #4834 (by @franksong2702). Reconciliation fix on the WebUI↔state.db sidebar path.
What it does
When the official Hermes Desktop App appends settled rows to the same Hermes Agent
state.dbsession that was started in the WebUI, the WebUI conversation sidebar showed a stale message count. It now refreshes the WebUI-origin row's count/timestamp fromstate.dbeven while external/CLI sessions are hidden. The no-duplicate-prefix merge and single-reconciled-transcript-on-next-turn behaviors (pre-existing) are now covered by regression tests for this Desktop-continuation scenario.Gate (deep — this is the crown-jewel reconciliation family where the #4772 P0 lives)
CHANGELOG wording tightened per the Opus review to reflect that the dedup/writeback guarantees are tested here, not introduced.
Closes #4834.