From 4b2aaece22963e1fcfff5994361f7a86aa13fb74 Mon Sep 17 00:00:00 2001 From: liuhao1024 Date: Sun, 5 Jul 2026 00:32:23 +0800 Subject: [PATCH] fix(model): default --provider switches to session-only persistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When /model is called with --provider but without --global or --session, the switch now defaults to session-only instead of persisting to config.yaml. Provider switches are typically exploratory — the user is trying a different backend for this conversation, not reconfiguring the default. --global can still force persist when desired. This addresses a regression from fad4b40d9 where /model switched to persist-by-default, causing /model xxx --provider xxx to overwrite the global config when the user only intended a temporary switch. Fixes #58290 --- cli.py | 4 +++- gateway/slash_commands.py | 4 +++- hermes_cli/model_switch.py | 14 +++++++++++-- .../test_model_switch_persist_default.py | 20 +++++++++++++++++++ tui_gateway/server.py | 4 +++- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/cli.py b/cli.py index d2dbbbb019552..90bc64dbd3336 100644 --- a/cli.py +++ b/cli.py @@ -7947,7 +7947,9 @@ def _handle_model_switch(self, cmd_original: str): # config-gated default, --global forces persist, otherwise defer to # model.persist_switch_by_default (defaults to True so /model survives # across sessions). - persist_global = resolve_persist_behavior(is_global_flag, is_session) + persist_global = resolve_persist_behavior( + is_global_flag, is_session, explicit_provider + ) # --refresh: wipe the on-disk picker cache before building the # provider list. Forces a live re-fetch of every authed provider's diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index 56bb7cd6ab3c3..5d41942e8f97b 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -1399,7 +1399,9 @@ async def _handle_model_command(self, event: MessageEvent) -> Optional[str]: force_refresh, is_session, ) = parse_model_flags(raw_args) - persist_global = resolve_persist_behavior(is_global_flag, is_session) + persist_global = resolve_persist_behavior( + is_global_flag, is_session, explicit_provider + ) # --refresh: bust the disk cache so the picker shows live data. if force_refresh: diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index b9a50f6df63d1..36078c2491592 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -365,14 +365,22 @@ def parse_model_flags(raw_args: str) -> tuple[str, str, bool, bool, bool]: return (model_input, explicit_provider, is_global, force_refresh, is_session) -def resolve_persist_behavior(is_global: bool, is_session: bool) -> bool: +def resolve_persist_behavior( + is_global: bool, + is_session: bool, + explicit_provider: str = "", +) -> bool: """Decide whether a ``/model`` switch should persist to ``config.yaml``. Resolution order: 1. ``--session`` explicitly opts out → ``False`` (this session only). 2. ``--global`` explicitly opts in → ``True``. - 3. Otherwise defer to ``model.persist_switch_by_default`` in + 3. ``--provider`` given without an explicit persist flag → ``False`` + (session only). Provider switches are typically exploratory — the + user is trying a different backend for this conversation, not + reconfiguring the default. ``--global`` can still force persist. + 4. Otherwise defer to ``model.persist_switch_by_default`` in ``config.yaml`` (defaults to ``True``, so a plain ``/model `` survives across sessions — the behavior users expect). @@ -384,6 +392,8 @@ def resolve_persist_behavior(is_global: bool, is_session: bool) -> bool: return False if is_global: return True + if explicit_provider: + return False try: from hermes_cli.config import load_config diff --git a/tests/hermes_cli/test_model_switch_persist_default.py b/tests/hermes_cli/test_model_switch_persist_default.py index 912bd7afe47e1..874fa40bc533c 100644 --- a/tests/hermes_cli/test_model_switch_persist_default.py +++ b/tests/hermes_cli/test_model_switch_persist_default.py @@ -96,6 +96,26 @@ def test_session_overrides_global_when_both_set(self): with _config({"model": {"persist_switch_by_default": True}}): assert resolve_persist_behavior(True, True) is False + def test_provider_flag_defaults_to_session_only(self): + # --provider without --global/--session → session only. + with _config({"model": {"persist_switch_by_default": True}}): + assert resolve_persist_behavior(False, False, "anthropic") is False + + def test_provider_with_global_still_persists(self): + # --provider + --global → persists. + with _config({"model": {"persist_switch_by_default": False}}): + assert resolve_persist_behavior(True, False, "anthropic") is True + + def test_provider_with_session_still_session_only(self): + # --provider + --session → session only. + with _config({"model": {"persist_switch_by_default": True}}): + assert resolve_persist_behavior(False, True, "anthropic") is False + + def test_no_provider_uses_config_default(self): + # No --provider → respects config default (True). + with _config({"model": {"persist_switch_by_default": True}}): + assert resolve_persist_behavior(False, False, "") is True + # --------------------------------------------------------------------------- # helper diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 878b28d02c217..815df3aad045b 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2628,7 +2628,9 @@ def _apply_model_switch( _force_refresh, is_session, ) = parsed_flags - persist_global = resolve_persist_behavior(is_global_flag, is_session) + persist_global = resolve_persist_behavior( + is_global_flag, is_session, explicit_provider + ) if not model_input: raise ValueError("model value required")