Support --mode and --context with yaml files

This commit is contained in:
Alberto Simoes committed 2026-01-06 15:21:42 +00:00
1 parent 72ce32a65a
commit 870aa2cc3a
2 files changed
+58 -14

No files matched your search

+41 -10
View File
@@ -41,6 +41,11 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
It is formatted by the agent (see SerenaAgent._format_prompt()).
"""
description: str = ""
_yaml_path: Path | None = field(default=None, repr=False, compare=False)
"""
Internal field storing the path to the YAML file this mode was loaded from.
Used to support loading modes from arbitrary file paths.
"""
def _tostring_includes(self) -> list[str]:
return ["name"]
@@ -54,14 +59,24 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
@classmethod
def from_yaml(cls, yaml_path: str | Path) -> Self:
"""Load a mode from a YAML file."""
with open(yaml_path, encoding=SERENA_FILE_ENCODING) as f:
yaml_as_path = Path(yaml_path).resolve()
with Path(yaml_as_path).open(encoding=SERENA_FILE_ENCODING) as f:
data = yaml.safe_load(f)
name = data.pop("name", Path(yaml_path).stem)
return cls(name=name, **data)
name = data.pop("name", yaml_as_path.stem)
return cls(name=name, _yaml_path=yaml_as_path, **data)
@classmethod
def get_path(cls, name: str) -> str:
"""Get the path to the YAML file for a mode."""
def get_path(cls, name: str, instance: Self | None = None) -> str:
"""Get the path to the YAML file for a mode.
:param name: The name of the mode
:param instance: Optional mode instance. If provided and it has a stored path, that path is returned.
:return: The path to the mode's YAML file
"""
# If we have an instance with a stored path, use that
if instance is not None and instance._yaml_path is not None:
return str(instance._yaml_path)
fname = f"{name}.yml"
custom_mode_path = os.path.join(SerenaPaths().user_modes_dir, fname)
if os.path.exists(custom_mode_path):
@@ -137,6 +152,12 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
maps tool names to custom descriptions, default descriptions are extracted from the tool docstrings.
"""
_yaml_path: Path | None = field(default=None, repr=False, compare=False)
"""
Internal field storing the path to the YAML file this context was loaded from.
Used to support loading contexts from arbitrary file paths.
"""
single_project: bool = False
"""
whether to assume that Serena shall only work on a single project in this context (provided that a project is given
@@ -152,17 +173,27 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
@classmethod
def from_yaml(cls, yaml_path: str | Path) -> Self:
"""Load a context from a YAML file."""
with open(yaml_path, encoding=SERENA_FILE_ENCODING) as f:
yaml_as_path = Path(yaml_path).resolve()
with yaml_as_path.open(encoding=SERENA_FILE_ENCODING) as f:
data = yaml.safe_load(f)
name = data.pop("name", Path(yaml_path).stem)
name = data.pop("name", yaml_as_path.stem)
# Ensure backwards compatibility for tool_description_overrides
if "tool_description_overrides" not in data:
data["tool_description_overrides"] = {}
return cls(name=name, **data)
return cls(name=name, _yaml_path=yaml_as_path, **data)
@classmethod
def get_path(cls, name: str) -> str:
"""Get the path to the YAML file for a context."""
def get_path(cls, name: str, instance: Self | None = None) -> str:
"""Get the path to the YAML file for a context.
:param name: The name of the context
:param instance: Optional context instance. If provided and it has a stored path, that path is returned.
:return: The path to the context's YAML file
"""
# If we have an instance with a stored path, use that
if instance is not None and instance._yaml_path is not None:
return str(instance._yaml_path)
fname = f"{name}.yml"
custom_context_path = os.path.join(SerenaPaths().user_contexts_dir, fname)
if os.path.exists(custom_context_path):
+17 -4
View File
@@ -354,12 +354,15 @@ class SerenaDashboardAPI:
context_info = {
"name": context.name,
"description": context.description,
"path": SerenaAgentContext.get_path(context.name),
"path": SerenaAgentContext.get_path(context.name, instance=context),
}
# Get active modes
modes = self._agent.get_active_modes()
modes_info = [{"name": mode.name, "description": mode.description, "path": SerenaAgentMode.get_path(mode.name)} for mode in modes]
modes_info = [
{"name": mode.name, "description": mode.description, "path": SerenaAgentMode.get_path(mode.name, instance=mode)}
for mode in modes
]
active_mode_names = [mode.name for mode in modes]
# Get active tools
@@ -392,11 +395,16 @@ class SerenaDashboardAPI:
all_mode_names = SerenaAgentMode.list_registered_mode_names()
available_modes: list[dict[str, str | bool]] = []
for mode_name in all_mode_names:
try:
mode_path = SerenaAgentMode.get_path(mode_name)
except FileNotFoundError:
# Skip modes that can't be found (shouldn't happen for registered modes)
continue
available_modes.append(
{
"name": mode_name,
"is_active": mode_name in active_mode_names,
"path": SerenaAgentMode.get_path(mode_name),
"path": mode_path,
}
)
@@ -404,11 +412,16 @@ class SerenaDashboardAPI:
all_context_names = SerenaAgentContext.list_registered_context_names()
available_contexts: list[dict[str, str | bool]] = []
for context_name in all_context_names:
try:
context_path = SerenaAgentContext.get_path(context_name)
except FileNotFoundError:
# Skip contexts that can't be found (shouldn't happen for registered contexts)
continue
available_contexts.append(
{
"name": context_name,
"is_active": context_name == context.name,
"path": SerenaAgentContext.get_path(context_name),
"path": context_path,
}
)