Commit Graph
1453 Commits
Author SHA1 Message Date
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-hermes f5f882b1dd docs: fairly count absorbed-but-closed contributor PRs (backfill @Isla-Liu trio) (#4401) 2026-06-17 20:08:08 -07: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-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
nesquena-hermesandRod Boev 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-hermesandRod Boev 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-hermesandRod Boev 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
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
nesquena-hermesandlinux-fertxo 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-hermesanddso2ng 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
nesquena-hermesandstarship-s 7ab18c4ae6 docs(changelog): v0.51.473 — clean cancelled user context chains (#4341)
Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-17 05:05:00 +00:00
nesquena-hermesandkaishi00 15f85533a5 docs(changelog): v0.51.472 — paste image+text together (#1620 regression, #4339)
Co-authored-by: kaishi00 <kaishi00@users.noreply.github.com>
2026-06-17 04:44:16 +00:00
nesquena-hermesandRod Boev 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 92df66d199 docs(changelog): v0.51.469 — transcript virtualization fix + opt-out (#4325) 2026-06-17 02:34:55 +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
nesquena-hermes 27062fa0e9 release: stamp v0.51.468 (Release QC — onboarding OAuth single-flight) 2026-06-17 00:27:28 +00:00
nesquena-hermes f9937cbb25 docs(changelog): v0.51.467 — symlinks in workspace file tree (#4228) 2026-06-16 23:26:11 +00:00
nesquena-hermes 46b6d059b0 release: stamp v0.51.466 (Release QA — active transcript reconciles on session events) 2026-06-16 22:36:19 +00:00
nesquena-hermes a1f468674f release: stamp v0.51.465 (Release PZ — workspace null-byte hardening) 2026-06-16 22:20:15 +00:00
nesquena-hermes 34ae806a70 release: stamp v0.51.464 (Release PY — scannable model picker) 2026-06-16 22:10:44 +00:00
nesquena-hermes ea9cd16850 fix(providers): gate credential_pool detection to known model providers (#4324)
Fixes the Photon phantom-providers regression from #4247 (v0.51.461): the
credential-pool detection loop added every auth.json credential_pool key to
detected_providers without checking it names a model provider, so the Photon
iMessage plugin's photon/photon_project/photon_user messaging credentials each
rendered as a phantom provider carrying the full auto-detected model catalog
(251 models on the reporter's machine).

Gated on a new _is_known_model_provider() check (custom:* slug, static
_PROVIDER_DISPLAY/_PROVIDER_MODELS entry, or registered model-provider plugin)
on both the load_pool and raw-dict fallback paths; the group-builder's generic
fallback no longer paints an unrecognized id with the global catalog. Real
pool-keyed providers (copilot, aliased google->gemini) still surface.

Integrates the stronger of two independent same-fix PRs — #4330's implementation
(thorough docstring, try/except-hardened plugin check, preserved #1881 comment,
defensive else branch) + its 8-case test that mocks at the WebUI boundary
(stubs hermes_cli, never imports the bundled agent package that's absent in CI,
exercises both the load_pool and ImportError raw-dict paths) — over #4329's
2-case test. Both rodboev (#4329) and the maintainer agent (#4330) independently
found and fixed the same regression.

Reported by Nic.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-16 21:45:43 +00:00
nesquena-hermesandrodboev c521937887 fix review: fail-closed null-byte handling for workspace paths (#4273)
Gate follow-ups on the converged head, all fail-closed hardening with no
contradiction to the existing security model:
- _as_posix_path rejects embedded null bytes (returns None).
- _safe_resolve catches ValueError (null-byte .resolve()) -> raw path so the
  block gate rejects cleanly.
- _workspace_access_error returns a clean 'Invalid path' validation error on
  stat() ValueError instead of an uncaught 500.
- /api/workspaces/add route preflight resolve() now catches
  (ValueError, OSError, RuntimeError) -> clean 400.

Reconciliation note: an interim gate suggestion to restore ~user/~root
expansion and block /root was REVERTED — the full suite surfaced
tests/test_batch_fixes.py::TestRootWorkspaceUnblocked, which pins the
deliberate #510/#521 decision that /root is NOT blocked (Hermes runs as root
in many deployments, where /root is the legitimate home, allowed via the home
carve-out). Expanding ~root for a non-root deployment would register root's
home, so the PR's original 'leave ~user literal' behavior is the correct,
safer choice. Kept only the null-byte fixes; added a guard test pinning that
/root stays unblocked.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-16 19:40:26 +00:00
nesquena-hermes 32f4e53218 docs(changelog): v0.51.463 — harden remote POSIX workspace trust boundaries (#3664) 2026-06-16 18:58:45 +00:00
nesquena-hermes d49e55b0db docs(changelog): v0.51.462 — keep session sidebar visible on slow loads (#4167) 2026-06-16 18:42:28 +00:00
nesquena-hermesandakrhin 29d15d7360 fix review: profile-scope _CREDENTIAL_POOL_CACHE + getattr-safe runtime_api_key (#4247)
Codex gate caught a cross-profile leak: _CREDENTIAL_POOL_CACHE was keyed
by provider id alone, so a custom provider configured under profile A made
profile B report has_key=True from a stale cache hit (then 401 at request).
Key the cache by (active-profile auth-store path, provider id) at all
get/set/evict sites via _credential_pool_profile_tag(). Also make the two
new bare _entry.runtime_api_key reads getattr-safe (OAuth-only entries lack
the attr), matching the guard the author already applied at providers.py.
Adds a cross-profile isolation regression test.

Co-authored-by: akrhin <akrhin@users.noreply.github.com>
2026-06-16 18:22:07 +00:00
nesquena-hermes af32f564d1 docs(changelog): v0.51.461 — credential pool fallback for custom providers (#4247) 2026-06-16 17:44:47 +00:00
nesquena-hermes 33f6bf9754 docs(changelog): v0.51.460 — recognize command STT providers in voice probe (#4312) 2026-06-16 17:21:29 +00:00
nesquena-hermes 42bea392be docs(changelog): v0.51.459 — normalize repair-merged user context (#4089) 2026-06-16 17:08:56 +00:00
nesquena-hermes ca175beca7 docs(changelog): v0.51.458 — O(D²) context-backfill, prevents server hang (#4314) 2026-06-16 16:56:11 +00:00
nesquena-hermes 048761a002 docs(changelog): v0.51.457 — gateway watcher non-blocking startup (#4297) 2026-06-16 16:44:31 +00:00
nesquena-hermesandnesquena-hermes 44032ab5ab Release PQ (v0.51.456): start.sh detects an already-running server (#4308) (#4309)
* Release PQ (v0.51.456): start.sh detects an already-running server (#4308)

start.sh now probes /health (best-effort, 2s, curl/wget) before launching
bootstrap.py; if a server already answers it prints a clear 'already running +
how to restart' message and exits 0 instead of spawning a child that fails to
bind. Brings the detached start.sh path to parity with ctl.sh's double-start
refusal. Container path (server.py direct) unaffected.

Co-authored-by: tomas-forgac

* gate fix (Codex CORE): parse $@ for --host/--host=/positional port in the start.sh preflight

bootstrap.py accepts a positional port + --host that override the env defaults;
the preflight probed env/defaults only, so './start.sh <altport>' while a server
ran on the env-default port falsely reported 'already running' and refused the
legitimate launch. Now scans $@ the same way bootstrap.py's argparse does before
building the /health probe URL.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-16 07:24:50 -07:00
50e7589a92 Release PP (v0.51.455): age-gate stale background-process completions (#4306, #4029) (#4307)
_drain_webui_process_notifications now drops (consumes, never requeues) a
completion whose completed_at is older than a configurable cap
(HERMES_WEBUI_STALE_COMPLETION_MAX_AGE_SECONDS, default 6h; 0 disables).
Fail-open: events without completed_at (legacy agent) are never dropped.
Additive (+192/-0). Closes #4029.

Co-authored-by: allenliang2022

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-16 06:58:38 -07:00
dc90ec9be4 Release PO (v0.51.454): complete the Japanese UI translation (#4301) (#4302)
Translated 69 remaining English fallbacks in the ja locale block to parity
with English; proper nouns/abbreviations intentionally left as-is. Values only,
no key changes (key parity verified). 265 locale/i18n tests pass.

Co-authored-by: koshikai

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 23:00:54 -07:00
ecf637c66d Release PN (v0.51.453): bridge approval controls to the gateway runs API (#4229, #4203) (#4299)
Approve/deny on a remote-gateway backend now forwards to the gateway runs API
(run_id server-derived, existing auth+CSRF preserved, capability-probed + 60s
cached). Non-gateway/local approval path unchanged — relay only fires when a
session has an active gateway run (_STREAM_RUN_IDS). Old gateways get a clear
upgrade message instead of a silent no-op.

Co-authored-by: rodboev

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 22:25:24 -07:00
2cc2c80074 Release PM (v0.51.452): reject symlinked memory-file writes (#4242) (#4298)
Refuse to write memory (MEMORY.md/USER.md/SOUL.md) through a symlinked target
file, extending the skills/plugins symlink-rejection hardening (#4217/#4234/#4240).
Parent-dir symlink intentionally allowed per maintainer decision (only the
concrete target file is guarded). + 3 regression tests.

Co-authored-by: zapabob

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 22:10:27 -07:00
nesquena-hermesandnesquena-hermes 2fa67ad699 Release PL (v0.51.451): show named-profile external sessions in the all-profiles list (#4067) (#4296)
* Release PL (v0.51.451): show named-profile external sessions in the all-profiles list (#4067, #4065)

All-profiles session enumeration now scans across profiles only when the
request explicitly asks (all-profiles view); cross-profile import resolves
to the active profile's state.db for unqualified requests (foreign id -> 404),
mirroring the #3982/#3991 detail/export profile-scoping gate.

Co-authored-by: rodboev

* gate fix (Codex CORE + Opus): close cross-profile leak on the import_cli refresh/existing-session branch

The already-imported branch of _handle_session_import_cli forced allow_all_profiles=True
whenever the globally-stored session carried a profile, with no active-profile visibility
gate — so an unqualified request from profile A could read/refresh a session imported under
profile B's live state.db (both advisors reproduced the foreign-transcript leak). Now gated
exactly like the /api/session detail + export endpoints: unqualified requests require
_session_visible_to_active_profile; explicit all_profiles requires a matching profile.
+ 3 regression tests.

* gate fix (Codex SILENT, re-gate): thread source_filter into the all-profiles CLI scan (models.py:4113)

The all-profiles branch keys its cache on source_filter but called
_load_cli_sessions_uncached without it, so /api/sessions?all_profiles=1 with a
source filter returned every-source sessions under a filtered key. + regression test.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 21:55:31 -07:00
792adc653a Release PK (v0.51.450): preserve custom provider namespace prefixes in model normalization (#4278) (#4294)
* Release PK (v0.51.450): preserve custom provider namespace prefixes in model normalization (#4278)

Token-boundary check in _normalize_provider_id (exact token or prefix
followed strictly by ':' or '/'); explicit aliases for hyphenated
first-party names (google-gemini, google-ai-studio, claude-code); bare-prefix
family detection no longer fires on namespaced model IDs.

Closes #4278. Supersedes #4280 (rodboev) whose '-' boundary re-collapsed
claude-relay->anthropic.

Co-authored-by: b3nw

* gate fix (Codex CORE): add openai-api to _PROVIDER_ALIASES so the :/ token boundary doesn't drop the registered openai-api provider id to '' (would silently repair OpenAI API sessions to the profile default) + 2 regression tests

* gate fix (Opus): gate the streaming send-path bare-prefix loop on '/' not in model so custom namespaces (gemini_cli/, gpt_proxy/, claude-relay/) survive the send path — same #4278 collision Opus found in _apply_profile_provider_context_to_streaming_model; + regression test + CHANGELOG

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 21:06:08 -07:00
1286ea7b3d Release PJ (v0.51.449): stop recovered turns replaying into later context (#4283) (#4291)
* stage-4286: stop recovered turns replaying into later context (fixes #4283)

* Release PJ: stop recovered turns replaying into later context (#4283)

rodboev's #4286. Codex SAFE + Opus net-positive-and-safe + 9152/0 clean suite.
Single-row prefix allowance gated to _recovered+role==user+offset0.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 20:02:57 -07:00
32458c442b Release PI (v0.51.448): fix blank transcript viewport regression (#4277) (#4288)
* stage-4281: restore virtualized transcript viewport anchors (fixes #4277, #3714 regression)

* Release PI: fix blank transcript viewport regression (#4277, #3714 follow-up)

rodboev's #4281. Codex SAFE + Opus net-positive-and-safe + 20 virtualization/
scroll tests green. Viewport-anchor restore + blank-state recovery.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 18:43:14 -07:00
nesquena-hermesandnesquena-hermes 70b7cf865c Release PH (v0.51.447): toolset selector shows active-profile defaults (#3100) (#4287)
* stage-3632: surface active-profile toolset defaults in selector (#3100)

* Release PH: toolset selector shows active-profile defaults (#3100) — CHANGELOG

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 18:00:13 -07:00
0b0e2a3213 Release PG (v0.51.446): model picker show-all overflow for large catalogs (#3691) (#4282)
* stage-3712: model picker show-all overflow + fix per-row count leak (#3691)

Self-rebased rodboev's #3712 onto master. FIX (Nathan-caught): the per-row
provider chip stripped the '(N of M)' overflow count — that count belongs on the
group heading only, and was reading as nonsense post-expand (row 30 showing
'15 of 30'). Updated test to assert heading-carries-count + rows-do-not.

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

* stage-3712: + dedupe overflow expand (Codex SILENT) — reuse existing injected option

Codex caught: select hidden overflow model from search → reopen → Show all could
duplicate the row (_appendOverflowOptionsToGroup blindly appended every extra
model without checking the select for an already-injected option). Now moves/reuses
an existing same-value option instead of re-creating it.

* stage-3712: + fix expanded-heading double-count (Opus) — strip decoration, count rendered rows

Opus caught: post-expand the heading showed 'Nous (2 of 4) (4)' (appended its own
count onto the already-decorated backend label) and used modelCount (incl hoisted-
configured) vs rendered rows. Now strips the '(N of M)' suffix when expanded +
counts groupRows.length. Test asserts no double count on out[expanded].

* stage-3712: + include extra_models in Settings/Cron/Profile/Aux native selectors (Codex CORE)

Codex caught: the server overflow split moves models>15 into extra_models for ALL
providers, but the 4 native <select> builders in panels.js (settingsModel:6690,
cronFormModel:1112, profileFormModel:5784, _auxProviders:8287) read g.models only
— silently dropping the overflow tail for large providers. Now concat
models+extra_models at all 4 sites. Test guard added.

* Release PG: model picker show-all overflow for large provider catalogs (#3691)

Self-rebased rodboev's #3712 onto v0.51.445; full browser-tested (desktop+mobile,
before/after) + Nathan screenshot-approved. 4 gate-caught issues fixed inline:
per-row count leak (Nathan), overflow-expand dedup (Codex), heading double-count
(Opus), and native Settings/Cron/Profile/Aux selectors dropping the overflow tail
(Codex CORE). Codex SAFE + Opus SHIP + 41 picker tests green.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 17:16:36 -07:00
40591f146f test(#3664): Windows pytest-harness batch #3 (#4274) (#4276)
rodboev test-only #4274 (test_issue609, test_sprint3, + 2 others). Linux
assertions stay at full strictness. 63 tests pass. No version stamp (tests-only).
Note: #4273 was NOT included — it touches api/workspace.py runtime (home-expansion
in the blocked-roots boundary), needs a separate full gate.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 15:57:18 -07:00