Commit graph

4441 commits

Author SHA1 Message Date
nesquena-hermes
67f81b60a2
Merge pull request #4426 from nesquena/release/stage-4417
Release v0.51.493 — RC (ignore malformed background-process wakeup events, #4417)
2026-06-18 11:12:06 -07: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
8bd0b38abe
Merge pull request #4425 from nesquena/release/stage-4418
Release v0.51.492 — RB (large context window preserved on session reload, #4248)
2026-06-18 10:46:13 -07: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-hermes
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
ai-ag2026
352e1893cb test: cover malformed wakeup edge cases 2026-06-18 19:30:24 +02:00
Frank Song
531c5787c6 fix: refine context reload fallback guard 2026-06-19 00:05:52 +08:00
Frank Song
9f174f6cc3 test: update session context fallback guard 2026-06-18 23:19:58 +08:00
Frank Song
f9d6b127e3 fix: preserve large context window on session reload 2026-06-18 23:13:19 +08:00
ai-ag2026
ff0ac7c7a4 fix: ignore malformed process wakeup events 2026-06-18 16:17:27 +02:00
nesquena-hermes
bc11cfe6d2
Merge pull request #4409 from nesquena/stage-4405
Release v0.51.491 — RA (rollback restore writes contained to workspace #4405)
2026-06-17 22:15:46 -07:00
nesquena-hermes
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-hermes
f46a447dc3
Merge pull request #4408 from nesquena/stage-4372
Release v0.51.490 — QZ (large plain-text pastes become .md attachments #4372)
2026-06-17 22:12:03 -07:00
nesquena-hermes
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-hermes
9f14dd6686
Merge pull request #4407 from nesquena/stage-4402
Release v0.51.489 — QY (outline button no longer collides with scroll control #2124)
2026-06-17 21:58:19 -07:00
nesquena-hermes
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-hermes
d5f9e98252
Merge pull request #4406 from nesquena/stage-4216
Release v0.51.488 — QW (rescue state.db user prompts before newer sidecar tail #4216)
2026-06-17 21:37:46 -07:00
nesquena-hermes
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-hermes
cbf6f747b4
Merge pull request #4404 from nesquena/stage-4398
Release v0.51.487 — QX (multi-container Docker: staged source writable #4395)
2026-06-17 21:19:45 -07:00
nesquena-hermes
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-hermes
5ced430628
Merge pull request #4403 from nesquena/stage-4399
Release v0.51.486 — QV (archived cron runs stay hidden on state.db re-projection #4397)
2026-06-17 20:45:50 -07:00
nesquena-hermes
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-hermes
f5f882b1dd
docs: fairly count absorbed-but-closed contributor PRs (backfill @Isla-Liu trio) (#4401) 2026-06-17 20:08:08 -07:00
nesquena-hermes
a6214d0927
Merge pull request #4400 from nesquena/stage-4394
Release v0.51.485 — QU (mobile: 44px hamburger + browser edge-swipe #4394)
2026-06-17 19:50:29 -07:00
nesquena-hermes
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-hermes
265786c2fe
Merge pull request #4396 from nesquena/stage-4223
Release v0.51.484 — QT (collapsible paused cron section #4026)
2026-06-17 19:18:42 -07:00
nesquena-hermes
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
f5efa1f89f
Merge pull request #4392 from nesquena/fix-gateway-sync-flake
test: fix recurring gateway_sync StopIteration flake (show_cli_sessions isolation)
2026-06-17 18:33:08 -07: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-hermes
f5512aa507
Merge pull request #4389 from nesquena/stage-4346-cluster
Release v0.51.483 — QS (virtual-scroll scroll-jump fixes #4367 + #4368)
2026-06-17 18:04:14 -07:00
nesquena-hermes
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-hermes
a34bcc16c1
Merge pull request #4388 from nesquena/stage-4386
Release v0.51.482 — QR (archived cron sessions stay hidden #4385)
2026-06-17 17:46:55 -07:00
nesquena-hermes
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-hermes
9a66a18c44
Merge pull request #4382 from nesquena/stage-3628
Release v0.51.481 — QP (gateway start/stop/restart controls #3628)
2026-06-17 15:18:23 -07:00
nesquena-hermes
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-hermes
41d51ecbcf
Merge pull request #4379 from nesquena/stage-4353
Release v0.51.480 — QO (inline PDF preview renders all pages #4353)
2026-06-17 14:14:18 -07:00
nesquena-hermes
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
nesquena-hermes
4f776a12fd
Merge pull request #4376 from nesquena/stage-3567
Release v0.51.479 — QN (named context blocks in the composer #2543)
2026-06-17 13:25:10 -07:00
nesquena-hermes
8246b4aad4 feat(#2543): collect selected chat text as named context blocks
"Reply with selection" no longer appends the selected text straight into
the composer. It now adds a named chip ("Context N") to a row above the
message box (composerSelectionChips). Chips can be renamed (double-click,
esc()-escaped) or removed (x); multiple stack. On send(),
_flushSelectionBlocksToComposer() materializes pending blocks into the
composer as **Name:** + markdown-quoted text, then the normal send path
runs unchanged on composer.value.

Regression-safety:
- _flushSelectionBlocksToComposer early-returns when no blocks are pending,
  so send() is byte-identical for anyone who never uses the feature.
- loadSession() AND newSession() both clear pending blocks before replacing
  S.session, so chips never leak across a sidebar switch OR a New Chat
  (the New Chat clear + its regression test were added per Codex review).
- chip name/text rendered via esc(); XSS-safe.

Verified live (headless CDP drive): add/render/remove/rename, flush
produces correct markdown, empty-flush no-op, session-switch clears,
injected <img onerror=...> chip-name escaped (not executed).

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 20:15:02 +00:00
nesquena-hermes
0d202e6f77
Merge pull request #4374 from nesquena/stage-3576
Release v0.51.478 — QM (read-only LLM Wiki browser in Insights #2941)
2026-06-17 12:56:59 -07:00
nesquena-hermes
4e5eebd0d6 feat(#2941): add read-only LLM Wiki browser in the Insights panel
Surfaces the Hermes agent's LLM Wiki as a read-only knowledge-base
observability section in Insights (status counts + browsable read-only
pages). Unconfigured shows a clear "Unavailable" empty state.

Strictly read-only, auth-gated. The wiki file-serving path is hardened
against the full traversal/symlink/TOCTOU class (Codex + Opus review, each
vector regression-tested + toothless-checked):
- _llm_wiki_page_files resolves the wiki root once as the trust base;
  skips a section whose resolved path escapes the root; requires each *.md
  target's RESOLVED path under both real root and real section; rejects
  dot-prefixed segments on lexical AND resolved-relative paths (closes
  symlink-to-hidden/out-of-tree + symlinked-section escapes). Browse + read
  share this allowlist.
- /api/wiki/page rejects abs paths + a real '..' SEGMENT (legit v1..v2.md
  still opens); requires the resolved target in the allowlist; reads the
  resolved path with O_NOFOLLOW; and fstat-verifies the open fd's
  (st_dev, st_ino) against the identity captured at allowlist time — closing
  final-component AND parent-directory symlink-swap TOCTOU races. Any
  mismatch/vanish -> clean 404, never a 500.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 19:47:45 +00:00
nesquena-hermes
d59cae5984
Merge pull request #4371 from nesquena/stage-4364
Release v0.51.477 — QL (gateway runs-API routing now opt-in #4362)
2026-06-17 10:52:43 -07:00
nesquena-hermes
84d3da7230 fix(#4362): gate gateway runs-API routing behind explicit opt-in
The WebUI gateway chat backend routed every browser chat turn through the
gateway's /v1/runs API whenever the gateway advertised approval/runs
support (gateway_chat.py capability gate). The runs API creates an isolated
Api_Server session per turn, so mid-conversation context was lost between
messages. Routing through /v1/runs is now gated behind an explicit opt-in
(_gateway_use_runs_api_enabled: HERMES_WEBUI_GATEWAY_USE_RUNS_API env var or
webui_gateway_use_runs_api config key, env-over-config precedence, default
off). The capability gate now requires BOTH the opt-in flag AND gateway
approval support before using /v1/runs; otherwise normal chat stays on
/v1/chat/completions, preserving the session via X-Hermes-Session-Id.
Operators who want the in-gateway-chat approval-events flow re-enable it
with the flag.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 17:41:45 +00:00
nesquena-hermes
bb08d36588
Merge pull request #4370 from nesquena/stage-brick
Release v0.51.476 — QK (cross-provider model-pick #4363 + non-git update-status #4356)
2026-06-17 09:55:32 -07:00
edoumo
a41663da29 docs(changelog): stamp v0.51.476 Release QK (#4363 cross-provider model-pick + #4356 non-git update-status) 2026-06-17 16:36:26 +00:00
edoumo
9194ade18f fix: report non-git installs as "can't check" instead of "up to date" (#4356)
Docker images and pip installs have no .git directory, so _check_repo
returned None and the frontend interpreted both update-target parts being
falsy as "Up to date" — a stale Docker image would misleadingly look
current. _check_repo now returns a minimal sentinel dict ({name, behind:
None, no_git: True}) instead of None so the Settings panel can distinguish
"can't check" from "up to date", and _formatUpdateTargetStatus /
checkUpdatesNow surface a localized "Can't check for updates" state
(settings_update_no_git, added across all 13 locale blocks). In mixed
deployments where one component has a git checkout and the other does not,
the valid git-side update info is still shown. The sentinel's behind: None
flows safely through the existing notification/summary builders, which all
gate on int(info.get('behind') or 0) > 0.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 16:36:26 +00:00
edoumo
446cd2cd98 fix: prevent cross-provider model selection from silently reverting to default (#4363)
When the model dropdown rebuilds (panel switch, provider refresh, or a
live-model fetch that times out and serves a fallback catalog), a
cross-provider selection could vanish from the refreshed catalog and the
browser would snap the <select> back to its first <option> (the default
model). _reconcileModelDropdownSelection now routes each selection branch
through an _applyOrEnsure helper that delegates to
_ensureModelOptionInDropdown, re-injecting the chosen model as a custom
option when it is missing from the rebuilt catalog instead of returning
null. Branch order and per-branch model/provider arguments are unchanged.

Maintainer note: simplified the contributor's helper to delegate straight
to _ensureModelOptionInDropdown (which already applies-first-then-injects),
removing a redundant double scan; re-pointed the
test_populate_model_dropdown_reconciles_selection_after_rebuild string
assertions to the new form (the behavioral node-driver tests for boot-default
precedence and in-page selection preservation are unchanged and still pass).

Co-authored-by: Ed <edou@edoudoux.com>
2026-06-17 16:22:51 +00:00
nesquena-hermes
502f806d5b
Merge pull request #4352 from nesquena/stage-3510
Release v0.51.475 — ElevenLabs TTS engine (#3510)
2026-06-16 23:08:13 -07:00
nesquena-hermes
fd014a1143 harden(#3510): re.fullmatch for voice_id validation (Opus review, defense-in-depth) 2026-06-17 05:56:54 +00:00