Commit Graph
100 Commits
Author SHA1 Message Date
nesquena-hermesandperejaslav d56123f796 feat: add max reasoning effort level (salvage of #4627)
Salvaged from #4627 by @perejaslav. Core Hermes already supports
reasoning_effort=max; this closes the WebUI parity gap (Anchor-A) — adds
max to VALID_REASONING_EFFORTS + dropdown/chip/reasoning-command, keeps
the degrade ladder for capped models.

Co-authored-by: perejaslav <perejaslav@users.noreply.github.com>
2026-07-11 01:13:36 +00:00
nesquena-hermesandigorroncevic 89408b7b2d test: skip fail-closed pricing test on openrouter-group pollution
The pricing3-null case failed on one CI shard with StopIteration: pytest-shard's
partitioning interleaves this test with modules that mutate config.cfg away from
the openrouter active provider, so get_available_models() omits the openrouter
group and next(...) raised. Make the group lookup resilient (next(..., None) +
pytest.skip) — the exact defensive guard the sibling live-fetch tests in this
file already use for the same pollution. Passes both with and without hermes_cli.

Co-authored-by: igorroncevic <igorroncevic@users.noreply.github.com>
2026-07-10 16:54:26 +00:00
nesquena-hermesandstarship-s 07b3f411d7 fix: preserve same-session load ownership on redundant reload + guard hint cleanup
Gate review (Codex) reproduced two SILENT regressions in the generation-token
ownership change, both validated against PR head + merge base:

1. sessions.js:1428 — the no-op early-return did NOT fire for a redundant
   same-session load (currentSid===sid, !_loadingSessionId was false because a
   keep-stale refresh was in flight). The redundant loadSession() then bumped
   _loadSessionGeneration and CANCELLED the authoritative in-flight refresh —
   stale rows remained while message_count advanced, so later polling could not
   detect them. Restore the pre-existing behavior: also return when the in-flight
   load is for the SAME sid (!_loadingSessionId || _loadingSessionId===sid).

2. sessions.js:2781 — _ensureMessagesLoaded's finally cleared the
   transcript-width reload hint unconditionally, so a stale same-session response
   collapsed a long session to the default tail window. Guard the cleanup with
   _ownsLoad() so only the owning load clears the hint.

Both fixes retain the PR's cross-session, same-session-rejection, and INFLIGHT
race behavior (47 tests pass incl. the new isolation suite + the 4 edited files).

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-07-10 16:52:37 +00:00
nesquena-hermesandigorroncevic 2dc6530f2b test: guard optional hermes_cli import in fail-closed pricing test
The new test_openrouter_free_tier_pricing_fails_closed did an unguarded
'from hermes_cli import models', but hermes_cli (the agent package) is an
optional dependency not installed in the WebUI CI test environment, so the
test failed with ModuleNotFoundError on 4-5 shards (master green, PR head red).

Wrap the import in the same try/except guard the sibling tests in this file
already use — when hermes_cli is present we force its live-fetch to empty so
the fail-closed static path is exercised; when absent, that static path is
already the only one and the api/config.py fix under test still runs via the
mocked urlopen. Verified: 10/10 with hermes_cli, 5/5 with it blocked.

Co-authored-by: igorroncevic <igorroncevic@users.noreply.github.com>
2026-07-10 16:49:18 +00:00
nesquena-hermes 898f46dd3d docs(changelog): #5789 gateway SSE terminal idle-timeout 2026-07-09 08:04:03 +00:00
nesquena-hermesandai-ag2026 dfd9b57183 fix(gateway): keep 600s read-timeout default (gate finding — 120s would kill legit long silent tool calls)
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 08:03:02 +00:00
nesquena-hermes 087d469ee8 docs(changelog): #5557 centralize static-asset resolution 2026-07-09 06:29:01 +00:00
nesquena-hermes 1c70f7b987 docs(changelog): #5803 session-load perf 2026-07-09 06:06:43 +00:00
nesquena-hermes e34afa58b8 docs(changelog): #5800 streaming lock-free contract doc 2026-07-09 05:50:54 +00:00
nesquena-hermes 264e733fce docs(changelog): #5768 localize background-wakeup label 2026-07-09 04:36:32 +00:00
nesquena-hermes 9b1fb1f3dc docs(changelog): #5798 single-flight health probe 2026-07-09 03:54:39 +00:00
nesquena-hermes cbf949f6a8 docs(changelog): #5799 run-journal next-seq cache 2026-07-09 03:49:40 +00:00
nesquena-hermes dadd6f117c docs(changelog): #5801 login retry clearInterval 2026-07-09 03:42:12 +00:00
nesquena-hermes 8c96d42be3 docs(changelog): #5766 background wakeup status rows 2026-07-09 03:22:27 +00:00
nesquena-hermes dd3ffda0b6 docs(changelog): #5762 service-tier from agent metadata 2026-07-09 03:17:25 +00:00
nesquena-hermes 1d3cf5028b docs(changelog): #5773 single reasoning renderer path 2026-07-09 03:13:03 +00:00
nesquena-hermes 7b6a23f902 docs(changelog): #5770 code-block containment 2026-07-09 03:08:48 +00:00
nesquena-hermes db7b146de9 docs(changelog): #5721 workspace switcher a11y 2026-07-09 02:41:05 +00:00
nesquena-hermes e0a23ba0f5 docs(changelog): #5729 sidebar child-stream bucket 2026-07-09 02:36:20 +00:00
nesquena-hermes 6c8944450c docs(changelog): #5786 terminal EventSource error recovery 2026-07-09 02:26:49 +00:00
nesquena-hermes 5f2ec9e1f0 docs(changelog): #5761 OpenCode Go catalog refresh 2026-07-09 01:23:33 +00:00
nesquena-hermes a1aca345e1 docs(changelog): #5782 drain-loop backoff 2026-07-09 01:18:50 +00:00
nesquena-hermes 80276850a6 docs(changelog): #5785 passkey-login Content-Length 2026-07-09 01:05:56 +00:00
nesquena-hermes 2c46e9e251 docs(changelog): #5787 metering session begin/end pairing 2026-07-09 00:51:59 +00:00
nesquena-hermes d4120b47b5 test(updates): update timeout-guard assertions for the channel-threaded apply/force bodies 2026-07-08 22:52:53 +00:00
nesquena-hermes 236a2c8032 fix(updates): thread offered channel through apply/force to close the debounce race
Codex gate CORE: /api/updates/apply + /force ignored the request-body channel
and re-read the saved setting, so a channel switch whose debounced autosave
hadn't landed could apply the OLD channel. The banner now sends the channel the
CHECK reported for each target; the endpoints validate it against the enum and
thread it into apply_update/apply_force_update (None → saved-setting fallback,
preserving prior behavior). Agent stays channel-neutral server-side.
2026-07-08 22:42:54 +00:00
nesquena-hermes 37adf8b7ce docs(changelog): #5784 evict run-journal writer-lock cache on delete 2026-07-08 21:14:04 +00:00
nesquena-hermes 81a77efc91 docs(changelog): #5783 serialize daemon-thread start under lifecycle lock 2026-07-08 20:58:54 +00:00
nesquena-hermes 4f9d63218b docs(changelog): #5781 sweep completion-dedup map by delivery lifecycle 2026-07-08 20:42:35 +00:00
nesquena-hermes 3a8de74758 docs(changelog): #5780 open state.db read-only for lineage reads + watcher 2026-07-08 20:18:25 +00:00
nesquena-hermes c95c1f492d docs(changelog): #5772 scope MCP list reads to active profile 2026-07-08 16:19:34 +00:00
nesquena-hermes e4eb330da5 docs(changelog): #5751 non-virtualized transcript scroll jump-back 2026-07-07 21:17:41 +00:00
nesquena-hermes b40bc50d67 docs(changelog): #5742 desktop stale-snapshot scroll compensation 2026-07-07 20:13:23 +00:00
nesquena-hermes 5915f9b5b5 docs(changelog): #5463 mirror WebUI usage counters to state.db 2026-07-07 20:05:18 +00:00
nesquena-hermes cf769dc823 docs(changelog): #5723 clear dead steer busy state 2026-07-07 16:42:08 +00:00
nesquena-hermesandrodboev 2717ea0ac5 feat(chat): add final answer only activity display mode (#5706, picker grid fixed)
Clean rebase of rodboev's #5746 (rebase-first).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-07 14:38:08 +00:00
nesquena-hermes b65158c0ce docs(changelog): #5701 iPad keyboard composer inset 2026-07-07 05:16:34 +00:00
nesquena-hermes 1a2dcc7231 docs(changelog): #5737 drop empty tool_calls before API send 2026-07-07 04:52:55 +00:00
nesquena-hermes f36dad7b23 test(tls): drain full stdout for the TLS-fallback assertion (flake fix)
test_tls_startup_failure_fallback_to_http read only the first 2000 bytes of
server stdout to assert 'TLS setup failed'. When the test interpreter lacks
optional deps (requests/websockets), startup emits a burst of plugin/tool
import warnings + the startup-config banner BEFORE the TLS line, pushing the
marker past the 2000-byte window -> spurious failure. Drain all
currently-available stdout (5s deadline, short-circuit on the marker) instead
of a single fixed-size read. Verified failing->passing in a fresh venv without
the optional deps. Test-only; distinct from the agent-package-isolation flake
cause already fixed in the same test.
2026-07-07 04:09:15 +00:00
nesquena-hermes 4fb7b03200 docs(changelog): #5567 model-picker provider resolution fix 2026-07-07 03:52:04 +00:00
nesquena-hermesandb3nw 8d496ceaef fix(#5567): resolve model provider by matching value, not the selected option
Frontend fix for the model-picker provider-contamination vector of #5567 (the
backend HERMES_HOME vector shipped in #5577). Credit @b3nw for the root-cause trace.

_modelStateForSelect(sel, modelId) resolved the provider from sel.selectedOptions[0]
(the dropdown's CURRENTLY-selected option) instead of the option whose value matches
modelId. During a profile/tab switch or a model-list rebuild the dropdown transiently
still holds the PREVIOUS profile's default option (e.g. an ollama model), so the wrong
provider was stamped onto a model it doesn't own, persisted into the session's
model_provider, and (since _modelProviderForSend reads the stored value first) re-sent
on every turn -> 'Provider ollama...no API key' for a provider the session never used.

Fix: resolve the provider from the option whose VALUE === modelId; trust the
currently-selected option only when its value matches (preserving the user's exact
pick in the same-value/different-provider collision case). A genuinely-absent model
yields a null provider so the backend re-infers.

This is the WRITE-site fix — it stops all NEW contamination (new-session + send paths
route through _modelStateForSelect). Both Codex and Fable reviewed it SAFE.

Repairing sessions ALREADY poisoned before this shipped is deliberately deferred to a
separate follow-up: a 3-round Codex+Fable gate proved a frontend load-time repair that
reads the shared #modelSelect DOM is a cross-session race surface (it re-manufactured
the reverse-contamination it aimed to fix), and it can't reliably heal live-only-model
sessions anyway. The robust place for that repair is the backend chat-start boundary,
where session + profile identity is unambiguous. Already-poisoned sessions self-heal on
their next healthy send / manual re-pick in the meantime.

Tests: node-behavioral resolver scenarios (tab-switch race, same-value collision,
missing-model null, match-by-value, default->null) + the unrelated-selected-option
case from #5727. Partially addresses #5567 (write-vector); follow-up tracks the
already-poisoned-session repair.

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-07-07 03:42:16 +00:00
nesquena-hermes eb02c8ecd0 docs(changelog): Windows git subprocess console-window fix (#5692) 2026-07-07 02:19:40 +00:00
nesquena-hermes 6c7746aef6 docs(changelog): iteration-limit graceful summary (#5717, #5494) 2026-07-07 01:44:48 +00:00
nesquena-hermes 16c3b4d91e docs(changelog): add Czech locale entry (#5546) 2026-07-06 21:37:22 +00:00
nesquena-hermes e7fa04ef24 merge master into review-5655 (base refresh for up-to-date branch protection) 2026-07-06 03:05:41 +00:00
nesquena-hermesandFrank Song f501898791 release: show compression-recovery card from session state on rebuilt sessions (#5655)
Follow-up to #5538: the recovery card now also renders from _activeCompressionRecoveryPayload() on the final assistant message when a rebuilt/legacy session lacks the per-message _compressionRecovery marker (guarded to last/turn-final assistant only; no double-render; cleared recovery still hidden). Gate: Codex SAFE, Fable SHIP-UX, suite 12179/0, browser clean.

Co-authored-by: Frank Song <franksong2702@gmail.com>
2026-07-06 02:56:55 +00:00
nesquena-hermes 8929977b5b review: merge #5655 (77f54eeb) 2026-07-06 02:46:52 +00:00
nesquena-hermesandRod Boev 7d3057c7c9 release: reload config cache on profile switch (#5645, #5619)
Root fix for the #5619 profile-switch config-cache bug: split the reload gate so a profile-PATH change forces reload unconditionally (was wrongly suppressed by in-memory overrides → stale default-profile config/models/keys), while a file-mtime change still respects overrides. Folded in a test-isolation fix (Codex): the stage302 regression test leaked _cfg_cache/_cfg_mtime/_cfg_path/_cfg_fingerprint/cfg globals (deepcopy snapshot + clear/update restore, since reload_config mutates _cfg_cache in place). Full gate: Codex SAFE (2 rounds), suite 12178/0, 121 profile/provider tests.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-06 02:33:45 +00:00
nesquena-hermes f7fe895c63 review: merge #5645 (00a69521) 2026-07-06 02:09:22 +00:00
nesquena-hermesandRod Boev c601e11324 release: fork read-only cron sessions into a new writable chat (#5555)
Relaxes the /api/session/branch read-only gate so a canonical-cron session can be forked into a new WebUI-owned session (source never saved). Gate hardening folded in: the read-only branch gate now covers BOTH the synthesized/missing-sidecar path AND the persisted/loaded path (Codex found a stored read-only non-cron session could be branched + saved); relaxation keys on the server-authoritative resolved source (source_tag/raw_source/source == 'cron'), never the id prefix. Full gate: Codex adversarial SAFE, 84 branch/claim tests + suite 12175/0, browser clean.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-06 02:02:52 +00:00
nesquena-hermes f10cb2a35c review: merge #5555 (11eed2af) 2026-07-06 01:44:21 +00:00
nesquena-hermesandRod Boev 936c5b051b release: redesign cron preset schedule builder — sentence UI with time picker + day dropdowns (#5554)
Reworks the #5552 preset builder per maintainer UX feedback + Fable design advisory: one natural 'Schedule: [preset] on [day] at [time]' row; native time picker (themed) replacing hour/minute number boxes; day-of-week + day-of-month dropdowns; raw cron field shown only on Custom; live cron preview; Custom last; new jobs default to Daily 09:00; keystroke-clamp + mid-typing-hide bugs fixed. New i18n keys across all 14 locales. Full gate: Codex SAFE, Fable SHIP-UX, suite 12160/0, browser clean.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-06 01:35:55 +00:00
nesquena-hermes 13ecbd79fb review: merge #5554 (2e27ff64) 2026-07-06 00:38:30 +00:00
nesquena-hermesandRod Boev 02e18d4d69 release: restore prose-level fade for Transparent Stream (#5506)
Re-adds the per-word prose fade for newly-streamed assistant text in Transparent Stream WITHOUT reintroducing the row-level entrance flicker (#5367). Reduced-motion honored at both JS and CSS layers; thinking/tool rows not animated. Option A: the fade is part of Transparent Stream regardless of the off-by-default 'Fade text effect' toggle; settings description clarified accordingly. Gate: Codex SAFE, Fable SHIP-UX, suite 12158/0, browser-smoke clean.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-06 00:26:45 +00:00
nesquena-hermes e6b2ac9119 review: merge #5506 (0c1bdce0) 2026-07-06 00:16:29 +00:00
nesquena-hermesandRod Boev c2e9277c46 release: reuse the rich model picker in Preferences (#5502)
Generalizes renderModelDropdown() to serve both the composer and Preferences pickers (backward-compatible defaults keep the composer identical). Folded in Fable UX fixes: label->chip association, downward shadow for the down-opening settings picker, and mobile keyboard-pop suppression across all 3 render paths (open/internal-filter/refresh) with focus preserved while typing. Full gate (4 rounds): Codex SAFE, Fable SHIP-UX, suite 12153/0, browser clean; composer picker verified unchanged.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-05 23:22:31 +00:00
nesquena-hermes d5dae70c43 review: merge #5502 (087c694c) 2026-07-05 22:35:58 +00:00
nesquena-hermesandRod Boev eef981d3dc release: copyable workspace relative paths (#5548)
Adds 'Copy relative path' to the workspace file-tree context menu + preview header. Folded in gate fixes: preview-header button folds to icon-only on a narrow pane via @container rightpanel (not viewport @media), with data-i18n-title/aria-label so the tooltip+accessible name localize. Full gate: Codex SAFE, Fable SHIP-UX, suite 12147/0, browser clean.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-05 22:26:31 +00:00
nesquena-hermes f53bfb6c97 review: merge #5548 (bac619e767) 2026-07-05 21:57:42 +00:00
d76d11abb4 release: tiny-fix batch — sidebar auto-refresh on new session (#5632) + composer-divider hidden when left group empty (#5499)
Both gate-clean: Codex SAFE, Fable SHIP-UX, suite 12140/0, browser-smoke clean. #5499 supersedes #5451.

Co-authored-by: nankingjing <1079826437@qq.com>
Co-authored-by: wangyichen <wang.echen@foxmail.com>
2026-07-05 21:39:01 +00:00
nesquena-hermes 57a8467266 stage: merge #5499 (aa201b1550) 2026-07-05 21:21:35 +00:00
nesquena-hermes baddaccca1 stage: merge #5632 (8fc3179a8a) 2026-07-05 21:21:34 +00:00
nesquena-hermesandFrank Song 76639b34f0 release: compression-recovery action — one-click focused continuation on compression_exhausted (#5538)
Folds in gate fixes: map the stale-card 409 to a neutral info toast + retire the card (finally-guard so it stays retired), + consumed-state CSS. Full gate: Codex SAFE, Fable SHIP-UX, suite 12140/0, browser-smoke clean.

Co-authored-by: Frank Song <franksong2702@gmail.com>
2026-07-05 21:14:53 +00:00
nesquena-hermes 5d81a317ec stage: merge #5538 (d284fcd0) 2026-07-05 20:51:59 +00:00
nesquena-hermes 665b7b47df release: complete 664 remaining UI translations across 8 locales (#5535) + CHANGELOG 2026-07-05 17:43:45 +00:00
nesquena-hermes 79e485a038 stage: merge #5535 (c1aa45fceb) 2026-07-05 17:43:11 +00:00
nesquena-hermes adce95d0e3 release: fix live mobile scroll jump-back regression (#5637) — scope content-visibility to user rows + recover recycled virtual-scroll anchor (#5638, #5635) + CHANGELOG 2026-07-05 17:32:53 +00:00
nesquena-hermes 74d807a07c stage: merge #5638 (f657014aa3) 2026-07-05 17:32:22 +00:00
nesquena-hermes c0b0321081 stage: merge #5635 (7811b13f49) 2026-07-05 17:32:22 +00:00
nesquena-hermes c6d957904a release: low-risk docs+tests batch — env var docs (#5536), gateway approval docs (#5549), utf-8 test reads (#5537), messaging-clear watermark test (#5589) + CHANGELOG 2026-07-05 17:19:27 +00:00
nesquena-hermes 2fe1976eac stage: merge #5589 (cc7896cfab) 2026-07-05 17:10:06 +00:00
nesquena-hermes dc4c31ee1b stage: merge #5549 (0ec29b14ec) 2026-07-05 17:10:06 +00:00
nesquena-hermes 278e506468 stage: merge #5537 (0fd954fe78) 2026-07-05 17:10:06 +00:00
nesquena-hermes 806c15b417 stage: merge #5536 (4bfc9c8aad) 2026-07-05 17:10:05 +00:00
nesquena-hermes 8230785097 release #5626: exclude inactive compacted state-db rows from context replay + CHANGELOG 2026-07-05 09:14:40 +00:00
nesquena-hermes 1165bd9ca2 fix(#5626): ignore inactive compacted state-db rows in context replay
get_state_db_session_messages() returned archived (active=0) rows that in-place
compaction had marked inactive. WebUI reconciliation feeds this reader straight
into the next model-facing context (reconciled_state_db_messages_for_session,
prefer_context=True), so those archived rows got pulled back in — resurrecting
pre-compaction history and making every later turn re-trigger compression. Now,
WHEN the messages table exposes an `active` column, inactive rows are excluded by
default (`AND (active IS NULL OR active != 0)` — NULL/legacy rows preserved), with
an `include_inactive=True` escape hatch for explicit recovery/audit callers.
Schemas without the column are unaffected (guarded by `'active' in available`).

Contributor stage; ungated on arrival. Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-05 08:59:21 +00:00
nesquena-hermes 3051dc4dda release #5588: reset memoized password-hash cache around every test (isolation flake) + CHANGELOG 2026-07-05 08:44:41 +00:00
nesquena-hermes 4c9690bf59 fix(#5588): reset memoized password-hash cache around every test (isolation flake)
tests/test_issue803.py::TestProfileCookieHelpers failed when run after
tests/test_session_static_assets.py (passed in isolation). Root cause:
api.auth.get_password_hash() memoizes the resolved hash process-wide
(_AUTH_HASH_CACHE / _AUTH_HASH_COMPUTED) and is NOT keyed on the
HERMES_WEBUI_PASSWORD env var. test_session_static_auth_exemption sets that env
var + populates the cache; monkeypatch pops the env on teardown but the cache
stays populated, so is_auth_enabled() reads stale True and build_profile_cookie
raises "requires a request handler when auth is enabled".

Fix: an autouse conftest fixture (_reset_password_hash_cache) that invalidates the
cache before AND after every test — matching the existing _isolate_hermes_config_path
/ _restore_profile_home_globals autouse-reset convention. Fixes the whole class
regardless of which test sets the password; no-op when auth is off. Adds a
direct regression test (cache a password, pop env, assert auth reads disabled +
build_profile_cookie doesn't raise). Paired-run repro now passes.
2026-07-05 08:35:14 +00:00
nesquena-hermes 4f0b6576b5 release #5582: keep fork children out of compression stitch + CHANGELOG 2026-07-05 08:16:30 +00:00
nesquena-hermes dbc2345fea fix(#5571): keep fork children out of compression stitch
_webui_sidecar_lineage_messages_for_display stitches a session's pre-compression
snapshot parents for full-transcript display. A cleared fork child could resurrect
its ORIGINAL (non-fork) parent's transcript when that parent was later compressed.
Now, when the root session is itself a fork (session_source=='fork'), the stitch
walk allows fork-sourced snapshot parents but stops at the first non-fork
ancestor — so a compressed fork continuation still shows its own pre-compression
turns, while an ordinary fork child stays isolated from the original parent.
Additive guard (one extra break condition); non-fork lineage display unchanged.

Contributor stage; gate-pass. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 08:07:38 +00:00
nesquena-hermes 1e4d237cc2 release #5459: include attachments in steer submissions (+ retry-dedup + identity chip clear) + CHANGELOG 2026-07-05 07:20:24 +00:00
nesquena-hermes c78d5083ad fix(#5459): dedup steer upload on retry + remove only delivered files by identity (gate)
Codex gate found two SILENT defects, fixed:
1) Failed-steer RETRY re-uploaded the same File objects (uploadPendingFiles ran
   again with clearPending:false). Added _steerUploadCache keyed by ownerSid +
   file signature so a retry with the same staged set reuses the uploaded paths;
   invalidated when the staged set changes (signature miss) or on accepted steer.
2) Accepted steer cleared ALL S.pendingFiles, dropping files staged during the
   upload/API await. Now removes ONLY the delivered snapshot by object identity,
   preserving newly staged files.

(The owner-session draft is still cleared on accepted steer — clearing the
delivered session's draft is correct even after a mid-upload switch; the existing
test asserts this.) Tests updated for the identity-based removal + new
cache/dedup guard. 69 steer/composer tests pass, node --check clean.
2026-07-05 07:10:23 +00:00
nesquena-hermes ceea2438eb fix: include attachments in steer submissions
Steering (submitting mid-stream) dropped any pending composer attachments. Now the
pending files are uploaded before the /steer payload is sent, the uploaded file
paths are appended to the steer text so the active agent can inspect them with
tools, file chips stay staged if the steer fails (so nothing is lost), and
file-only busy submissions are handled.

Contributor stage; gate-pass. Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-05 06:50:34 +00:00
nesquena-hermes 7ad9707c4b fix(ui): align mobile sidebar close (X) with the new-conversation (+) button
On the mobile sidebar header the close (X) button rendered an 18px glyph — the
only 18px .panel-head-btn icon — sitting ~6px lower than the adjacent 14px
new-conversation (+) button, an inconsistent pair. Match the X glyph to 14px and
vertically center it on the ~41px panel-head row so both icons share one size and
baseline (top:calc(safe-top - 2px) keeps the existing safe-area offset so the X
still clears the notch, with no extra drawer spacing). 44x44 tap target kept for
mobile touch; mobile @media(max-width:640px) only, and the X is display:none on
desktop, so desktop is unaffected. + CHANGELOG + source-guard tests.
2026-07-05 06:40:26 +00:00
nesquena-hermes 9d85c23c5f release #5602: iOS PWA session-switch instant sidebar close + race guard + CHANGELOG 2026-07-05 05:43:45 +00:00
nesquena-hermes 4559ca85f0 fix(#5409): iOS PWA session-switch — close sidebar instantly + prevent _loadingSessionId race
Two iOS-PWA bugs when tapping an old/large session:
1) closeMobileSidebar() ran AFTER the slow `await _openSidebarSession()` (3-15s
   for large sessions on WKWebView), so the sidebar stayed open with only a tiny
   spinner. Moved closeMobileSidebar() to run synchronously BEFORE the await in
   the tap handler + lineage/child open paths for instant feedback.
2) _loadingSessionId race: an SSE-triggered idle reload
   (_scheduleActiveSessionIdleReload / refreshActiveSessionIfExternallyUpdated,
   fed by idle-reconcile/poll/visibility/focus) could overwrite _loadingSessionId
   and silently cancel an in-flight session switch. Both paths now skip while a
   different session's loadSession() is in flight.

Contributor stage; gate-pass. Co-authored-by: luperrypf <luperrypf@users.noreply.github.com>
2026-07-05 05:42:51 +00:00
nesquena-hermes f864c276ff release #5611: opencode-go uses curated static model list (no probe 404s) + CHANGELOG 2026-07-05 05:14:44 +00:00
nesquena-hermes 14ad45bd1f fix(#5311): skip live /v1/models probe for opencode-go — use curated static list
OpenCode Go's /v1/models endpoint returns models from the full public catalog that
are NOT enabled on the Go tier; selecting any such probe-only model 404s
('model not found') when the chat request is sent. get_available_models now skips
the live probe for opencode-go (bare branch) so the next `if not raw_models` falls
through to the curated static _PROVIDER_MODELS['opencode-go'] list — the actual
Go-tier models. Narrow, provider-scoped; no other provider affected.

Contributor stage; gate-pass. Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-05 05:05:30 +00:00
nesquena-hermes fbd3c8c26b release #5505: session-list retry feedback + a11y + stuck-retry guard + CHANGELOG 2026-07-05 04:58:04 +00:00
nesquena-hermesandrodboev f6cc48b834 fix(#5501): clear pending retry state on render invalidation (Codex gate)
Codex re-gate SILENT: a retry whose fetch is invalidated mid-flight (e.g. a
profile switch) left the button stuck as an inert 'Retrying…' with no request in
flight — the stale catch returns before _showSessionListLoadError and the
.finally() bails when the skeleton removed the old button. _invalidateSessionListRenders()
now clears the pending retry markers (retrying, _retryFailedFocus) so the next
repaint shows an actionable idle Retry. Regression test added (fails without the
fix); test harness extracts _invalidateSessionListRenders + declares its render-gen
globals.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:48:50 +00:00
nesquena-hermesandrodboev 9be4048c72 fix(#5501): fold in Fable a11y hardening — live region, aria-disabled, focus restore, ellipsis
Fable UX gate SHIP-WITH-UX-FIXES (non-blocking). Applied all: (1) role=status +
aria-live=polite on the error note so retry failure/restore is announced to
screen readers; (2) pending state uses aria-disabled (not the disabled property)
so the button retains keyboard focus, with click/keydown guards keeping it inert;
(3) on a failed retry, the fresh rebuilt Retry button reclaims keyboard focus
(_retryFailedFocus flag, rAF-guarded) so keyboard users aren't dropped to <body>;
(4) label uses a true ellipsis 'Retrying…' matching the app's other progress
string. CSS styles [aria-disabled=true] alongside [aria-busy]. Tests updated +
new a11y assertions; 8/8 pass, node --check clean.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:39:14 +00:00
nesquena-hermes 7349a9434f fix(#5501): improve session-list retry feedback
The sidebar's load-error Retry button behaved like a raw idle browser button while
the next slow fetch was pending, so it looked broken/unresponsive. Retry now paints
an immediate pending state (label 'Retrying...', disabled, aria-busy), keeps the
retry state local to the error block via _renderSessionListLoadErrorNote(), and
lets the existing success/failure paths replace the error object when the request
settles (restoring 'Retry' on failure). Also adds proper styling.

Contributor stage; gate-pass. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:39:14 +00:00
nesquena-hermes 2346f4b7f8 release #5539: raise mobile approval-card height cap + CHANGELOG 2026-07-05 04:18:09 +00:00
nesquena-hermes 39a4d67e5c fix(#5385): increase approval card height cap on mobile
The mobile command/tool approval popup rendered too small — options didn't all fit
and required scrolling within the cramped popup to select (Android WebUI wrapper).
Raises the mobile @supports(dvh) .approval-inner max-height cap from
min(52dvh,360px) to min(60dvh,420px) so the options fit without in-popup scrolling;
still overflow-y:auto so very tall content scrolls rather than clipping. Scoped to
the mobile dvh rule; desktop unaffected. Adds a source-level guard test.

Contributor stage; gate-pass. Co-authored-by: nankingjing <nankingjing@users.noreply.github.com>
2026-07-05 04:04:02 +00:00
nesquena-hermes c21a7629f4 release #5534: CORS preflight echoes only same-origin/allowlisted origins + CHANGELOG 2026-07-05 02:45:07 +00:00
nesquena-hermes 8da96a84d3 refactor(#5534): move CORS preflight header logic into api/routes (keep server.py thin)
The initial fix added ~16 lines to server.py, tripping test_server_py_under_750_lines
(server.py must stay a thin dispatcher; logic belongs in api/). Moved the whole
preflight-response into apply_cors_preflight_headers(handler) in api/routes.py;
server.py's do_OPTIONS is now a 4-line dispatcher. server.py 764 -> 746 lines.
Same security behavior (same-origin/allowlist echo, Vary: Origin, no wildcard,
header-less denial); test rewritten to the header-emitting API + added Vary and
no-header-on-denial assertions and a routes.py wildcard regression guard.
2026-07-05 02:35:10 +00:00
nesquena-hermes 4e8978f04c fix(security): CORS preflight echoes only same-origin or allowlisted origins
do_OPTIONS answered every CORS preflight with Access-Control-Allow-Origin: *,
advertising broader cross-origin access than a real request is granted — on a
password-less deployment that lets any site read authenticated responses. It now
echoes the request Origin only when _check_same_origin_browser_request approves
it (the exact same-origin / HERMES_WEBUI_ALLOWED_ORIGINS policy the CSRF gate
enforces for real requests), adds Vary: Origin, and never emits *. A disallowed
origin gets a 200 with no CORS headers (browser treats as preflight denial).
Default deployments (no allowlist) are unaffected.

Contributor stage; gate-pass. Co-authored-by: mo7al876any <mo7al876any@users.noreply.github.com>
2026-07-05 02:18:51 +00:00
nesquena-hermes d1f18ac47c release #5584: skip clear-sentinel backups during recovery + CHANGELOG 2026-07-05 02:11:33 +00:00
nesquena-hermes bb818a56ba fix(#5570): also suppress restore when post-clear messages exist (gate fix)
Codex gate CORE: the exact-empty sentinel only suppressed restore for a bit-empty
cleared sidecar. If the user cleared, then sent ONE post-clear message, and the
stale pre-clear .json.bak still existed (unlink failed), recovery saw
bak_count>live_count and RESTORED the pre-clear transcript on top of the new
message. Added _live_supersedes_backup_by_clear_generation: suppress restore when
the live sidecar carries a clear_generation the backup lacks AND has a non-empty
transcript AND still shows the clear boundary reset (watermark==boundary==0.0).
Scoped to non-empty live so empty clear-shaped sidecars keep their existing
recovery semantics (active/pending/partial variants still restore). Fails open on
unreadable/partial reads. +2 regression tests (post-clear-message not restored;
post-clear-after-real-compaction still restores).
2026-07-05 02:10:30 +00:00
nesquena-hermes 8a6937978c fix(#5570): skip clear sentinel backups during recovery
Closes the crash-window resurrection corner in the #5532 clear-conversation
data-loss fix. /api/session/clear writes a .json.bak before truncating, and
because the cleared sidecar's truncation_watermark is 0.0, startup recovery saw
bak_count > live_count and could restore the backup — resurrecting the cleared
transcript after a crash/restart in the clear window.

Fix: stamp a unique per-clear `clear_generation` UUID on the sidecar when
clearing a session that had messages (persisted via Session model). Recovery's
_session_records_clear_sentinel() returns no_action when the live sidecar carries
a clear_generation the backup doesn't share AND the live sidecar matches the
exact cleared-empty shape (messages/context empty, watermark/boundary 0.0,
pending fields cleared). Same-generation backups stay recoverable; unreadable or
partial matches FAIL OPEN (normal recovery), so a real crash-loss is never
suppressed.

Contributor stage; gate-pass. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 02:10:30 +00:00