The consume-clear guard now checks _pendingPickMatch (the pending onchange
marker match) instead of the broadened _explicitPick, which may also be
true from the new _isCrossProviderPick inference without a pending marker
to consume.
Two-part fix for the model selector jumping back to the profile default
after the first message when a cross-provider model (e.g. a custom relay
like custom:glm-free-relay) is selected.
Backend (api/routes.py):
- _catalog_has_provider() now matches compound provider IDs (e.g.
custom:zenmux-relay) whose normalized form (custom) is a raw
catalog provider_id. Previously the three existing checks missed
normalized-vs-raw comparison for compound hints, causing the catalog
lookup to fail and the fallback branch to silently revert the model
to the profile default on every non-explicit turn.
Frontend (static/messages.js):
- The explicit_model_pick marker was consumed after the first send(),
so subsequent sends lost the signal and the server repaired the
model back to the profile default (#3737 regression).
- Now, when the session has a non-default model from a different
provider than the profile's active provider, every send() sends
explicit_model_pick=true so the server honors the user's choice
across the entire conversation.
- The pending onchange marker is still consumed after the first send
(unchanged behavior); only the explicit_pick inference is broadened.
The legacy /v1/chat/completions approval handler (added for #4549) rendered
the approval card but never populated _STREAM_RUN_IDS, so /api/approval/respond
could not relay the user's choice back to the gateway to resume the parked run
— it fell through to the local approval path and returned {ok: false}. Record
the run_id from the approval payload (no-op when absent), mirroring the runs-API
path. Adds a behavioral regression test that fails on the pre-fix head.
Codex ship-gate finding (CORE). Co-authored-by: rodboev <rodboev@users.noreply.github.com>
PR #4522 (fix /api/profiles UnboundLocalError 500) was authored by
@MinhoJJang, not @TomBanksAU. The wrong handle was carried forward from
the immediately preceding release (v0.51.529 / #4518, which IS TomBanksAU).
Corrects the credit on the v0.51.530 entry only.
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Codex SILENT finding: the new 'if not target.exists(): return {}' guard ran
BEFORE the ambient-resolver short-circuit, so a matching ambient profile home
whose directory does not physically exist (fresh install / monkeypatched cfg)
returned {} instead of deferring to get_config() — breaking in-memory overrides
and changing the default-profile missing-home path. Move the guard below the
ambient short-circuit; add a regression test (matching-but-nonexistent ambient
home still defers to get_config()).
CI failed on shard 2 across ALL python versions (deterministic, not a flake):
test_list_profiles_prefers_matching_isolated_default_home did
mock.patch('hermes_cli.profiles.list_profiles', ...), whose string target forces
a REAL import of hermes_cli — not installed on the WebUI CI path (bundled agent
absent) -> ModuleNotFoundError. Passed locally only because hermes_cli is
importable here. Per the never-import-agent/hermes_cli-in-a-WebUI-test rule:
inject a stub hermes_cli + hermes_cli.profiles into sys.modules so
list_profiles_api()'s internal
resolves to the stub at the WebUI boundary. Verified: all 24 tests pass with
hermes_cli import BLOCKED (CI simulation).
The reload-based state-dir tests (importlib.reload(api.config) with mutated env)
leaked recomputed STATE_DIR/SESSION_DIR globals + stale references into later
tests in the full sequential run -> 17 spurious failures in cancel/stream/
health/state-isolation suites (all green in isolation). Restoring env + reloading
couldn't fully undo it (fixture/monkeypatch teardown ordering + other modules
holding by-value STATE_DIR refs). Rewrite the 3 tests to import api.config in a
fresh SUBPROCESS under the target env and read STATE_DIR from stdout — truly
isolated, can't pollute the test process. Same assertions (HERMES_HOME-derived
default, unset->platform default w/ cross-check, STATE_DIR override precedence).
Opus flagged a real upgrade gap on the #4449 state-dir contract folded into
#4454: a deployment that relocated HERMES_HOME to a non-default base WITHOUT
setting HERMES_WEBUI_STATE_DIR sees WebUI state move to $HERMES_HOME/webui on
upgrade (empty app at the new address). Add (1) README note documenting the
move + how to keep the old location, and (2) two backward-compat regression
tests pinning the invariants: HERMES_HOME-unset stays at the platform default
(normal installs don't relocate), and HERMES_WEBUI_STATE_DIR takes precedence.
The /api/profiles handler re-imported _is_isolated_profile_mode locally inside
handle_get, which made the name function-local for the ENTIRE handle_get scope
-> the earlier use at the /api/projects branch (routes.py:8000) hit
UnboundLocalError (F823), failing 6 test shards + lint. It's already a
module-level re-export (routes.py:429), so the local re-import is redundant;
removing it lets every in-function use resolve to the module global. Also drop
an unused 'from pathlib import Path' in the new #4449 state-dir test.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>