Conversation
This PR fixes the sticky composer model leaking across chats (cold-resumed grok thread inheriting another chat's Gemini pick) via three coordinated changes: identity-scoped model/provider atoms in the primary session view, always-
Minor: |
|
Updated with resolutions for review feedback:
|
Re-checked head
No blockers from this review. |
8b8954e to
afae49c
Compare
…e session Cold-resuming a stored chat session that had no active slice yet fell back to the global composer sticky atoms ($currentModel / $currentProvider). That sticky is the last pick from ANY chat in the window, causing models selected in one chat to leak onto other threads after app restart, gateway reconnect, or Cmd+N. Additionally, choosing a model from the active primary session picker did not specify --session, allowing the gateway default to potentially rewrite other sessions, and turn-start config sync automatically adopted config.yaml defaults onto existing desktop sessions. - In session-view.tsx, introduce primaryIdentityField to isolate model/provider for stored sessions from the draft sticky atom. - In use-model-controls.ts, append --session when changing model in an active primary session so the switch remains session-scoped. - In tui_gateway/model_switch.py, exempt desktop sessions from auto-adopting config.yaml model changes at turn start (unless opted-in via follow_profile_config). - Add tests covering cold resume, live slice preference, session scoping, and desktop session config-sync bypass.
…ateway persistence resolution
A desktop turn whose RAM model pin is missing still has the picked model in the session row. Config sync treated that as an unpinned chat and rewrote the process-shared client onto model.default, so sibling chats snapped to gemini together.
afae49c to
86d36b3
Compare
Summary
Fixes an issue in Hermes Desktop where resuming a stored chat session or switching models leaks the global composer sticky model across sessions.
Problem
PRIMARY_SESSION_VIEW.$modeland$providerfell back to$currentModeland$currentProvider(the composer sticky). As a result, a model selected in one chat (e.g. Gemini) leaked onto another thread (e.g. a Grok session).--sessionscope on picker changes: Changing the model from the active primary session picker sentconfig.setwithout--session, allowing the switch to alter global defaults._sync_agent_model_with_configon the gateway automatically pulledconfig.yamlchanges into active desktop sessions at turn start, overriding session-specific models.source == "desktop", so after a resume that lost the in-RAM pin (rebuild, reload race) the next turn read "agent ≠ config" as a profile edit and rewrote it ontomodel.default. Because the OpenAI client is process-shared, sibling chats snapped to the default together. Observed: several ACP-attached chats flipped from their persisted model to the profile's Gemini default after a client reload.Solution
apps/desktop/src/app/chat/session-view.tsx: AddprimaryIdentityFieldso stored sessions without an active slice resolve to empty string instead of falling back to the draft sticky atoms ($currentModel/$currentProvider). True drafts (no stored ID) still follow the last picked model.apps/desktop/src/app/session/hooks/use-model-controls.ts: Append--sessiontoconfig.setfor primary session picker selections so model changes remain scoped to the active session.tui_gateway/model_switch.py: Exempt desktop sessions (session.get("source") == "desktop") from turn-start config sync unless opted in viafollow_profile_config.tui_gateway/model_switch.py(source-agnostic backstop):_session_db_model()reads the chat's persisted model; when it already differs frommodel.defaultthe chat is a session pick, so config sync records the target but does not switch. A chat still on the default (or with no row) keeps adopting profile edits as before.Test plan
npx vitest run src/app/chat/session-view.test.ts src/app/session/hooks/use-model-controls.test.tsx(33 passed)uv run --with pytest pytest tests/tui_gateway/test_tui_gateway_server.py -k "test_config_sync"(11 passed)tests/tui_gateway/test_desktop_model_sync_guard.py(new: skips when persisted model differs; still adopts when persisted matches default / row missing). Rebased on currentmain; the tui_gateway suites show the same failure set before and after this branch (no new failures).