Commit graph

6418 commits

Author SHA1 Message Date
Frank Song
c8b79bcaa7 Merge latest master into compression recovery action
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-05 03:29:59 +08: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
Frank Song
61b87cd3e2 Merge master into compression recovery action
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-05 03:23:32 +08: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
nesquena-hermes
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
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
Frank Song
f25571342e Keep recovery metadata after branch parent field
Restore the compact() source-order expectation used by the branch metadata contract test while keeping focused compression recovery markers in the compact payload.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-04 22:36:19 +08:00
Frank Song
1e9d42c053 Live Stream: harden compression recovery action
Make focused compression-recovery continuation creation idempotent by marking child sessions and reusing the existing child on repeated start requests. Also surface malformed successful recovery responses in the UI instead of silently re-enabling the action.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-04 22:30:57 +08:00
Frank Song
dfc0686dbd Live Stream: add compression recovery action 2026-07-04 21:42:32 +08: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-hermes
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-hermes
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
nesquena-hermes
d27ef5d196 harden #5511: active provider owns its OWN providers: allowlist (complete ownership model)
Codex 5th CORE: the ownership set only covered static _PROVIDER_MODELS, not the
active provider's own config.yaml providers.<active>.models allowlist. So an
active provider defined purely via providers: (no static catalog) could be
hijacked by another slug's overlapping entry earlier in config order. Fix folds
the active provider's own providers: allowlist (list + dict shapes, copilot
excluded) into _provider_models_set — completing the ownership model so the
active provider owns its declared models from EITHER the static catalog OR its
own providers: entry. Regression test (fail-without-fix verified). 40 model_resolver
tests green.

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:28:44 +00:00
nesquena-hermes
f2604a22bd harden #5511: _canonicalise_provider_id accepts alias targets in _PROVIDER_MODELS (Gemini class)
Codex 4th CORE (root latent bug in the shared canonicalizer): _canonicalise_provider_id
only accepted an alias resolution if the target was in _PROVIDER_DISPLAY. But some
canonical targets (e.g. 'gemini') are indexed in _PROVIDER_MODELS and NOT
_PROVIDER_DISPLAY, so 'google-gemini'->'gemini' was rejected and returned raw
'google-gemini' — leaving the ownership guard empty and letting providers.openai.models
hijack an active Gemini-owned model. Fix mirrors the direct-hit check (line 1445):
accept alias targets in EITHER _PROVIDER_DISPLAY OR _PROVIDER_MODELS. This is the
root fix for the whole alias-canonicalization class (gemini/google-gemini/
google-ai-studio etc.), not just the reported site. Blast-radius verified: 81
canonicalise/provider-alias tests across 5 files still green; x-ai still stays x-ai,
opencode_go still folds. Gemini regression test added (fail-without-fix verified).

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:23:45 +00:00
nesquena-hermes
4399ff02e0 harden #5511: canonicalise active provider for ownership set (latent alias bug)
Codex 3rd CORE (pre-existing latent bug the providers: scan exposes): the
_provider_models_set ownership lookup used the RAW config_provider (e.g. 'z-ai')
but _PROVIDER_MODELS is keyed by canonical slug ('zai'), so an aliased active
provider got an empty ownership set -> _skip_custom_providers guard-2 silently
failed -> another providers.<slug>.models entry could hijack an active-owned
model (verified: active z-ai + glm-5 + providers.openai.models.glm-5 -> openai).
Fix: canonicalise config_provider before the _PROVIDER_MODELS lookup (and reuse
the canonical slug for the scan's ownership guard). Regression test with an
aliased active provider (verified fail-without-fix).

This is the root-cause fix for the alias class — the ownership guard now holds
for every aliased provider (zai/kilo/etc.), not just the reported site.

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:17:05 +00:00
nesquena-hermes
f8c9f9e08c harden #5511: providers: scan honors active-provider ownership guard
Codex 2nd CORE: the new providers: scan didn't honor the _skip_custom_providers
active/default ownership guard (config.py:2535) that the custom_providers scan
uses. So with an active provider (e.g. ai-gateway) + model.default=gpt-5 AND a
providers.openai.models.gpt-5 entry, the scan hijacked gpt-5 to openai instead of
the active ai-gateway path. Fix: when _skip_custom_providers is true, the
providers: scan only considers the active provider's own canonical slug (skip all
other slugs). Regression tests: active-provider-owns-default not hijacked (fails
without fix); active provider's OWN providers: entry still matches.

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:10:47 +00:00
nesquena-hermes
cca3b6c279 release #5511: exclude Copilot from providers: routing scan + regression tests + CHANGELOG
Gate-cert (nesquena-hermes) + Codex found 1 CORE: akay64's new providers: scan in
resolve_model_provider() iterated ALL providers.<slug>.models INCLUDING
providers.copilot.models — but that key is a documented per-model SETTINGS map
(reasoning_effort/limits), NOT a routing allowlist (config.py exception). So a
Copilot per-model settings entry hijacked that model's routing to Copilot
(Codex reproduced gpt-5 -> copilot). Fix (Codex-exact): skip copilot in the scan.

