mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-03 09:13:49 +00:00
Merge pull request #1590 from oraios/serena-config-headless-mode
Add SerenaConfig.with_headless_mode_overrides and apply it
This commit is contained in:
6 files changed
+43
-67
No files matched your search
+3
-6
@@ -429,8 +429,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
modes_selection_def: ModeSelectionDefinition | None = None
|
||||
if modes:
|
||||
modes_selection_def = ModeSelectionDefinition(default_modes=modes)
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config.web_dashboard = False
|
||||
serena_config = SerenaConfig.from_config_file().with_headless_mode_overrides()
|
||||
print(serena_config.default_modes)
|
||||
print(serena_config.base_modes)
|
||||
|
||||
@@ -928,10 +927,8 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
|
||||
logging.configure(level=logging.INFO)
|
||||
project_path = os.path.abspath(project)
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config = SerenaConfig.from_config_file().with_headless_mode_overrides()
|
||||
serena_config.language_backend = LanguageBackend.LSP
|
||||
serena_config.gui_log_window = False
|
||||
serena_config.web_dashboard = False
|
||||
proj = Project.load(project_path, serena_config=serena_config)
|
||||
|
||||
# Create log file with timestamp
|
||||
@@ -1113,7 +1110,7 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
|
||||
agent = SerenaAgent(
|
||||
project=None,
|
||||
serena_config=SerenaConfig(web_dashboard=False, log_level=logging.INFO),
|
||||
serena_config=SerenaConfig(log_level=logging.INFO).with_headless_mode_overrides(),
|
||||
context=serena_context,
|
||||
)
|
||||
tool = agent.get_tool_by_name(tool_name)
|
||||
|
||||
@@ -1027,6 +1027,19 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
|
||||
config._save()
|
||||
return config
|
||||
|
||||
def with_headless_mode_overrides(self) -> "SerenaConfig":
|
||||
"""
|
||||
Modifies this instance to apply overrides for headless mode, where any GUI/user interaction-based features are disabled.
|
||||
This is intended to be applied for cases where a `SerenaConfig` instance is needed to instantiate a `SerenaAgent` instance
|
||||
while the user is not expected to interact with the system (e.g. a CLI command or a test).
|
||||
|
||||
:return: the instance with overrides applied for headless mode
|
||||
"""
|
||||
self.gui_log_window = False
|
||||
self.web_dashboard = False
|
||||
self.jetbrains_launch_command = None
|
||||
return self
|
||||
|
||||
@cached_property
|
||||
def project_paths(self) -> list[str]:
|
||||
return sorted(str(project.project_root) for project in self.projects)
|
||||
|
||||
@@ -20,7 +20,7 @@ def _create_test_project(
|
||||
ignored_paths=project_ignored_paths or [],
|
||||
ignore_all_files_in_gitignore=False,
|
||||
)
|
||||
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=global_ignored_paths)
|
||||
serena_config = SerenaConfig(ignored_paths=global_ignored_paths).with_headless_mode_overrides()
|
||||
return Project(
|
||||
project_root=str(project_root),
|
||||
project_config=config,
|
||||
@@ -149,7 +149,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
|
||||
ignored_paths=[],
|
||||
ignore_all_files_in_gitignore=False,
|
||||
)
|
||||
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"])
|
||||
serena_config = SerenaConfig(ignored_paths=["node_modules"]).with_headless_mode_overrides()
|
||||
registered = RegisteredProject(
|
||||
project_root=str(self.project_path),
|
||||
project_config=config,
|
||||
@@ -169,7 +169,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
|
||||
project_root=str(self.project_path),
|
||||
project_config=config,
|
||||
)
|
||||
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=[])
|
||||
serena_config = SerenaConfig(ignored_paths=[]).with_headless_mode_overrides()
|
||||
project = registered.get_project_instance(serena_config=serena_config)
|
||||
assert not project.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js"))
|
||||
|
||||
@@ -181,7 +181,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
|
||||
(serena_dir / "project.yml").write_text(
|
||||
'project_name: "test_project"\nlanguages: ["python"]\nignored_paths: []\nignore_all_files_in_gitignore: false\n'
|
||||
)
|
||||
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"])
|
||||
serena_config = SerenaConfig(ignored_paths=["node_modules"]).with_headless_mode_overrides()
|
||||
registered = RegisteredProject.from_project_root(
|
||||
str(self.project_path),
|
||||
serena_config=serena_config,
|
||||
@@ -197,7 +197,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
|
||||
ignored_paths=[],
|
||||
ignore_all_files_in_gitignore=False,
|
||||
)
|
||||
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"])
|
||||
serena_config = SerenaConfig(ignored_paths=["node_modules"]).with_headless_mode_overrides()
|
||||
project = Project(
|
||||
project_root=str(self.project_path),
|
||||
project_config=config,
|
||||
@@ -237,7 +237,7 @@ class TestGlobalIgnoredPathsWithGitignore:
|
||||
ignored_paths=["build"],
|
||||
ignore_all_files_in_gitignore=True,
|
||||
)
|
||||
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"])
|
||||
serena_config = SerenaConfig(ignored_paths=["node_modules"]).with_headless_mode_overrides()
|
||||
project = Project(
|
||||
project_root=str(self.project_path),
|
||||
project_config=config,
|
||||
@@ -258,14 +258,12 @@ class TestSerenaConfigIgnoredPaths:
|
||||
|
||||
def test_serena_config_default_ignored_paths(self) -> None:
|
||||
"""SerenaConfig defaults to empty ignored_paths."""
|
||||
config = SerenaConfig(gui_log_window=False, web_dashboard=False)
|
||||
config = SerenaConfig().with_headless_mode_overrides()
|
||||
assert config.ignored_paths == []
|
||||
|
||||
def test_serena_config_with_ignored_paths(self) -> None:
|
||||
"""SerenaConfig can be created with explicit ignored_paths."""
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
ignored_paths=["node_modules", "*.log", "build"],
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
assert config.ignored_paths == ["node_modules", "*.log", "build"]
|
||||
@@ -208,11 +208,9 @@ def _make_config_with_project(
|
||||
) -> tuple[SerenaConfig, str]:
|
||||
"""Create a SerenaConfig with a single registered project and return (config, project_name)."""
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
log_level=logging.ERROR,
|
||||
language_backend=global_backend,
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
project = Project(
|
||||
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
|
||||
project_config=ProjectConfig(
|
||||
@@ -252,11 +250,9 @@ class TestEffectiveLanguageBackend:
|
||||
def test_no_project_uses_global_backend(self):
|
||||
"""When no startup project is provided, effective backend is the global one."""
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
log_level=logging.ERROR,
|
||||
language_backend=LanguageBackend.LSP,
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
agent = SerenaAgent(project=None, serena_config=config)
|
||||
try:
|
||||
assert agent.get_language_backend() == LanguageBackend.LSP
|
||||
@@ -338,90 +334,68 @@ class TestGetConfiguredProjectSerenaFolder:
|
||||
"""Tests for SerenaConfig.get_configured_project_serena_folder (pure template resolution)."""
|
||||
|
||||
def test_default_location(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
)
|
||||
config = SerenaConfig().with_headless_mode_overrides()
|
||||
result = config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
assert result == os.path.abspath("/home/user/myproject/.serena")
|
||||
|
||||
def test_custom_location_with_project_folder_name(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="/projects-metadata/$projectFolderName/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
result = config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
assert result == os.path.abspath("/projects-metadata/myproject/.serena")
|
||||
|
||||
def test_custom_location_with_project_dir(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="$projectDir/.custom-serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
result = config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
assert result == os.path.abspath("/home/user/myproject/.custom-serena")
|
||||
|
||||
def test_custom_location_with_both_placeholders(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="/data/$projectFolderName/$projectDir/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
result = config.get_configured_project_serena_folder("/home/user/proj")
|
||||
assert result == os.path.abspath("/data/proj/home/user/proj/.serena")
|
||||
|
||||
def test_default_field_value(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
)
|
||||
config = SerenaConfig().with_headless_mode_overrides()
|
||||
assert config.project_serena_folder_location == DEFAULT_PROJECT_SERENA_FOLDER_LOCATION
|
||||
|
||||
def test_rejects_unknown_placeholder(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="$projectDir/$unknownVar/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
with pytest.raises(SerenaConfigError, match=r"Unknown placeholder '\$unknownVar'"):
|
||||
config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
|
||||
def test_rejects_typo_projectDirs(self):
|
||||
"""$projectDirs should not be silently treated as $projectDir + 's'."""
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="$projectDirs/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
with pytest.raises(SerenaConfigError, match=r"Unknown placeholder '\$projectDirs'"):
|
||||
config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
|
||||
def test_rejects_typo_projectfoldername_lowercase(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="/data/$projectfoldername/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
with pytest.raises(SerenaConfigError, match=r"Unknown placeholder '\$projectfoldername'"):
|
||||
config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
|
||||
def test_no_placeholders_is_valid(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="/fixed/path/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
result = config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
assert result == os.path.abspath("/fixed/path/.serena")
|
||||
|
||||
def test_error_message_lists_supported_placeholders(self):
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="$bogus/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
with pytest.raises(SerenaConfigError, match=r"\$projectDir.*\$projectFolderName|\$projectFolderName.*\$projectDir"):
|
||||
config.get_configured_project_serena_folder("/home/user/myproject")
|
||||
|
||||
@@ -452,7 +426,7 @@ class TestProjectSerenaDataFolder:
|
||||
return project
|
||||
|
||||
def test_default_config_creates_in_project_dir(self):
|
||||
config = SerenaConfig(gui_log_window=False, web_dashboard=False)
|
||||
config = SerenaConfig().with_headless_mode_overrides()
|
||||
project = self._make_project(config)
|
||||
expected = os.path.abspath(str(self.project_path / SERENA_MANAGED_DIR_NAME))
|
||||
assert project.path_to_serena_data_folder() == expected
|
||||
@@ -461,10 +435,8 @@ class TestProjectSerenaDataFolder:
|
||||
custom_base = Path(self.test_dir) / "metadata"
|
||||
custom_base.mkdir()
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location=str(custom_base) + "/$projectFolderName/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
project = self._make_project(config)
|
||||
expected = os.path.abspath(str(custom_base / "myproject" / ".serena"))
|
||||
assert project.path_to_serena_data_folder() == expected
|
||||
@@ -474,10 +446,8 @@ class TestProjectSerenaDataFolder:
|
||||
existing_serena = self.project_path / SERENA_MANAGED_DIR_NAME
|
||||
existing_serena.mkdir()
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location="/nonexistent/path/$projectFolderName/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
project = self._make_project(config)
|
||||
assert project.path_to_serena_data_folder() == str(existing_serena)
|
||||
|
||||
@@ -491,10 +461,8 @@ class TestProjectSerenaDataFolder:
|
||||
custom_serena.mkdir(parents=True)
|
||||
|
||||
config = SerenaConfig(
|
||||
gui_log_window=False,
|
||||
web_dashboard=False,
|
||||
project_serena_folder_location=str(custom_base) + "/$projectFolderName/.serena",
|
||||
)
|
||||
).with_headless_mode_overrides()
|
||||
project = self._make_project(config)
|
||||
assert project.path_to_serena_data_folder() == str(custom_serena)
|
||||
|
||||
|
||||
@@ -758,7 +758,7 @@ SAFE_DELETE_SUCCEEDS_CASES = [
|
||||
|
||||
@pytest.fixture
|
||||
def serena_config():
|
||||
config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
|
||||
config = SerenaConfig(log_level=logging.ERROR).with_headless_mode_overrides()
|
||||
|
||||
# Create test projects for all supported languages
|
||||
test_projects = []
|
||||
@@ -867,7 +867,7 @@ class TestSerenaAgent:
|
||||
* an invalid project path is specified at startup
|
||||
All cases must not raise an exception.
|
||||
"""
|
||||
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False)
|
||||
serena_config = SerenaConfig().with_headless_mode_overrides()
|
||||
SerenaAgent(project=project, serena_config=serena_config)
|
||||
|
||||
def _symbol_matches_expected_name(self, symbol: dict, expected_name: str) -> bool:
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_all_tool_parameters_have_type(context):
|
||||
For every tool exposed by Serena, ensure that the generated
|
||||
Open‑AI schema contains a ``type`` entry for each parameter.
|
||||
"""
|
||||
cfg = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
|
||||
cfg = SerenaConfig(log_level=logging.ERROR).with_headless_mode_overrides()
|
||||
registry = ToolRegistry()
|
||||
cfg.included_optional_tools = tuple(registry.get_tool_names_optional())
|
||||
factory = SerenaMCPFactory(transport="stdio", context=context)
|
||||
|
||||
Reference in new issue
Block a user