Commit graph

1808 commits

Author SHA1 Message Date
nesquena-hermes
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
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-hermes
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-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
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
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-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-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
Rod Boev
aed7da07e9 fix(kanban): skip workspace validation in edit mode (#3797) 2026-06-18 20:51:17 -04:00
Rod Boev
7fb58478bf fix(kanban): correct parent/child inversion in dependency removal (#3797) 2026-06-18 20:36:33 -04:00
Rod Boev
cb812a0aae fix(i18n): add missing kanban keys to zh, zh-Hant, fr, tr, pl (#3797) 2026-06-18 20:30:55 -04:00
Rod Boev
a3c1647afb fix(kanban): correct dependency endpoint and payload field names (#3797) 2026-06-18 20:06:38 -04:00
Rod Boev
32c8fa0626 fix(chat): keep jump-to-question visible on mobile (#3851) 2026-06-18 19:48:53 -04:00
Rod Boev
947b770a57 feat(kanban): add task workspace and dependency controls (#3797) 2026-06-18 19:48:47 -04:00
Tom Tong
fcc6a253b5 frontend: subpath-safe fetch() in /model command
cmdModel() had the only two root-absolute fetch('/api/...') calls left in the
frontend. Under a reverse proxy that mounts the app at a subpath (e.g.
jupyter-server-proxy at /proxy/<port>/), a root-absolute path escapes the mount
and 404s, so /model can't load the catalog or update the session. Resolve both
against document.baseURI (falling back to location.href) like every other fetch
in the frontend already does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:44:43 +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-hermes
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
santastabber
f8d57a0747 fix(sidebar): hide archived parent child rows 2026-06-19 06:28:17 +10:00
Rod Boev
a4058e31e4 Move sub-group rendering inside heading scope to fix wrapper ReferenceError 2026-06-18 16:27:07 -04:00
Rod Boev
851cc0e313 Fix lexical scope errors caught by cross-provider review 2026-06-18 16:15:39 -04:00
Rod Boev
7aa0649e3e feat(#4336): add vendor prefix sub-grouping in model picker for OpenRouter and Nous 2026-06-18 15:47:11 -04:00
Rod Boev
d85979cd69 fix(#4373): mark process-wakeup turns as synthetic and filter from visible transcript 2026-06-18 15:47:06 -04:00
Rod Boev
8c68bc6bf7 feat(#4391): add Ctrl/Cmd+, shortcut for settings toggle 2026-06-18 15:46:53 -04: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
bergeouss
d812f70116 fix(#4313): guard EventSource.close() calls to suppress ERR_ABORTED noise
Add readyState !== CLOSED (2) guards to all EventSource.close() calls across
messages.js, sessions.js, panels.js, and terminal.js. Chromium reports
net::ERR_ABORTED when close() is called on an already-closed EventSource,
polluting the console during normal lifecycle transitions (session switch,
panel close, stream teardown).

Key hotspots:
- closeLiveStream() double-close via onerror → _closeSource chain
- _closeSessionEventsSSE, _gatewaySSE.onerror missing try/catch
- _kanbanStopPolling, terminal close handlers

20 close() calls guarded across 4 files, no structural changes to stream
lifecycle.
2026-06-18 17:25:18 +00: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
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
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
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
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
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
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
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
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
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
ef6f43c69c Release v0.51.475 — ElevenLabs TTS engine (#3510)
Ships @linux-fertxo's ElevenLabs TTS engine: selectable alongside Browser and
Edge TTS, using the agent's existing ELEVENLABS_API_KEY + tts.elevenlabs config.
ElevenLabs is a built-in Hermes TTS provider (Anchor A parity). Backend validates
voice_id as a safe path segment, honors the 5000-char cap + rate limit.

Rebased onto current master (552 behind; net code-diff applied, byte-identical to
PR head). Added regression coverage (test_issue3510_elevenlabs_tts.py): missing-key
503, config voice_id traversal rejection, happy-path mp3 stream, shared char-cap.

Co-authored-by: linux-fertxo <linux-fertxo@users.noreply.github.com>
2026-06-17 05:50:37 +00:00
nesquena-hermes
849aba1c8a Release v0.51.474 — reconcile WebUI session detail source flags (#4348)
Ships @dso2ng's fix: /api/session now reconciles stale CLI source flags
against the state.db WebUI-origin row, and _isExternalSession() refuses to
classify an explicit WebUI-source session as external — so a WebUI-native
transcript with a stale sidecar isn't force-reloaded like an imported one.

Rebased onto master (contributor's net code-diff applied; byte-identical to
PR head). CHANGELOG stamped v0.51.474.

Co-authored-by: dso2ng <dso2ng@users.noreply.github.com>
2026-06-17 05:11:45 +00:00
kaishi00
ad99ff4976 fix(composer): attach image AND paste text when clipboard carries both
The paste handler (boot.js) bailed entirely when the clipboard contained
both text and image items, preventing image attachment in the most common
real-world scenario: right-click → Copy Image from browsers, Notes, Slack,
etc. — these almost always carry a text/html or text/plain representation
alongside the image.

Previous behavior (from #1620 fix):
  - clipboard has text only → normal text paste (correct)
  - clipboard has image only → attach image (correct)
  - clipboard has BOTH → text pasted, image silently dropped (wrong)

New behavior:
  - clipboard has text only → normal text paste (unchanged)
  - clipboard has image only → preventDefault + attach image (unchanged)
  - clipboard has BOTH → let text paste normally AND attach image

The key change: preventDefault() is now only called when no text is
present (true screenshot paste), so the browser handles text insertion
naturally while addFiles() runs regardless.

Fixes the regression introduced by the #1620 fix. Closes #1620.
2026-06-17 04:37:29 +00:00
nesquena-hermes
95266ef50c fix(#3850): broaden settings-search indexing + invalidate stale searches (review)
Codex+Opus review fixes:
1. Index query missed the common toggle shape <label><input><span data-i18n>>
   — only label[data-i18n] was indexed, so most checkbox settings (Virtualize
   long transcripts, Browser notifications, etc.) were silently unsearchable.
   Broaden to 'label[data-i18n], label [data-i18n], label' and make
   _resolveSettingsField resolve any [data-i18n] node to its .settings-field.
2. Stale async-render race: _beginSettingsPanelSession now bumps
   _settingsSearchSeq + clears the input/results, and the outside-click dismiss
   handler also bumps the seq, so an in-flight index build from a prior session
   can't paint into the dropdown. + 4 regression tests.
2026-06-17 04:12:54 +00:00
nesquena-hermes
757e3739fb Release v0.51.471 — searchable settings (#3850)
Ships @rodboev's searchable-settings feature (#3850/#3936): a search box in
Settings that finds any field across all 5 sections with breadcrumbs +
click-to-navigate+highlight+clear. Rebased fresh onto master (post v0.51.470),
5 feature files byte-identical to PR head, re-stamped v0.51.471 (was v0.51.469,
collided with the shipped transcript-virtualization release).

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 04:05:00 +00:00
nesquena-hermes
5a8d24c936 fix(#4343): default transcript virtualization OFF (experimental/opt-in) + force-off migration
Virtualization (#500/#4214) caused a scroll-up flicker on long sessions with
tall tool-call rows (variable-height anchor oscillation), distinct from the
stream-completion jump fixed in #4325. Per maintainer decision, flip the
'Virtualize long transcripts' preference to experimental/opt-IN (default OFF)
for everyone until the scroll behavior is reliable.

- api/config.py: virtualize_transcript default True->False; add
  virtualize_transcript_optin marker (both bool-allowlisted); load_settings
  force-off migration — a stored true WITHOUT the post-flip opt-in marker is
  reset to off, so 100% of existing users land on off; an explicit opt-in
  (marker present) is honored.
- boot.js / panels.js: default-off semantics (===true), persist the opt-in
  marker on enable, checkbox not pre-checked.
- index.html / i18n.js (en): relabel experimental + flicker warning.
- Tests: rewrite the #4325 toggle assertions for the opt-in contract + 4 new
  migration tests (unset/stale-true/explicit-optin/marker-without-true).

Phase B (root-cause scroll-stability) tracked separately. Thanks @b3nw.
2026-06-17 03:41:47 +00:00
nesquena-hermes
65c74988a8 fix(#4325): log renderMessages failure on virtualization toggle (greptile P2)
The opt-out toggle's change handler wrapped renderMessages in a silent
empty catch. Keep the defensive try (a toggle must never wedge the
settings handler) but console.warn on failure so a broken render is
diagnosable instead of swallowed.
2026-06-17 02:48:21 +00:00
nesquena-hermes
810414b438 feat(#4325): opt-out toggle to disable transcript virtualization
Adds a Preferences setting 'Virtualize long transcripts' (default ON, opt-out).
When disabled, _currentMessageVirtualWindow returns a full non-virtualized window
so the entire transcript renders at once (also short-circuits the virtual
re-measure loop). Server-persisted (virtualize_transcript bool, default True),
hot-applies via renderMessages on toggle. i18n in all 13 locales.

Pairs with @rodboev's #4328 stream-end anchor scroll fix (cherry-picked into
this branch) to fully resolve #4325.
2026-06-17 01:52:06 +00:00
Rod Boev
31191a6c50 fix(scroll): mount virtualized-out anchor row before same-frame restore 2026-06-17 01:42:44 +00:00
Rod Boev
387af5d9c9 fix(scroll): remove synchronous _programmaticScroll toggle around DOM clear 2026-06-17 01:42:44 +00:00
Rod Boev
152b44c138 Clear _programmaticScroll after innerHTML wipe to prevent stuck flag on early-return paths 2026-06-17 01:42:44 +00:00
Rod Boev
b24aa661dc fix(#4325): use semantic viewport anchoring for stream-end scroll restore 2026-06-17 01:42:44 +00:00