1564 Commits

Author SHA1 Message Date
Botomir
8d8a88680e fix: record fallback routing metadata
Some checks failed
Browser smoke / browser-smoke (push) Has been cancelled
Tests / lint (push) Has been cancelled
Tests / test (3.11, 0) (push) Has been cancelled
Tests / test (3.11, 1) (push) Has been cancelled
Tests / test (3.11, 2) (push) Has been cancelled
Tests / test (3.12, 0) (push) Has been cancelled
Tests / test (3.12, 1) (push) Has been cancelled
Tests / test (3.12, 2) (push) Has been cancelled
Tests / test (3.13, 0) (push) Has been cancelled
Tests / test (3.13, 1) (push) Has been cancelled
Tests / test (3.13, 2) (push) Has been cancelled
2026-07-10 08:07:32 +02:00
nesquena-hermes
6e6931a8c3 Merge #4028 (align profile cookie env var with HERMES_WEBUI_ prefix) onto master 2026-06-13 07:20:58 +00:00
nesquena-hermes
e4bdcbfeae Merge #3823 (tool iteration limit terminal state) onto master
# Conflicts:
#	CHANGELOG.md
2026-06-13 05:06:46 +00:00
nesquena-hermes
c02c31c837 Merge #4006 (scroll auto-follow setting + pin guards) onto master
# Conflicts:
#	api/config.py
#	static/panels.js
2026-06-13 03:54:11 +00:00
nesquena-hermes
92f7a24448 Merge #4024 Transparent Stream onto master (rebase)
# Conflicts:
#	CHANGELOG.md
2026-06-13 00:40:17 +00:00
nesquena-hermes
e8529c84e5 Merge #4060: pass custom provider keys to context probes (#4059) 2026-06-13 00:17:16 +00:00
nesquena-hermes
6ec5527386 harden(#4057): pass local skills dir to _skill_category_from_path explicitly (Opus SHOULD-FIX)
_active_skill_search_dirs filters to existing dirs, so on a host with no local
skills dir but configured external dirs the local root is dropped from the list
and the position-based skills_dirs[0]==local assumption misclassified the first
external root as local (its flat skills silently lost their category label).
Pass the local dir explicitly (backward-compatible optional param defaulting to
the old skills_dirs[0] behavior) + regression test for the absent-local-dir case.
2026-06-12 20:28:46 +00:00
nesquena-hermes
2fea2d588b Merge #4058 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes
4ad3299449 Merge #4057 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes
10481d4f34 Merge #4051 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes
ffa9f533ef Merge #4048 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes
82fcf665eb harden(#4052): tolerate providers.*.models list-of-dicts keyed by model/name (Opus SHOULD-FIX)
The richer static-catalog builder's group-building loop used a strict item["id"]
extraction that would KeyError (caught → degrade to the minimal one-model catalog)
for legal config shapes where providers.<id>.models is a list of dicts keyed by
"model"/"name" rather than "id". Mirror the tolerant id-or-model-or-name resolution
the detection loop already uses, skip entries with nothing usable, and add a
regression test. No behavior change for the common list-of-strings / list-of-id-dicts
shapes.

docs(changelog): stamp #4052 model-picker budget fallback as v0.51.370 (Release MI)
2026-06-12 20:05:12 +00:00
b3nw
9ac9f8d690 fix(#4059): log unresolved custom provider key templates 2026-06-12 16:23:35 +00:00
Rod Boev
4d8fa0245e fix(#3928): stabilize the static fallback contract 2026-06-12 11:55:43 -04:00
b3nw
b2eda41b49 fix(#4059): pass custom provider keys to context probes
Resolve custom provider API keys from the matched config snapshot and pass them through session hydration plus streaming fallback context-length probes. This prevents authenticated /v1/models endpoints from falling back to the default 256K window and clobbering larger persisted session metadata.
2026-06-12 15:53:48 +00:00
Benoît Vézina
8003faa801 feat(tts): add French (fr-CA, fr-FR) voices to Edge TTS allowlist
Adds 4 fr-CA + 3 fr-FR Edge neural voices so francophone users can use
the Edge TTS engine instead of receiving HTTP 400 "invalid voice" on
every utterance. Pure superset of the existing allowlist; no validation
or rate-limit behavior changes.

Tests mirror the test_issue2931 in-process / mocked-edge_tts pattern:
each new voice is parametrized through _handle_tts and asserted to
reach synthesis (HTTP 200); fr-BE-CharlineNeural (real Edge voice
but intentionally unlisted) is asserted to still 400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-12 09:44:24 -04:00
Rod Boev
ddce08fd70 docs(skills): pin the local-first search-dir contract 2026-06-12 09:36:10 -04:00
Rod Boev
1f42f3190f fix(skills): categorize flat external skill dirs (#4053) 2026-06-12 09:21:47 -04:00
Rod Boev
76f3083223 fix(#3928): keep fallback badge helper out of source-marker tests 2026-06-12 08:43:06 -04:00
Rod Boev
87183b9517 fix(#3928): return a real static catalog when /api/models times out 2026-06-12 08:35:22 -04:00
Rod Boev
1529dd0841 fix(commands): route /reload-skills through the WebUI allowlists (#4043) 2026-06-12 08:31:11 -04:00
Rod Boev
ddcaf6091c fix(#4040): allow Tauri IPC host in connect-src 2026-06-12 07:50:43 -04:00
gaku
6a13feaf8b refactor(profiles): align profile cookie env var with HERMES_WEBUI_* naming
The profile cookie has been configurable since #1756 via
WEBUI_PROFILE_COOKIE_NAME, the lone WebUI env var missing the HERMES_WEBUI_
prefix shared by every other setting (e.g. HERMES_WEBUI_COOKIE_NAME from #3981).

- Read HERMES_WEBUI_PROFILE_COOKIE_NAME first (canonical name)
- Keep WEBUI_PROFILE_COOKIE_NAME as a deprecated alias so existing deployments
  are unaffected; behavior is unchanged, only the name is aligned
- Warn once per process for the legacy name (this resolver runs on every
  request, so the deprecation log must not fire per-request)
- Add resolution tests covering canonical, legacy, precedence, blank, and
  warn-once paths
2026-06-12 09:55:49 +00:00
Mohamed Habib
43bb015135 merge: sync transparent stream branch with master 2026-06-12 11:25:53 +03:00
Mohamed Habib
2005f1b115 merge: sync transparent stream branch with master 2026-06-12 11:13:16 +03:00
nesquena-hermes
005b2ca460 fix(#4022): preserve different-endpoint config base_url override (Codex CORE)
Codex caught a regression: the contributor's _runtime_preferred_base_url preferred
the runtime base_url for ALL non-custom providers, which would clobber an explicit
providers.<id>.base_url override pointing at a different host/port (LM Studio at a
LAN IP, an OpenRouter mirror). Now only prefer the runtime URL when it's the SAME
scheme+host+port as the configured one (the #3895 /v1-dedup case is path-only on
the same host); a genuinely different configured endpoint wins. Added
_same_base_url_endpoint() + 2 regression tests (different-endpoint preserved,
same-endpoint normalization).
2026-06-12 07:54:48 +00:00
nesquena-hermes
983f059acd Merge #4022 — honor runtime target model+base_url in WebUI streaming (#3895) 2026-06-12 07:48:26 +00:00
nesquena-hermes
03799f8e4a harden(#4023): apply Opus security findings — verify-side name-pattern gate + require handler when auth enabled
Opus independent security review concurred SAFE and surfaced 2 LOW defense-in-depth
items, both applied: (1) verify_profile_cookie_value now validates the profile name
against _PROFILE_ID_RE itself (not only in get_profile_cookie) so a future second
caller can't return an unvalidated name; (2) build_profile_cookie raises when auth is
enabled and no handler is passed, so a future call site can't silently emit an
unsigned (session-unbound) profile cookie. +3 regression tests.
2026-06-12 07:37:48 +00:00
nesquena-hermes
aef15ca559 Merge #4023 — bind active profile cookie to session (#803) 2026-06-12 07:19:46 +00:00
ai-ag2026
de3b756558 fix: merge missing webui lineage display rows 2026-06-12 08:00:11 +02:00
hinotoi-agent
4dca506858 Bind profile cookie to auth session 2026-06-12 10:52:56 +08:00
Frank Song
02e72163a6 Fix tool-limit terminal failure annotation 2026-06-12 10:25:18 +08:00
Mohamed Habib
306a148fbf merge: sync transparent stream branch with master 2026-06-12 05:09:01 +03:00
Frank Song
136568fe81 fix(streaming): tighten tool limit terminal guards 2026-06-12 10:03:31 +08:00
Frank Song
08ba5a6676 Fix tool-limit terminal detection 2026-06-12 10:03:31 +08:00
Frank Song
ec23dac922 Handle tool limit terminal state 2026-06-12 10:03:31 +08:00
Mohamed Habib
fcfaa4686a feat: add transparent stream activity display 2026-06-12 04:45:41 +03:00
Rod Boev
6e133a4f44 fix(#3895): carry target model through credential self-heal 2026-06-11 21:08:24 -04:00
Rod Boev
e62a31e463 fix(#3895): preserve configured base-url fallback through self-heal 2026-06-11 21:06:16 -04:00
Rod Boev
a692314acf fix(#3895): honor runtime target model and base_url in WebUI streaming 2026-06-11 20:56:22 -04:00
Rod Boev
a82eb1622f fix(#3958): keep reasoning effort status on the active session model 2026-06-11 18:46:17 -04:00
nesquena-hermes
7a5c3bc374 fix(#3994): scope happy-path guard to read_only flag; complete #1436 mock stub
Refined the Codex-CORE fix: the happy-path (already-stored session) guard rejects
only an explicit read_only flag — a stored messaging session already owns its
sidecar, so the messaging-fork risk is specific to the materialize FALLBACK
(which creates a sidecar) where the _is_messaging_session_record check stays.
Also completed test_issue1436's _stub_session MagicMock (read_only=False,
_loaded_metadata_only=False) — bare MagicMock auto-attrs were truthy, tripping
the new read-only guard + _ensure_full_session_before_mutation reload.
2026-06-11 21:45:36 +00:00
nesquena-hermes
6f41fe7490 fix(#3994): reject read-only + messaging sessions on BOTH happy-path and materialize fallback (Codex CORE x2)
Codex caught two data-integrity gaps in the contributor's guard: (1) the
get_session() happy path returned a stored session without checking read_only/
messaging, so an already-imported read-only session could be mutated via
rename/update/move; (2) the fallback only checked cli_meta.read_only, but agent
rows normalize messaging sources WITHOUT setting read_only — materializing a
writable sidecar for a state.db-owned messaging session forks its title/state.
Now reject getattr(s,'read_only') OR _is_messaging_session_record on the happy
path, and cli_meta.read_only OR _is_messaging_session_record(cli_meta) in the
fallback. Replaced the messaging-stub test with 3 regression tests (stored
read-only, stored messaging, messaging cli_meta without read_only flag).
2026-06-11 21:41:51 +00:00
nesquena-hermes
89fbc898f3 Merge #3994 — materialize CLI sessions on rename/move/update (#3985) 2026-06-11 21:35:13 +00:00
Rod Boev
4dc4786aae fix(#3979): treat malformed providers config as unconfigured 2026-06-11 16:55:14 -04:00
Ming
e05b99b7fd fix(scroll): guard _scrollAfterMessageRender and _finishDone against pin hysteresis snap-back
Two scroll-state bugs cause the viewport to snap to the bottom after
streaming completes, even when the user has scrolled up to read:

1. _scrollAfterMessageRender (ui.js): when preserveScroll=true and
   _scrollPinned=true (re-set by the 250px near-bottom hysteresis),
   _followMessagesAfterDomReplace() calls scrollToBottom(), overriding
   the user's position. Added !_messageUserUnpinned guard so explicitly
   scrolled-up users get their snapshot restored instead.

2. _finishDone (messages.js): the explicit scrollToBottom() at L3302
   fires when shouldFollowOnDone is true, but that flag only checks a
   120px near-bottom threshold. Added _isMessagePaneNearBottom(250)
   gate so users reading 250px+ above bottom keep their position.

Also adds a new 'auto_scroll_follow' setting (default: off) that gates
scrollIfPinned() and _shouldFollowMessagesOnDomReplace(). When disabled,
the viewport never auto-scrolls during streaming — the user controls
scroll position manually and uses the ↓ button to jump to bottom.

7-file setting-add pattern applied:
- api/config.py: _SETTINGS_DEFAULTS + _SETTINGS_BOOL_KEYS
- static/boot.js: both init paths
- static/ui.js: scrollIfPinned() + _shouldFollowMessagesOnDomReplace()
- static/index.html: checkbox in appearance settings
- static/i18n.js: en + zh-CN translations
- static/panels.js: payload, autosave, populate, full save
- messages.js: no additional changes (gated via _shouldFollowMessagesOnDomReplace)

Tests updated: test_tars_scroll_reset_regressions, test_issue1690,
test_issue3545. All 116 scroll-related tests pass.
2026-06-11 21:48:14 +02:00
Dima Diall
46f87028c2 fix(routes): materialize CLI sessions on rename/move/update (fixes #3985)
When a session exists in Hermes Agent state.db but has no WebUI sidecar
(SESSION_DIR/{sid}.json), mutation routes (rename, move, update) would
return 404 "Session not found" despite the session appearing in the sidebar.

This mirrors the existing fallback in /api/session/archive:
- Try get_session() first (WebUI store)
- On KeyError, look up CLI metadata via _lookup_cli_session_metadata()
- For messaging/Claude Code (read_only): return 403 instead of silent 404
- For regular CLI sessions: import_cli_session() to materialize sidecar
- Preserve source_tag/raw_source/session_source/etc. for lineage

Routes updated:
- /api/session/rename
- /api/session/update (workspace switch)
- /api/session/move

Refs: #3746 (same class: session discovery vs mutation mismatch),
#3915 (session store empty but data exists in Agent store)
2026-06-11 17:06:16 +01:00
gakugaku
d9c35f58dd feat(auth): make session cookie name configurable via env var
Add HERMES_WEBUI_COOKIE_NAME so multiple WebUI instances sharing a
hostname (different ports) can use distinct auth cookie names. Browsers
scope cookies by host, not host+port (RFC 6265), so same-host instances
otherwise trample each other's `hermes_session` cookie and log users out.

- Resolve the cookie name via _resolve_cookie_name(): env > default,
  mirroring the existing _resolve_session_ttl() pattern.
- Keep `hermes_session` as the default for backwards compatibility.
- Validate against the RFC 6265 token grammar; fall back to the default
  with a logged warning on empty or malformed values.
- Cover default, override, empty, invalid, and Set-Cookie paths in
  tests/test_auth_sessions.py::TestCookieNameResolution.
2026-06-11 08:15:35 +00:00
Hermes Agent
f2ef2851d3 Release LV stage — v0.51.358 (#3964 first-run password bootstrap local gate)
#3964 [security] gate first-password bootstrap (_set_password on POST /api/settings
while auth disabled) to local clients — blocks remote unauth first-run ownership.
Uses request-start auth snapshot (auth_enabled_before), so no mid-request TOCTOU.
Self-rebased onto v0.51.357 (8-behind, 3-dot fidelity verified byte-identical).

#3970 (oauth single-flight) DROPPED from this stage: Codex+Opus both caught a
check-then-insert race — _pending_oauth_flow_for releases the lock before the
device-code request + flow insertion, so concurrent unauth starts still spawn
multiple workers (Codex empirically reproduced w/ 2 threads). Returned to author
w/ the atomic per-(provider,home) start-lock fix. Re-gating the (N-1) stage.

Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-06-11 04:39:34 +00:00
Hermes Agent
85ee6ec2c8 fix(profiles): address greptile P2 flags on #3960
- _load_models_cache_from_disk: resolve _get_models_cache_path() once
  (was called twice — exists() then open()) (greptile P2).
- conftest: use a _MISSING sentinel for the _active_profile restore guard so
  it restores whenever api.profiles was importable pre-test, independent of the
  value (greptile P2; _active_profile defaults to 'default' so the prior
  is-not-None guard was functionally fine but technically value-dependent).

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-11 01:56:58 +00:00