fix(todos): hydrate cold-load state from session snapshot - #3373
Conversation
68b8824 to
8cf6936
Compare
|
Pulled this into a worktree and read Cross-repo contract checkThe module docstring says it mirrors for msg in reversed(history):
if msg.get("role") != "tool": continue
content = msg.get("content", "")
if '"todos"' not in content: continue
data = json.loads(content)
if "todos" in data and isinstance(data["todos"], list):
last_todo_response = data["todos"]; break
One behavior divergence worth a comment (not a blocker)The backend treats an explicit empty // static/panels.js:2691
if (d && Array.isArray(d.todos) && d.todos.length) return d.todos;So during an upgrade window where the server hasn't been restarted yet (no Redaction coverageNice catch wiring Verification suggestionThe route is hooked behind |
8cf6936 to
374bc33
Compare
|
@nesquena-hermes Thanks — this matches my read as well. Spot-check for the empty-list / msg_limit case:
So the sidecar is coming from the full transcript, not the truncated display window.
Commands/results: On the legacy fallback divergence: agreed. I left |
|
Shipped in v0.51.212 (Release GF). Thanks @v2psv — the new |
…➔ 0.51.230) (#798) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.210` → `0.51.230` | --- ### Release Notes <details> <summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary> ### [`v0.51.230`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051230--2026-06-03--Release-GX-stage-p14--extract-think-blocks-to-mreasoning--LLM-Wiki-last-writer) [Compare Source](nesquena/hermes-webui@v0.51.229...v0.51.230) ##### Fixed - Assistant message `<think>…</think>` blocks are now extracted into `m.reasoning` instead of being stored inline in `m.content` — **both client-side (streaming/inflight state) and server-side at save time**. Reasoning-only providers such as `MiniMax-M3` (OpenAI-compat) previously left the thinking trace inside the assistant content, bloating persisted session files by 30–50% and bypassing the `m.reasoning` field the thinking card reads on reload. A new `_splitThinkFromContent()` (in `static/messages.js`) and its server-side twin `_split_thinking_from_content()` (in `api/streaming.py`, applied to the final assistant message before `s.save()`) extract a single **leading** block (after lstrip) for all three known tag pairs, matching the live renderer's `_streamDisplay`/`_parseStreamState` semantics exactly: a closed `<think>…</think>` that appears mid-body (e.g. a literal tag inside a fenced code block) stays visible content and is never moved into reasoning, a partial/unclosed block is left intact, and any pre-existing `m.reasoning` (from a separate `on_reasoning` stream) is preserved/merged. So the persisted session file — not just the in-browser copy — is compacted on reload ([#​3455](nesquena/hermes-webui#3455) part 1, [@​gsurenull](https://github.com/gsurenull)). - The LLM Wiki status panel's `Last writer` field is now populated (it always showed `Not available` since the panel shipped in [#​1257](nesquena/hermes-webui#1257)). The reader uses a 3-tier fallback — most-recent page frontmatter (`updated_by`/`writer`/`author`), the most recent `log.md` action verb, then a static `ai-agent` fallback — and reads only frontmatter + log headings, never page bodies, preserving the private-safe status contract ([#​3455](nesquena/hermes-webui#3455) part 2, [@​gsurenull](https://github.com/gsurenull); closes [#​1257](nesquena/hermes-webui#1257)). ### [`v0.51.229`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051229--2026-06-03--Release-GW-stage-p13--model-never-silently-snaps-a-versioned-name-to-a--tier-variant) [Compare Source](nesquena/hermes-webui@v0.51.228...v0.51.229) ##### Fixed - `/model <name>` no longer silently snaps a complete versioned model name to a longer `-tier` variant (and a different price tier). When the typed name ends in a version number (e.g. `mimo-v2.5`) and the catalog has only a longer suffixed variant (e.g. `xiaomi/mimo-v2.5-pro`), both the dropdown matcher (`_findModelInDropdown`) and the command fallback (`_bestModelMatch`) now reject the snap unless the extra text *continues the version* (`.` + digit), rather than upgrading the user to a `-pro`/`-flash` tier they did not type. When nothing matches cleanly, `/model` now shows a *"No model matching … — did you mean …?"* suggestion toast instead of silently switching. Legitimate fuzzy shorthand is preserved (`/model gpt-5` → `gpt-5.4-mini`, `/model claude` → `claude-opus-4.6`, `/model mimo-v2` → `mimo-v2.5-pro`), as is exact-match priority ([#​3368](nesquena/hermes-webui#3368), with [@​garyd9](https://github.com/garyd9); thanks [@​yutaotie](https://github.com/yutaotie) for confirmation). ### [`v0.51.228`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051228--2026-06-03--Release-GV-stage-p12--workspace-file-tree-drop--large-markdown-preview) [Compare Source](nesquena/hermes-webui@v0.51.227...v0.51.228) ##### Fixed - Dropping an OS file onto the **workspace file tree** now uploads it into the workspace only, instead of *also* attaching it to the chat composer. The tree's drag handlers now stop event propagation for OS `Files` drops so the document-level composer drop handler no longer fires for the same drop ([#​3411](nesquena/hermes-webui#3411), [@​pamnard](https://github.com/pamnard)). - Moderately large Markdown documents in the **workspace preview** are no longer forced into plain-text too early. The rich-render ceiling is raised (64 KB / 1500 lines → 256 KB / 5000 lines, and the backend file-read limit 200 KB → 400 KB), and files above the limit gain a **"Render as markdown anyway"** button that force-renders the already-loaded content without a second fetch ([#​3378](nesquena/hermes-webui#3378), [@​starGazerK](https://github.com/starGazerK)). ### [`v0.51.227`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051227--2026-06-03--Release-GU-stage-p11--keep-the-active-New-Chat-visible-in-the-sidebar) [Compare Source](nesquena/hermes-webui@v0.51.226...v0.51.227) ##### Fixed - A freshly-created **New Chat** now stays visible and selected in the sidebar before its first message is sent. The sidebar intentionally filters inactive 0-message sessions, but that filter also hid the *currently active* blank chat until the user sent a turn — so starting a New Chat could make the selected row vanish from the list. The active ephemeral session is now injected into the sidebar render rows (only when the server-side list omits it), while inactive empty sessions stay filtered as before. Starting a New Chat from a CLI-filtered sidebar also switches the source filter back to WebUI so the active chat isn't immediately hidden ([#​3408](nesquena/hermes-webui#3408), [@​AJV20](https://github.com/AJV20)). ### [`v0.51.226`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051226--2026-06-03--Release-GT-stage-p9--mobile-composer-context-usage-ring--activity-feed-default-expand-setting) [Compare Source](nesquena/hermes-webui@v0.51.225...v0.51.226) ##### Added - **Settings → Appearance: "Expand activity feed by default"** — a new checkbox (default off) that expands new Activity disclosures by default as turns arrive. Manual per-turn collapse/expand still wins (an explicit user toggle is preserved), and live "Waiting on model" rows now explain what the agent is doing before and after tool calls ([#​3080](nesquena/hermes-webui#3080), [@​AJV20](https://github.com/AJV20)). ##### Changed - The mobile composer's config button now shows a **context-usage ring** (an SVG progress ring with a centered percentage) in place of the static sliders icon, color-coded green (≤50%) / orange (≤85%) / red (>85%) and reset to 0% on a new session, so context-window pressure is visible at a glance on mobile ([#​3062](nesquena/hermes-webui#3062), [@​NottheGuy007](https://github.com/NottheGuy007)). ### [`v0.51.225`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051225--2026-06-03--Release-GS-stage-p7--remote-gateway-health-probe-resolves-gatewaystate) [Compare Source](nesquena/hermes-webui@v0.51.224...v0.51.225) ##### Fixed - The remote-gateway health probe now correctly reports `gateway_state`, so the Tasks/Cron banner lights up for Docker / remote-gateway deployments. The probe previously hit `/health` and `/status` (neither returns `gateway_state`) and never queried `/health/detailed` (which does), so `gateway_state == "running"` was never observed remotely. The probe now tries `/health/detailed` first, parses the JSON body of a 2xx response to extract `gateway_state`, and unifies the gateway base-URL env precedence to `GATEWAY_HEALTH_URL` > `HERMES_GATEWAY_HEALTH_URL` > `HERMES_API_URL` ([#​3355](nesquena/hermes-webui#3355), [@​rodboev](https://github.com/rodboev)). ### [`v0.51.224`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051224--2026-06-03--Release-GR-stage-p6--profile-toolskill-config-authoritative-on-the-streaming-worker) [Compare Source](nesquena/hermes-webui@v0.51.223...v0.51.224) ##### Fixed - Profile tool/skill restrictions are now respected for WebUI chats even when the per-session "Tool Restrictions" field is left blank. The streaming agent runs on a detached worker thread that does not inherit the per-request thread-local profile context, so the ambient `get_config()` resolved the process-global `default` profile and loaded its `platform_toolsets.cli` (all tools) instead of the session profile's configured list — inflating a tools-disabled profile's prompt from \~400 to \~15K input tokens. The worker now reads the session's own profile config explicitly via a new `get_config_for_profile_home()` helper (a race-free direct disk read with no shared-cache mutation), so toolsets, prefill context, and fallback chains all match the profile the session actually runs under ([#​3294](nesquena/hermes-webui#3294), [@​nesquena-hermes](https://github.com/nesquena-hermes)). ### [`v0.51.223`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051223--2026-06-02--Release-GQ-stage-p5--openai-api-first-class-picker-provider--MiniMax-M3) [Compare Source](nesquena/hermes-webui@v0.51.222...v0.51.223) ##### Fixed - GPT models now appear in the model picker when hermes-agent exposes its built-in OpenAI provider under the `openai-api` slug (the one activated by `OPENAI_API_KEY` / `OPENAI_BASE_URL`, distinct from `openai-codex`). `openai-api` is now a first-class picker provider in `_PROVIDER_DISPLAY` / `_PROVIDER_MODELS` rather than an alias of `openai` — an alias would have fixed the display but broken the send path, since the agent registry has `openai-api` and not `openai`. Env detection for `OPENAI_API_KEY` was also corrected to surface `openai-api` instead of a bare `openai` the agent registry can't resolve ([#​3443](nesquena/hermes-webui#3443), [@​rodboev](https://github.com/rodboev)). ##### Changed - MiniMax default model catalog upgraded to M3 in the model picker ([#​3374](nesquena/hermes-webui#3374), [@​octo-patch](https://github.com/octo-patch)). ### [`v0.51.222`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051222--2026-06-02--Release-GP-stage-p4--backend-bugfix-batch-title-language-drift--orphaned-CLI-sidecar-prune--pin-quota-lineage) [Compare Source](nesquena/hermes-webui@v0.51.221...v0.51.222) ##### Fixed - Auto-generated session titles no longer persist in the wrong language. The title-language guard previously only rejected English titles for *German* conversation starts, so an English chat whose LLM-generated title came back in Chinese, Russian, or another script sailed through and was saved. `_title_language_mismatch` now also does a language-agnostic cross-script check: when the conversation start has a clear dominant writing script and the generated title introduces a substantial amount of a different script (CJK / Cyrillic / Arabic / etc.), the title is rejected and generation falls back to the deterministic topic title. The threshold tolerates a borrowed technical term (a CJK title with one English word still trips; an English title with a single foreign place-name does not), and the legacy German→English heuristic is preserved ([#​3293](nesquena/hermes-webui#3293)). - WebUI sidebar now reconciles orphaned imported-CLI sessions. When a CLI/agent session is opened in the WebUI it gets a WebUI-owned sidecar so it can render and reopen; previously, if the user then deleted that session from the CLI / local Hermes storage, nothing pruned the sidecar and the stale row lingered in the sidebar indefinitely (there is no WebUI delete affordance for CLI rows). Orphaned sidecars whose backing session no longer exists are now pruned on reconciliation ([#​3238](nesquena/hermes-webui#3238)). - Pin quota is now counted by visible session lineage rather than raw session rows, so continuation siblings in the same sidebar-visible lineage no longer each consume a separate pin slot. Previously a pinned session that had been compressed/continued into multiple rows could exhaust the pin limit with what the user sees as a single pinned conversation. The limit check now collapses each lineage to its visible root before counting against `pinned_sessions_limit` ([#​3288](nesquena/hermes-webui#3288), [@​andrewkangkr](https://github.com/andrewkangkr)). ### [`v0.51.221`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051221--2026-06-02--Release-GO-stage-p3e--block-all-workspace-symlink-escapes-security) [Compare Source](nesquena/hermes-webui@v0.51.220...v0.51.221) ##### Security - The workspace file API now blocks **all** symlink escapes from the selected workspace, not just symlinks pointing at system directories. Previously a symlink placed inside a workspace could resolve to an arbitrary external host path (e.g. `~/.ssh`, `~/.hermes/auth.json`) and be read through `/api/list` / `read_file_content` — and since that API is reachable by LLM agent tool calls, an imported or crafted workspace could expose credentials. `safe_resolve_ws` now requires the resolved path stay under the workspace root, `list_dir` hides escaping symlinks (they could never be opened anyway), and `read_file_content` rejects them. Symlinks that resolve back under the workspace still work normally. The directory-list, file-read, file-upload, and archive-extraction paths are additionally hardened against a symlink-swap **TOCTOU** race: each path is opened component-by-component from the workspace root with `O_NOFOLLOW` (an anchored `openat` walk on Linux/macOS, with a plain-open fallback on platforms without `dir_fd` support such as Windows, where creating symlinks needs admin anyway), so a symlink raced into any component after the containment check cannot redirect the read/list/write outside the workspace. Note: an intentional in-workspace symlink pointing to an external directory is no longer followed ([#​3398](nesquena/hermes-webui#3398), [@​Hinotoi-agent](https://github.com/Hinotoi-agent)). ### [`v0.51.220`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051220--2026-06-02--Release-GN-stage-p3c--fix-aux-title-generation-with-provider-model-ids) [Compare Source](nesquena/hermes-webui@v0.51.219...v0.51.220) ##### Fixed - Manual session-title regeneration and background auxiliary title generation no longer fail with `422` / `llm_error_aux` when `auxiliary.title_generation.model` in `config.yaml` is set using the WebUI model-picker's `@provider:model` format (e.g. `@gemini:gemini-3.1-flash-lite`). The `@provider:` prefix is now normalized away via the canonical helper before the id reaches the provider API ([#​3430](nesquena/hermes-webui#3430), [@​pamnard](https://github.com/pamnard)). ### [`v0.51.219`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051219--2026-06-02--Release-GM-stage-p3b--extend-URI-scheme-model-ID-fix-to-backend-normalization--matching) [Compare Source](nesquena/hermes-webui@v0.51.218...v0.51.219) ##### Fixed - Extended the [#​3429](nesquena/hermes-webui#3429) URI-scheme fix beyond the visible model chip (fixed in v0.51.218) to the model-identity normalization and matching paths: `api/config.py` `_norm_model_id` / `_get_label_for_model` and `static/ui.js` `_normalizeConfiguredModelKey` no longer strip the first `/`-segment of a `scheme://` id (e.g. `gpt://${FOLDER}/model/latest`), where the slashes are path separators rather than a provider prefix. This prevents the [#​3360](nesquena/hermes-webui#3360 identity collision/mislabel for URI-shaped model IDs in dropdown matching, badge assignment, and configured-entry dedup. Backend/front-end parity is covered by tests ([#​3436](nesquena/hermes-webui#3436), [@​b3nw](https://github.com/b3nw)). ### [`v0.51.218`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051218--2026-06-02--Release-GL-stage-p3a--fix-getModelLabel-mangling-URI-scheme-model-IDs) [Compare Source](nesquena/hermes-webui@v0.51.217...v0.51.218) ##### Fixed - The composer model chip no longer shows env-var path junk for model IDs that use a URI scheme (e.g. Yandex `gpt://${FOLDER}/deepseek-v4-flash/latest`). A regression from [#​3366](nesquena/hermes-webui#3366) (v0.51.210): `getModelLabel()` stripped the first `/`-segment, which for a `scheme://` id landed inside the `://` and left `/${FOLDER}/…`. The label now detects a URI scheme, drops scheme + authority, and takes the last meaningful path segment (skipping `${…}` placeholders and bare version tails like `latest`); non-URI multi-slash IDs keep their [#​3360](nesquena/hermes-webui#3360) behavior ([#​3429](nesquena/hermes-webui#3429)). ### [`v0.51.217`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051217--2026-06-02--Release-GK-stage-p2f--decode-and-complete-zh-Hant-locale-strings) [Compare Source](nesquena/hermes-webui@v0.51.216...v0.51.217) ##### Changed - Decoded the `zh-Hant` (Traditional Chinese) locale block from `\u`-escaped sequences to literal Chinese text and backfilled missing keys so `zh-Hant` now has full coverage of the English key set. Makes future locale review readable and prevents newer UI keys from falling back to English for Traditional Chinese users. Locale-only — no runtime behavior change ([#​3414](nesquena/hermes-webui#3414), [@​PeterDaveHello](https://github.com/PeterDaveHello)). ##### Fixed - Added the missing `provider_mismatch_warning` string to the French (`fr`) locale. It was absent entirely; the gap was masked by a stale duplicate of the same key in the `zh-Hant` block that [#​3414](nesquena/hermes-webui#3414) removed, so all locales now carry the key. ### [`v0.51.216`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051216--2026-06-02--Release-GJ-stage-p2e--fix-consecutive-user-turn-rejection-on-strict-chat-templates) [Compare Source](nesquena/hermes-webui@v0.51.215...v0.51.216) ##### Fixed - WebUI session/delivery context (connected platforms, home channels, scheduled-task delivery hints) is now injected into the ephemeral **system prompt** instead of being appended as a prefill `user` message. The old prefill produced two consecutive `user` turns (session context + the actual message), which models with strict chat templates (Mistral, Gemma via llama.cpp) reject with a Jinja 500. The same context is preserved — just delivered in a role-alternation-safe place ([#​3324](nesquena/hermes-webui#3324), [@​aether-agent](https://github.com/aether-agent), closes [#​3276](nesquena/hermes-webui#3276)). ### [`v0.51.215`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051215--2026-06-02--Release-GI-stage-p2d--deduplicate-legacy-messages-in-append-only-merge) [Compare Source](nesquena/hermes-webui@v0.51.214...v0.51.215) ##### Fixed - `merge_session_messages_append_only` now deduplicates true duplicate legacy messages (same role, content, AND exact timestamp) that could accumulate in state, while preserving legitimately-repeated identical turns whose timestamps differ even slightly. This avoids both the stale-duplicate buildup and the data-loss class where collapsing same-second distinct turns would drop real messages ([#​3393](nesquena/hermes-webui#3393), [@​thanhtoantnt](https://github.com/thanhtoantnt), closes [#​3346](nesquena/hermes-webui#3346)). ### [`v0.51.214`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051214--2026-06-02--Release-GH-stage-p2c--preserve-loaded-transcript-width-on-same-session-external-refresh) [Compare Source](nesquena/hermes-webui@v0.51.213...v0.51.214) ##### Fixed - A same-session external refresh (e.g. a background poll triggering a force-reload of the conversation you're reading) no longer collapses a long transcript back to the default 30-message tail window and jumps the viewport to a different slice. The already-loaded transcript width and scroll position are now captured before the in-memory transcript is cleared and preserved across the authoritative reload ([#​3326](nesquena/hermes-webui#3326), [@​viraatdas](https://github.com/viraatdas), closes [#​3239](nesquena/hermes-webui#3239)). ### [`v0.51.213`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051213--2026-06-02--Release-GG-stage-p2b--keep-gateway-context-visible-in-chat-transcripts) [Compare Source](nesquena/hermes-webui@v0.51.212...v0.51.213) ##### Fixed - Gateway-backed chat now backfills model-context turns into the visible transcript before saving the latest reply, while keeping hidden `[context compaction]` markers out of the visible transcript. Previously a context-compacted gateway session could collapse the sidebar/header message count to a two-message conversation (and drop older visible turns) while the assistant was responding to hidden prior context. Older visible turns are preserved and compaction markers stay hidden from `saved.messages` ([#​3300](nesquena/hermes-webui#3300), [@​AJV20](https://github.com/AJV20)). ### [`v0.51.212`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051212--2026-06-02--Release-GF-stage-batch2--i18n-regenerate-title-strings--self-restart-argv--todos-cold-load) [Compare Source](nesquena/hermes-webui@v0.51.211...v0.51.212) ##### Fixed - Localized the five `session_title_regenerate*` session-menu strings (the "Regenerate title" action, its description, and the regenerating/regenerated/failed states) that shipped as English text in every non-English locale. Translated across it, ja, ru, es, de, zh, zh-Hant, pt, ko, fr, and tr, matching each locale's existing terminology; `zh`/`zh-Hant` keep the `\u`-escaped style of those blocks ([#​3396](nesquena/hermes-webui#3396), [@​vanshaj-pahwa](https://github.com/vanshaj-pahwa), closes [#​3364](nesquena/hermes-webui#3364)). - Self-update re-exec now distinguishes source checkouts from frozen/packaged builds: a frozen binary (`sys.frozen`) re-execs with `sys.argv` as-is, while source checkouts keep the `[sys.executable] + sys.argv` CPython idiom. Previously the frozen path re-inserted the binary as `argv[1]`, turning re-exec into a no-op that left the WebUI stuck "offline" after every self-update ([#​3395](nesquena/hermes-webui#3395), [@​PatrickNoFilter](https://github.com/PatrickNoFilter)). - The Todos panel now hydrates correctly on a cold session load (page refresh) even when the latest todo tool result is outside the truncated display window: `/api/session` derives a compact `todo_state` sidecar from the full settled transcript, and an explicit empty todo list is honored as the current state instead of falling through to an older non-empty write. A malformed historical tool message can never break session loading ([#​3373](nesquena/hermes-webui#3373), [@​v2psv](https://github.com/v2psv)). ### [`v0.51.211`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051211--2026-06-02--Release-GE-stage-batch1--reasoning-heuristics--model-shortest-match--Copilot-env-token-filter) [Compare Source](nesquena/hermes-webui@v0.51.210...v0.51.211) ##### Fixed - Generalized reasoning-effort capability checks in `_candidate_supports_reasoning` to target whole model families (GPT-5+, Claude 4/3.7, Qwen-3, Kimi, Minimax, Mimo, GLM, Step, and DeepSeek) instead of anchoring on hardcoded version numbers or vendor formats. This prevents the thinking-level configuration selector from being hidden on custom providers, new model releases, or when names carry suffixes like `-free` or `:free` (common on integrations such as Kilo Code or OpenCode Zen). The GPT heuristic is now version-anchored (5+) to avoid falsely enabling reasoning\_effort for gpt-4o/4.1/3.5 on aggregator providers ([#​3379](nesquena/hermes-webui#3379), [@​b3nw](https://github.com/b3nw), closes [#​3377](nesquena/hermes-webui#3377)). - The `/model` slash command no longer selects a longer model variant when a shorter name is a prefix of it (e.g. `/model mimo-v2.5` selecting `mimo-v2.5-pro`). The fuzzy fallback now prefers an exact id/label match and otherwise the shortest matching option, applied to both the main and bare-name (`provider/...`) fallbacks ([#​3394](nesquena/hermes-webui#3394), [@​vanshaj-pahwa](https://github.com/vanshaj-pahwa), closes [#​3368](nesquena/hermes-webui#3368)). - `GITHUB_TOKEN` and `GH_TOKEN` environment variables are now filtered from the Copilot credential pool alongside the seeded `gh`-CLI token, so a classic PAT (`ghp_*`) auto-detected from the environment no longer makes Copilot appear in the model picker when the Copilot API can't use it. User-specific `COPILOT_GITHUB_TOKEN` is still respected ([#​3382](nesquena/hermes-webui#3382), [@​happy5318](https://github.com/happy5318)). </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/798
## Release v0.51.261 — Release IC (stage-r11) Live Todos panel via an explicit `todo_state` SSE contract. ### Fixed | Issue/PR | Author | Fix | |----------|--------|-----| | #3373 follow-up (#3454) | @v2psv | The Todos side panel now tracks `todo` tool state **live during an active run** instead of staying stale until settle / rolling back on a mid-stream reload. A dedicated `todo_state` SSE event sends a full, redacted, idempotent snapshot on todo-tool completion (no more truncated `tool_complete.preview`); the same `api.todo_state` parser feeds live + cold-load; live snapshots persist into INFLIGHT so reload/reattach restores the panel; cold-load vs INFLIGHT reconciled by timestamp (incl. the `coldTs===0` compressed-session edge); legacy reverse-scan kept as fallback for old servers. | ### Gate - Full pytest suite: **7692 passed, 0 failed** - ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN - Codex (regression): **SAFE TO SHIP** — verified the new `todo_state` SSE handler composes with existing dispatch (no double-subscribe), INFLIGHT persistence is cleared on terminal/cancel (composes with discard_session + turn-journal), timestamp reconciliation can't let a stale local snapshot win, redaction holds, the legacy reverse-scan fallback still works with no double-render, and the `models.py` change is todo-scoped (no CLI-classification interaction). Co-authored-by: v2psv <v2psv@users.noreply.github.com>
…➔ 0.51.265) (#835) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.252` → `0.51.265` | --- ### Release Notes <details> <summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary> ### [`v0.51.265`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051265--2026-06-04--Release-IG-stage-r15--owner-aware-cancelStream--un-held) [Compare Source](nesquena/hermes-webui@v0.51.264...v0.51.265) ##### Fixed - **Stop/Cancel no longer leaves the UI falsely idle, drops the cancellation transcript, or leaks the old stream's tokens into the next turn.** `cancelStream()` is now owner-aware and terminal-settle-aware: for the *active* session it leaves the live SSE transport open so the backend's terminal `cancel` event clears INFLIGHT, renders "Task cancelled", and refreshes the sidebar; it only tears down the SSE for *stale* owner paths (a different stream started before the cancel returned); and it clears local busy state only on exact ownership (`S.activeStreamId===streamId`), so a new turn started mid-cancel is never clobbered. ([#​3344](nesquena/hermes-webui#3344), [@​franksong2702](https://github.com/franksong2702)) ### [`v0.51.264`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051264--2026-06-04--Release-IF-stage-r14--sidebar-cron-overflow--messaging-source-labels-un-held) [Compare Source](nesquena/hermes-webui@v0.51.263...v0.51.264) ##### Fixed - **Cron sessions no longer flood the CLI session window in the sidebar** (so Discord/Telegram/Slack sessions stop vanishing from it). `_load_cli_sessions_uncached()` passed `exclude_sources=None`, overriding the default `("cron","webui")` exclusion, so hundreds of cron rows filled the 20-row visible limit. ([#​3585](nesquena/hermes-webui#3585), [@​rodboev](https://github.com/rodboev)) - **Imported messaging sessions keep their source label after a sidebar refresh, and open + send correctly.** `_load_cli_sessions_uncached()` hardcoded `is_cli_session: True` for every state.db row; it now uses `is_cli_session_row()` so Discord/Telegram/Slack rows classify correctly, and the sidebar open/import path was extended (`_isMessagingSession()`) so opening a reclassified messaging session still imports it (no transient stub → no `/api/chat/start` 404 on the next send). ([#​3586](nesquena/hermes-webui#3586), [@​rodboev](https://github.com/rodboev)) ### [`v0.51.263`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051263--2026-06-04--Release-IE-stage-r13--codex-runtime-slash-command--activity-default-test) [Compare Source](nesquena/hermes-webui@v0.51.262...v0.51.263) ##### Fixed - **`/codex-runtime` (and `/codex_runtime`) now run as a WebUI slash command** instead of being sent to the model as a normal chat message — routed through the slash-command executor reusing the agent's shared `codex_runtime_switch` parser/apply logic. ([#​3621](nesquena/hermes-webui#3621), [@​luanxu-dev](https://github.com/luanxu-dev)) ##### Tests - Added regression coverage for the already-shipped `activity_feed_expanded_default` setting (default Activity-group expansion). ([#​3595](nesquena/hermes-webui#3595), [@​rodboev](https://github.com/rodboev)) ### [`v0.51.262`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051262--2026-06-04--Release-ID-stage-r12--zh-localization--Docker-reveal-path--recall-prefill-role-fix) [Compare Source](nesquena/hermes-webui@v0.51.261...v0.51.262) ##### Fixed - **WebUI no longer sends adjacent `user` roles to strict chat templates when recall prefill is configured.** A configured recall prefill could end with `role:user`, and both in-process streaming and Gateway chat then appended the live browser user turn — producing consecutive `user` messages that strict templates (Mistral/Gemma/Jinja) reject. A new `_normalize_prefill_messages_before_user_turn()` drops a terminal `user` prefill turn at that boundary (in both `api/streaming.py` and `api/gateway_chat.py`), preserving safe assistant prefill context. ([#​3432](nesquena/hermes-webui#3432), [@​franksong2702](https://github.com/franksong2702)) - **"Reveal in file manager" now works for Docker deployments** where the workspace is a host directory mounted into the container — container paths (e.g. `/workspace`) are translated back to the host path so the reveal resolves on the host. ([#​2558](nesquena/hermes-webui#2558), [@​rodboev](https://github.com/rodboev)) ##### Changed - **Completed the Chinese (Simplified) localization** — translated the remaining untranslated `zh` strings (MCP server controls, tool-list pagination/summary) to Chinese, preserving all interpolation placeholders. ([@​Lyr-GW](https://github.com/Lyr-GW)) ### [`v0.51.261`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051261--2026-06-04--Release-IC-stage-r11--live-Todos-panel-via-a-todostate-SSE-contract) [Compare Source](nesquena/hermes-webui@v0.51.260...v0.51.261) ##### Fixed - **The Todos side panel now tracks `todo` tool state live during an active run**, instead of staying stale until the turn settled and occasionally rolling back on a reload/SSE-reattach mid-stream. A dedicated `todo_state` SSE event emits a full, redacted, idempotent todo snapshot when the `todo` tool completes (no longer relying on the truncated `tool_complete.preview`); the same `api.todo_state` parser feeds both live emission and cold-load hydration; live snapshots persist into INFLIGHT so reload/reattach restores the panel before the next event; and cold-load vs INFLIGHT snapshots are reconciled by timestamp (including the `coldTs===0` compressed-session edge). The legacy reverse-scan fallback is kept for old servers / upgrade windows. ([#​3373](nesquena/hermes-webui#3373) follow-up, [@​v2psv](https://github.com/v2psv)) ### [`v0.51.260`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051260--2026-06-04--Release-IB-stage-r8--un-held-safety-fixes--cronTTSmcpstate-toast-batch) [Compare Source](nesquena/hermes-webui@v0.51.259...v0.51.260) ##### Fixed - **Self-update recovers safely from a stash-pop conflict without losing local changes.** Previously the conflict path ran `git reset --merge` then `git stash drop`, permanently discarding the user's local modifications while reporting success. It now keeps the stash, returns a clear "your changes are preserved in `stash@{0}`" message, and does not restart on conflict. ([#​3535](nesquena/hermes-webui#3535), [@​rodboev](https://github.com/rodboev)) - **Auth cookie `Secure` flag no longer locks out plain-HTTP LAN/Tailscale users.** `Secure` is now driven only by real TLS evidence (`HERMES_WEBUI_SECURE`, a TLS socket, or the opt-in `HERMES_WEBUI_TRUST_FORWARDED_PROTO` + `X-Forwarded-Proto`); a non-loopback plain-HTTP client is no longer force-marked Secure (which made browsers drop the cookie → login loop). SameSite stays `Lax`. ([#​1909](nesquena/hermes-webui#1909) slice 3, [@​rodboev](https://github.com/rodboev)) - **Clearer cron/gateway diagnostics for single-container Docker deployments** where gateway-backed chat is configured but no gateway daemon runs, so scheduled jobs silently don't fire. ([#​2785](nesquena/hermes-webui#2785), [@​franksong2702](https://github.com/franksong2702)) - **Long TTS responses no longer cut off partway.** Long assistant text is chunked at sentence boundaries before `SpeechSynthesis.speak()` / `/api/tts`, working around the browser's \~32K silent-truncation limit. ([@​lambyangzhao](https://github.com/lambyangzhao)) - **Persistent-state changes are now visible in chat.** A small success toast appears when an agent turn has **saved memory** or **created/updated a skill** during a normal WebUI turn. ([#​3340](nesquena/hermes-webui#3340), [@​rly09](https://github.com/rly09)) - **`/reload-mcp` now runs as a client command instead of falling through to the LLM as a prompt.** It is exposed by `/api/commands` and appeared in slash autocomplete but wasn't marked `cli_only`, so the WebUI dispatched it as a normal message. ([@​franksong2702](https://github.com/franksong2702)) ### [`v0.51.259`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051259--2026-06-04--Release-IA-stage-r7--edge-TTS-Content-Length--orphaned-toolcalls-strip) [Compare Source](nesquena/hermes-webui@v0.51.258...v0.51.259) ##### Fixed - **Edge-TTS playback no longer has a \~31s delay / playback error.** `_handle_tts` streamed audio without a `Content-Length` on an HTTP/1.0 server, so clients waited for connection close; audio is now buffered and sent with `Content-Length`. ([#​3582](nesquena/hermes-webui#3582), [@​rodboev](https://github.com/rodboev)) - **CLI-bridge message reconstruction strips orphaned `tool_calls`** (assistant `tool_calls` with no matching `tool` response, left by an aborted bridge) so the next request no longer 400s on strict providers. ([#​3583](nesquena/hermes-webui#3583), [@​rodboev](https://github.com/rodboev)) ### [`v0.51.258`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051258--2026-06-04--Release-HZ-stage-r6--update-banner-from-any-panel--inline-thinking-on-settlement) [Compare Source](nesquena/hermes-webui@v0.51.257...v0.51.258) ##### Fixed - The "update available" banner now appears from **any panel** (Settings → System "Check now", etc.), not just the Chat view — the banner element was positioned so it only rendered visibly on the Chat surface. ([#​3597](nesquena/hermes-webui#3597), [@​rodboev](https://github.com/rodboev)) - Under Simplified Tool Calling, an assistant turn that produced **thinking but no tool calls** now renders that thinking inline on settlement instead of burying it in an empty collapsed activity group. ([#​3592](nesquena/hermes-webui#3592), [@​rodboev](https://github.com/rodboev)) ### [`v0.51.257`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051257--2026-06-04--Release-HY-stage-r5--provider-refresh-route--SessionDB-self-heal) [Compare Source](nesquena/hermes-webui@v0.51.256...v0.51.257) ##### Fixed - **"Refresh Models" on a provider card no longer returns "Error: Not found".** The composer/Settings sent `POST /api/models/refresh` but no route matched, so it always 404'd. Added the route, wired to the existing `invalidate_provider_models_cache(provider_id)`. ([#​3546](nesquena/hermes-webui#3546), [@​rodboev](https://github.com/rodboev)) - **Credential self-heal no longer writes to a dead `SessionDB` handle.** When the first request triggered a credential refresh, the cached agent was evicted/closed but the retry rebuilt a new agent from kwargs still holding the old, closed `SessionDB` — so persistence silently targeted a dead handle. Per-request `SessionDB` construction is now centralized and refreshed on the self-heal retry. ([#​3548](nesquena/hermes-webui#3548), [@​franksong2702](https://github.com/franksong2702)) ### [`v0.51.256`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051256--2026-06-04--Release-HX-stage-r4--bound-WebUI-memory-growth-and-idle-CPU-on-large-installs) [Compare Source](nesquena/hermes-webui@v0.51.255...v0.51.256) ##### Fixed - **Bounded WebUI memory growth and idle CPU on large installs** ([#​3506](nesquena/hermes-webui#3506)). On a profile with hundreds of sessions and tens of thousands of messages, the WebUI Python process could climb from \~100 MB to \~1.5 GB RSS over several days and hold high CPU while idle. Three root causes were fixed: (1) the `session_lifecycle._sessions` dict grew without bound — entries were created but never removed — so every session id the WebUI ever touched leaked a permanent entry; a new safe `discard_session()` now drops the entry at the agent-eviction boundaries, but only when there is no in-flight commit and no uncommitted memory work (the retry invariant is preserved); (2) the in-memory agent/session cache sizes are now operator-tunable via `HERMES_WEBUI_AGENT_CACHE_MAX` and `HERMES_WEBUI_SESSIONS_MAX`, and the agent-cache default was lowered from 50 to 25 (each cached agent pins a full conversation transcript, so this is the dominant lever on resident memory); (3) the gateway session watcher re-ran an expensive per-session `MAX(messages.timestamp)` aggregation every 5 seconds even when nothing changed — it now first computes a cheap `sessions`-table-only fingerprint (\~15× cheaper, measured on a 1.5 GB state.db) and only runs the full projection when that fingerprint actually changes. ([#​3506](nesquena/hermes-webui#3506), [@​nesquena-hermes](https://github.com/nesquena-hermes); reported with detailed profiling by [@​djenttleman](https://github.com/djenttleman)) ### [`v0.51.255`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051255--2026-06-04--Release-HW-stage-r3--pid-scoped-turn-journal-shards-for-multi-process-safety) [Compare Source](nesquena/hermes-webui@v0.51.254...v0.51.255) ##### Fixed - The turn journal (the crash-recovery backbone for session state) now writes to **pid-scoped shards** (`{sid}~{pid}.jsonl`) instead of a single shared `{sid}.jsonl`, so two processes writing concurrently (e.g. during a self-restart overlap) can't interleave-corrupt each other's large JSON lines. `read_turn_journal` merges all shards plus the legacy file and sorts events by `created_at`, so recovery is unchanged and fully backward-compatible. Terminal events are now flagged explicitly. ([@​rodboev](https://github.com/rodboev)) ### [`v0.51.254`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051254--2026-06-04--Release-HV-stage-r2--mobilecancelscroll-fixes--custom-provider-model-dedup) [Compare Source](nesquena/hermes-webui@v0.51.253...v0.51.254) ##### Fixed - Client rendering no longer drops assistant-only partial-tool-call rows (`_partial_tool_calls`) from the visible transcript or fallback tool-card reconstruction after cancellation. This keeps interrupted turns visible and ensures interrupted tool calls still render as settled tool cards in history. ([#​3528](nesquena/hermes-webui#3528), [@​franksong2702](https://github.com/franksong2702)) - On Android, briefly backgrounding the WebUI no longer hard-reloads the whole page — offline recovery now soft-reattaches the live stream instead of a full reload, so you don't lose composer/scroll state on a transient disconnect. ([#​3550](nesquena/hermes-webui#3550), [@​lurebat](https://github.com/lurebat)) - On iOS Safari, inserting a handoff/compression card mid-stream (or reconnecting via `refreshSession()`) no longer snaps the conversation to the top. ([#​3479](nesquena/hermes-webui#3479), [@​mvanhorn](https://github.com/mvanhorn)) - Named custom providers (`@custom:name:model`) now deduplicate against bare model IDs correctly, without regressing Ollama multi-colon tags (e.g. `qwen2.5:7b-instruct-q4`) — only `@custom:`-prefixed IDs strip the two-segment prefix; everything else keeps the single-prefix strip. ([#​3478](nesquena/hermes-webui#3478), [@​JayC-L](https://github.com/JayC-L)) ### [`v0.51.253`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051253--2026-06-04--Release-HU-stage-r1--low-risk-batch-scrollbadgecounttest-fixes--compat-docs) [Compare Source](nesquena/hermes-webui@v0.51.252...v0.51.253) ##### Fixed - Long streaming responses no longer snap back to the bottom on completion when you've scrolled up to read — the final DOM-replace "follow" window is tightened (1200px → 120px) so only readers still at the tail get auto-scrolled. ([#​3525](nesquena/hermes-webui#3525), [@​TomBanksAU](https://github.com/TomBanksAU)) - The topbar transcript count now distinguishes a partially-loaded long transcript ("loaded of total" using the server-side `message_count`) instead of implying the loaded tail is the full conversation, and surfaces the older-message count on the "Load earlier messages" affordance. Fully-loaded transcripts keep using the tool-row-filtered loaded count. ([@​ai-ag2026](https://github.com/ai-ag2026)) - Sidebar rows now render messaging source badges (Telegram, Discord, etc.) as chips, not just CLI ones — the sidebar badge was still gated on `is_cli_session` after [#​3502](nesquena/hermes-webui#3502) fixed the topbar. ([#​3502](nesquena/hermes-webui#3502) follow-up, [@​rodboev](https://github.com/rodboev)) - The `.pre-header+pre` margin override is now scoped under `.msg-body` so it wins over `.msg-body pre`, removing the unwanted 10px gap between a file-header bar and its code block. ([@​Karlineal](https://github.com/Karlineal)) ##### Tests - `test_ctl_script.py` now kills orphan fake-python process trees on Windows (MSYS2 signal propagation is unreliable). ([#​3537](nesquena/hermes-webui#3537), [@​rodboev](https://github.com/rodboev)) - conftest `_discover_python` now checks the Windows venv layout (`Scripts/python.exe`) so the test server doesn't silently start with the wrong Python. ([#​3577](nesquena/hermes-webui#3577), [@​rodboev](https://github.com/rodboev)) ##### Docs - Documented an explicit WebUI–Agent compatibility policy: separate the displayed WebUI runtime version from the tested/pinned pair compatibility contract, and clarified Docker upgrade pinning guidance to keep releases aligned until the stable boundary work in [#​1925](https://github.com/nesquena/hermes-webui/issues/1925)/[#​2491](https://github.com/nesquena/hermes-webui/issues/2491) lands. ([#​3232](nesquena/hermes-webui#3232), [@​franksong2702](https://github.com/franksong2702)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/835
## Release v0.51.261 — Release IC (stage-r11) Live Todos panel via an explicit `todo_state` SSE contract. ### Fixed | Issue/PR | Author | Fix | |----------|--------|-----| | nesquena#3373 follow-up (nesquena#3454) | @v2psv | The Todos side panel now tracks `todo` tool state **live during an active run** instead of staying stale until settle / rolling back on a mid-stream reload. A dedicated `todo_state` SSE event sends a full, redacted, idempotent snapshot on todo-tool completion (no more truncated `tool_complete.preview`); the same `api.todo_state` parser feeds live + cold-load; live snapshots persist into INFLIGHT so reload/reattach restores the panel; cold-load vs INFLIGHT reconciled by timestamp (incl. the `coldTs===0` compressed-session edge); legacy reverse-scan kept as fallback for old servers. | ### Gate - Full pytest suite: **7692 passed, 0 failed** - ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN - Codex (regression): **SAFE TO SHIP** — verified the new `todo_state` SSE handler composes with existing dispatch (no double-subscribe), INFLIGHT persistence is cleared on terminal/cancel (composes with discard_session + turn-journal), timestamp reconciliation can't let a stale local snapshot win, redaction holds, the legacy reverse-scan fallback still works with no double-render, and the `models.py` change is todo-scoped (no CLI-classification interaction). Co-authored-by: v2psv <v2psv@users.noreply.github.com>
Summary
This fixes cold-load/reload behavior for the existing Todos panel.
api/todo_state.pyto derive a compacttodo_statesnapshot from the full settled session transcript.todo_stateto/api/session?messages=1responses for both WebUI sessions and CLI-backed sessions.session.todo_statewhen available, while keeping the old reverse-scan over raw messages as a fallback for old servers / upgrade windows.todos: []snapshot as valid current state, so a cleared task list no longer falls through to an older non-empty todo write.Why this fix
The Todos panel already exists and already tries to reconstruct todo state from tool messages. The bug is that reload/session reopen can display stale or missing state when:
msg_limitwindow, ortodos: []), which the legacy frontend treated as "keep scanning" and could replace with an older non-empty list.This PR only makes the existing panel hydrate from the server's authoritative settled transcript.
Test plan
Ran locally with the project venv:
Result:
Additional gates:
Result: