diff --git a/src/serena/agent.py b/src/serena/agent.py index 91431192..1f6c09ae 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -2,6 +2,7 @@ The Serena Model Context Protocol (MCP) Server """ +import json import multiprocessing import os import platform @@ -35,7 +36,16 @@ from serena.ls_manager import LanguageServerManager from serena.project import MemoriesManager, Project from serena.prompt_factory import SerenaPromptFactory from serena.task_executor import TaskExecutor -from serena.tools import ActivateProjectTool, GetCurrentConfigTool, OpenDashboardTool, ReplaceContentTool, Tool, ToolMarker, ToolRegistry +from serena.tools import ( + ActivateProjectTool, + GetCurrentConfigTool, + OpenDashboardTool, + ReadMemoryTool, + ReplaceContentTool, + Tool, + ToolMarker, + ToolRegistry, +) from serena.util.gui import system_has_usable_display from serena.util.inspection import iter_subclasses from serena.util.logging import MemoryLogHandler @@ -81,6 +91,9 @@ class AvailableTools: def contains_tool_name(self, tool_name: str) -> bool: return tool_name in self._tool_name_set + def contains_tool_class(self, tool_class: type[Tool]) -> bool: + return self.contains_tool_name(tool_class.get_name_from_cls()) + class ToolSet: """ @@ -579,7 +592,7 @@ class SerenaAgent: stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - start_new_session=True, # Detach from parent process + start_new_session=False, ) def get_exposed_tool_instances(self) -> list["Tool"]: @@ -648,11 +661,39 @@ class SerenaAgent: # If a project is active at startup, append its activation message if self._active_project is not None: - system_prompt += "\n\n" + self._active_project.get_activation_message() + system_prompt += "\n\n" + self.get_project_activation_message() log.info("System prompt:\n%s", system_prompt) return system_prompt + def get_project_activation_message(self) -> str: + """ + :return: a message providing information about the project upon activation (e.g. programming language, memories, initial prompt) + :raise: AssertionError if no project is active + """ + proj = self._active_project + assert proj is not None, "A project must be active before calling this." + if proj.is_newly_created: + msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}. " + else: + msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated." + if self._language_backend == LanguageBackend.LSP: + languages_str = ", ".join([lang.value for lang in proj.project_config.languages]) + msg += f"\nProgramming languages: {languages_str}." + msg += "File encoding: {proj.project_config.encoding}." + + include_memories = self._active_tools.contains_tool_class(ReadMemoryTool) + if include_memories: + project_memories = proj.memories_manager.list_project_memories() + if project_memories: + msg += ( + f"\n{json.dumps(project_memories.to_dict())}\n" + + "Use the `read_memory` tool to read these memories later if they are relevant to the task." + ) + if proj.project_config.initial_prompt: + msg += f"\nAdditional project-specific instructions:\n {proj.project_config.initial_prompt}" + return msg + def _update_active_modes(self) -> None: """ Updates the active modes based on the Serena configuration, the active project configuration (if any), diff --git a/src/serena/project.py b/src/serena/project.py index 5995c3e7..a20623e3 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -1,4 +1,3 @@ -import json import logging import os import re @@ -291,7 +290,7 @@ class Project(ToStringMixin): self.language_server_manager: LanguageServerManager | None = None self._language_server_manager_init_error: Exception | None = None - self._is_newly_created = is_newly_created + self.is_newly_created = is_newly_created self._agent: Optional["SerenaAgent"] = None # create .gitignore file in the project's Serena data folder if not yet present @@ -379,26 +378,6 @@ class Project(ToStringMixin): def path_to_project_yml(self) -> str: return self.serena_config.get_project_yml_location(self.project_root) - def get_activation_message(self) -> str: - """ - :return: a message providing information about the project upon activation (e.g. programming language, memories, initial prompt) - """ - if self._is_newly_created: - msg = f"Created and activated a new project with name '{self.project_name}' at {self.project_root}. " - else: - msg = f"The project with name '{self.project_name}' at {self.project_root} is activated." - languages_str = ", ".join([lang.value for lang in self.project_config.languages]) - msg += f"\nProgramming languages: {languages_str}; file encoding: {self.project_config.encoding}" - project_memories = self.memories_manager.list_project_memories() - if project_memories: - msg += ( - f"\nAvailable project memories: {json.dumps(project_memories.to_dict())}\n" - + "Use the `read_memory` tool to read these memories later if they are relevant to the task." - ) - if self.project_config.initial_prompt: - msg += f"\nAdditional project-specific instructions:\n {self.project_config.initial_prompt}" - return msg - def read_file(self, relative_path: str) -> str: """ Reads a file relative to the project root. diff --git a/src/serena/tools/config_tools.py b/src/serena/tools/config_tools.py index 7a0472cf..bdab4706 100644 --- a/src/serena/tools/config_tools.py +++ b/src/serena/tools/config_tools.py @@ -28,8 +28,8 @@ class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject): :param project: the name of a registered project to activate or a path to a project directory """ - active_project = self.agent.activate_project_from_path_or_name(project) - result = active_project.get_activation_message() + self.agent.activate_project_from_path_or_name(project) + result = self.agent.get_project_activation_message() result += "\nIMPORTANT: If you have not yet read the 'Serena Instructions Manual', do it now before continuing!" return result