Possibility to load modes and contexts by name from $HOME/.serena

This commit is contained in:
Michael Panchenko authored and Michael Panchenko committed 2025-07-17 00:28:29 +02:00
1 parent cad4df59cc
commit e596319492
4 files changed
+60 -21

No files matched your search

+2 -2
View File
@@ -21,7 +21,7 @@ from sensai.util.logging import LogTime
from serena import serena_version
from serena.analytics import RegisteredTokenCountEstimator, ToolUsageStats
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
from serena.config.serena_config import SerenaConfig, ToolSet, get_serena_managed_dir
from serena.config.serena_config import SerenaConfig, ToolSet, get_serena_managed_in_project_dir
from serena.constants import (
SERENA_LOG_FORMAT,
)
@@ -62,7 +62,7 @@ class LinesRead:
class MemoriesManager:
def __init__(self, project_root: str):
self._memory_dir = Path(get_serena_managed_dir(project_root)) / "memories"
self._memory_dir = Path(get_serena_managed_in_project_dir(project_root)) / "memories"
self._memory_dir.mkdir(parents=True, exist_ok=True)
def _get_memory_file_path(self, name: str) -> Path:
+41 -14
View File
@@ -14,7 +14,15 @@ from sensai.util import logging
from sensai.util.string import ToStringMixin
from serena.config.serena_config import ToolInclusionDefinition
from serena.constants import CONTEXT_YAMLS_DIR, DEFAULT_CONTEXT, DEFAULT_MODES, INTERNAL_MODE_YAMLS_DIR, MODE_YAMLS_DIR
from serena.constants import (
DEFAULT_CONTEXT,
DEFAULT_MODES,
INTERNAL_MODE_YAMLS_DIR,
SERENAS_OWN_CONTEXT_YAMLS_DIR,
SERENAS_OWN_MODE_YAMLS_DIR,
USER_CONTEXT_YAMLS_DIR,
USER_MODE_YAMLS_DIR,
)
if TYPE_CHECKING:
pass
@@ -64,13 +72,19 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
@classmethod
def from_name(cls, name: str) -> Self:
"""Load a registered Serena mode."""
yaml_path = os.path.join(MODE_YAMLS_DIR, f"{name}.yml")
if not os.path.exists(yaml_path):
fname = f"{name}.yml"
custom_mode_path = os.path.join(USER_MODE_YAMLS_DIR, fname)
if os.path.exists(custom_mode_path):
log.info(f"Loading custom mode {name} from {custom_mode_path}")
return cls.from_yaml(custom_mode_path)
own_yaml_path = os.path.join(SERENAS_OWN_MODE_YAMLS_DIR, fname)
if not os.path.exists(own_yaml_path):
raise FileNotFoundError(
f"Mode {name} not found in {MODE_YAMLS_DIR}. You can load custom modes by using from_yaml() instead. "
f"Mode {name} not found in {USER_MODE_YAMLS_DIR} or in {SERENAS_OWN_MODE_YAMLS_DIR}. You can load custom modes by using from_yaml() instead. "
f"Available modes: {cls.list_registered_mode_names()}"
)
return cls.from_yaml(yaml_path)
return cls.from_yaml(own_yaml_path)
@classmethod
def from_name_internal(cls, name: str) -> Self:
@@ -81,9 +95,12 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
return cls.from_yaml(yaml_path)
@classmethod
def list_registered_mode_names(cls) -> list[str]:
def list_registered_mode_names(cls, include_user_modes: bool = True) -> list[str]:
"""Names of all registered modes (from the corresponding YAML files in the serena repo)."""
return sorted([f.stem for f in Path(MODE_YAMLS_DIR).glob("*.yml")])
modes = [f.stem for f in Path(SERENAS_OWN_MODE_YAMLS_DIR).glob("*.yml")]
if include_user_modes:
modes += [f.stem for f in Path(USER_MODE_YAMLS_DIR).glob("*.yml")]
return sorted(set(modes))
@classmethod
def load_default_modes(cls) -> list[Self]:
@@ -134,13 +151,20 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
@classmethod
def from_name(cls, name: str) -> Self:
"""Load a registered Serena context."""
yaml_path = os.path.join(CONTEXT_YAMLS_DIR, f"{name}.yml")
if not os.path.exists(yaml_path):
fname = f"{name}.yml"
custom_context_path = os.path.join(USER_CONTEXT_YAMLS_DIR, fname)
if os.path.exists(custom_context_path):
log.info(f"Loading custom context {name} from {custom_context_path}")
return cls.from_yaml(custom_context_path)
own_yaml_path = os.path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, fname)
if not os.path.exists(own_yaml_path):
raise FileNotFoundError(
f"Context {Path(yaml_path).stem} not found in {CONTEXT_YAMLS_DIR}. You can load a custom context by using from_yaml() instead.\n"
f"Context {Path(own_yaml_path).stem} not found in {USER_CONTEXT_YAMLS_DIR} or in {SERENAS_OWN_CONTEXT_YAMLS_DIR}. "
f"You can load a custom context by using from_yaml() instead.\n"
f"Available contexts:\n{cls.list_registered_context_names()}"
)
return cls.from_yaml(yaml_path)
return cls.from_yaml(own_yaml_path)
@classmethod
def load(cls, name_or_path: str | Path) -> Self:
@@ -151,14 +175,17 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
return cls.from_yaml(name_or_path)
except FileNotFoundError as e:
raise FileNotFoundError(
f"Context {name_or_path} not found in {CONTEXT_YAMLS_DIR}. You can load a custom context by using from_yaml() instead.\n"
f"Context {name_or_path} not found in {SERENAS_OWN_CONTEXT_YAMLS_DIR}. You can load a custom context by using from_yaml() instead.\n"
f"Available contexts:\n{cls.list_registered_context_names()}"
) from e
@classmethod
def list_registered_context_names(cls) -> list[str]:
def list_registered_context_names(cls, include_user_contexts: bool = True) -> list[str]:
"""Names of all registered contexts (from the corresponding YAML files in the serena repo)."""
return sorted([f.stem for f in Path(CONTEXT_YAMLS_DIR).glob("*.yml")])
contexts = [f.stem for f in Path(SERENAS_OWN_CONTEXT_YAMLS_DIR).glob("*.yml")]
if include_user_contexts:
contexts += [f.stem for f in Path(USER_CONTEXT_YAMLS_DIR).glob("*.yml")]
return sorted(set(contexts))
@classmethod
def load_default(cls) -> Self:
+3 -2
View File
@@ -21,6 +21,7 @@ from serena.constants import (
PROJECT_TEMPLATE_FILE,
REPO_ROOT,
SELENA_CONFIG_TEMPLATE_FILE,
SERENA_MANAGED_DIR_IN_HOME,
SERENA_MANAGED_DIR_NAME,
)
from serena.util.general import load_yaml, save_yaml
@@ -113,7 +114,7 @@ class SerenaConfigError(Exception):
pass
def get_serena_managed_dir(project_root: str | Path) -> str:
def get_serena_managed_in_project_dir(project_root: str | Path) -> str:
return os.path.join(project_root, SERENA_MANAGED_DIR_NAME)
@@ -293,7 +294,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
if is_running_in_docker():
return os.path.join(REPO_ROOT, cls.CONFIG_FILE_DOCKER)
else:
config_path = str(Path.home() / SERENA_MANAGED_DIR_NAME / cls.CONFIG_FILE)
config_path = os.path.join(SERENA_MANAGED_DIR_IN_HOME, cls.CONFIG_FILE)
# if the config file does not exist, check if we can migrate it from the old location
if not os.path.exists(config_path):
+14 -3
View File
@@ -3,15 +3,26 @@ from pathlib import Path
_repo_root_path = Path(__file__).parent.parent.parent.resolve()
_serena_pkg_path = Path(__file__).parent.resolve()
SERENA_MANAGED_DIR_NAME = ".serena"
_serena_in_home_managed_dir = Path.home() / ".serena"
SERENA_MANAGED_DIR_IN_HOME = str(_serena_in_home_managed_dir)
REPO_ROOT = str(_repo_root_path)
PROMPT_TEMPLATES_DIR = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts")
MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "modes")
SERENAS_OWN_CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts")
"""The contexts that are shipped with the Serena package, i.e. the default contexts."""
USER_CONTEXT_YAMLS_DIR = str(_serena_in_home_managed_dir / "contexts")
"""Contexts defined by the user. If a name of a context matches a name of a context in SERENAS_OWN_CONTEXT_YAMLS_DIR, the user context will override the default one."""
SERENAS_OWN_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "modes")
"""The modes that are shipped with the Serena package, i.e. the default modes."""
USER_MODE_YAMLS_DIR = str(_serena_in_home_managed_dir / "modes")
"""Modes defined by the user. If a name of a mode matches a name of a mode in SERENAS_OWN_MODE_YAMLS_DIR, the user mode will override the default one."""
INTERNAL_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "internal_modes")
"""Internal modes, never overridden by user modes."""
SERENA_DASHBOARD_DIR = str(_serena_pkg_path / "resources" / "dashboard")
SERENA_ICON_DIR = str(_serena_pkg_path / "resources" / "icons")
SERENA_MANAGED_DIR_NAME = ".serena"
DEFAULT_ENCODING = "utf-8"
DEFAULT_CONTEXT = "desktop-app"