Added the regression tests the PR shipped 0 of: user-defined allowlist routes;
active-openai + providers.copilot.models (dict + list shapes) stays openai
(both verified to fail without the exclusion).

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:06:05 +00:00
nesquena-hermes
ec9f77c319 Merge commit '87404d76f036' into release/stage-5511 2026-07-04 06:57:54 +00:00
nesquena-hermes
8e26cc03d0
Merge pull request #5520 from nesquena/release/stage-p1
Release — session SSE contract RFC (#5513, addresses #4812)
2026-07-03 23:55:45 -07:00
nesquena-hermes
a94dacb679 release #5513: fix live-emission anchor + source-validate all run-journal anchors
Codex re-gate finding: line 83 cited api/routes.py:15703-15762 for live SSE id:
emission, but that range is replay/helper code — the actual _sse_with_id() call
sites are 15918 (live /api/chat/stream), 15811 (runner-observe), 15721/15734
(journal replay). Corrected the bullet with the real, distinct anchors.

Also extended the test (test_rfc_run_journal_anchors_land_on_real_source) to
validate each named-symbol anchor (_parse_run_journal_event_id/_after_seq,
_runner_event_id, _replay_run_journal) + the live-emission anchor against the
actual source token, so no stale line number can silently pass.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-04 06:48:35 +00:00
nesquena-hermes
6182ae044b release #5513: correct RFC source anchors + self-validating test
Codex Phase-1 gate found the RFC cited stale/wrong source anchors (docs-only, but
actively misleading to future implementers): /api/sessions/events is routed at
routes.py:12345-12346 (RFC said 12320-12321 = /api/chat/cancel);
_handle_session_events_stream at 16177 (RFC said 16142 = a gateway-sse comment);
replay reads after_event_id/after_seq QUERY PARAMS (RFC said Last-Event-ID header
— that's the *proposed* new-endpoint contract, not current). Corrected all six
routes.py anchors + the replay-mechanism description, verified each against master.

The accompanying test asserted the WRONG anchors and passed (codifying the error).
Rewrote test_rfc_cites_current_global_endpoint_source to parse every routes.py
anchor from the RFC and verify it lands on the real /api/sessions/events route +
_handle_session_events_stream definition — so it now catches drift instead of
locking in a stale number.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-04 06:37:18 +00:00
nesquena-hermes
174dec88eb Merge commit 'dcaf5a69aa84' into release/stage-p1 2026-07-04 06:25:00 +00:00
nesquena-hermes
46b630db52
Merge pull request #5518 from nesquena/release/stage-5516
Release — transcript re-pin when composer grows (#5516, closes #5514/#5515)
2026-07-03 23:20:18 -07:00
nesquena-hermes
947fa2b92d release #5516: fix stale test docstring (#composerWrap not textarea) 2026-07-04 06:15:42 +00:00
nesquena-hermes
c77f4cf014 release #5516: composer-grow re-pin — hot-path optimization + CHANGELOG
Addresses Greptile P2 nit: autoResize() now only calls the re-pin when the
composer actually grew (offsetHeight before/after compare), skipping the DOM
read on a no-height-change keystroke. The #composerWrap ResizeObserver remains
the safety net for growth paths that bypass autoResize. Test tightened to assert
the grow-guard. CHANGELOG entry added (self-built, release owns changelog).
2026-07-04 06:06:05 +00:00
nesquena-hermes
042d7f3ec7 Merge commit '7e7e71f6953f' into release/stage-5516 2026-07-04 05:59:19 +00:00
nesquena-hermes
259729bb0a
Merge pull request #5517 from nesquena/release/stage-5504
Release — deleted-session ghost fix (#5504, closes #5498)
2026-07-03 22:52:28 -07:00
nesquena-hermes
7e7e71f695 fix(chat): widen composer re-pin observer to #composerWrap (Fable #5516 finding)
Fable senior review (COMMENT, no blockers) + DRY nit:

- Widen the ResizeObserver from #msg to the whole #composerWrap so growth from the
  attachment tray / selection chips (not just textarea rows) also re-pins a bottom-
  pinned reader. Fable flagged tray/chip growth as a likely REMAINING #5515 trigger
  — same viewport-shrink physics, now covered by the same guarded re-pin at one seam.
  #composerWrap contains #msg, so the textarea path is still covered.
- DRY: _repinMessagesAfterComposerResize() now reuses _messageBottomDistance()
  instead of re-implementing the bottom-distance expression inline.

Re-verified live (composer grow -> bottomDist stays 0) + 524 scroll/composer
regression tests green + 6 issue tests green.
2026-07-04 05:52:14 +00:00
Rod Boev
dcaf5a69aa test(docs): harden heartbeat rfc contract guard (#4812) 2026-07-04 01:49:24 -04:00