Commit Graph
6435 Commits
Author SHA1 Message Date
nesquena-hermes 2cc2eb2b3c Merge pull request #5590 from nesquena/release/stage-5558
release #5558: keep Transparent Stream final replies visible after settle
2026-07-04 15:23:21 -07:00
nesquena-hermes a7527bef13 release #5558: keep Transparent Stream final replies visible after settle + CHANGELOG 2026-07-04 22:19:25 +00:00
nesquena-hermesandrodboev ae43091993 fix(#5550): keep Transparent Stream final replies visible after settle
The live-anchor scene renderer hardcoded mode:'compact_worklog' in
_renderAnchorLiveScene / _projectLiveAnchorActivityScene / the settle projection,
regardless of the user's actual active display mode. In Transparent Stream mode
the final reply was therefore projected with compact-worklog display hints, so on
turn-settle it folded into the collapsed worklog disclosure group (a refresh fixed
it because the stored transcript was correct). Now the renderer reads the real
active mode via _anchorSceneActiveMode() (window.chatActivityMode() ->
_chatActivityDisplayMode -> _transparentStream, default compact_worklog) and
applies per-mode display hints via _anchorSceneRowDisplayHintForMode(), so the
final answer stays a visible chronological reply in Transparent Stream.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-04 22:09:50 +00:00
nesquena-hermes f09d9bd446 Merge pull request #5586 from nesquena/release/stage-5541-r3
release #5541: iOS PWA streaming freeze + SSE reconnect reliability
2026-07-04 15:06:02 -07:00
nesquena-hermesandluperrypf cebfdf41f9 fix(#5541): iOS PWA streaming freeze + SSE reconnect reliability
Two mobile reliability fixes on the crown-jewel chat streaming path:
(1) content-visibility:auto on off-screen .msg-row under @media(pointer:coarse)
so WKWebView skips layout/paint for off-screen rows during streaming (kills the
long-chat freeze). The LIVE turn is kept content-visibility:visible via the
STABLE #liveAssistantTurn id (covers ALL render modes — Compact Worklog,
Transparent Stream, restored-live — not just the Transparent-Stream path that
stamps data-live-assistant-turn), so a normal live turn on touch never blanks
mid-stream and its height keeps growing so the new-message cue still fires.
contain-intrinsic-size:auto 1px preserves flick-scroll momentum. Scoped to
touch — desktop find-in-page untouched. Deliberately does NOT set
overflow-anchor:none (inert on iOS WebKit; re-opens #4856/#5338).
(2) SSE reconnect ladder extended 4->6 steps + a last-ditch
_restoreSettledSession full-session poll (8s watchdog) after retries exhaust, so
a response completed during an iOS Tailscale/VPN reconnect is recovered without
an error banner.

Co-authored-by: luperrypf <luperrypf@users.noreply.github.com>
2026-07-04 21:51:38 +00:00
nesquena-hermes 56a133cc72 Merge pull request #5581 from nesquena/release/stage-5543-r3
release #5543: fire-and-forget memory commit + drain-safe managed stop
2026-07-04 14:44:22 -07:00
nesquena-hermes e974f03829 release #5543: fire-and-forget memory commit + drain-safe managed stop + CHANGELOG 2026-07-04 21:41:05 +00:00
nesquena-hermesandluperrypf 19d00e3586 fix(#5543): fire-and-forget commit_session_memory + drain-safe managed stop
Move the 1-5s commit_session_memory() extraction off the POST /api/session/new
request thread (was blocking + New Chat) into a fire-and-forget daemon thread,
guarded by the existing per-session in-flight serialization.

Data-loss prevention on managed stop:
- server.py installs a SIGTERM handler that requests an orderly stop so
  serve_forever()'s finally block runs drain_all_on_shutdown() (the default
  SIGTERM disposition terminated without unwinding, losing in-flight commits).
- session_lifecycle: bounded self-unregistering background-commit registry;
  a _draining flag so no NEW worker starts once the drain begins (the inline
  generation-drain commits any late arrival instead); a 30s overall drain
  deadline so a stuck worker cannot hang the stop path forever.

Co-authored-by: luperrypf <luperrypf@users.noreply.github.com>
2026-07-04 21:40:21 +00:00
nesquena-hermes 04717ad305 Merge pull request #5580 from nesquena/release/stage-5544-r3
release #5544: re-pin auto-scroll at true bottom after scroll-to-bottom
2026-07-04 14:39:02 -07:00
nesquena-hermes 3b541b745f release #5544: re-pin auto-scroll at true bottom + CHANGELOG 2026-07-04 21:24:39 +00:00
nesquena-hermesandluperrypf 4dc87e9069 fix(#5544): re-pin auto-scroll only at true bottom after scroll-to-bottom
A single scroll-up during streaming set _messageUserUnpinned=true and
scrollIfPinned() then permanently stopped auto-follow (only scrollToBottom()
cleared it) — a permanent auto-follow lockout even after the user returned to
the bottom. scrollIfPinned() now re-pins, but ONLY when the reader has genuinely
reached the true-bottom tail (<=80px) AND shows no active scroll intent
(wheel/key/touch/non-message), reusing the listener's _nearBottomCount debounce.
Proximity alone (the ~250px nearBottom band) must never re-pin — that is the
#4295 invariant. Restores the listener's <=80px true-bottom gate too.

Co-authored-by: luperrypf <luperrypf@users.noreply.github.com>
2026-07-04 21:24:17 +00:00
nesquena-hermes 627c6a173c Merge pull request #5576 from nesquena/release/stage-5563-r2
release #5563: align forked context to display for large/compacted sessions
2026-07-04 14:15:49 -07:00
nesquena-hermes e588901f2f release #5563: align forked context to display for large/compacted sessions + CHANGELOG 2026-07-04 21:07:49 +00:00
nesquena-hermesandb3nw 322c9fdb62 fix(#5563): align forked context to display for large (compacted) sessions
Fork ("Fork from here") on a large/compacted session could cut context off a
real turn boundary — slicing mid-turn or leaving a dangling tool call, feeding
a malformed context to the model. Align the fork cut to a resolved turn boundary
via a signature matcher handling both compacted and non-compacted display
coordinate spaces; errs toward under-keeping with send-time sanitization backstop.

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-07-04 20:55:06 +00:00
nesquena-hermes 4dfeb78bd0 Merge pull request #5574 from nesquena/release/stage-5532b
Release — clear-conversation P0 data-loss (#5532, #5556, #5553)
2026-07-04 13:18:48 -07:00
nesquena-hermes 3f599ead04 fix(test): drop the one genuinely-unused _seed_session_dir binding (lint)
CI lint (diff-scoped ruff F) flagged 'models =' unused at line 92 (that test
uses only the seed side-effect). Removed the assignment there; kept it at line
320 where test_clear_survives_startup_recovery legitimately reads models.SESSION_DIR.
Ruff clean, 11 #5532 tests pass.
2026-07-04 20:15:20 +00:00
nesquena-hermesandrodboev 81e89ec7b2 release #5532: clear-conversation P0 data-loss (combined, rebuilt on de-brittled base)
Rebuilt the combined #5556+#5553 fix on v0.51.859 (now has the flake fix + the
#5542 RFC de-brittle, so no anchor-test collateral). Nathan's call: rodboev's
#5556 primary + fold self-built #5553 improvements, credit both.

Clear handler now:
- routes through shared truncate_session_at_keep(s,0) (single source of truth,
  sets watermark=_truncation_watermark_for([])==0.0, the #2914 sentinel that
  blocks state.db append-merge replay)
- detaches compression lineage ONLY when the parent is a pre_compression_snapshot
  (Codex-caught: preserve genuine fork parent links for nesting + "Forked from")
- rodboev's persisted-clear read-back verification + stale .bak removal

RFC conflict resolved in favor of the de-brittled (symbol-anchor) master version.
Ships both PRs' test files (state_db_replay + clear_truncation_watermark incl the
fork-preservation regression). Opus (on the amended tree): both resurrection paths
closed, detach lineage-safe, verification sound — SHIP. 3 non-blocking follow-ups
filed (#5570 .bak crash-window, #5571 fork-stitch corner, #5572 messaging clear).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-04 19:58:38 +00:00
nesquena-hermes 5e26abca89 Merge commit 'ea2dc31c09bc' into release/stage-5532b
# Conflicts:
#	docs/rfcs/session-sse-contract-v1.md
2026-07-04 19:56:02 +00:00
nesquena-hermes 4154f949fb Merge pull request #5569 from nesquena/fix/5542-debrittle-rfc-anchors
fix(#5542): de-brittle SSE-contract RFC anchors — cite by symbol, not line number
2026-07-04 12:54:58 -07:00
nesquena-hermes 4c1cdcba3e Merge remote-tracking branch 'origin/master' into fix/5542-debrittle-rfc-anchors 2026-07-04 19:44:59 +00:00
nesquena-hermes 4c89fcfaab Merge pull request #5566 from nesquena/release/stage-5547
Release — Transparent Stream running-tool status (#5547, #5523)
2026-07-04 12:25:01 -07:00
nesquena-hermes d03c6caa61 Merge remote-tracking branch 'origin/master' into release/stage-5547 2026-07-04 19:12:25 +00:00
nesquena-hermes 971629af4e Merge pull request #5561 from nesquena/fix/suite-flakes
Fix the two chronic full-suite test flakes (nous_picker + service_tier)
2026-07-04 12:10:30 -07:00
Rod Boev ea2dc31c09 fix(#5532): prevent clear from replaying state.db history 2026-07-04 15:08:26 -04:00
nesquena-hermesandrodboev 3dee3d299e release #5547: Transparent Stream running-tool status fix + CHANGELOG
rodboev's one-arg fix (#5523): _anchorSceneTransparentNodeForRow passed a
hardcoded settled=true into _transparentToolStatus even when the caller supplied
settled:false, so a running tool rendered as done in Transparent Stream. Now
passes the caller's actual  value. +19/3f, 93 tests green.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-04 19:03:58 +00:00
nesquena-hermes 3550af894c Merge commit '6cf5706f78ef' into release/stage-5547 2026-07-04 18:57:58 +00:00
nesquena-hermes d025b37186 fix(tests): service_tier flake TRUE root cause is stale global cfg, not yaml cache
Two full-suite iterations pinned the real vector: set_hermes_default_model() calls
resolve_model_provider() which reads the module-global config.cfg (config.py:2488).
A prior lmstudio test leaves cfg={model:{provider:lmstudio,base_url:1234}} in
_cfg_cache; resolve_model_provider then returns lmstudio's provider+base_url, which
set_hermes_default_model persists to the test's OWN config file -> the on-disk
assertion sees 'provider: lmstudio' instead of the written 'provider: openai'.
The autouse fixture now _cfg_cache.CLEAR()s its CONTENTS (not just re-aliases cfg),
so resolve_model_provider reads an empty model cfg and can't inject a stale
provider/base_url. Deterministically proven: reproduced the exact lmstudio leak with
a probe test (fixture on=PASS; _cfg_cache.clear() line removed=the exact full-suite
failure reproduces). Also clears _yaml_file_cache + fingerprint/mtime for defense.
2026-07-04 18:56:58 +00:00
nesquena-hermes 3a70ab5871 fix(tests): service_tier flake root cause is _yaml_file_cache poison, not just cfg rebind
The full-suite run revealed the TRUE mechanism: the failure is at the DISK
assertion (before get_auxiliary_models) — set_hermes_default_model() read a
STALE lmstudio config and wrote it back. Root cause: the module-global
_yaml_file_cache is keyed on (str(config_path), st_mtime_ns, st_size); pytest
reuses tmp_path base dirs across the session, so a prior lmstudio test's
config.yaml shares both the path string AND (mtime_ns,size) with this test's tiny
file (same coarse mtime tick) → stale cache hit serves the wrong provider's
config. The autouse fixture now ALSO clears _yaml_file_cache (proven: 2->0), in
addition to the cfg/_cfg_fingerprint reset for the get_auxiliary_models read path.
Fixed the duplicate decorator introduced in the prior commit.
2026-07-04 18:43:45 +00:00
nesquena-hermes ea8ee80a9c Debrittle #5513 session-SSE-contract RFC anchors to symbols (#5542)
The RFC (docs/rfcs/session-sse-contract-v1.md) cited hardcoded absolute
line numbers like `api/routes.py:12345-12346` and `api/routes.py:16177`,
and tests/test_issue4812_session_sse_contract_rfc.py validated those exact
lines against source. Any PR that shifted routes.py lines (e.g. #5543,
#5534) broke the test — a chronic brittle-failure class (#5542).

Root fix: anchor by SYMBOL, never by line number.

RFC:
- Strip every `api/routes.py:<NNNN>` / `<NNNN>-<NNNN>` and
  `api/streaming.py:<NNNN>` line-number suffix; keep the symbol name
  (route string, function/constant names) + the file, e.g.
  "_handle_session_events_stream() in api/routes.py".
- Update the inventory note to state names are the stable anchors and the
  RFC deliberately avoids line numbers so a source-layout shift can't
  invalidate the doc or its contract test.

Test:
- Rewrite the two line-anchor tests to assert (a) each cited symbol still
  exists in api/routes.py and (b) the RFC names that symbol — dropping the
  `api/routes.py:<line>` regex bounds-checks entirely. Invariant preserved:
  "the RFC's cited symbols are real and named in the doc", NOT "the cited
  line numbers are exact".
- Add test_rfc_uses_no_hardcoded_routes_line_numbers as a regression guard
  that fails if any `*.py:<line>` anchor is reintroduced into the RFC.

Proven de-brittled: prepending 31 lines to api/routes.py (scratch, reverted)
leaves all 34 tests green.

Refs #5542, #5513
2026-07-04 18:42:22 +00:00
nesquena-hermes f3f328df3d fix(tests): eliminate the two chronic full-suite flakes (nous_picker + service_tier)
Both fail ONLY in the full suite, not in isolation — they've been noise on every
release gate. Root-caused and fixed at the source (no retries/skips):

1. test_issue1567 nous_picker (test_hermes_cli_unavailable_falls_back_to_static_4):
   NOT actually a flake — a real assertion that silently broke as the curated list
   grew. It asserted every curated @nous: id (now 28) appears in the picker group's
   visible 'models', but when the list exceeds _NOUS_FEATURED_THRESHOLD (25) the
   dropdown intentionally shows only the featured subset and stashes the rest under
   'extra_models'. Fixed to assert the invariant that matters ('no curated entry is
   DROPPED') against the visible+extra_models union, so it tracks the real behavior
   instead of drifting to failure every time the catalog grows.

2. test_issue4536 service_tier (test_main_service_tier_roundtrip_via_auxiliary_endpoint):
   Classic test-isolation leak. get_auxiliary_models() reads the module-global
   config.cfg after reload_config(); if an EARLIER full-suite test rebinds
   config.cfg via monkeypatch and leaves 'cfg is not _cfg_cache',
   _cfg_has_in_memory_overrides() stays True and get_auxiliary_models() reads that
   stale dict instead of the freshly-reloaded temp config. Added an autouse fixture
   that re-aliases cfg to _cfg_cache and clears the override fingerprint before AND
   after each test in the class. PROVEN: reproduced the failure deterministically
   with a leak-probe test, confirmed the fixture neutralizes it (fixture off = FAIL,
   fixture on = PASS).
2026-07-04 18:32:43 +00:00
Rod Boev 6cf5706f78 test(#5523): align persisted anchor status invariant 2026-07-04 13:51:32 -04:00
Rod Boev 3d483d3701 test(#5523): harden transparent status regression slice 2026-07-04 13:46:59 -04:00
Rod Boev c85db4f3d7 fix(#5523): show running tools as running in Transparent Stream 2026-07-04 13:37:39 -04:00
nesquena-hermes 980e98dc7e Merge pull request #5528 from nesquena/release/stage-5471
Release — composer draft-restore suppression, root-fixed (#5471)
2026-07-04 01:55:23 -07:00
nesquena-hermesandruizanthony 6baf501dce harden #5471: dual-signature suppression (Opus root fix) — covers stale prefix + attachment
Opus found the exact-match signature fails open in the COMMON case: the server's
last persisted draft is often a PREFIX of the submitted text (Enter-to-send within
400ms cancels the pending debounced save), so matching only the submitted text
misses it and the prefix restores (the literal 'tail of the already-submitted
message' symptom). Root fix (Opus-recommended): capture BOTH signatures at send —
the remembered SERVER-draft signature (_composerDraftPayloadSignatureForSid, read
before the reset) AND the submitted-payload signature — and suppress a restore
matching EITHER. The prefix IS the remembered server draft; the remembered files
went through the same lossy serialization so they self-match; a genuinely new
cross-tab draft matches neither and restores. Subsumes the file-canonicalization
fix (kept as belt-and-suspenders). Suppression stores signatures[] not signature.

Regression tests: stale-prefix suppressed (Opus Finding 1), file round-trip
self-matches (Codex Finding 2), cross-tab-different still restores. Test harness
updated to model send-time server draft vs later restore draft as distinct.

Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-04 08:51:13 +00:00
nesquena-hermesandruizanthony 5a8dc91bbb harden #5471: canonicalize draft files before persist+sign (attachment round-trip)
Codex found a SILENT edge in the payload-signature fix: a live browser File
JSON-serializes to {} when the draft is POSTed to the server, so a text+attachment
send's suppression signature (computed from the rich File) never matched the
restored server draft (files:[{}]) — suppression cleared and the just-sent tail
could repopulate for the attachment case. Fix: _composerDraftFilesForPersist()
canonicalizes files to a stable serializable shape (name/path/size/type/lastModified)
used at BOTH _saveComposerDraft/_saveComposerDraftNow (persist) AND
_composerDraftPayloadSignature (sign), so the sent signature matches the
round-tripped server draft. Regression test self-demonstrates lossy-mismatch vs
canon-match.

Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-04 08:37:15 +00:00
nesquena-hermes 1d4d224f67 release #5471: payload-scoped composer draft-restore suppression + CHANGELOG
ruizanthony's re-push answering the gate-cert SILENT finding: the 30s post-send
draft-restore suppression discriminated only by sid+TTL, so a legitimately NEW
cross-tab draft for the same session was silently swallowed for up to 30s. Fix
adds a payload signature (_composerDraftPayloadSignature): suppression now only
blocks a restore whose payload matches the just-sent one; a different-signature
draft restores immediately. Verified the fix-spec is implemented (sessions.js:64-104).
2026-07-04 08:27:39 +00:00
nesquena-hermes 85898453dd Merge commit '68a692a8e07b' into release/stage-5471 2026-07-04 08:26:49 +00:00
nesquena-hermes edfccb9cd4 Merge pull request #5526 from nesquena/release/stage-5522
Release — Windows git.exe resolution for version/cache-busting (#5522)
2026-07-04 01:19:58 -07:00
nesquena-hermes 658624af7f release #5522: Windows git.exe resolution for version/cache-bust + CHANGELOG
allenliang2022's fix: WEBUI_VERSION degraded to 'unknown' on Windows when git
wasn't on the launching process PATH (venv/service/non-interactive launcher),
freezing the ?v= static-asset cache stamp so browsers served stale JS/CSS after
upgrade. Extends the pre-existing _resolve_git_executable() with a win32-only
Git-for-Windows registry lookup (WOW64 64/32 + HKCU) + common install-path
fallbacks, only when shutil.which('git') fails. Non-win32 paths untouched.
5 tests incl non-windows-never-touches-registry guard + e2e version recovery.
2026-07-04 08:07:29 +00:00
nesquena-hermes f65598113d Merge commit '2c03277a9738' into release/stage-5522 2026-07-04 08:06:47 +00:00
Agent Zero 68a692a8e0 fix(composer): scope draft restore suppression to payload 2026-07-04 08:06:33 +00:00
Anthony Ruiz b1a200f166 fix(composer): suppress stale draft restore after send 2026-07-04 08:06:33 +00:00
nesquena-hermes 72746342d3 Merge pull request #5524 from nesquena/release/stage-5456
Release — Transparent Stream churn reduction (#5456, #5367)
2026-07-04 01:04:38 -07:00
allenliang2022 2c03277a97 test(webui): make Windows git-fallback tests platform-independent
The known-dirs-probe test built its expected path from os.environ['ProgramFiles']
+ os.path.join, but the production code uses os.path.expandvars('%ProgramFiles%..').
On the Linux CI runners expandvars does not expand %VAR% syntax and os.path.join
uses '/', so the expected path diverged from what the code produced and the test
failed (assert None == 'C:\Program Files/Git/cmd/git.exe').

Mock the OS-specific surfaces (winreg, os.path.exists/expandvars/join) so the
Windows code path is exercised deterministically on any host. Verified the two
previously-failing assertions now pass under posix os.path semantics, and a
mutation check (reverting the fix) still makes the three behavior tests fail.
2026-07-04 15:50:05 +08:00
nesquena-hermes 2f97625323 release #5456: transparent-stream churn reduction + CHANGELOG
Stacey2911's follow-up to the identity-preserving live-row reconcile: skip
redundant innerHTML rewrite when unchanged, skip reinsert when already in
position, stop thinking-block scrollbar flicker. Deep-review subagent + gate:
no identity-invariant break (attrs/className/_tcData/interactive-state all still
synced unconditionally; only redundant DOM writes gated). Staged on current
master (resolves the stale-base #4295 test artifact — 114 transparent/anchor
tests green on merge).
2026-07-04 07:46:18 +00:00
nesquena-hermes 7a8b5ef7cf Merge commit '6771fc2cdc34' into release/stage-5456 2026-07-04 07:45:19 +00:00
nesquena-hermes 2981f71181 Merge pull request #5521 from nesquena/release/stage-5511
Release — route user-defined providers: models (#5511), hardened
2026-07-04 00:42:19 -07:00
allenliang2022 861cc9da77 test(webui): cover Windows git.exe registry/known-path fallback
Parallels tests/test_issue5175_macos_launchd_git.py for the Windows case:
verifies _resolve_git_executable() resolves git via the Git-for-Windows
registry InstallPath and known install dirs when it is off PATH, that
_detect_webui_version() recovers a real stamp instead of 'unknown', and
falsifiable guards that the registry path is Windows-only and returns None
when git is genuinely absent.
2026-07-04 15:39:39 +08:00
allenliang2022 53cbfe23c6 fix(webui): resolve git.exe via Windows registry/known paths so WEBUI_VERSION doesn't degrade to 'unknown'
When the WebUI server is launched from a venv python whose PATH does not
include git (e.g. hermes-agent venv on Windows), shutil.which('git')
returns None and WEBUI_VERSION falls back to 'unknown'. That freezes the
?v=<stamp> static-asset cache key, so browsers keep serving stale cached
ui.js/messages.js even after the server is restarted with fixed code —
frontend fixes silently never reach clients (recurring scroll jump-back).

Add a Windows fallback to _resolve_git_executable(): read the
Git-for-Windows registry InstallPath (HKLM + WOW6432Node + HKCU), then
probe common install dirs. Mirrors the existing darwin /usr/bin/git
fallback. No behavior change on posix or when git is already on PATH.
2026-07-04 15:36:59 +08:00