Commit Graph
8654 Commits
Author SHA1 Message Date
nesquena-hermes 26f2456205 docs(changelog): #7744 #7742 flaky-test fixes 2026-09-23 03:51:35 +00:00
nesquena-hermes fd0842cc4d Merge #7742 2026-09-23 03:51:06 +00:00
nesquena-hermes 671905b89d Merge #7744 2026-09-23 03:51:05 +00:00
nesquena-hermes 6f14958bdc Merge pull request #7746 from nesquena/stage/7436-reconnect-replay
Release: #7436 skip redundant reconnect tool-card replay (@atchisonbrent)
2026-09-22 20:28:23 -07:00
happy5318 8d0cdaefbe fix(i18n): preserve explicit saved English on first-visit bootstrap
Closes #7622.

Round-3 follow-up to the maintainer review at
https://github.com/nesquena/hermes-webui/pull/7730#issuecomment-5786493511.
The round-2 'primary === "en"' heuristic overrode users who had
genuinely picked English: when the server-stored 'language' was 'en'
the resolver treated it as the schema default and skipped it, falling
through to localStorage / the browser hint.  This round addresses all
three maintainer findings:

1. BRICK — server stops injecting the schema default.  Drop
   '"language": "en"' from api/config.py:_SETTINGS_DEFAULTS so a
   fresh install returns settings['language'] == None.  Existing
   settings.json files that already carry 'language: "en"' still
   report 'en' (the field is just absent from the defaults dict; the
   per-file load path is unchanged).  'language' is added back to
   _SETTINGS_ALLOWED_KEYS so the Settings modal save path still
   validates and persists explicit user picks (the BCP-47 regex is
   unchanged).

2. CORE — browser-language detection is now in one guarded helper.
   static/i18n.js exposes _detectBrowserLanguageHint(), used by
   loadLocale(), boot.js, and panels.js.  A throwing navigator
   accessor in some embedded webviews can no longer abort boot or
   settings hydration before the model/provider/plugin/extension
   sections are populated.

3. CI RED — restore the test shape.  boot.js and panels.js call
   resolvePreferredLocale(s.language, localStorage.getItem('hermes-lang'),
   _detectBrowserLanguageHint()) inline so
   tests/test_language_precedence.py::test_boot_and_settings_panel_use_shared_locale_precedence
   matches the expected argument list.  The TestComposedResolverPrecedence
   tests in tests/test_issue7622_initial_locale_from_browser.py are
   re-pinned to the round-3 contract (server primary is None for a
   fresh install; an explicit 'en' primary from a user-saved file
   beats a non-English browser hint).

Tests (22/22 i18n-related pass; 833/833 settings+i18n+locale pass; ruff
forward-gate --diff origin/master is clean):
- test_language_precedence.py: 8/8 (added guarded-helper, server-default,
  composed-resolver, and cross-file first-visit tests)
- test_issue7622_initial_locale_from_browser.py: 14/14 (re-pinned to
  round-3 contract; added explicit-saved-English regression test)
- test_login_locale.py and friends: unchanged behaviour for the
  save/load round trip
2026-09-23 11:21:51 +08:00
nesquena-hermes 394ff790e5 Merge master (exp-v0.52.344) into stage/7436-reconnect-replay
# Conflicts:
#	CHANGELOG.md
2026-09-23 03:21:30 +00:00
nesquena-hermes e0a0e9fbcd Merge pull request #7745 from nesquena/stage/7374-double-render
Release: #7374 stop rendering a settled assistant answer twice (@Thireus)
2026-09-22 20:19:58 -07:00
nesquena-hermes 77182f435a docs(changelog): #7436 reconnect tool-card replay 2026-09-23 02:29:53 +00:00
nesquena-hermes c5663e86a7 Merge PR #7436 from atchisonbrent: skip unchanged tool-card replay on reconnect 2026-09-23 02:29:39 +00:00
nesquena-hermes f26f7926c6 docs(changelog): #7374 duplicate assistant answer render 2026-09-23 02:29:01 +00:00
nesquena-hermes a6d6b3da85 Merge PR #7374 from Thireus: fix duplicate assistant answer render (#2051) 2026-09-23 02:28:29 +00:00
webtecnica 67135b3b1e test(models): pin live-rebuild budget in model-cache metadata tests
The three get_available_models() tests in tests/test_model_cache_metadata.py
depend on the rebuild finishing inside _LIVE_REBUILD_BUDGET_SECONDS (4.0s).
On a loaded host the rebuild exceeds it and api/config.py::get_available_models
serves the over-budget stale payload, failing the rebuilt-metadata assertions.

