Commit graph

6757 commits

Author SHA1 Message Date
allenliang2022
fbfb6da8fe fix(webui): require an actual captured topPadBefore before the gone-anchor topPad-delta (greptile P1)
When the anchor row is gone and a virtual top-spacer is present but the snapshot
carries no captured topPadBefore (null), Number(null) is 0, so the topPad-delta
branch would add the ENTIRE current spacer height to scrollTop and fling the reader
far from their content. Guard on an actual (non-null) captured topPadBefore; else
keep the raw fallback target. Mutation-checked (drop the null guard -> the reader is
flung 1300px and the test fails).
2026-07-07 20:12:41 +00:00
allenliang2022
7164847255 fix(webui): realign desktop stale-snapshot fallback with the app's scrollTop += delta idiom (#5637 follow-up)
Round-2 gate-cert proved the round-1 'snapshot.top + delta' + nearest-to-current
arbiter is mathematically unwinnable: a row's rect offset is scroll-relative
(rect.top - containerRect.top = rowContentPos - scrollTop), so a staged absolute
target only equals the true content-hold when scrollTop == snapshot.top — and in
that exact case the arbiter measures |raw - cur| = 0 and picks raw, writing the
stale value (the reported up-jump, unfixed).

Rework to the app's own realign idiom (mirrors _restoreMessageViewportAnchor and
_compensateScrollForMeasurementDelta): when a measurable anchor row exists, write
el.scrollTop += (currentOffset - capturedOffset) from the LIVE scrollTop — a no-op
when already aligned, a heal when not, no arbiter. Restore the per-tier lookup
guard (key -> sessionIdx, never the rawIdx degradation that maps to a different
message after a virtualization re-window). Genuinely-gone anchor: mirror the
topPad-delta idiom the snapshot already carries (topPadBefore), else keep raw.

