Ensure consistent mode configuration for project

Enabled modes must not depend on project activation order.
Always apply, in order: SerenaConfig, ProjectConfig, current CLI overrides
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-01-28 19:49:00 +01:00
1 parent 5b103ee281
commit 40a3075cc0
1 file changed
+22 -23
+22 -23
View File
@@ -331,26 +331,20 @@ class SerenaAgent:
self.prompt_factory = SerenaPromptFactory() self.prompt_factory = SerenaPromptFactory()
self._project_activation_callback = project_activation_callback self._project_activation_callback = project_activation_callback
# initialize active modes based on the global configuration # initialize members for modes and tools
self._mode_overrides = modes
self._active_modes = ActiveModes() self._active_modes = ActiveModes()
self._active_modes.apply(self.serena_config) self._active_tools: AvailableTools = self._exposed_tools
# activate a project configuration (if provided or if there is only a single project available) # activate a project configuration (if provided or if there is only a single project available)
if project is not None: if project is not None:
try: try:
# Note: This will apply the project's mode selections (if any). self.activate_project_from_path_or_name(project, update_modes_and_tools=False)
# We do not yet update the tool set, as we need to apply mode overrides first (see below).
self.activate_project_from_path_or_name(project, update_tools=False)
except Exception as e: except Exception as e:
log.error(f"Error activating project '{project}' at startup: {e}", exc_info=e) log.error(f"Error activating project '{project}' at startup: {e}", exc_info=e)
# apply modes from the provided mode selection overrides (precedence over project configuration) # update active modes and active tools (considering the active project, if any)
if modes is not None: self._update_active_modes_and_tools()
self._active_modes.apply(modes)
# determine the subset of active tools (depending on active modes and the active project)
self._active_tools: AvailableTools = self._exposed_tools
self._update_active_tools()
# start the dashboard (web frontend), registering its log handler # start the dashboard (web frontend), registering its log handler
# should be the last thing to happen in the initialization since the dashboard # should be the last thing to happen in the initialization since the dashboard
@@ -529,7 +523,7 @@ class SerenaAgent:
:param mode_names: List of mode names or paths to use :param mode_names: List of mode names or paths to use
""" """
self._active_modes.apply(ModeSelectionDefinition(default_modes=mode_names)) self._active_modes.apply(ModeSelectionDefinition(default_modes=mode_names))
self._update_active_tools() self._update_active_modes_and_tools()
log.info(f"Set modes to {[mode.name for mode in self.get_active_modes()]}") log.info(f"Set modes to {[mode.name for mode in self.get_active_modes()]}")
@@ -561,12 +555,20 @@ class SerenaAgent:
log.info("System prompt:\n%s", system_prompt) log.info("System prompt:\n%s", system_prompt)
return system_prompt return system_prompt
def _update_active_tools(self) -> None: def _update_active_modes_and_tools(self) -> None:
""" """
Update the active tools based on enabled modes and the active project. Updates the active modes and, subsequently, the active tools based on the modes and the active project.
The base tool set already takes the Serena configuration and the context into account The base tool set already takes the Serena configuration and the context into account
(as well as any internal modes that are not handled dynamically, such as JetBrains mode). (as well as any internal modes that are not handled dynamically, such as JetBrains mode).
""" """
# determine active modes from serena config, active project, and mode overrides
self._active_modes = ActiveModes()
self._active_modes.apply(self.serena_config)
if self._active_project:
self._active_modes.apply(self._active_project.project_config)
if self._mode_overrides:
self._active_modes.apply(self._mode_overrides)
# apply modes # apply modes
tool_set = self._base_tool_set.apply(*self._active_modes.get_modes()) tool_set = self._base_tool_set.apply(*self._active_modes.get_modes())
@@ -615,15 +617,12 @@ class SerenaAgent:
""" """
return self.serena_config.language_backend == LanguageBackend.LSP return self.serena_config.language_backend == LanguageBackend.LSP
def _activate_project(self, project: Project, update_tools: bool = True) -> None: def _activate_project(self, project: Project, update_modes_and_tools: bool = True) -> None:
log.info(f"Activating {project.project_name} at {project.project_root}") log.info(f"Activating {project.project_name} at {project.project_root}")
self._active_project = project self._active_project = project
# update the active modes based on the project configuration if update_modes_and_tools:
self._active_modes.apply(project.project_config) self._update_active_modes_and_tools()
if update_tools:
self._update_active_tools()
def init_language_server_manager() -> None: def init_language_server_manager() -> None:
# start the language server # start the language server
@@ -654,7 +653,7 @@ class SerenaAgent:
log.info(f"Added new project {project_instance.project_name} for path {project_instance.project_root}") log.info(f"Added new project {project_instance.project_name} for path {project_instance.project_root}")
return project_instance return project_instance
def activate_project_from_path_or_name(self, project_root_or_name: str, update_tools: bool = True) -> Project: def activate_project_from_path_or_name(self, project_root_or_name: str, update_modes_and_tools: bool = True) -> Project:
""" """
Activate a project from a path or a name. Activate a project from a path or a name.
If the project was already registered, it will just be activated. If the project was already registered, it will just be activated.
@@ -670,7 +669,7 @@ class SerenaAgent:
f"Project '{project_root_or_name}' not found: Not a valid project name or directory. " f"Project '{project_root_or_name}' not found: Not a valid project name or directory. "
f"Existing project names: {self.serena_config.project_names}" f"Existing project names: {self.serena_config.project_names}"
) )
self._activate_project(project_instance, update_tools=update_tools) self._activate_project(project_instance, update_modes_and_tools=update_modes_and_tools)
return project_instance return project_instance
def get_active_tool_names(self) -> list[str]: def get_active_tool_names(self) -> list[str]: