From 271e2006d225f749e04926a1f0c9b3b6e79371e6 Mon Sep 17 00:00:00 2001 From: Bahaa Zahika Date: Mon, 9 Feb 2026 01:08:37 +0200 Subject: [PATCH] Fix switch_modes tool silently failing to change modes set_modes() applied requested modes directly to self._active_modes, then called _update_active_modes_and_tools() which recreates _active_modes from scratch (global config -> project config -> CLI overrides), discarding the change. Fix: store the requested modes into self._mode_overrides so they survive the rebuild and take highest priority, matching the behavior of CLI --mode flags. Also adds test coverage for set_modes() which was previously untested. Bug was introduced in commit 40a3075 on Jan 26, 2026. --- src/serena/agent.py | 2 +- test/serena/test_set_modes.py | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 test/serena/test_set_modes.py diff --git a/src/serena/agent.py b/src/serena/agent.py index a4b21978..1997b7de 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -522,7 +522,7 @@ class SerenaAgent: :param mode_names: List of mode names or paths to use """ - self._active_modes.apply(ModeSelectionDefinition(default_modes=mode_names)) + self._mode_overrides = ModeSelectionDefinition(default_modes=mode_names) self._update_active_modes_and_tools() log.info(f"Set modes to {[mode.name for mode in self.get_active_modes()]}") diff --git a/test/serena/test_set_modes.py b/test/serena/test_set_modes.py new file mode 100644 index 00000000..dadae4ba --- /dev/null +++ b/test/serena/test_set_modes.py @@ -0,0 +1,75 @@ +"""Tests for SerenaAgent.set_modes() to verify that mode switching works correctly.""" + +import logging + +from serena.agent import SerenaAgent +from serena.config.serena_config import ModeSelectionDefinition, SerenaConfig + + +class TestSetModes: + """Test that set_modes correctly changes active modes.""" + + def _create_agent(self, modes: ModeSelectionDefinition | None = None) -> SerenaAgent: + config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR) + return SerenaAgent(serena_config=config, modes=modes) + + def test_set_modes_changes_active_modes(self) -> None: + """Test that calling set_modes actually changes the active modes.""" + agent = self._create_agent(modes=ModeSelectionDefinition(default_modes=["editing", "interactive"])) + + initial_mode_names = sorted(m.name for m in agent.get_active_modes()) + assert "editing" in initial_mode_names + assert "interactive" in initial_mode_names + + # Switch to planning mode + agent.set_modes(["planning", "interactive"]) + + new_mode_names = sorted(m.name for m in agent.get_active_modes()) + assert "planning" in new_mode_names + assert "interactive" in new_mode_names + assert "editing" not in new_mode_names + + def test_set_modes_overrides_config_defaults(self) -> None: + """Test that set_modes takes precedence over config defaults.""" + config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR) + config.default_modes = ["editing", "interactive"] + agent = SerenaAgent(serena_config=config) + + # Verify config defaults are active + initial_mode_names = [m.name for m in agent.get_active_modes()] + assert "editing" in initial_mode_names + + # Switch modes — should override config defaults + agent.set_modes(["planning", "one-shot"]) + + new_mode_names = [m.name for m in agent.get_active_modes()] + assert "planning" in new_mode_names + assert "one-shot" in new_mode_names + assert "editing" not in new_mode_names + + def test_set_modes_persists_after_repeated_calls(self) -> None: + """Test that set_modes result persists (modes don't revert).""" + agent = self._create_agent(modes=ModeSelectionDefinition(default_modes=["editing"])) + + agent.set_modes(["planning"]) + mode_names_1 = [m.name for m in agent.get_active_modes()] + assert "planning" in mode_names_1 + + # Call get_active_modes again — should still be planning + mode_names_2 = [m.name for m in agent.get_active_modes()] + assert mode_names_1 == mode_names_2 + + def test_set_modes_can_switch_back(self) -> None: + """Test that modes can be switched back to original after switching away.""" + agent = self._create_agent(modes=ModeSelectionDefinition(default_modes=["editing", "interactive"])) + + # Switch away + agent.set_modes(["planning", "one-shot"]) + assert "planning" in [m.name for m in agent.get_active_modes()] + + # Switch back + agent.set_modes(["editing", "interactive"]) + mode_names = [m.name for m in agent.get_active_modes()] + assert "editing" in mode_names + assert "interactive" in mode_names + assert "planning" not in mode_names