Commit Graph
100 Commits
Author SHA1 Message Date
nesquena-hermes 0a30790689 ci: re-trigger (shard 3.11/2 flake — shard-2 isolation run green locally) 2026-06-20 07:12:51 +00:00
nesquena-hermes 2f3abb00be stage #4540 + CHANGELOG 2026-06-20 07:12:51 +00:00
nesquena-hermes 348381bda3 stage #4542 @696bf798 (incl perf: pre-scan burst IDs) + CHANGELOG 2026-06-20 06:56:34 +00:00
nesquena-hermes 2b398bb2b1 stage #4537: keep open model picker stable during stream sync (#4531, head 92de7615 incl duplicate-rebuild fix) + v0.51.535 CHANGELOG 2026-06-20 06:10:04 +00:00
nesquena-hermes 15fe6add30 stage #4524: clarify 409-terminal (id-guarded, loading-cleared) + SSE-notify on expiry (#4504) + v0.51.534 CHANGELOG 2026-06-20 05:36:56 +00:00
nesquena-hermes 90505c0a8e stage #3285: gateway restart from agent-health alert (POST /api/health/restart) + v0.51.533 CHANGELOG 2026-06-20 04:40:49 +00:00
nesquena-hermes e1e2bbc7ef fix(config): order ghost-home guard after ambient short-circuit (#4516 Codex gate)
Codex SILENT finding: the new 'if not target.exists(): return {}' guard ran
BEFORE the ambient-resolver short-circuit, so a matching ambient profile home
whose directory does not physically exist (fresh install / monkeypatched cfg)
returned {} instead of deferring to get_config() — breaking in-memory overrides
and changing the default-profile missing-home path. Move the guard below the
ambient short-circuit; add a regression test (matching-but-nonexistent ambient
home still defers to get_config()).
2026-06-20 03:57:56 +00:00
nesquena-hermes acd2c186ef stage #4516: apply config defaults to non-default profile reads (#4465 / #4513) + v0.51.532 CHANGELOG 2026-06-20 03:51:55 +00:00
nesquena-hermes a60ba78298 stage #4517: plugins panel active-provider badge via is_active_provider (#4496) + v0.51.531 CHANGELOG 2026-06-20 03:35:44 +00:00
nesquena-hermes 51e2fce823 stage #4522: fix /api/profiles UnboundLocalError 500 (regression from v0.51.528) + v0.51.530 CHANGELOG 2026-06-20 03:18:33 +00:00
nesquena-hermes 6592416b35 stage #4518: match per-response jump button to session jump pill (#2246) + v0.51.529 CHANGELOG 2026-06-20 02:07:44 +00:00
nesquena-hermes 078fb99589 test(#2698): stub hermes_cli.profiles via sys.modules, not mock.patch (CI shard-2 fix)
CI failed on shard 2 across ALL python versions (deterministic, not a flake):
test_list_profiles_prefers_matching_isolated_default_home did
mock.patch('hermes_cli.profiles.list_profiles', ...), whose string target forces
a REAL import of hermes_cli — not installed on the WebUI CI path (bundled agent
absent) -> ModuleNotFoundError. Passed locally only because hermes_cli is
importable here. Per the never-import-agent/hermes_cli-in-a-WebUI-test rule:
inject a stub hermes_cli + hermes_cli.profiles into sys.modules so
list_profiles_api()'s internal
resolves to the stub at the WebUI boundary. Verified: all 24 tests pass with
hermes_cli import BLOCKED (CI simulation).
2026-06-20 01:47:22 +00:00
nesquena-hermes 7cd464efa6 test(#4449): isolate state-dir contract tests via subprocess (fix suite-ordering leak)
The reload-based state-dir tests (importlib.reload(api.config) with mutated env)
leaked recomputed STATE_DIR/SESSION_DIR globals + stale references into later
tests in the full sequential run -> 17 spurious failures in cancel/stream/
health/state-isolation suites (all green in isolation). Restoring env + reloading
couldn't fully undo it (fixture/monkeypatch teardown ordering + other modules
holding by-value STATE_DIR refs). Rewrite the 3 tests to import api.config in a
fresh SUBPROCESS under the target env and read STATE_DIR from stdout — truly
isolated, can't pollute the test process. Same assertions (HERMES_HOME-derived
default, unset->platform default w/ cross-check, STATE_DIR override precedence).
2026-06-20 01:22:26 +00:00
nesquena-hermes 4d4b11b43c test+docs(#4449): backward-compat state-dir tests + upgrade migration note (Opus gate)
Opus flagged a real upgrade gap on the #4449 state-dir contract folded into
#4454: a deployment that relocated HERMES_HOME to a non-default base WITHOUT
setting HERMES_WEBUI_STATE_DIR sees WebUI state move to $HERMES_HOME/webui on
upgrade (empty app at the new address). Add (1) README note documenting the
move + how to keep the old location, and (2) two backward-compat regression
tests pinning the invariants: HERMES_HOME-unset stays at the platform default
(normal installs don't relocate), and HERMES_WEBUI_STATE_DIR takes precedence.
2026-06-20 00:51:08 +00:00
nesquena-hermes 8b598ac133 changelog: stamp v0.51.528 (Release SM) for #2698 isolated HERMES_HOME mode 2026-06-20 00:49:06 +00:00
nesquena-hermesandrodboev 0952fcc606 fix(routes): drop redundant local _is_isolated_profile_mode import (#4454 gate)
The /api/profiles handler re-imported _is_isolated_profile_mode locally inside
handle_get, which made the name function-local for the ENTIRE handle_get scope
-> the earlier use at the /api/projects branch (routes.py:8000) hit
UnboundLocalError (F823), failing 6 test shards + lint. It's already a
module-level re-export (routes.py:429), so the local re-import is redundant;
removing it lets every in-function use resolve to the module global. Also drop
an unused 'from pathlib import Path' in the new #4449 state-dir test.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-20 00:48:20 +00:00
nesquena-hermes ca19574b75 fix(notifications): make hidden-tracker stream-owned + clear on terminal paths (#4416 Codex gate)
Codex SHIP-WITH-FIXES: non-done terminal paths (apperror/cancel/stream-error
finalize) didn't clear _STREAM_WAS_HIDDEN, so a stale wasHidden=true could be
inherited by a later same-sid reconnect -> spurious notification. Make each
entry stream-owned ({streamId, wasHidden}) so a stale entry from a prior stream
is never trusted (reconnect keeps state only when streamId matches; done reads
via the owned entry), and add _clearStreamHidden(activeSid, streamId) on the
apperror/cancel/finalize-fallback terminal paths (belt-and-suspenders).
2026-06-19 23:54:23 +00:00
nesquena-hermesandbergeouss d473f6c5a6 fix(notifications): notify on completion if tab was hidden during the stream (#4416)
A background tab's SSE is throttled by Chromium, so the stream 'done' event
arrives late (after the user returns, document.hidden=false) and the
response-complete notification was suppressed by the live visibility check.
Track per-stream whether the tab was hidden at ANY point during the stream
(_STREAM_WAS_HIDDEN, set at attach, OR'd by one idempotent visibilitychange
listener, read+cleared at done) and pass forceHidden accordingly.
sendBrowserNotification bypasses ONLY the live visibility gate on forceHidden,
still honoring the user's notifications-enabled setting — so a watched stream
stays silent (matches Slack/Discord/Gmail/Claude) but a backgrounded one
notifies. Correct fix for the #4416 root cause; supersedes #4421's always-fire
approach (which would also fire when the tab was never hidden).

Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
2026-06-19 23:48:42 +00:00
nesquena-hermes 7bedfaecff ci: re-trigger (shard 3.13/2 flake — shard-2 isolation run + full suite both green locally) 2026-06-19 23:20:21 +00:00
nesquena-hermesandbergeouss f57c0330ec fix(sessions): scope #4354 unstick to skip the active mid-send session (#2689 Opus gate)
Gate-found (Opus SHIP-WITH-FIXES; Codex SAFE — split resolved by reading the
contract: no dropped-turn, but a real transient start-race flicker). #4424
removed the _sendInProgress guards entirely; that re-opened the #2689 start-race
SYMPTOM for the one session mid-send (a /api/sessions poll landing during the
/api/chat/start round-trip would blank the just-sent bubble + queue a spurious
force-reload). Re-add the guard SCOPED to sid===_sendInProgressSid only, in both
_reconcileActiveSessionIdleStateFromList and _purgeStaleInflightEntries — a
long-hung session has _sendInProgress===false so it still gets unstuck (#4354),
only the in-flight start window is protected. Also fixes Opus's test-masking
finding: test_inflight_send_start_race now strips JS comments before asserting +
requires a real , so a comment can't green-light the #2689 guard, and
adds coverage for the reconcile path.

Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
2026-06-19 23:02:58 +00:00
nesquena-hermes 0a1e5d97a4 stage #4424: unstuck Running indicator when SSE dies during provider retry (#4354) + v0.51.526 CHANGELOG 2026-06-19 22:51:02 +00:00
nesquena-hermes 9ade727e63 stage #4494: expose built-in personalities in WebUI config (#4465) + v0.51.525 CHANGELOG 2026-06-19 22:26:30 +00:00
nesquena-hermes 580a157ab1 stage #4447: opt-in chunked SSE for buffering proxies (#4447) + v0.51.524 CHANGELOG 2026-06-19 22:09:17 +00:00
nesquena-hermes 139663f4a7 changelog: stamp v0.51.523 (Release SH) for #4375 wiki listing cache + hardlink rejection 2026-06-19 21:11:57 +00:00
nesquena-hermesandrodboev 0fe707b2bf fix(wiki): reject hardlinked page files in allowlist + read revalidation (#4375 Codex gate)
Gate-found (Codex CORE, repro'd): a hardlink at a clean *.md page name can
carry an arbitrary inode (incl. one outside the wiki) through the O_NOFOLLOW +
inode-identity read check — O_NOFOLLOW does not block hardlinks. Reject any
page file with st_nlink > 1 in BOTH the allowlist walk and the read-path
revalidation. Pre-existing on master (the inode recapture predates this PR's
cache); closing it here while on the read path. +1 toothed regression test
(prime cache -> hardlink listed page to outside secret -> assert 404, no leak).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-19 21:11:11 +00:00
nesquena-hermes 36730fe9f9 changelog: stamp v0.51.522 (Release SG) for #4488 live-model key scoping 2026-06-19 20:43:59 +00:00
nesquena-hermes 0c8e6ebf27 stage #4488: scope live model provider keys (#4488) [CHANGELOG pending post-4506] 2026-06-19 20:43:31 +00:00
nesquena-hermes 87aefad22f stage #4489: scope imported sessions to active profile (#4489) + v0.51.521 CHANGELOG 2026-06-19 20:25:43 +00:00
nesquena-hermes 99c12c6c8d changelog: stamp v0.51.520 (Release SE) for #4310 untracked-file recovery 2026-06-19 20:10:02 +00:00
nesquena-hermes 93c72adc8a stage #4486: untracked-file update collision recovery (#4310) [CHANGELOG pending post-4502] 2026-06-19 20:09:30 +00:00
nesquena-hermes 5a8ce51eb7 changelog: stamp v0.51.519 (Release SD) for #4492 stale active-run reconcile 2026-06-19 19:53:26 +00:00
nesquena-hermesandai-ag2026 54089a0696 fix(streams): only reap stale ACTIVE_RUNS entries when worker is gone from STREAMS (#4492 Codex gate)
Gate-found: reaping on started_at age alone could pop a genuinely-live worker
that's mid-teardown past the 180s ceiling (active_stream_id cleared during final
writeback, STREAMS entry still present) — ACTIVE_RUNS is worker-lifecycle truth
for health/background-wakeup/agent-cache consumers. Add a 'not in STREAMS'
guard (snapshot taken sequentially before ACTIVE_RUNS_LOCK, no nesting) and pop
by the real dict key. +1 regression test (toothed). Keeps the anti-permanent-409
age behavior intact.

Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-19 19:53:04 +00:00
nesquena-hermes e74bae1d35 stage #4492: reconcile stale active-run registry (#4492) [CHANGELOG pending post-4501] 2026-06-19 19:53:04 +00:00
nesquena-hermes 4b7a17bf33 stage #4487: read-only memory write 403 (#4480) + v0.51.518 CHANGELOG 2026-06-19 19:22:43 +00:00
nesquena-hermes 8d56dcdbed stage #4491: multi-container gateway URL (#4483) + v0.51.517 CHANGELOG 2026-06-19 19:09:00 +00:00
nesquena-hermes d357731dcc Merge PR #4493: stage pre-session toolsets before the first message (#4490) 2026-06-19 18:45:21 +00:00
nesquena-hermes bd3d079c6a fix(toolsets): trim staging comment + widen #2518 provenance test window
The pre-session toolset comment pushed the #2518 provenance comment past the
test_issue2518 4000-char window assertion. Trim the comment to one line and
widen the test window to 5000 (the fallback block it actually guards is well
within range; newSession() has legitimately grown).
2026-06-19 18:44:58 +00:00
nesquena-hermesandclaw-io 4e8e6714d5 fix(toolsets): only forward staged toolsets from empty composer + clear on session load (#4490 Codex follow-up)
Gate-found: staged _pendingSessionToolsets could leak into a later New Chat
started from an existing session (newSession forwarded it whenever it was an
array; loadSession never cleared it). Gate the forward on !S.session and clear
the staged value when a real session loads. +2 regression tests.

Co-authored-by: claw-io <claw-io@users.noreply.github.com>
2026-06-19 18:35:46 +00:00
nesquena-hermes 193f6ffc28 changelog: stamp v0.51.515 (Release RZ) for #4479 gateway approval fix 2026-06-19 18:17:02 +00:00
nesquena-hermes e94ce7df72 Merge PR #4495: correct gateway approval URL + thread session history into runs API (#4479) 2026-06-19 18:16:45 +00:00
nesquena-hermes 1ef3cd810a changelog: stamp v0.51.514 (Release RY) for #4481 fix 2026-06-19 17:59:06 +00:00
nesquena-hermes 334caac5a2 Merge PR #4482: fix post-start live attach after UI bookkeeping throw (#4481) 2026-06-19 17:58:44 +00:00
nesquena-hermes 97e977fd1e release: v0.51.513 — credential-pool quota for all pooled providers (#4365, #4360)
Maintainer fix on top: wrap /api/provider/quota in profile_env_for_active_request
(Codex gate caught default-profile cross-read). Codex SAFE + Opus SAFE + suite 9575.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4365
Closes #4360
2026-06-19 05:28:22 +00:00
nesquena-hermes 83a479d7a5 fix(#4365): wrap /api/provider/quota in profile_env_for_active_request
Codex gate caught that the new load_pool() path runs under the process-default
profile (sibling endpoints /api/providers + /api/models/live already wrap).
Without it a multi-profile client sees+seeds the default profile's pool
(#4247/#4067 profile-isolation class).
2026-06-19 05:27:42 +00:00
nesquena-hermes 3dd2e9ddfe release: v0.51.512 — interrupted-turn user prompt no longer replays (#4393, #4283)
Maintainer fix on top: pass-4 recovered-user keep now uses actual kept
neighbours (was forward-scan-only → adjacent-user 400). + 3 regression tests.
Codex SAFE + Opus 'ship it' + suite 9575.

Co-authored-by: kaishi00 <kaishi00@users.noreply.github.com>

Closes #4393
Closes #4283
2026-06-19 05:23:51 +00:00
nesquena-hermes c845e14087 fix(#4283/#4393): pass-4 recovered-user keep decision uses actual kept neighbours
Codex+Opus gate caught that the forward-scan-only keep produced [user, _recovered
user, assistant] -> adjacent users -> strict-provider 400 once the anchoring
assistant's prev kept neighbour was a user. Keep a recovered user ONLY when it
separates two assistants (prev_kept==assistant AND next==assistant); mirror in
_api_safe_message_positions. + 3 regression tests.
2026-06-19 05:19:52 +00:00
nesquena-hermesandrodboev f040d2a047 release: v0.51.511 — virtual transcript renders during programmatic scrolls (#4468)
Fixes a regression from #4434 (v0.51.500). Author (rodboev) gave explicit
go-ahead. Gated earlier: Codex SAFE + Opus SAFE + browser-smoke + suite.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4468
2026-06-19 04:36:39 +00:00
nesquena-hermes e480adae66 release: v0.51.510 — Kanban task workspace + dependency controls (#4453, #3797)
Maintainer fixes on top of the contributor PR: dependency direction, XSS-safe
ID encoding, dark-theme dependency input + task-autocomplete datalist,
self-dep guard, i18n backfill. Remaining parity gaps tracked in #4470.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4453
Closes #3797
2026-06-19 04:24:16 +00:00
nesquena-hermes 43580f9490 i18n: backfill kanban_dependency_self across all locales (locale-parity) 2026-06-19 04:16:50 +00:00
nesquena-hermes 76770425f4 fix(#4453): correct dependency direction, JS-context-encode IDs (XSS), dark-theme the dependency input + datalist autocomplete, reject self-dep 2026-06-19 04:05:15 +00:00
nesquena-hermes 4a52fe728a Merge commit 'refs/pull/4453/head' of github.com:nesquena/hermes-webui into review/4453 2026-06-19 03:55:18 +00:00
nesquena-hermesandrodboev 46f234f4fa release: v0.51.509 — notice when connected gateway can't do approvals (#4456, #4300)
Includes a showToast arg-order fix (4000ms,'warning') caught at gate.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4456
2026-06-19 03:14:08 +00:00
nesquena-hermesandtomtong2015 6a7c18fc6d release: v0.51.508 — /model command works under reverse-proxy subpath (#4448)
Co-authored-by: tomtong2015 <tomtong2015@users.noreply.github.com>

Closes #4448
2026-06-19 03:02:29 +00:00
nesquena-hermes 779da64e50 Merge commit 'refs/pull/4448/head' of github.com:nesquena/hermes-webui into release/stage-4448 2026-06-19 02:54:51 +00:00
nesquena-hermes a0c5a92706 release: v0.51.507 — jump-to-question button stays usable on mobile (#4455, #3851)
Verified at 390px: icon (arrow) stays visible, text label hidden, button not cut off.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4455
Closes #3851
2026-06-19 02:48:22 +00:00
nesquena-hermes 26c5415660 Merge commit 'refs/pull/4455/head' of github.com:nesquena/hermes-webui into release/stage-4455 2026-06-19 02:28:37 +00:00
nesquena-hermes fa92baa63d release: v0.51.506 — harden destructive workspace Git paths vs repo-local execution (#3879, #3777)
6-round RCE-class security hardening, converged. Codex SAFE (live exploit probes) + Opus SAFE (flag-off-reachability enumeration) + full suite 9508 on the byte-identical gated head. Nathan signed off.

Co-authored-by: Rod Boev <rod.boev@gmail.com>

Closes #3879
Closes #3777
2026-06-19 02:14:13 +00:00
nesquena-hermes f94508994f release: v0.51.505 — allow macOS Mail message: links in chat markdown (#4420, #4319)
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>

Closes #4420
Closes #4319
2026-06-19 01:58:28 +00:00
nesquena-hermesandrodboev eeee30fc6e release: v0.51.504 — group OpenRouter/Nous models by vendor in model picker (#4440)
Browser-stress-tested end-to-end (collapse parent/sub, search filter+restore,
select-from-subgroup, flat single-vendor rows) — Nathan approved from screenshots.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4440
2026-06-19 01:41:09 +00:00
nesquena-hermes b3a848967e Merge commit 'refs/pull/4440/head' of github.com:nesquena/hermes-webui into release/stage-4440b 2026-06-19 01:31:54 +00:00
nesquena-hermesandsantastabber d5c71c6f2d test: cover archived webhook session projections (#4441)
Test-only coverage extension (extends #4385 to the webhook source); no version bump.

Co-authored-by: santastabber <santastabber@users.noreply.github.com>

Closes #4441
2026-06-19 01:21:34 +00:00
nesquena-hermes 045874d2b5 Merge commit 'refs/pull/4441/head' of github.com:nesquena/hermes-webui into release/stage-4441b 2026-06-19 01:21:22 +00:00
nesquena-hermes 0fa0c39d58 release: v0.51.503 — process-wakeup turns no longer render as user messages (#4439, #4373)
Co-authored-by: Rod Boev <rod.boev@gmail.com>

Closes #4439
Closes #4373
2026-06-19 01:12:33 +00:00
nesquena-hermes b28ef26d15 harden: clear pending_user_source on the compression-snapshot load branch too (Opus symmetry nit) 2026-06-19 01:05:01 +00:00
nesquena-hermes 7827297011 Merge commit 'refs/pull/4439/head' of github.com:nesquena/hermes-webui into release/stage-4439b 2026-06-19 00:55:13 +00:00
nesquena-hermes af5fc4ff5e docs: note title-only UPDATE fallback gap in fingerprint comment (Opus/Codex follow-up) 2026-06-18 22:58:30 +00:00
nesquena-hermes d5ccaf0c27 fix(perf): fingerprint reads read-only + index-only MAX(rowid), no table scan / no lock stall (Codex gate)
Codex CORE findings on the fingerprint: (1) default busy timeout could stall
/api/sessions ~10s on a locked state.db; (2) COUNT(*) forced a full messages
table SCAN (~16-22ms/refresh on 200k rows). Fix: open file:...?mode=ro with
timeout=0.05 + PRAGMA busy_timeout=50 (returns fast, falls back to stat stamp on
lock), and use index-only MAX(rowid) per table (no COUNT, no scan). Verified:
EXPLAIN shows SEARCH not SCAN, 0.36ms on 200k msgs, 0.16ms while EXCLUSIVE-locked.
Fingerprint shape is now (sessions_max_rowid, messages_max_rowid); regression
tests updated.
2026-06-18 22:53:58 +00:00
nesquena-hermes f8d5ad2b1e fix(sessions): commit-reliable state.db content fingerprint in cache keys (real flake root cause)
Codex root-cause consult confirmed: the CLI-session cache (_CLI_SESSIONS_CACHE,
5s TTL) and session-list cache were keyed on (st_mtime_ns, st_size) of state.db
+ WAL sidecars. Under WAL-mode writes those stamps collide (same mtime-ns bucket
+ WAL frame size after checkpoint), serving a stale cache -> freshly-committed
gateway/CLI session intermittently absent from /api/sessions (the recurring
test_gateway_sync flake AND a real up-to-5s sidebar delay for gateway writes).

PRAGMA data_version is NOT usable here (fresh per-request connection always
reports its own initial value, never advances — verified). Fix = a cheap
content fingerprint (COUNT/MAX(rowid) over sessions+messages) read on a fresh
connection, which DOES advance on every commit and is immune to mtime
granularity. Wired into _sqlite_file_stat_cache_key (models) + the routes-layer
source stamp. + 3 regression tests + corrected the earlier false root-cause
comments.
2026-06-18 22:48:07 +00:00
nesquena-hermes 1cc90e2955 fix: also invalidate session-list cache on show_previous_messaging_sessions toggle (Codex gate)
Codex completeness finding: show_previous_messaging_sessions is a third
visibility setting in the /api/sessions cache key (bypasses messaging dedupe)
with the same stale-cache bug. Added it to the invalidation tuple.
2026-06-18 22:25:32 +00:00
nesquena-hermes 7c21fe4a93 fix(sessions): invalidate session-list cache on CLI-visibility toggle + de-flake node test timeouts
ROOT FIX (product bug): POST /api/settings never invalidated the session-list
cache, so toggling show_cli_sessions/show_cron_sessions could serve a stale
sidebar for up to the cache TTL when the settings-file mtime stamp collided with
a cached entry. The handler now clears the cache directly on those toggles. This
is also the root cause of the recurring test_gateway_sync CI flake (session
absent from /api/sessions right after the visibility toggle).

DEFENSE-IN-DEPTH: cleanup_test_sessions fixture now resets show_cli_sessions to
default BEFORE each test too, not only in teardown.

FLAKE FIX (test infra): bump 31 node-subprocess correctness-harness timeouts from
10s -> 30s across 23 test files. Node spawn + V8 warmup under parallel CI shard
load can exceed 10s, causing TimeoutExpired flakes (e.g. test_1325 on #4445).
Only real node subprocess.run() sites changed — mock signatures, HTTP urlopen,
and deliberate timeout-behavior tests (test_api_timeout, timeout=0/1/2) untouched.
2026-06-18 22:21:03 +00:00
nesquena-hermes c5cb0baede release: v0.51.501 — Ctrl/Cmd+, opens Settings (#4438, #4391)
Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4438
Closes #4391
2026-06-18 21:36:17 +00:00
nesquena-hermes 02834ecb95 Merge commit 'refs/pull/4438/head' of github.com:nesquena/hermes-webui into release/stage-4438 2026-06-18 21:35:49 +00:00
nesquena-hermesandrodboev 670e3dbdca release: v0.51.500 — fix stuck-scroll on long sessions with dense tool blocks (#4434)
Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4434
2026-06-18 21:26:54 +00:00
nesquena-hermes 1f85596038 release: v0.51.499 — hide stranded child rows of archived parents (#4414, #4293)
Display-only sidebar fix (does not mutate archived state; rows reappear with
show-archived). Chosen as the interim #4293 fix since the backend-cascade
alternative #4423 was bounced for a fork-archival mutation bug.

Co-authored-by: santastabber <santastabber@users.noreply.github.com>

Closes #4414
Closes #4293
2026-06-18 21:06:46 +00:00
nesquena-hermesandlunarnexus 0a5912f0fb release: v0.51.498 — filter core shell-identity vars from projected profile env (#4432)
Co-authored-by: lunarnexus <lunarnexus@users.noreply.github.com>

Closes #4432
2026-06-18 20:06:26 +00:00
nesquena-hermes 23527bf846 Merge commit 'refs/pull/4432/head' of github.com:nesquena/hermes-webui into release/stage-4432 2026-06-18 19:58:18 +00:00
nesquena-hermes 9a54fe9aeb fix(#4410): pre-check workspace leaf type via lstat + close fd on non-regular (Codex gate v2)
Codex re-gate found two issues in the uncapped-read fix: (1) open_anchored_fd
opens blocking O_RDONLY, which HANGS on a workspace FIFO/special file swapped in
at a regular-file path; (2) the non-regular fstat branch returned without
closing the fd (leak). Fix: lstat the symlink-resolved target BEFORE any open to
skip non-regular leaves (no blocking open on a FIFO), and close the fd on the
non-regular fstat branch. Adds a threaded FIFO-no-hang regression test.
2026-06-18 19:22:29 +00:00
nesquena-hermesandHinotoi-agent 94909590ad fix(#4410): read workspace diff content uncapped + symlink-safe (Codex gate)
Codex regression gate found that routing workspace reads through
read_file_content() introduced a 400KB MAX_FILE_BYTES cap not present on master
(Path.read_text), so a large but legitimate workspace file raised ValueError ->
caught -> None -> the rollback diff falsely reported it as DELETED. Replace with
an uncapped anchored O_NOFOLLOW read (safe_resolve_ws + open_anchored_fd +
S_ISREG fstat check) that keeps the symlink-escape protection but has no size
cap. Adds a >400KB regression test (RED on the capped path, GREEN with the fix).

Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-06-18 19:18:11 +00:00
nesquena-hermes 35f040d19f Merge commit 'refs/pull/4410/head' of github.com:nesquena/hermes-webui into release/stage-4410
# Conflicts:
#	CHANGELOG.md
2026-06-18 19:11:38 +00:00
nesquena-hermes 3cc4c859cf release: v0.51.496 — compact provider quota chip on narrow composer (#4419)
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>

Closes #4419
Closes #4358
2026-06-18 18:48:11 +00:00
nesquena-hermesandbergeouss df16a2eb10 release: v0.51.495 — guard EventSource.close() to suppress ERR_ABORTED noise (#4422)
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>

Closes #4422
2026-06-18 18:23:41 +00:00
nesquena-hermes 348078a617 Merge commit 'refs/pull/4422/head' of github.com:nesquena/hermes-webui into release/stage-4422 2026-06-18 18:22:37 +00:00
nesquena-hermesandtomas-forgac a308fdd5e8 release: v0.51.494 — TLS-aware launcher health probes (#4412)
Absorbs #4412 (tomas-forgac) rebased onto fresh master, plus two
Codex-regression-gate fixes: (1) use the scheme that actually answered the
probe for the ready/already-up URL + browser-open, (2) honor an explicit
HERMES_WEBUI_HEALTH_URL override in the WSL autostart probe.

Co-authored-by: tomas-forgac <tomas-forgac@users.noreply.github.com>

Closes #4412
2026-06-18 18:13:46 +00:00
nesquena-hermes 18d5df74a9 release: v0.51.493 — ignore malformed background-process wakeup events (#4417) 2026-06-18 18:04:28 +00:00
nesquena-hermes d228195da9 merge #4417: ignore malformed process wakeup events (resolve CHANGELOG) 2026-06-18 17:56:40 +00:00
nesquena-hermes c7fc114c85 release: v0.51.492 — preserve large context window on session reload (#4248, #4418) 2026-06-18 17:39:50 +00:00
nesquena-hermesandfranksong2702 561843ee9d fix(#4248): normalize slash-qualified model id in reload identity check
Codex regression gate found that _session_model_identity_matches normalized
only the @provider:model form, not provider/model (slash). A session stored as
'deepseek/deepseek-v4-1m' resolving to bare 'deepseek-v4-1m' was treated as a
model change -> the 256k fallback bypassed the accept-guard and clobbered the
persisted 1M window (the same #4248 bug on slash-qualified IDs). Mirror the
slash-aware split already used by _model_matches_configured_default. Adds a
route-level regression + unit test for the slash shape.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-18 17:37:52 +00:00
nesquena-hermesandhinotoi-agent 330e0672b8 fix(#4405): contain rollback checkpoint restore writes to the workspace
Restore previously used pathname-based shutil.copy2() which follows an in-tree
symlink whose target escapes the workspace. Route restore writes through the
workspace-anchored file helpers so a restored file always lands inside the
workspace tree. +regression test (escaping symlink refused, normal restore ok).

Co-authored-by: hinotoi-agent <paperlantern.agent@gmail.com>
2026-06-18 05:12:49 +00:00
nesquena-hermesandsantastabber 299fab5631 feat(#4372): attach large composer text pastes as .md files
Pastes over 4000 chars or 100 lines attach as a timestamped pasted-text-*.md in
the upload tray instead of flooding the composer. Image-paste path unchanged; an
oversize paste that exceeds MAX_UPLOAD_BYTES falls through to a normal inline
paste so nothing is lost.

Co-authored-by: santastabber <204424800+santastabber@users.noreply.github.com>
2026-06-18 05:09:11 +00:00
nesquena-hermesandMohamed Habib 4ac9684223 fix(#2124): stop the conversation-outline button colliding with the scroll control
The outline FAB was position:fixed at z-index 9999 and could stack on top of the
scroll-to-bottom button. Anchor it inside the messages shell (position:absolute,
z-index 12) so the two controls don't overlap.

Co-authored-by: Mohamed Habib <Habib1001-m@users.noreply.github.com>
2026-06-18 04:46:50 +00:00
nesquena-hermesandDennis Soong c592ae3acc fix(#4216): insert state.db-only user prompts chronologically in merge_session_messages_append_only
A user prompt that exists only in Hermes state.db (no sidecar copy) and falls
chronologically BEFORE a newer sidecar message tail was being appended after the
tail (or skipped) by merge_session_messages_append_only — losing or mis-ordering
it on load. The merge now inserts such a state.db user message at its correct
chronological position via _insert_state_message_chronologically, with a
role-aware tie-break for equal timestamps. Append-only invariant + dedup
(seen_content_keys) preserved.

Deterministic-ordering guard (maintainer add, addresses Codex review): on an
equal-timestamp collision the insert no longer lands at a slot whose left
neighbour shares the message's role — it advances past the same-role
equal-timestamp run, so an already-answered earlier turn can't be re-ordered
into user,user,assistant. (The agent core merges adjacent users before send, so
this was benign in practice, but the guard keeps the merged transcript correctly
ordered regardless.) +1 same-second multi-turn regression test, toothless-checked.

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
2026-06-18 04:25:11 +00:00
nesquena-hermesandenihcam 60218c302d fix(#4395): make staged hermes-agent source writable after rsync/cp in docker_init
Multi-container Docker deploys died at startup with 'could not create
hermes_agent.egg-info: Permission denied'. docker_init.bash stages the :ro
hermes-agent-src into /tmp/hermes-agent-build to avoid EROFS, but rsync -a/cp -a
preserve mode bits — a source mounted 555 left the staged copy 555, so
setuptools couldn't write egg-info next to the package. chmod -R u+w the staged
tree after the copy (error_exit on failure) so the build dir is genuinely
writable, not just owned by hermeswebui.

Co-authored-by: enihcam <enihcam@users.noreply.github.com>
2026-06-18 04:15:08 +00:00
nesquena-hermesandTomBanksAU 5c62f849fa fix(#4397): keep archived cron runs hidden on state.db re-projection
Follow-up to #4385/#4388: that fix stopped stale cron sidecars from being
treated as CLI rows, but get_cli_sessions() still re-projected the same cron
run from Hermes state.db with a hardcoded archived: false after all_sessions()
omitted the hidden sidecar — so an archived run reappeared on refresh.

- New _state_projection_sidecar_metadata(sid) helper returns UI-owned title +
  archived flag from the WebUI sidecar (load_metadata_only), degrading
  gracefully to {title:None, archived:False} on any miss.
- Both state.db-projection paths (standard CLI + cron fallback) now use it and
  carry the archived flag onto the projected row (was hardcoded False), so an
  archived cron/tool/API run stays hidden regardless of which path surfaces it.
  Title-preference behavior (#1486 rename persistence) is preserved.

Co-authored-by: TomBanksAU <246584738+TomBanksAU@users.noreply.github.com>
2026-06-18 03:33:44 +00:00
nesquena-hermesandkaishi00 ed827248ba feat(#4394): mobile — 44px hamburger tap target + browser edge-swipe sidebar
- Hamburger button: 32x32 -> 44x44 tap target (iOS HIG minimum), SVG icon
  20 -> 24px. flex-shrink:0 preserved; spacer keeps the title centered.
- Edge-swipe-to-open-sidebar: edge zone 28 -> 80px, trigger 72 -> 64px, max
  vertical 48 -> 56px; removes the _isPwaStandalone() gate in
  _onPwaSidebarSwipeStart so the gesture works in a normal mobile browser, not
  only the installed PWA. Still _isDesktopWidth()-blocked, touch/pen-only,
  interactive-target-excluded, and requires horizontal intent (dx>=64 &&
  dx>|dy|*1.5), so scroll/tap won't trigger it.

Known tradeoff (Nathan-approved ship-as-is): in mobile BROWSER the 80px left
edge zone overlaps iOS Safari's back-swipe; degrades gracefully (PWA, the
primary mobile target, has no back gesture).

Verified live at 390x844: hamburger renders 44x44 + tap opens sidebar; a
left-edge swipe opens the sidebar in a non-PWA context. 66 existing mobile/
swipe/titlebar tests pass.

Co-authored-by: kaishi00 <kaishi00@users.noreply.github.com>
2026-06-18 02:40:13 +00:00
nesquena-hermesandSanjay Santhanam a195c9f589 feat(#4026): collapse paused cron jobs into a collapsible sidebar section
Paused/disabled cron jobs are partitioned out of the active list into a
collapsible 'Paused (N)' <details> section so they don't drown the active
jobs. _cronList stays the single source of truth — only the render is split,
so openCronDetail / _cronNewJobIds / detail-refresh are untouched. Reuses the
already-translated cron_status_paused label (zero new i18n keys). Collapsed
state persisted in localStorage.

Maintainer polish on absorb: wrapped both localStorage read/write in try/catch
(repo convention — a storage throw in private mode must not dump the cron
sidebar into the error banner) + added the CHANGELOG entry.

Co-authored-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
2026-06-18 02:08:28 +00:00
nesquena-hermes 1c3b1ecdc9 test(gateway_sync): fix intermittent StopIteration flake (test-isolation root cause)
The A3 flake (test_sessions_response_backfills_imported_messaging_source_metadata
failing with StopIteration on a sharded CI run while passing in isolation +
sequentially) had two compounding causes, both fixed:

1. ROOT CAUSE — show_cli_sessions setting leak across shard-ordered tests.
   33 gateway_sync tests flip show_cli_sessions=True but only ~30 reset it,
   and cleanup_test_sessions didn't reset it. Under pytest-shard ordering a
   test could run with the setting in an unexpected state, so its session was
   occasionally absent from /api/sessions. cleanup_test_sessions now resets
   show_cli_sessions to its default (off) after every test, the same way it
   already resets last_workspace.

2. SYMPTOM — bare next(generator) with no default raised an opaque
   StopIteration (read as a mystery flake) instead of a diagnosable failure
   when the session was absent. The 5 bare next() call sites over /api/sessions
   results now use next(..., None) + an explicit assertion naming the missing
   session, so any future isolation regression fails clearly instead of
   masquerading as a flake.

No production code change; tests + fixture only.
2026-06-18 01:19:20 +00:00
nesquena-hermesandRod Boev f4d014194c fix(#4346): virtual-scroll height + measurement scroll-jump cluster (#4367, #4368)
Two coherent fixes for the #4346 transcript-virtualization scroll-jump
cluster, shipped together (contributor-approved) because both touch
static/ui.js + the same virtualization test file and interact (the
cache-reset path uses the per-role default from #4367). Both only run when
the default-OFF experimental "Virtualize long transcripts" setting is on.

#4367 — per-role virtual scroll height estimates:
  Replaces the flat 140px unmeasured-row estimate with per-role fallbacks
  (user 120 / assistant 160 / tool_call 400 / default 140) via
  _messageVirtualDefaultHeightForRole + _messageVirtualRoleForEntry, so
  tool-call rows (which measure 500-2000px) aren't under-counted before
  measurement. Additive + backward-compat (opts.roleForIdx optional → flat
  default when absent). The two sibling offset calculators route through the
  per-role defaults too.

#4368 — anchor-based scroll compensation during measurement:
  Adds _compensateScrollForMeasurementDelta (capture anchor + scrollTop →
  render → re-pin scrollTop by the anchor row's measured delta; null/missing/
  <2px guards; programmatic-scroll flag managed) wrapping
  renderMessages({preserveScroll:true}) in _scheduleMessageVirtualizedRender,
  plus a 150ms scroll-active guard deferring measurement refresh while the user
  scrolls, and session-switch cleanup of the three scroll-settle globals.

Maintainer note: combining the two required the test harness for
test_clear_height_cache_resets_scroll_settle_globals (#4368) to also eval
_messageVirtualDefaultHeightForRole + define MESSAGE_VIRTUAL_DEFAULT_ROW_HEIGHTS
(#4367), since the combined _clearMessageVirtualHeightCache resets the estimate
via the per-role helper. Both PRs' test sets retained.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-18 00:50:53 +00:00
nesquena-hermesandTomBanksAU 15da64f6ba fix(#4385): keep archived cron sessions hidden after list refresh
A stale sidecar with is_cli_session=true could make an archived
cron/tool/API session resurface in the Sessions tab on refresh. The
session loader now treats explicit cron/tool/api source metadata as
non-CLI even when the sidecar claims is_cli_session, and preserves cron
identity when the archive/materialization fallback creates a session.

Co-authored-by: TomBanksAU <246584738+TomBanksAU@users.noreply.github.com>
2026-06-18 00:28:02 +00:00
nesquena-hermesandRod Boev a41a1bf393 feat(#3628): gateway start/stop/restart controls in Settings → System
Adds WebUI controls to manage the messaging gateway lifecycle without a
terminal. _handle_gateway_lifecycle routes start/stop/restart to the
existing platform-aware `hermes gateway {action}` CLI via a list-argv
subprocess (no shell), scoped to the active profile, with bounded
timeout/error handling and a status-card refresh. Auth + CSRF gated;
profile name charset-validated; stdout/stderr never leaked to the client.

Maintainer hardening (warm-up SHOULD-FIX): added _GATEWAY_ACTION_LOCK, a
server-side single-flight guard. The client disables its button in-flight,
but a scripted authed client could otherwise fire overlapping
start/stop/restart calls and spawn concurrent gateway subprocesses. A
non-blocking acquire now returns 409 on contention (mirrors the self-update
_apply_lock pattern), released in finally. Added a regression test
(toothless-checked: without the lock the 2nd action spawns a subprocess +
returns 200).

Cross-platform: delegates to the mature hermes_cli gateway subcommand
(systemctl/launchctl), pathlib paths, PYTHONUTF8=1 + BROWSER=echo in env.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 22:08:33 +00:00
nesquena-hermesandGouranga satapathy 98a81118a1 fix(#4353): render all PDF pages in a scrollable inline preview
The inline PDF preview rendered only page 1, so multi-page documents
looked clipped. loadPdfInline now renders every page (capped at MAX_PAGES=20
for memory) sequentially into a scrollable .pdf-preview-body, shows the page
count in the header, and appends a "Showing the first N of M pages" notice
(pdf_truncated, 13 locales) when the PDF exceeds the cap.

Robustness: renderPage(i) attaches each canvas only AFTER its render
promise resolves, and a per-page failure is caught and skipped
(`.then(()=>renderPage(i+1)).catch(()=>renderPage(i+1))`) so one corrupt
page can't halt the preview or surface an unhandled rejection. XSS-safe
(esc(fname), t() with numeric args).

Co-authored-by: Gouranga satapathy <29521030+gourangasatapathyvit@users.noreply.github.com>
2026-06-17 20:57:38 +00:00