Pin the budget to 0.0 (synchronous rebuild), the same pattern already used by
tests/test_issue2513_custom_provider_remote_models.py and
tests/test_copilot_provider_model_settings_not_allowlist.py.

Closes #7735
2026-09-22 22:58:20 -03:00
webtecnica a8180bc51f fix(context-length): guard provider ownership on global model.base_url backfill (#7535)
A session on a built-in registry provider (e.g. opencode-go, endpoint in
PROVIDER_REGISTRY, empty base_url by design) had its empty base_url slot
backfilled with the global model.base_url owned by a *different* provider
(custom:ark). The context-length resolver then probed the wrong endpoint's
/models catalog and fell back to the generic 128K catalog entry for a
1M-context model, while the on-disk session and SSE stream carried 1,000,000.

Only fill the global model.base_url slot when the session provider IS the
configured model.provider owner, mirroring the ownership predicate used for
model_cfg's API key in _context_length_config_api_key_for_provider.
2026-09-22 22:54:12 -03:00
webtecnica 26702fd19f Remove flaky source-text oracle test (issue #7726)
test_delete_cli_session_still_opens_writable_connection asserted on
inspect.getsource(models.delete_cli_session) text, so it failed only in
single-process full-suite runs when an earlier test leaves a
monkeypatched/decorated wrapper bound on api.models.

The writable-connection contract is already covered end to end by
tests/test_issue1494_state_db_fd_leak.py::test_delete_cli_session_closes_connection,
which performs a real delete and asserts the rows are gone. Remove the
source oracle without a textual replacement; keep a short comment
pointing at the behavioral coverage.
2026-09-22 22:49:23 -03:00
nesquena-hermes 18db77a132 Merge pull request #7741 from nesquena/stage/rm-343-batch
Release: #7631 sidebar window setting (@carlotestor), #7716 hidden-tab 404 poll (@happy5318), #7734 OpenRouter GLM default (@MuhammadUsamaMX)
2026-09-22 18:15:56 -07:00
nesquena-hermes 34feb50c5b docs(changelog): #7631 #7716 #7734 2026-09-23 00:44:29 +00:00
nesquena-hermes 8a2d79ca43 Merge PR #7734 (rebased onto master; maintainer conflict resolution + follow-ups) into stage/rm-343-batch 2026-09-23 00:43:14 +00:00
nesquena-hermes 087a2df4a2 Merge PR #7716 into stage/rm-343-batch 2026-09-23 00:43:14 +00:00
nesquena-hermes d79640f6b1 Merge PR #7631 into stage/rm-343-batch 2026-09-23 00:43:13 +00:00
nesquena-hermes 090728c41d test(#7520): teach the #7514 projection tests the one glm-4.5-flash remap slot
The #7514 tests pinned a pure namespace projection (same ids modulo zai/ ->
z-ai/, same labels). #7520 intentionally serves z-ai/glm-4.5-air in the
glm-4.5-flash slot because OpenRouter does not carry glm-4.5-flash. Encode that
single explicit remap in the reference translation; keep order, length and
every other label pinned.
2026-09-23 00:13:11 +00:00
happy5318 b471e7d1b8 fix(i18n): fall back to browser language on first visit (#7622)
Closes #7622.

The renderer's `loadLocale()` was strictly a localStorage-or-English
fallback — non-English speakers had to dig into Settings after every
fresh install.  This change consults `navigator.languages[0]` (or
`navigator.language` when the ordered list is missing) ONLY when no
stored preference exists, so:

  - first visit on a zh-CN browser → UI lands on `zh` immediately
  - first visit on an en-US browser → still `en` (regression guard)
  - first visit on a kl-GL browser (unsupported) → falls back to `en`
  - any second visit (or after the user picked a language in
    Settings) → the stored value wins, the browser hint is never
    consulted again for that browser profile

Tests (8/8 pass): a node driver loads the real `static/i18n.js`,
mocks `localStorage` and `navigator`, and asserts the language
that `loadLocale()` ultimately passes to `setLocale(lang)`.  The
test covers the four core happy/sad paths plus two pinning tests for
the 'stored wins' contract.

`resolveLocale()` already strips the region subtag (zh-CN → zh,
en-US → en, fr-FR → fr), so the `navigator.language` value is
reduced to a key in `LOCALES` without any extra work in this PR.
The fix is strictly additive: `loadLocale()`'s precedence chain
becomes (navigator hint) → (stored) → 'en' on first visit, and
remains (stored) → 'en' on every subsequent visit.
2026-09-23 07:59:27 +08:00
nesquena-hermes e3c21a386c fix(#7520): drop the '(free)' label from z-ai/glm-4.5-air and make the regression test non-vacuous
OpenRouter bills z-ai/glm-4.5-air as a paid model (0.13/0.85 per 1M), so the
wizard must not label it free. Since #7514 the projection offers the dead id
as z-ai/glm-4.5-flash, not zai/glm-4.5-flash, so the original assertion passed
on master; assert the z-ai spelling (fails pre-fix, passes post-fix).
2026-09-22 23:56:38 +00:00
Muhammad Usama d5349a169d fix: resolve #7520 — remap dead zai/glm-4.5-flash to z-ai/glm-4.5-air in OpenRouter wizard 2026-09-22 23:55:21 +00:00
nesquena-hermes 65b6e4d390 Merge pull request #7739 from nesquena/stage/7445-readonly
Release: #7445 keep state.db readers strictly read-only and stop idle polling (@ruizanthony)
2026-09-22 16:50:37 -07:00
nesquena-hermes e2a7acaf2e fix(#7445): require the Agent schema_version marker and sessions(id) PK before indexing
Codex round-3: a look-alike chat DB carrying the seven indexed column names was indexed. Every Agent state.db since the first schema (440c244cac) has a schema_version row and sessions keyed on id; require both before any CREATE INDEX. Test oracles now ignore sqlite_autoindex_* (TEXT PRIMARY KEY).
2026-09-22 23:24:10 +00:00
nesquena-hermes 3b1b735540 fix(state-db): validate the Agent schema up front; 'skipped' only for the known legacy gap
Codex delta gate: same-named messages/sessions tables with foreign columns
exited 0 with every index 'skipped', and a same-named incompatible index on
the legacy gap was skipped past its validation. Now: validate every keyed
column before creating anything; only sessions.last_activity_at may be
absent (reported 'skipped'); a same-named index on that gap is
'Incompatible index'. Regressions for both shapes.
2026-09-22 23:09:15 +00:00
nesquena-hermes 5729b0155c fix(state-db): fail loud when state.db has no agent table instead of skipping everything
A missing *column* is an older agent schema (skip that index); a missing
*table* means the path is not an agent state.db. Without this the new
skip path reported every index 'skipped' with exit 0 on an unrelated DB.
2026-09-22 23:00:14 +00:00
nesquena-hermes ebcba4386d docs: #7445 CHANGELOG + troubleshooting 'skipped' index status 2026-09-22 22:56:26 +00:00
nesquena-hermes b6d2a2ef17 fix(state-db): skip read indexes an older schema can't hold instead of rolling back the rest
On a pre-2026-07-28 Agent schema, sessions has no last_activity_at, so the
fingerprint index's CREATE failed inside the single BEGIN IMMEDIATE and rolled
back idx_messages_session too, leaving messages unindexed: the exact older-agent
case the troubleshooting doc says the tool fixes. Gate each index on its columns
being present and report 'skipped'. (Opus advisor SHOULD-FIX on #7445.)
2026-09-22 22:56:02 +00:00
nesquena-hermesandruizanthony 8635487d9b fix(state-db): keep non-UTF-8 POSIX paths readable in the read-only URI
state_db_file_uri() quoted the POSIX path as a str; an undecodable filename
byte is carried as a surrogateescape code point, which quote() rejects with
UnicodeEncodeError, and get_cli_sessions() swallowed that as an empty
session list. Quote os.fsencode() bytes instead (what Path.as_uri() did on
master). Regression test: a b'\xff' HERMES_HOME component opens read-only.

Maintainer fold-in during release gating (Codex re-gate finding).

Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-09-22 22:56:02 +00:00
nesquena-hermes c8b1f87583 Merge PR #7445 from ruizanthony: fix(sqlite): keep state.db readers strictly read-only and stop idle polling 2026-09-22 22:56:02 +00:00
nesquena-hermes 959177b001 Merge pull request #7737 from nesquena/stage/7569-journal
Release: #7569 run-journal live snapshot rebuild (perf, solo)
2026-09-22 15:38:05 -07:00
nesquena-hermes e372cf62ac docs(changelog): #7569 run-journal live snapshot rebuild 2026-09-22 22:27:20 +00:00
nesquena-hermes 7109a9aed5 Merge PR #7569 from happy5318: perf(run-journal): rebuild live snapshots without quadratic work 2026-09-22 22:27:01 +00:00
nesquena-hermes e14896926f Merge pull request #7736 from nesquena/stage/rm-0922-evening
Release: #6937 launch-failure cleanup, #7021 compression overlap, #7312 profiles Docker 500, #7727 CJK titles
2026-09-22 15:11:23 -07:00
nesquena-hermes db46f4e8d7 test(#7312): skip the agent-backed positive control when agent.skill_utils is a stub
tests/test_issue4783_profile_skills_mtime_cache.py installs a bare
types.ModuleType stub for agent.skill_utils via sys.modules.setdefault and
never restores it. On CI (no hermes-agent checkout) that stub satisfies
pytest.importorskip, so the positive control ran against an rglob walk with
MagicMock frontmatter and failed 9 == 7 on shard 4 of 5, all Pythons.

Require a file-backed module; the control still runs (and passes) against
the real agent package.
2026-09-22 22:04:45 +00:00
nesquena-hermes 6e94d1275e docs(changelog): #7312 #7727 #6937 #7021 2026-09-22 21:14:19 +00:00
nesquena-hermes 243a688e45 chore(profiles): debug-log the skill_utils ImportError fallback (#7312 advisor should-fix) 2026-09-22 21:14:09 +00:00
nesquena-hermes 0a152c548d Merge PR #7021 (contributor head 2e12075400) 2026-09-22 21:14:09 +00:00
nesquena-hermes 47f8e028cd Merge PR #6937 (contributor head 047dfa40a5) 2026-09-22 21:14:09 +00:00
nesquena-hermes e5839b1a20 Merge PR #7727 (contributor head bbdfbae2f7) 2026-09-22 21:14:09 +00:00
nesquena-hermes 2ca576e1df Merge PR #7312 (contributor head aeab2fb99f) 2026-09-22 21:14:08 +00:00
carlotestor 5c24458dff fix(sidebar): derive the route session cap from the shared limit
The env override never reached /api/sessions because the route kept a
separate hard-coded cap of 20. Resolve the window in api.config before
profile init, clamp it at 200, and refuse a profile .env override.
2026-09-22 17:50:56 +00:00
nesquena-hermes 30b407439a Merge pull request #7728 from nesquena/stage/7244-chrono
Ship #7244: chronological state.db row placement (@Hexeption)
2026-09-22 10:33:29 -07:00
nesquena-hermes 44f8d3ed37 fix(models): thread incoming_provenance + protect multi-tool blocks
Rebase threading fix and a backward scan so a chronological insert
cannot split a multi-result tool block.
2026-09-22 17:23:39 +00:00
Ubuntu bbdfbae2f7 docs: update _title_language_mismatch docstring for #7693 fix 2026-09-22 22:20:28 +05:00
Ubuntu c42c5b4d30 fix: resolve #7693 — Cross-script title guard rejects valid CJK+English titles
When the user writes in CJK, the title language mismatch guard now allows
Latin characters in the title as long as the title also contains CJK
characters (cjk >= 2). This permits CJK titles with borrowed English
product/technical terms (e.g. 'WeChat Pay 回调失败排查', 'Python 代码修复')
while still rejecting pure-Latin titles for CJK conversations and
unrelated scripts (Cyrillic, Arabic, etc.).

Added regression tests: test_cjk_start_mixed_cjk_latin_title_is_allowed
and test_cjk_start_pure_latin_title_still_rejected.
2026-09-22 22:19:26 +05:00
Anthony Ruiz 7a30ea5f39 fix(api): fence chronological state merge by provenance 2026-09-22 16:37:14 +00:00
Anthony Ruiz 0c003ef803 docs: describe state.db transcript reconciliation 2026-09-22 16:37:14 +00:00
Anthony Ruiz 2153846bb1 fix(api): keep state-only rows in chronological order on merge
A user prompt could render at the very bottom of a conversation, below the
assistant turn that had already answered it.

merge_session_messages_append_only reconciles the sidecar with state.db. When a
state.db row carries a typed row id that is unique to state.db and absent from
the sidecar (the two stores do not share an id space),
row_id_preserves_source_multiplicity is true and the row bypasses the
sidecar-timestamp-range block -- including the
_insert_state_message_chronologically call that block owns. It then fell through
to the terminal merged_messages.append and landed at the tail regardless of its
timestamp.

The full display path re-sorts by timestamp afterwards
(_merged_webui_lineage_messages_for_display), which hid the defect. The
paginated path used for the initial render (msg_limit) does not, so the user saw
their own message below its own answer until a full reload reshuffled it.

Place such a row in its chronological slot when its timestamp predates the
sidecar tail, falling back to append when no safe slot exists. Rows that are
genuinely newest still append at the tail, so the append-only contract (never
drop a local row) is preserved.

Verified on the reported transcript: the merged tail now ends on the assistant
conclusion instead of the orphaned prompt, with the same message count.
2026-09-22 16:37:14 +00:00