Gate-cert tests now use scroll-dependent rect mocks (rect.top = rowContentPos -
scrollTop) so certified hold numbers are physically realizable; mutation-checked
(revert to raw -> the hold tests fail; drop topPad-delta -> that test fails).
2026-07-07 20:12:41 +00:00
nesquena-hermes
57aa5be2ed
Merge pull request #5753 from nesquena/release/stage-5463-v2
Release: mirror WebUI usage counters to state.db (#5463)
2026-07-07 13:12:06 -07:00
nesquena-hermes
5915f9b5b5 docs(changelog): #5463 mirror WebUI usage counters to state.db 2026-07-07 20:05:18 +00:00
kosta
0560f2615b fix(state-sync): mirror WebUI API call counts 2026-07-07 19:48:19 +00:00
kosta
059a06b70d fix(state-sync): mirror WebUI cache counters 2026-07-07 19:48:19 +00:00
nesquena-hermes
1b58e4df47
Merge pull request #5750 from nesquena/release/stage-5744
Release: clear dead steer busy state (#5723)
2026-07-07 09:45:35 -07:00
nesquena-hermes
cf769dc823 docs(changelog): #5723 clear dead steer busy state 2026-07-07 16:42:08 +00:00
rodboev
8b02a8c260 fix(#5723): complete stale-busy cleanup on dead-run recovery (Fable UX-gate)
_steerClearCurrentOwnerDeadRun cleared busy/activeStreamId/inflight but left
the status line, elapsed timer, and optimistic streaming badge lingering after
a recovered dead run. Add the rest of the app's stale-busy cleanup idiom
(setStatus/setComposerStatus/_clearActivityElapsedTimer/clearOptimisticSessionStreaming),
still bypassing setBusy(false) so its queue-drain can't clobber the restored draft.
2026-07-07 16:32:26 +00:00
nesquena-hermes
c3ff9f41c7
Merge pull request #5741 from nesquena/release/stage-5738
Release: keep the composer above iPad keyboards (#5701)
2026-07-06 22:20:06 -07:00
nesquena-hermes
b65158c0ce docs(changelog): #5701 iPad keyboard composer inset 2026-07-07 05:16:34 +00:00
rodboev
56a395c5f3 fix(#5701): ignore pinch-zoom scale in keyboard-inset (Fable UX-gate)
vv.scale != 1 means the visual-viewport shrinkage reflects zoom, not the
keyboard; on Chromium 'force enable zoom' that produced a large spurious inset
that jittered on pan. Bail out of the inset when |vv.scale-1|>0.05. + test.
2026-07-07 05:07:19 +00:00
rodboev
ae8d0bf3fb fix(#5701): keep the composer above iPad keyboards
On touch-primary devices (iPad Safari) the on-screen keyboard shrinks the
visual viewport but not the layout viewport, so the bottom-pinned composer
sits behind the keyboard. Compute a --keyboard-bottom-inset from visualViewport
and pad the composer up by it; clear it when the keyboard closes / on
non-touch / no-visualViewport. Reuses the existing visualViewport + pageshow
hooks; no-op on desktop/fine-pointer.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-07 05:05:11 +00:00
nesquena-hermes
021bc1280f
Merge pull request #5740 from nesquena/release/stage-5737
Release: drop empty tool_calls arrays before API send (#5737)
2026-07-06 21:56:50 -07:00
nesquena-hermes
1a2dcc7231 docs(changelog): #5737 drop empty tool_calls before API send 2026-07-07 04:52:55 +00:00
Swan-S
ccd62f9b9d fix(streaming): drop empty tool_calls arrays before API send (#5737)
Strict providers (DeepSeek v4, newer OpenAI) reject an assistant message
carrying tool_calls: [] with HTTP 400. _sanitize_messages_for_api already
strips orphaned tool results, but not an assistant message that literally
stores an empty tool_calls list.

Drop the empty key on ALL THREE sanitized-dict sites that must agree:
_sanitize_messages_for_api, _api_safe_message_positions, and the
_safe_projection inside _restore_reasoning_metadata (so a tool_calls: [] row
projects identically on both sides of the metadata-carry-forward alignment and
doesn't lose its reasoning/id/timestamp every turn — Fable gate follow-up,
folded in).

Added mutation-checked regression tests: empty tool_calls dropped + populated
chain preserved (both sanitizer paths), and metadata carry-forward survives a
tool_calls: [] row.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-07 04:43:46 +00:00
nesquena-hermes
ad7d0abf44
Merge pull request #5736 from nesquena/release/stage-tlsflake
test(tls): drain full stdout for the TLS-fallback assertion (flake fix)
2026-07-06 21:14:27 -07:00
nesquena-hermes
f36dad7b23 test(tls): drain full stdout for the TLS-fallback assertion (flake fix)
test_tls_startup_failure_fallback_to_http read only the first 2000 bytes of
server stdout to assert 'TLS setup failed'. When the test interpreter lacks
optional deps (requests/websockets), startup emits a burst of plugin/tool
import warnings + the startup-config banner BEFORE the TLS line, pushing the
marker past the 2000-byte window -> spurious failure. Drain all
currently-available stdout (5s deadline, short-circuit on the marker) instead
of a single fixed-size read. Verified failing->passing in a fresh venv without
the optional deps. Test-only; distinct from the agent-package-isolation flake
cause already fixed in the same test.
2026-07-07 04:09:15 +00:00
nesquena-hermes
b0c79fd4d3
Merge pull request #5735 from nesquena/release/stage-5567-resolver
Release: #5567 model-picker provider resolution fix
2026-07-06 20:56:50 -07:00
nesquena-hermes
4fb7b03200 docs(changelog): #5567 model-picker provider resolution fix 2026-07-07 03:52:04 +00:00
nesquena-hermes
8d496ceaef fix(#5567): resolve model provider by matching value, not the selected option
Frontend fix for the model-picker provider-contamination vector of #5567 (the
backend HERMES_HOME vector shipped in #5577). Credit @b3nw for the root-cause trace.

_modelStateForSelect(sel, modelId) resolved the provider from sel.selectedOptions[0]
(the dropdown's CURRENTLY-selected option) instead of the option whose value matches
modelId. During a profile/tab switch or a model-list rebuild the dropdown transiently
still holds the PREVIOUS profile's default option (e.g. an ollama model), so the wrong
provider was stamped onto a model it doesn't own, persisted into the session's
model_provider, and (since _modelProviderForSend reads the stored value first) re-sent
on every turn -> 'Provider ollama...no API key' for a provider the session never used.

Fix: resolve the provider from the option whose VALUE === modelId; trust the
currently-selected option only when its value matches (preserving the user's exact
pick in the same-value/different-provider collision case). A genuinely-absent model
yields a null provider so the backend re-infers.

This is the WRITE-site fix — it stops all NEW contamination (new-session + send paths
route through _modelStateForSelect). Both Codex and Fable reviewed it SAFE.

Repairing sessions ALREADY poisoned before this shipped is deliberately deferred to a
separate follow-up: a 3-round Codex+Fable gate proved a frontend load-time repair that
reads the shared #modelSelect DOM is a cross-session race surface (it re-manufactured
the reverse-contamination it aimed to fix), and it can't reliably heal live-only-model
sessions anyway. The robust place for that repair is the backend chat-start boundary,
where session + profile identity is unambiguous. Already-poisoned sessions self-heal on
their next healthy send / manual re-pick in the meantime.

Tests: node-behavioral resolver scenarios (tab-switch race, same-value collision,
missing-model null, match-by-value, default->null) + the unrelated-selected-option
case from #5727. Partially addresses #5567 (write-vector); follow-up tracks the
already-poisoned-session repair.

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-07-07 03:42:16 +00:00
nesquena-hermes
1d8208026c
Merge pull request #5728 from nesquena/release/stage-5419
Release: auto-switch profiles for cross-profile session links (#5419)
2026-07-06 19:39:22 -07:00
Daniel Harcek
407451977e fix: auto-switch profiles for session links (#5419)
A valid session:// deep link to a session owned by a DIFFERENT Hermes
profile used to look identical to a deleted session (404 -> frontend
self-heals away). Now GET /api/session returns a structured 409
session_profile_mismatch envelope (error/code/session_id/profile ONLY,
no transcript) ONLY when the owning profile is KNOWN, and loadSession()
catches it, switches to the owning profile, and retries once. Truly
missing/deleted or legacy None-profile sessions keep the 404 self-heal.

Gate fixes applied (Codex + Fable):
- Codex CORE: added a post-await stale-load guard after
  _switchProfileForSessionLoad so a navigation during the switch can't
  hijack the UI back to the old session.
- Fable Finding 1: only emit 409 when _session_profile is truthy;
  a None-profile (missing/legacy) session under a non-default active
  profile now keeps 404 instead of a useless profile=null 409 (which
  skipped self-heal + spun the SSE reconnect against a dead sid). Both
  detail branches. + 2 regression tests.
- Fable Finding 2: _switchProfileForSessionLoad now clears the sidebar
  skeleton + re-renders from cache on switch-POST failure (mirrors the
  #4671 canonical-switch catch), then rethrows, so a failed switch can't
  strand the sidebar on the skeleton.

Reconciled tests/test_issue1611_session_profile_filtering.py (4 tests) to
the 409 contract while preserving the no-leak boundary assertion.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-07 02:35:02 +00:00
nesquena-hermes
243c0f698d
Merge pull request #5725 from nesquena/release/stage-5692
Release: Windows git subprocess console-window fix (#5692)
2026-07-06 19:25:03 -07:00
nesquena-hermes
eb02c8ecd0 docs(changelog): Windows git subprocess console-window fix (#5692) 2026-07-07 02:19:40 +00:00
Jinho Seok
f106fcd1f5 fix(workspace-git): hide git subprocess console windows on Windows (#5692)
Add creationflags to _run_git() and _config_names_for_scope() so git
child processes don't accumulate hidden cmd.exe/conhost windows on Windows.

Uses a local module-level _windows_hide_flags() helper (mirrors the
api/updates.py pattern: CREATE_NO_WINDOW on win32, 0 elsewhere) rather than
importing from the OPTIONAL hermes_cli package — a standalone/agent-less
WebUI (and the repo's own CI runners, which don't pip-install hermes-agent)
must keep full workspace-git functionality. windows_hide_flags() is a safe
no-op (0) on non-Windows. Scoped to workspace git commands.

Adds mutation-checked tests asserting the kwarg is wired on both sites.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-07 02:07:39 +00:00
nesquena-hermes
8fee7da3ee
Merge pull request #5724 from nesquena/release/stage-5717
Release: iteration-limit graceful summary (#5717)
2026-07-06 18:48:36 -07:00
nesquena-hermes
6c7746aef6 docs(changelog): iteration-limit graceful summary (#5717, #5494) 2026-07-07 01:44:48 +00:00
t
64cee6165d stage 5717 on current master 2026-07-07 01:34:14 +00:00
b3nw
34fcc3a387 fix: drop messages-None writeback guard, reframe for invariant lineage
Per Greptile's review on #5717: the previous
`if result.get('messages') is not None` guard created an asymmetric
condition where the silent-failure classifier could miss the injected
fallback when the agent result had `messages: None` (which never
actually happens in practice since `finalize_turn` always returns a
list, but the asymmetry read as code smell). Drop the guard; the
writeback is now unconditional on `isinstance(result, dict)`.

Per the maintainer's review on #5717: there may be a
`finalize_turn` invariant on the agent branch this WebUI ships
against that already appends the fallback as an assistant row, in
which case the live bug is gone and this PR becomes a regression
guard. Reframe the test docstrings as dual-purpose pins of the user-
visible closure-text contract so they read correctly under both
interpretations.

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-07-07 00:04:27 +00:00
b3nw
02692cd64a fix: surface agent's graceful summary on iteration-limit exhaustion (closes #5494)
The WebUI already surfaces the model-generated summary when the agent's
tool-iteration budget is exhausted. The remaining gap: when the agent
hits the limit but its summary call yields no usable assistant content
(common with reasoning-only responses), the user sees a bare
`tool_limit_reached` error instead of any closure text.

`hermes-agent.handle_max_iterations()` always returns a non-empty
`final_response` (either the summary or a graceful fallback), and
`AIAgent` exposes that string on the result dict. The WebUI, by
contrast, only reads `result['messages']`, so an empty summary
left no final assistant answer and routed to the error path.

Inject `result['final_response']` as a final assistant turn when
`max_iterations_reached` fires and no usable answer exists in
`messages`. The existing `_mark_latest_assistant_tool_limit_status`
flow then attaches the status card and the user sees the closure
text instead of the bare error. Hermes-agent parity preserved (it's
literally the same string); the bare-error path stays for the case
where the agent died entirely (no fallback either).

Tests cover: fallback-injection surfaces closure text + status card,
no double-injection when a real summary already exists, the existing
'died entirely' apperror path is unchanged, and three unit cases for
the helper's skip conditions.

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-07-07 00:02:27 +00:00
nesquena-hermes
c4a3d4f816
Merge pull request #5716 from nesquena/release/stage-5664
Release: preserve workspace file-tree scroll position (#5664)
2026-07-06 16:29:05 -07:00
t
7fbb9bc98d docs(changelog): stamp #5664 workspace file-tree scroll preserve 2026-07-06 23:25:27 +00:00
t
4b93510ef6 stage 5664 on current master 2026-07-06 23:14:53 +00:00
nesquena-hermes
5ecd248e92
Merge pull request #5708 from nesquena/release/stage-5688b
Release: self-update .git/index.lock recovery (#5688)
2026-07-06 14:52:25 -07:00
t
9ae4ec8f09 docs(changelog): stamp #5688 self-update lock recovery 2026-07-06 21:46:39 +00:00
t
e2184b7b12 stage 5688 on current master (post parallel release) 2026-07-06 21:45:49 +00:00
nesquena-hermes
8d5d28903c
Merge pull request #5546: add Czech (cs) locale support
Complete Czech locale (1642/1642 key parity, all function values, login screen, test guard). Rebased + translation-quality fixes by maintainer; original author @ostravajih. Gate: full suite + Codex SAFE TO SHIP.
2026-07-06 14:42:12 -07:00
nesquena-hermes
16c3b4d91e docs(changelog): add Czech locale entry (#5546) 2026-07-06 21:37:22 +00:00
ostravajih
6da81f7a30 feat(i18n): add Czech (cs) locale support
Add a complete Czech (cs) locale to hermes-webui: full key parity with the
English reference (1642/1642 keys), real Czech translations for all string and
function-valued keys, Slavic plural helpers for tool/worklog summaries, the cs
login-screen locale in api/routes.py, and a dedicated tests/test_czech_locale.py
parity+placeholder+diacritics guard mirroring the other per-locale tests. All
15 locale-count assertions bumped 14->15.

Co-authored-by: ostravajih <ostravajih@users.noreply.github.com>
2026-07-06 21:27:46 +00:00
nesquena-hermes
bb0439dcfa
Merge pull request #5707 from nesquena/fix/last-two-flakes
Fix the last 2 chronic local full-suite test-isolation flakes (test-only)
2026-07-06 14:24:13 -07:00
nesquena-hermes
d5210b7efc lint: drop now-unused importlib.util import 2026-07-06 21:19:20 +00:00
nesquena-hermes
acfd0e9719 fix: defensive agent-importable probe (find_spec raises ValueError on spec-less partial agent module on CI) 2026-07-06 21:14:58 +00:00
nesquena-hermes
88d94556d2 docs(changelog): stamp last-2-flakes fix 2026-07-06 21:10:00 +00:00
t
cec093eef6 test: fix chronic full-suite agent/hermes_cli __path__ isolation poison (2 flakes)
Convert in-place pkg.__path__=[] mutations on the REAL agent/hermes_cli
packages to monkeypatch.setattr (auto-restored), and make
test_issue1574's _activate_spawn_fake_agent snapshot+restore the
HERMES_WEBUI_AGENT_DIR/PYTHONPATH/sys.path it mutates.

Fixes:
- test_v050259_sessiondb_fd_leak::test_session_db_close_is_idempotent
- test_tls_support::test_tls_startup_failure_fallback_to_http
2026-07-06 21:08:52 +00:00
nesquena-hermes
ade6de5294
Merge pull request #5704 from nesquena/fix/5696-profiles-ttl
fix(#5696): revert profiles cache TTL to 4s (stale profile rows)
2026-07-06 13:44:17 -07:00
t
5df8a06800 fix(#5696): revert profiles cache TTL 60s->4s (stale profile rows); contributor-flagged follow-up 2026-07-06 20:40:08 +00:00
nesquena-hermes
116d81d3c8
Merge pull request #5703 from nesquena/release/stage-5696
Release: session-load latency fix (#5696)
2026-07-06 13:36:48 -07:00
t
36176a9ee4 docs(changelog): stamp #5696 session-load perf 2026-07-06 20:21:04 +00:00
t
2454719f2e gate 5696 + fold-in: non-throwing HERMES_DEBUG_SLOW parse (Codex CORE fix) 2026-07-06 20:15:42 +00:00