Skip to content

fix(desktop): let explicit model switches escape broken config providers (salvage #42971) - #46796

Merged
austinpickett merged 1 commit into
mainfrom
salvage/model-switch-escape-42971
Jun 15, 2026
Merged

fix(desktop): let explicit model switches escape broken config providers (salvage #42971)#46796
austinpickett merged 1 commit into
mainfrom
salvage/model-switch-escape-42971

Conversation

@austinpickett

Copy link
Copy Markdown
Collaborator

Summary

Salvage of #42971 (@rodboev) onto current main. Fixes #42241.

When a desktop/dashboard session had no agent built yet and the user explicitly picked a provider in the model picker, config.set('model', …) would first try to initialize the agent from the (possibly broken) config default provider — failing before the user's explicit switch could take effect. The user got trapped on a misconfigured default with no way to switch out via the UI.

Confirmed still present on main: tui_gateway/server.py _apply_model_switch() else-branch unconditionally calls resolve_runtime_provider(requested=None), and the config.set model handler does _start_agent_build whenever session.get("agent") is None regardless of an explicit provider.

What changed

  • config.set pre-parses the model flags. If an explicit --provider is present and no agent exists yet, it skips the default-provider agent build and routes straight through _apply_model_switch with the explicit provider.
  • _apply_model_switch gained a parsed_flags passthrough (avoids double-parsing) and only falls back to resolve_runtime_provider(requested=None) when no explicit provider was given; otherwise it uses the explicit provider directly.
  • The desktop hook (use-model-controls.ts) sends config.set instead of slash.exec for active-session changes, so errors from the selected provider surface to the user instead of being swallowed.
  • Adds 2 backend tests (explicit provider skips broken default init; selected-provider errors surface as code 5001) and expands the desktop hook tests.

How this was salvaged

The original PR was 4 commits (core fix + i18n test alignment + a refactor that removed 26 lockfile lines + a follow-up that re-added them — net lockfile change zero). I applied the net diff of the 4 real files via 3-way merge onto current main. One conflict in _apply_model_switch's signature — main added a pin_session_override param since the PR was authored; kept both that and the new parsed_flags param. Dropped the no-op lockfile churn entirely.

Test plan

  • py_compile on server.py → OK
  • pytest tests/test_tui_gateway_server.py -k model20 passed (incl. the 2 new tests + the env-leak guard)
  • Desktop vitest changes applied cleanly (deps not installed locally; CI runs the desktop suite)

Credit

…ers (#42241)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: salvage/model-switch-escape-42971 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10940 on HEAD, 10931 on base (🆕 +9)

🆕 New issues (4):

Rule Count
unresolved-attribute 3
unsupported-operator 1
First entries
tests/test_tui_gateway_server.py:3051: [unresolved-attribute] unresolved-attribute: Attribute `append` is not defined on `int` in union `int | list[Unknown]`
run_agent.py:2920: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
tests/test_tui_gateway_server.py:3046: [unsupported-operator] unsupported-operator: Operator `+` is not supported between objects of type `int | list[Unknown]` and `Literal[1]`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`

✅ Fixed issues (1):

Rule Count
invalid-assignment 1
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`

Unchanged: 5758 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have labels Jun 15, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: #42971 (original by @rodboev — this is the salvage onto current main), #42241 (the issue it fixes), and #46288 (sibling desktop model-switch fix via a different mechanism — routing picker switches through config.set to avoid the slash worker). This salvage is the active/authoritative version of the #42971 fix; tracking lineage only.

@austinpickett
austinpickett merged commit ed20f5e into main Jun 15, 2026
29 checks passed
@austinpickett
austinpickett deleted the salvage/model-switch-escape-42971 branch June 15, 2026 19:36
rdnot added a commit to rdnot/hermes-agent-medical-research that referenced this pull request Jun 15, 2026
…kfile-fix.yml)

Upstream advanced from 5bfed0f (Jun 15) to 3e7e9b2 (Jun 16). Major upstream
changes since last merge:

- feat(skills): add optional payments skills (Stripe Link, MPP, Projects) NousResearch#31343
- feat(delegation): async background subagents via delegate_task(background=true) NousResearch#40946
- fix(desktop): read HERMES_HOME from Windows registry when env is stale NousResearch#46772
- fix(desktop): let explicit model switches escape broken config providers NousResearch#46796
- fix(dump): report effective terminal backend in hermes debug
- fix(inventory): deduplicate models between user-defined and aggregator providers
- fix(doctor): recognize nvidia as vendor-slug-accepting provider
- fix(discord): cap slash commands at Discord's 100-command limit
- fix(teams): package Microsoft Teams SDK as an installable extra NousResearch#46764
- fix(dashboard): scope chat sidebar model card to selected profile NousResearch#46665
- fix(deps): declare websockets as core dep + relax dev setuptools pin NousResearch#46744
- fix(desktop): pin @assistant-ui/store so the cluster shares one tap
- fix(install): make npm install -g packages reachable on PATH
- feat(honcho-setup): replace deployment-shape prompt with gateway-gated identity tree
- refactor(honcho): canonicalize identity-mapping on pinUserPeer, migrate legacy key
- mattermost: preserve thread-local delivery hygiene
- ci: rip out nix ci for now
- ...plus desktop Electron changes, web UI updates, website docs, honcho docs

Fork customizations preserved (all 22 verified intact):
- run_agent.py: max_iterations: int = 200
- agent/conversation_loop.py: _force_final_threshold, _build_tools_summary,
  _current_turn_tool_calls, _pending_tool_summary, tool_summary flow
- agent/turn_finalizer.py: 'tool_summary' key in result dict
- tools/web_tools.py: tiered fetcher (curl_cffi→scrapling→httpx),
  MAX_OUTPUT_SIZE=10000, _get_extract_backend, max_result_size_chars=500_000,
  LLM-summarization disabled, NCBI/PubMed/PMC hardening
- tools/budget_config.py: 400_000 / 500_000
- gateway/run.py: HERMES_MAX_ITERATIONS, "200"
- hermes_cli/config.py: max_turns: 200
- cli.py: result.get('tool_summary') display
- gateway/config.py: Platform.LINE = "line"
- gateway/authz_mixin.py: Platform.LINE: "LINE_ALLOWED_GROUPS"
- plugins/platforms/line/adapter.py: reply_only_mention, build_source

Conflict resolution:
- .github/workflows/nix-lockfile-fix.yml: removed by upstream.
  Was fork-only (disabled with if:false, requires APP_ID/APP_PRIVATE_KEY
  secrets the fork does not have). Upstream deletion is correct.

Backup branch: backup-main-pre-merge-20260616-063126
alanbratu pushed a commit to alanbratu/hermes-agent that referenced this pull request Jun 17, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
zmlgit pushed a commit to zmlgit/hermes-agent that referenced this pull request Jun 17, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
al3xar pushed a commit to al3xar/hermes-agent that referenced this pull request Jun 17, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
T02200059 pushed a commit to T02200059/hermes-agent that referenced this pull request Jun 18, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
Methodician added a commit to Methodician/hermes-agent that referenced this pull request Jul 4, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…ers (NousResearch#42241) (NousResearch#46796)

When a desktop/dashboard session had no agent built yet and the user explicitly
picked a provider in the model picker, config.set('model', ...) would first try
to initialize the agent from the (possibly broken) config default provider —
failing before the user's explicit switch could take effect, trapping them on a
misconfigured default.

config.set now pre-parses the model flags: if an explicit --provider is present
and no agent exists yet, it skips the default-provider agent build and routes
straight through _apply_model_switch with the explicit provider. _apply_model_switch
gained a parsed_flags passthrough (avoids double-parsing) and only falls back to
resolve_runtime_provider(requested=None) when no explicit provider was given.

The desktop hook now sends config.set instead of slash.exec for active-session
model changes, so errors from the selected provider surface to the user instead
of being swallowed.

Co-authored-by: rodboev <rod.boev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Desktop] Model switch fails when default provider in config.yaml lacks API key

3 participants