Commit Graph
100 Commits
Author SHA1 Message Date
nesquena-hermesandRod Boev 57a78e9284 fix(#4728): clear stale custom:* base_url + default wins without loaded session (gate findings)
Two gate-found CORE/SILENT edges fixed on-branch:
- api/config.py: switching the main default to a different custom:* provider kept
  the OLD provider's base_url, routing New Chat to the wrong endpoint. The
  base_url-clear on provider-change now covers custom:* too (was exempted).
- static/sessions.js: newSession() skipped the configured default when S.session
  was null but the picker held stale state (e.g. after deleting the last session),
  inheriting the stale model. Default now wins over legacy picker regardless of
  loaded-session state (explicit empty-composer override still takes priority).
- Updated 2 stale source-grep precedence assertions + widened a brittle 6000-char
  newSession() slice window to 7000 (assignment block grew; intent unchanged).
- 2 new non-vacuous regression tests (custom base_url clear; precedence).

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-24 20:17:29 +00:00
nesquena-hermes 27e259660c Release WL (v0.51.631): reconnect chronology keeps prior content ordered (#4849, closes #4811) 2026-06-24 19:58:18 +00:00
nesquena-hermesandRod Boev 3c8cfdc589 fix(#4811): regenerate row_id/seq from final order index so anonymous tool rows don't collide (gate finding)
Codex gate found a SILENT drop: reconnect scene rows are built with orderIndex=0,
so two tools WITHOUT a tool id at the same message index both got row_id
'...:tool:<idx>:0'; _completeSettledAnchorSceneForTurn dedupes by row_id, silently
dropping the second. Now the emit loop rewrites order_index AND regenerates the
index-derived row_id + seq + identity.seq from the final per-bucket position
(tid-based tool row_ids, already unique, are left untouched). Regression test
added + proven non-vacuous.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-24 19:48:56 +00:00
nesquena-hermes 4c1c7d8938 Release WK (v0.51.630): surface real git fetch errors on update failure (#4862, closes #4694) 2026-06-24 19:30:49 +00:00
nesquena-hermesandRod Boev 8d2689ccc0 harden(#4694): broaden query-secret redaction before surfacing fetch diagnostics (gate finding)
Codex gate: the apply path now surfaces sanitized non-network fetch stderr, but
_QUERY_SECRET_RE only redacted access_token/token/password/auth/key — leaving
client_secret, private_token, oauth_token, api_key in a remote URL exposed in
the user-facing error. Broadened the pattern to cover those + app_secret/secret
+ api[_-]?key. Regression test added + proven non-vacuous.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-24 19:29:06 +00:00
nesquena-hermes d313a899fc Release WJ (v0.51.629): macOS workspace drag-drop now lands (#4845, closes #4757) 2026-06-24 19:23:32 +00:00
nesquena-hermesandRod Boev c21d30e02b fix(#4757): defer window-drop cleanup a tick so target ondrop runs first (gate finding)
Codex re-gate: the capture-phase window 'drop' listener fired BEFORE the target
el.ondrop, clearing _wsActiveDragPath before _isWorkspaceTreeMoveDrag/_wsDragSrcPath
ran — re-breaking the macOS stripped-MIME move it set out to fix. setTimeout(_clearWsDragData,0)
lets the real drop handler complete, then clears the lingering flag.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-24 19:13:59 +00:00
nesquena-hermesandRod Boev 63e3f91654 harden(#4757): gate stripped-MIME drop on text/plain===tracked path + robust drag-flag cleanup (gate finding)
Codex gate found a SILENT hazard: if a workspace drag is abandoned without
ondragend firing (cancel/drop-outside/blur), _wsActiveDragPath lingered and a
later FOREIGN text/plain drag could be misread as a workspace move. Hardening:
- _wsDragSrcPath now only trusts the active flag when the drop's own text/plain
  EQUALS _wsActiveDragPath (a foreign/empty text drag resolves to '' -> no move).
- Shared _clearWsDragData() called from ondragend, a drop finally, and
  window-level dragend/drop/pagehide/blur fallbacks (lost-dragend cleanup).
- 2 regression tests (stale-flag foreign-text -> empty; _clearWsDragData resets),
  proven non-vacuous.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-24 19:06:30 +00:00
nesquena-hermes 4816f0a38e Release WI (v0.51.628): no ghost messages after edit/retry/undo (#4772, closes #4767) 2026-06-24 18:48:39 +00:00
nesquena-hermesandAlexeyDsov 6a7fe93e10 fix(#4767): gate boundary-advanced bypass on checkpoint consumption (no stale-suffix resurrection)
Codex final-gate found the boundary<watermark advanced signal made
sidecar_advanced_past_watermark globally true, so a stale ts>watermark suffix
row appearing in state.db BEFORE the edited checkpoint bypassed the skip and
resurrected. Gate the bypass on state_replay_idx >= len(sidecar_visible_sequence)
(checkpoint consumed); apply the same gate to the same-second CORE-B exception.
Regression test added + proven non-vacuous. All legit cases (state-only reply
after checkpoint, CORE-B same-second) still pass.

Co-authored-by: AlexeyDsov <alexeydsov@gmail.com>
2026-06-24 18:39:02 +00:00
nesquena-hermesandAlexeyDsov 695d9ab814 fix(#4767): keep state-only post-edit reply when sidecar tail equals advanced watermark (Codex final gate)
Final-gate Codex repro found a non-empty-sidecar drop: when the post-edit USER
turn is checkpointed into the sidecar (ts == advanced watermark) but its
ASSISTANT reply exists only in state.db (ts > watermark), the
sidecar_advanced_past_watermark guard was False (max_sidecar_timestamp ==
watermark, not >), so the 'timestamp > watermark' skip dropped the legitimate
reply. GET /api/session returned ['first','first reply','edited prompt'] and
omitted the assistant.

Fix: treat truncation_boundary < watermark (genuinely advanced) as advanced too,
so post-watermark state-only rows merge through even when the sidecar tail only
EQUALS the watermark. Conservative skip preserved for boundary None / == watermark.
Hoisted the loop-invariant boundary check out of the per-message loop. Regression
test added + proven non-vacuous.

Co-authored-by: AlexeyDsov <alexeydsov@gmail.com>
2026-06-24 18:21:34 +00:00
nesquena-hermesandAlexeyDsov 7317e29d82 fix(#4767): guard empty-sidecar reconstruction against not-advanced watermark (Opus gate)
Opus deep-gate found a latent data-loss the Codex harnesses missed: the
empty-sidecar branch kept at_or_after (ts>=watermark) UNCONDITIONALLY, which is
only valid once the watermark was ADVANCED strictly past the original cutoff
(a new turn committed -> boundary < watermark). In the not-advanced states the
content above the watermark is the deleted suffix, so it got resurrected — the
same data-loss class this fix exists to kill. Reachable via crash/cold-load
metadata-vs-sidecar divergence.

- Keep at_or_after ONLY when boundary < watermark (genuinely advanced).
- boundary == watermark (just truncated) OR boundary is None (legacy: a positive
  watermark always meant 'frozen at cutoff' pre-#4767) -> conservative ts<=watermark
  filter; never resurrects.
- Replaced the backward-scan heuristic with the provable boundary guard.
- Removed the now-dead 'or (not sidecar_messages and watermark>0)' clause in the
  non-empty-sidecar path (unreachable; empty sidecar returns earlier).
- Added 2 regression tests (boundary==watermark, legacy None-boundary), both proven
  non-vacuous; updated 2 contributor tests that assumed advanced-watermark-without-boundary
  (an unreachable state) to pass the boundary the real persistence path always writes.

Co-authored-by: AlexeyDsov <alexeydsov@gmail.com>
2026-06-24 18:08:50 +00:00
nesquena-hermesandAlexeyDsov 6ce9533d53 test(#4767): route-level CORE-A regression — /api/session full reload honors truncation_boundary
Co-authored-by: AlexeyDsov <alexeydsov@gmail.com>
2026-06-24 18:08:50 +00:00
nesquena-hermesandAlexeyDsov aac220b625 fix(#4767): thread truncation_boundary through remaining full-load merge call sites (CORE-A)
The contributor's empty-sidecar recovery uses the persisted truncation_boundary
to distinguish a legitimate prefix from a deleted suffix, but two production
call sites still passed only truncation_watermark — the default /api/session
full reload (routes.py ~9504) and _merged_session_messages_for_display
(routes.py ~6098) — so a normal full reload fell back to the old 'drop one
turn pair' heuristic and resurrected deleted suffix turns when an OLDER message
was edited leaving >=2 later turns. Thread truncation_boundary through both.

Co-authored-by: AlexeyDsov <alexeydsov@gmail.com>
2026-06-24 18:08:50 +00:00
nesquena-hermes ab71f4b906 fix: reset _oldestIdx on stream done so transcript stops jumping to first message (#4720)
After a turn completed on a long conversation that loaded truncated, the
viewport jumped to the first message. The done SSE handler replaced S.messages
with the full transcript payload and updated _messagesTruncated, but did NOT
reset _oldestIdx the way the canonical full-load paths do (sessions.js
_ensureMessagesLoaded, ui.js loadSession).

The read-position rework (#4613) keys the scroll anchor on an absolute index
(sessionIdx = _oldestIdx + rawIdx; rows tagged data-session-msg-idx). On done
the handler also expands the render window from the tail window (50) to all
messages, shifting every rawIdx. With _oldestIdx left stale, the captured
anchor resolved to an earlier row and the fallback used the raw scrollTop
against the now-taller full transcript, landing near the first message.

Reset _oldestIdx from d.session._messages_offset||0 alongside _messagesTruncated.
The done payload (_session_payload_with_full_messages) embeds the full transcript
and carries no offset, so this resets to 0 and keeps the absolute anchor aligned
with the rebuilt rows. No-op for non-truncated sessions; pinned-at-bottom readers
still follow to bottom (shouldFollowOnDone path unchanged).

Both gates clean (Codex SAFE TO SHIP, Opus ship-it) — verified the done payload
is always a full transcript with no offset, and every other _oldestIdx consumer
becomes more consistent.
2026-06-22 19:26:07 +00:00
nesquena-hermes 737b112f30 fix(#4662 review): suppress spurious switch timeout toast, dim group-label fade, re-guard in-progress branch
Addresses @rodboev + greptile review of #4671:
- panels.js switchToProfile(): pass timeoutToast:false to the /api/profile/switch
  POST so a superseded or transient-but-successful switch no longer pops api()'s
  generic 'Request timed out' error toast; the generation-guarded catch handler is
  now the single source of truth for switch failure.
- panels.js: re-check _switchGen after the awaited renderSessionList() in the
  in-progress-session branch so a rapid second switch can't have its workspace
  skeleton cleared / a stale toast popped by a slower earlier switch (mirrors the
  no-messages branch guard).
- style.css: group labels now fade in via skeletonFadeInDim (ends at opacity:.5)
  instead of the shared skeletonFadeIn (ends at opacity:1 with fill-mode:both),
  which was overriding their intended resting 50% opacity.
- 4 new static-source regression assertions locking the three fixes; CHANGELOG
  Unreleased entry updated (fixed-shape skeleton note + the three review fixes).
2026-06-22 19:10:08 +00:00
nesquena-hermes 42d1fbc728 Phase 1 (#4662): address remaining Greptile review (P2 + stagger)
Builds on Nathan's switch-failure workspace-skeleton-clear fix (which already
covered Greptile P1). Remaining two:

- [P2] Removed dead _clearSessionListSkeletonFlag() — the flag is reset inline in
  renderSessionListFromCache(); the helper had no call site.
- [stagger] The .skeleton-row:nth-child(N) fade-in delays skipped most rows
  because group-label siblings are interleaved with rows in the DOM (positions
  shift: rows land at child 2, 4-6, 8-11, so 9/10/11 got no delay). Replaced the
  CSS :nth-child rules with an inline per-row animationDelay set in JS (rowIndex
  * 0.025s, capped 0.2s) so every row gets its correct stagger regardless of
  group structure.

Tests: +1 behavioural (every row carries a real inline stagger delay; last row
not skipped). 26 pass.
2026-06-22 18:12:55 +00:00
nesquena-hermes e505543650 Phase 1 (#4662): CHANGELOG entry for profile-switch loading states 2026-06-22 18:12:55 +00:00
nesquena-hermes 7e731ab22b Phase 1 (#4662): fix virtualized-sidebar skeleton clobber (Codex gate round 2)
On long sidebars over the virtualization threshold, showSessionListSkeleton()
cleared the DOM but left the data-session-virtual-* window state + the queued
scroll RAF active. A pending _scheduleSessionVirtualizedRender() could then
repaint the PREVIOUS profile's cached rows over the skeleton before the new
/api/sessions response landed (SILENT regression Codex caught).

Fix:
- showSessionListSkeleton(): cancelAnimationFrame(_sessionVirtualScrollRaf) and
  delete the data-session-virtual-{total,start,end,filter,activeAnchor} markers
  so no scroll-driven render targets the skeleton; the real render rebuilds them.
- _scheduleSessionVirtualizedRender(): early-return while
  _sessionListSkeletonActive is true.

Tests: +2 static assertions; driver mock gains dataset + RAF stubs. 24 pass.
2026-06-22 18:12:25 +00:00
nesquena-hermes adcd06ff76 Phase 1 (#4662): address Codex + Opus gate findings
Codex (CORE regression): the parallelized loadDir('.') started BEFORE the
_switchGen stale-switch guard; empty-session switches reuse the session id, so a
superseded switch's /api/list could paint the wrong profile's tree. Reverted to
guard-before-workspace-render ordering (the up-front skeletons already give the
immediate cross-surface feedback; the parallel gain was marginal).

Opus findings:
- [blocker] Workspace skeleton stranded permanently when the new profile has no
  bound workspace (loadDir skipped → nothing clears #fileTree). Added
  clearWorkspaceTreeSkeleton() (only empties when a skeleton is present) and call
  it on the no-workspace path in BOTH switch branches.
- [low-med] Session-list skeleton stranded if an inline-rename input or row
  action menu was open (renderSessionListFromCache early-returns before the DOM
  swap). Dismiss _renamingSid + closeSessionActionMenu() before showing the
  skeleton.
- [low] No-op self-switch flashed skeleton→restore (activateCurrentProfile
  doesn't pre-check). Added an early-return when name === S.activeProfile.

Tests: +4 static assertions for the self-switch guard, rename/menu dismissal,
no-workspace skeleton clear, and the clear-helper definition. 22 pass.
2026-06-22 18:12:25 +00:00
nesquena-hermes 4abdb54ede Phase 1 (#4662): tests for profile-switch loading skeletons
- behaviour: node-driven, runs the actual showSessionListSkeleton() /
  showWorkspaceTreeSkeleton() builders against a mock DOM and asserts the
  produced structure (3 group labels + 8 single-line rows with title+stamp;
  8 tree rows with glyph+name, 7 file rows with a size bar, 1 dir without).
- static: asserts switchToProfile() shows both skeletons up front before the
  /api/profile/switch POST, parallelizes the list+workspace refresh, restores
  real content on failure; renderSessionListFromCache clears the skeleton flag;
  style.css has the classes, sheen+fade keyframes, reduced-motion disable, and
  light+dark theme tokens.
2026-06-22 18:12:25 +00:00
nesquena-hermes bafd7ddfd2 Phase 1 (#4662): strengthen skeleton shimmer contrast for perceptible motion
The initial sheen (15% sheen / 9% base, 1.4s) was too subtle to read as
animation. Bump to a 5-stop gradient (22-24% sheen / 8% base, 1.25s), define
explicit light-mode --skeleton-base/--skeleton-sheen tokens (was relying on
fallbacks only), and give the tree glyph the same shimmering gradient so it
animates with the bars. prefers-reduced-motion still disables the sweep.
2026-06-22 18:12:25 +00:00
nesquena-hermes b084535b24 Phase 1 (#4662): profile-switch loading skeletons (sidebar + workspace)
Show polished loading skeletons the instant a profile switch begins instead of
leaving the previous profile's conversation list and workspace file tree visible
until their fetches resolve (~1s).

- static/style.css: theme-token skeleton system (single-line session rows with
  group labels + timestamp bar; tree rows with glyph + name + size bar); subtle
  shimmer sweep; dark-mode tokens; prefers-reduced-motion fallback.
- static/sessions.js: showSessionListSkeleton() mirrors real sidebar anatomy
  (group headers + single-line rows); _sessionListSkeletonActive flag cleared on
  real render.
- static/workspace.js: showWorkspaceTreeSkeleton() mirrors the file-tree row.
- static/panels.js switchToProfile(): clear stale content + show both skeletons
  up front; parallelize the independent list + workspace refreshes (Promise.all);
  restore real content from cache on switch failure so a skeleton never strands.

Verified live: switch fires -> skeleton instant (0 stale rows) -> resolves to new
profile's real rows. Polished across 1920/1440/1280/390 + dark mode (screenshots
pending Nathan's visual review before gate+PR).
2026-06-22 18:12:25 +00:00
nesquena-hermes f3d443c6d4 docs(release): stamp v0.51.560 — recovered journal output reaches model context (#4608) 2026-06-21 10:19:20 +00:00
nesquena-hermes 215c819fae stage #4608 onto master (v0.51.559) — backfill run-journal-recovered rows into model context
allenliang2022: recovered assistant rows were added to the visible transcript but not context_messages,
so the model lost recovered output. Adds _append_recovered_turn_to_context with a role-dedup guard.
Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-06-21 10:11:35 +00:00
nesquena-hermes b2818cd46f docs(release): stamp v0.51.559 — cron model picker copy polish (#4031) 2026-06-21 09:57:22 +00:00
nesquena-hermes 879324290c fix(#4031): add cron_model_no_agent_hint to ALL 13 locales (strict locale-parity tests)
The repo enforces every locale defines every English key (test_*_locale.py). Adding the key to
English only broke 6 locale-coverage tests. Added the key (English fallback text) to all 13 locale
blocks. Full suite catches what targeted tests miss.
2026-06-21 09:49:40 +00:00
nesquena-hermes 3924966940 fix(#4031): define cron_model_no_agent_hint i18n key (was rendering the literal key)
Codex gate (SILENT): panels.js referenced t('cron_model_no_agent_hint') but the key was never added
to i18n.js. t() returns the key name for a missing translation (falls back to LOCALES.en, then the
key), so script-only cron jobs rendered the literal 'cron_model_no_agent_hint'. Added the English key
(other locales fall back to en) + strengthened the i18n-keys test to assert the value is defined.
2026-06-21 09:41:51 +00:00
nesquena-hermes 675bc7d9e7 stage #4031 onto master (v0.51.558) — polish cron model picker copy + regression tests
TomBanksAU: cron model picker copy (esc()+i18n fallbacks, no_agent hint for script-only jobs) +
195-line provider-picker regression test. Core picker already shipped; this is polish + guardrails.
Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
2026-06-21 09:36:24 +00:00
nesquena-hermes 7641e7d42e docs(release): stamp v0.51.558 — escape-target symlink display-only rows (#4581) + dialog Cancel fix 2026-06-21 09:17:46 +00:00
nesquena-hermes 96b5214523 fix(#4581): restore hidden Cancel button in showPromptDialog after a hideCancel confirm
Codex gate (SILENT): the new outside-symlink info dialog uses showConfirmDialog({hideCancel:true})
which sets the shared #appDialogCancel to display:none; showPromptDialog only reset textContent, so a
later rename/new-file prompt opened with no Cancel button. Fix: showPromptDialog now restores
cancelBtn.style.display='' first. + regression test.
2026-06-21 09:17:26 +00:00
nesquena-hermes 077de5455c harden(#4581): display-only escape symlinks must not disclose target path/metadata
Codex+Opus gate (info-leak): the display-only escape-target rows still returned the resolved
outside path (target), target-derived is_dir, and target stat size; ui.js showed the outside path
in the open-dialog. Fix: emit ONLY name/path/type/target_outside_workspace/mtime for outside rows
(no target, no size, is_dir=False); dialog uses a generic 'points outside workspace' message (dropped
{target} from external_link_open_confirm across all 13 locales). Containment was already intact (both
gates confirmed); this closes the path-disclosure. Tests updated to assert the no-leak property.
2026-06-21 09:15:22 +00:00
nesquena-hermes 1e18c336f1 stage #4581 onto master (v0.51.556) — surface escape-target symlinks as display-only workspace rows
claw-io: symlinks whose target resolves outside the workspace root were silently dropped; now emitted
with target_outside_workspace=True (display-only — safe_resolve_ws/open_anchored_fd still block
navigation). Dedup-winner over #4573 (stronger tests). Co-authored-by: claw-io <claw-io@users.noreply.github.com>
2026-06-21 09:15:22 +00:00
nesquena-hermes f763b8adf8 docs(release): stamp v0.51.557 — desktop question-jump discoverable (#4592) 2026-06-21 08:53:10 +00:00
nesquena-hermes 382856e170 stage #4592 onto master (v0.51.555) — keep per-turn question-jump button discoverable on desktop (#2246 follow-up)
santastabber: desktop ≥641px keeps the jump-to-question affordance visible (it's navigation) while
timestamp/actions chrome stays hover/focus-only, via :has(.msg-question-jump-btn) selectors. CSS+test only.
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 08:52:46 +00:00
nesquena-hermes 1ef996b112 docs(release): stamp v0.51.556 — id-ID Edge TTS voice + Listen button state (#4599) 2026-06-21 08:43:17 +00:00
nesquena-hermes 07e6a839fa stage #4599 onto master (v0.51.555) — add id-ID Edge TTS voice + fix Listen button playing-state (parity)
latipun7: adds id-ID-GadisNeural to the Edge TTS allowlist (Edge TTS is a core builtin -> parity, default-in)
+ picker label + sets speaking-state at start of _playEdgeTtsChunked so the Listen button reflects playback.
Co-authored-by: latipun7 <latipun7@users.noreply.github.com>
2026-06-21 08:35:03 +00:00
nesquena-hermes 760a5168c9 docs(release): stamp v0.51.555 — mobile reload recovery after compression (#2980/#3133-adjacent) 2026-06-21 08:19:15 +00:00
nesquena-hermes aaa14066b2 fix(#2980): drop unused 'import types' in cross-profile test (ruff F401 — the only CI lint blocker) 2026-06-21 08:18:29 +00:00
nesquena-hermes fbaaa90a64 test: widen run-journal static-window 18000->20000 (my +600-char #2980 continuation block pushed runtime_journal_snapshot past the brittle fixed offset; not a regression) 2026-06-21 08:17:32 +00:00
nesquena-hermes 64ca1f8686 harden(#2980): profile-scope continuation lookup + safe-sid + no speculative restore write
Gate findings (Codex 2-CORE / Opus SHIP-WITH-FIXES), both fixed:
1. cross-profile leak — _pre_compression_continuation_session_id scanned all profiles' sessions,
   matched only by parent_session_id; now filters children by _profiles_match(child, snapshot)
   + validates returned sid with is_safe_session_id. +cross-profile regression test.
2. restore-state poison — frontend wrote continuationSid to localStorage/URL BEFORE the inner
   loadSession proved it loadable; removed the speculative write (success path at the loaded id
   handles it). Updated the source-grep test to assert the safe form.
Bounded BFS (range(20)+seen) + recursion guard confirmed safe by both gates.
2026-06-21 08:17:32 +00:00
nesquena-hermes dbeb437bcc rebase #2980 onto master (v0.51.548) for ship-gate — recover mobile reloads after compression rotation
george-andra: GET /api/session surfaces continuation_session_id for a hidden pre-compression
snapshot; loadSession follows it so a mobile reload mid-compression recovers to the visible
continuation instead of a hidden snapshot (chat looked 'lost'). Resolved 2 conflicts (1615 behind):
sessions.js merged w/ #2971 re-arm guard; routes.py merged w/ _session_source_is_webui branch.
Co-authored-by: george-andra <george-andra@users.noreply.github.com>
2026-06-21 08:17:32 +00:00
nesquena-hermes 05d776d6d3 docs(release): stamp v0.51.554 — preserve mid-stream scroll anchor (#4578/#4295) 2026-06-21 07:36:46 +00:00
nesquena-hermes 5e818546f8 stage #4578 onto master (v0.51.553) — preserve mid-stream scroll anchor + honor manual unpin (#4295)
franksong2702: preserve the reader's semantic viewport anchor during mid-stream transcript rebuilds
before raw scrollTop fallback; remount virtualized anchors before restore w/ _programmaticScroll
scoped to the remount frame; treat captured userUnpinned/pinned snapshot as authoritative so
distance-inference can't re-pin a reader who scrolled away during a live reply. Browser-only scroll
state. Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-21 07:25:11 +00:00
nesquena-hermes 5eafbfb41a docs(release): stamp v0.51.553 — extensions diagnostics panel (#4588) 2026-06-21 07:19:49 +00:00
nesquena-hermes 813e5aa3b2 stage #4588 — extensions settings diagnostics panel 2026-06-21 07:10:05 +00:00
nesquena-hermes 4aa0c7a37f docs(release): stamp v0.51.552 — extension sidecar CSP docs (#4593) 2026-06-21 07:05:19 +00:00
nesquena-hermes 8f3f6ea67a stage #4593 onto master (v0.51.550) — document extension sidecar CSP support + CSP test coverage
santastabber: docs (README + EXTENSIONS.md) clarifying extension sidecar CSP behavior +
test_issue1909_csp_enforcement / test_issue2901_csp_connect_extra assertions. Docs+tests only, no
prod code. Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 07:04:31 +00:00
nesquena-hermes d281b7cf63 docs(release): stamp v0.51.551 — manual /compress preserves visible transcript (#4594/#3133) 2026-06-21 06:54:48 +00:00
nesquena-hermes 4bf9ff7518 stage #4594 onto master (v0.51.550) — preserve visible transcript after manual /compress (#3133)
Manual /compress wrote the compressed payload into BOTH s.messages (visible) and s.context_messages
(model-facing) + wiped s.tool_calls -> messages vanished from the window. Fix keeps s.messages +
tool history durable, deep-copies compressed only into s.context_messages, anchors off visible msgs.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-21 06:46:47 +00:00
nesquena-hermes d38b95076a docs(release): stamp v0.51.550 — Transparent Stream activity survives settle/reload (#4587/#4568) 2026-06-21 06:35:16 +00:00
nesquena-hermes 236b165b7a harden(#4587): align scene-builder final-answer filters to the renderer's >=0.9 ratio
Codex gate (SILENT): the scene-builder/hydrator _anchor_scene_row_looks_like_final_answer
(messages.js + routes.py) used a loose '>=80 + prefix' rule while the renderer
_anchorSceneProseMatchesFinalAnswer uses '>=80 + ratio>=0.9'. A short intermediate-prose row that
was a prefix of the final answer could be dropped from the persisted scene by the looser builder,
leaving the stricter renderer no row to show -> silent prose loss. Aligned both filters to the
renderer's rule (drop fewer -> preserve more intermediate prose). Matrix already GREEN; this closes
the edge the fixture didn't exercise.
2026-06-21 06:03:44 +00:00
nesquena-hermes 332b24bec9 stage #4587 onto master (v0.51.549) — render persisted anchor activity in Transparent Stream on reload (#4568)
Transparent Stream lost ALL tool/thinking activity on every rehydrate path (settle/switch/hide/blur/
reload) — only final answer survived, because _renderSettledAnchorSceneForMessage early-returned
outside Compact Worklog. Adds a transparent-mode settled renderer (same persisted activity_scene_v1,
event rows) + intermediate-prose preservation (suppress only the final-answer-dup prose). Compact
Worklog unchanged.
2026-06-21 05:39:39 +00:00
nesquena-hermes fbab5bb42b docs(release): stamp v0.51.549 — hotfix profile switching for single-user named profiles (#4589/#4586) 2026-06-21 05:32:53 +00:00
nesquena-hermes fba80e6981 harden(#4589): close the SECOND isolation-escape door (runtime-env path)
Codex re-gate (CORE, reproduced): _reload_dotenv() was guarded, but get_profile_runtime_env() also
copies a profile .env into runtime env, which profile_env_for_background_worker()/streaming apply to
os.environ -> HERMES_WEBUI_ISOLATED_PROFILE=0 in a profile .env still escaped isolation on that path.
Fix: filter _PROTECTED_ENV_KEYS in get_profile_runtime_env's .env loop + add the key to
_BLOCKED_RUNTIME_ENV_KEYS (gateway-parity filter). + runtime-path regression test.
2026-06-21 05:23:09 +00:00
nesquena-hermes cb7efedc38 harden(#4589): protect HERMES_WEBUI_ISOLATED_PROFILE from profile-.env override
Codex gate (CORE, reproduced): _reload_dotenv() copies a profile's .env into os.environ, so a
contained user could set HERMES_WEBUI_ISOLATED_PROFILE=0 in their own profile .env and silently
escape isolation. Fix: _PROTECTED_ENV_KEYS — _reload_dotenv refuses to override operator/deployment
keys from a profile .env (logs a warning). + regression test proving .env can't disable isolation.
2026-06-21 05:17:20 +00:00
nesquena-hermes 17eb82f087 stage HOTFIX #4589 onto master (v0.51.548) — require explicit opt-in for isolated profile mode (#4586)
Profile-isolation regression: isolated mode was inferred from HERMES_HOME path shape (*/profiles/<name>),
which the launcher exports for ANY normal named profile -> single-users on a named profile lost profile
switching + saw only 1 profile. Fix: HERMES_WEBUI_ISOLATED_PROFILE explicit opt-in is now the PRIMARY gate.
2026-06-21 05:08:59 +00:00
nesquena-hermes 4fa9abd9d2 docs(release): stamp v0.51.548 — extension load diagnostics (#4569) 2026-06-21 04:16:58 +00:00
nesquena-hermes 1b7c1fe619 rebase #4569 onto master (v0.51.547) for ship-gate — extension status diagnostics (#4561 follow-up)
santastabber: authenticated /api/extensions/status endpoint reporting WHY an extension didn't load
(stable codes + coarse sources only, no path/env/URL leak). Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 04:08:44 +00:00
nesquena-hermes a9a20d3d98 docs(release): stamp v0.51.547 — re-auth before disabling password auth (#3581) 2026-06-21 02:45:34 +00:00
nesquena-hermes 8ea7ad582e rebase #3581 onto master (v0.51.546) for review+ship-gate — require current password before disabling auth
starship-s: sudo-mode re-auth on POST /api/settings before any change/clear/passwordless transition
of password auth, + cosmetic auth-disabled-acknowledged nav badge. Operator-hardening (rubric-named).
Security-boundary + visible UI -> Nathan visual sign-off. Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-21 02:26:20 +00:00
nesquena-hermes 6b2ac55b8d stage #4566 + stamp v0.51.546 — fix flaky gateway_sync test (#4557)
rodboev: settings-write-version counter widens the session-list cache key + flush CLI-sessions
cache on visibility toggle, killing the mtime_ns-bucket collision flake. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-21 02:04:09 +00:00
nesquena-hermes a10f511a13 fix(#4561): catch RecursionError on deeply-nested manifest (BRICK)
Codex+Opus ship-gate: a <=64KB but deeply-nested JSON manifest makes json.loads raise
RecursionError, which escaped _read_manifest_urls into the app-shell route -> every page
load 503s. Add RecursionError to the fail-safe except + regression test (verified
red-without/green-with). Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 00:42:58 +00:00
nesquena-hermes 3e229141af stage #4561 + stamp v0.51.545 — extension manifest asset bundles
santastabber. HERMES_WEBUI_EXTENSION_MANIFEST: bundle extension assets via a 64KB JSON manifest
instead of long URL lists. Same same-origin allowlist validator, traversal-guarded manifest path,
default-off. Nathan blessed (richer extension system). Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 00:37:08 +00:00
nesquena-hermes 2471e277cb rebase #3933 + stamp v0.51.544 — opt-in render markdown in user messages
rodboev. Default-off; OFF path byte-identical to master (cache key 'u' + _renderUserFencedBlocks
unchanged when window._renderUserMarkdown falsy). ON routes user text through the same renderMd
SAFE_TAGS/_isSafeUrl sanitizer as assistant msgs. Nathan override-approved (likes the idea, opt-in).
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-21 00:03:02 +00:00
nesquena-hermes feeb5b760a docs(release): stamp v0.51.543 — stable assistant turn anchors (#4411/#3926) 2026-06-20 22:38:38 +00:00
nesquena-hermes cf178d6b0a test: assert direct-assignment anchor-scene snapshot export (not recursive wrapper)
The #4411 snapshot test codified the OLD recursive wrapper signature verbatim
(window.X=function(){return X()}), which is the brick the recursion fix removes.
Update lines 829-830 to assert the correct direct-assignment export form + that the
wrapper form is absent — so the test now guards AGAINST the brick instead of for it.
Codex final re-confirm flagged this stale assertion (product code already correct).
2026-06-20 22:26:18 +00:00
nesquena-hermes 54818261e1 test: widen window-collision classifier to recognize long bare-identifier exports
The #2715/#2771 brick-guard (test_window_function_collision) capped the window.X=RHS
capture at 32 chars and required a trailing terminator to classify a bare identifier,
so a very long function name (e.g. _renderLiveAnchorActivitySceneSnapshotForStream, 47
chars) was misclassified as 'other' → false positive on the now-correct direct-assignment
exports. Widen capture to 80 chars + recognize an identifier running to end-of-window.
Still flags the real object/number/other bug shapes.
2026-06-20 22:15:41 +00:00
nesquena-hermes 6c1fe51ca6 fix(#4411): BRICK — infinite recursion in live anchor-scene window exports
Codex ship-gate (CORE). ui.js:9241 re-exported three top-level functions via
same-name wrappers (window.X = function(){ return X() }). In a classic script a
top-level `function X(){}` IS window.X, so the wrapper reassigns that global to
itself → infinite recursion → RangeError: Maximum call stack size exceeded on
every live worklog render / session reattach / snapshot-restore path (send,
sessions.js:1264/1375/570). Replace with direct assignments. JS-only brick the
pytest suite can't catch (no JS execution) — confirmed by classic-script semantics
+ will verify live in-browser. Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 22:09:34 +00:00
nesquena-hermes 81a4945be8 harden(#4411): read-side anchor-scene ref-ambiguity guard (Opus finding)
Mirror the write-side _find_anchor_scene_message ambiguity guard on the read side:
_hydrate_anchor_activity_scenes now counts ref occurrences and skips ref-based
attachment for any ref resolving to >1 assistant message (falls through to the
positional index match), so a duplicate content-digest ref can't double-render the
same worklog scene. Low-severity defense-in-depth on the crown-jewel render surface.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 22:05:01 +00:00
nesquena-hermesandfranksong2702 001148d208 fix(#4411): guard /api/session/anchor-scene against cross-profile writes
Codex ship-gate (CORE): _handle_session_anchor_scene loaded the session via
_get_or_materialize_session (by-id, no profile scoping) and persisted
anchor_activity_scenes without the active-profile visibility check GET /api/session
applies — an authenticated request under profile A could write anchor scenes onto a
profile-B session. Add the _session_visible_to_active_profile guard (404, no write),
mirroring routes.py:~8922. Regression test verified red-without/green-with the guard.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 22:02:56 +00:00
nesquena-hermes 6951d10ae5 rebase #4411 onto master (v0.51.542) for deep-review — Stable Assistant Turn Anchors capstone
franksong2702's activity_scene_v1 anchor-scene model: live-stream/settled/refresh/re-entry
show the same ordered turn. Rebased clean (no conflicts) onto current master.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 21:48:55 +00:00
nesquena-hermes 01985bba04 docs(release): stamp v0.51.542 — model picker syncs with agent catalog (#4413) 2026-06-20 21:38:35 +00:00
nesquena-hermes acd57d847d rebase #4413 re-pushed head (553e9584) for re-gate
Converged both bounce items: enrich-existing-only (continue on webui_list-is-None,
no new providers -> routing side-effect gone, routes.py no longer touched) + respects
per-provider ID prefix convention (@nous:). Co-authored-by: kaishi00 <kaishi00@users.noreply.github.com>
2026-06-20 21:22:53 +00:00
nesquena-hermes 5eeb43a1f7 test: update disk-cache round-trip assertions for reconstructed aliases
Two existing tests asserted _load_models_cache_from_disk()/get_available_models()
return EXACTLY the saved payload; the loaders now also return aliases reconstructed
from config (empty {} when no config aliases). Update both assertions to expect the
aliases field. Caught by the full suite + Codex re-gate.
2026-06-20 19:33:57 +00:00
nesquena-hermesandstarship-s 85e3be6a42 fix(#4555): preserve /api/models aliases contract on disk-cache fallback
Codex ship-gate (SILENT): both disk loaders dropped `aliases` (the save path
never persisted them), so a disk-cache hit — strict cold-path OR the new stale
fallback — served /api/models with empty aliases, silently breaking `/model
<alias>` slash-command resolution (static/commands.js resolves slash aliases
only from /api/models.aliases). Add _model_aliases_from_config() helper and
reconstruct aliases from current config in BOTH loaders (fixes the pre-existing
strict-loader gap too). Adds a regression test.

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-20 19:25:51 +00:00
nesquena-hermesandstarship-s fbafe1e623 harden(#4555): isolate stale-cache path in test fixture + enforce schema-version equality on stale fallback
Applies Opus ship-gate findings: (1) the autouse fixture now points
_get_models_cache_path at an isolated temp file so the over-budget static-fallback
test can't read a real ~/.../models_cache.json and become an order-dependent flake;
(2) _load_stale_models_cache_from_disk rejects a cross-schema cache (its groups/badge
shape may be incompatible with the current picker). Adds a cross-schema-rejection test.

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-20 19:19:52 +00:00
nesquena-hermes 3d9487cbaf docs(release): stamp v0.51.541 — stale models cache fallback on catalog timeout (#3928) 2026-06-20 19:11:56 +00:00
nesquena-hermes a46c368854 docs(release): stamp v0.51.540 — cross-provider model selection + gateway reasoning effort 2026-06-20 18:44:43 +00:00
nesquena-hermes 1ee5666870 docs(release): stamp v0.51.539 — gateway session_id + slash-model routing 2026-06-20 17:42:42 +00:00
nesquena-hermes 79bb2a9a34 docs(release): stamp v0.51.538 — gateway legacy approval relay (#4549) 2026-06-20 17:36:45 +00:00
nesquena-hermes 5020f8de9a fix: record gateway run_id on legacy approval event for response relay
The legacy /v1/chat/completions approval handler (added for #4549) rendered
the approval card but never populated _STREAM_RUN_IDS, so /api/approval/respond
could not relay the user's choice back to the gateway to resume the parked run
— it fell through to the local approval path and returned {ok: false}. Record
the run_id from the approval payload (no-op when absent), mirroring the runs-API
path. Adds a behavioral regression test that fails on the pre-fix head.

Codex ship-gate finding (CORE). Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-20 17:26:46 +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 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 62a65a4ff1 review polish: surface plugin-provider check failures at warning (greptile P2) + fix #4253 comment mislabel
Independent review (APPROVED clean) + greptile both flagged one non-blocking nit:
_is_known_model_provider swallowed a transient _is_plugin_model_provider failure
at debug level, so a real plugin-backed provider could briefly vanish from the
picker with no visible log. Promoted to logger.warning(exc_info=True).

Also corrected the endpoint-error-hint comments in ui.js: they cited #4253, which
is actually a cron release (mislabel carried from #4279's body). Comment-only —
no logic change to the browser-verified collapsible-groups code.
2026-06-16 22:09:57 +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-hermesandakrhin 872a8a391e feat(model-picker): collapsible provider groups — collapsed-except-selected, cleaner show-more, de-noised rows
Builds on #4279's collapsible-group foundation and extends it into a complete
provider-group UX:

- Groups start COLLAPSED except the group that owns the selected model, so the
  picker opens scannable instead of as a long flat list. Search auto-expands
  matching groups (and searches hidden overflow models); clearing the search
  restores the collapsed-except-selected view.
- 'Show more' restyled as a distinct '+ Show all N models' expander (muted
  italic, indented, no model-id line) that reveals overflow IN PLACE — the group
  stays open, other groups keep their state, and the view scrolls to the newly
  revealed models instead of resetting to the top. Falls back to a full
  re-render in minimal/headless DOM. Endpoint-error groups (#4253) and the
  expanded group persist correctly across a search round-trip.
- Per-row provider chip suppressed for rows already under their own provider
  heading (removes the repetitive 'provider on every row' noise); kept for
  hoisted/search rows.

Verified end-to-end on desktop + mobile 390px (collapse/expand, search +
overflow-search + clear-reset + no-results, load-more in-place + stay-open +
scroll-to-new, endpoint-error visibility). #3691 driver tests updated for the
collapsible-group structure + de-noised chip. In-place-path CI coverage tracked
in #4331.

Co-authored-by: akrhin <akrhin@users.noreply.github.com>
2026-06-16 21:45:30 +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-hermesandrodboev b3e0896356 fix(workspace): harden remote POSIX workspace trust boundaries (#3664) (#4273)
Normalize both the candidate and terminal.cwd before the remote-POSIX
containment check so `{cwd}/../other/...` lexical escapes are rejected, and
restore the blocked-system-root gate BELOW the home-directory allow-list in
both resolve_trusted_workspace() and validate_workspace_to_add() so valid
systemd-homed paths under /var/home/... stay allowed. Also normalize POSIX
shapes at the source (_as_posix_path) so every downstream block probe is
shape-consistent (/etc/../home/user -> /home/user).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-16 18:58:31 +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-hermesandai-ag2026 0b57a7101f fix: keep session sidebar visible on slow loads (#4167)
Sidebar vanished during resilient/slow session loads. Preserve sidebar
fields on resilient loads + add an opt-in api() retry so a slow /api/sessions
shows a retryable error state instead of an empty sidebar.

Maintainer fixes for the 3 review-blocking SILENT regressions:
- Restore read_only / is_read_only / gateway_routing to the sidebar response
  allowlist, so read-only (imported CLI + Claude Code) sessions keep their
  rename/action-menu/swipe suppression and the gateway model label renders.
  (gateway_routing_history intentionally NOT sent — the label reader prefers
  the latest gateway_routing, and the full history would bloat each row.)
- Scope-tag the session cache (active profile + all-profiles flag). When a
  /api/sessions refresh fails right after a profile switch, the catch path now
  clears the stale rows and shows the error state instead of re-rendering the
  PRIOR profile's sessions.

Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-16 18:42:01 +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-hermesandakrhin b81a92ecd5 fix: credential pool fallback for custom providers (#4247)
Custom/aliased providers with keys in the credential pool (added via
hermes auth add) were reported unconfigured in the WebUI because provider
detection only checked env vars. Add _has_explicit_pool_credentials() that
checks the pool while filtering the ambient gh_cli entry (so Copilot doesn't
falsely show configured on any machine with gh installed), cache-routed to
avoid the cold per-provider load_pool latency, and resolve provider aliases
before load_pool() at every retrieval site so detection and retrieval read
the same pool (glm->zai, github->copilot, etc.). /api/models/live wrapped in
profile scope; getattr-safe runtime_api_key for OAuth-only entries.

Co-authored-by: akrhin <akrhin@users.noreply.github.com>
2026-06-16 17:44:10 +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-hermesandLLMinem ed7bd05530 fix(voice): recognize command STT providers in capability probe (#4312)
The /api/transcribe/capability probe's resolve_provider() only knew the
built-in STT providers, so an explicit stt.provider mapping to a Hermes
command-type provider was reported unavailable and the browser fell back
unnecessarily. Add command_provider_available(), delegating to the agent's
_resolve_command_stt_provider_config(provider, cfg) as the single source of
truth, consulted as the final fallthrough after the built-in chain (the
agent rejects built-in names internally, so ordering can't shadow a native
provider). Added a clarifying comment that command providers are
intentionally omitted from the auto-detect tuple to match the agent's
_get_provider() precedence.

Co-authored-by: LLMinem <LLMinem@users.noreply.github.com>
2026-06-16 17:21:11 +00:00