From 4b9ce9ad4460ee7ec83de4e7389674e2967a9585 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Sep 2026 12:17:03 +0200 Subject: [PATCH] Place the REPL's facade listing in the tool description or the activation message * single-project sessions: in the REPL tool's description (fixed at registration) * otherwise: in the project activation message (facades depend on the activated project) * REPL tool description wording adjusted (listing location, notebook semantics) Peripheral changes: * is_tool_function_available: in REPL mode, active tools without facade counterparts count as available --- src/serena/agent.py | 12 +++++++++--- src/serena/tools/repl_tools.py | 20 ++++++++++++-------- test/serena/test_mcp.py | 4 ++++ test/serena/test_serena_agent.py | 7 +++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index f5a466f4..7e4bc490 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -1251,10 +1251,15 @@ class SerenaAgent: msg += self._render_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) + "\n" self._project_prompt_status.mark_mode_prompts_as_provided(session_id) - # add project-specific prompt + # add the project's prompt (if any) if proj.project_config.initial_prompt: msg += "\n" + self._render_prompt(proj.project_config.initial_prompt, tag="project-instructions") + # when the REPL is active, add information on available facades if the agent is not in single-project mode + # (for single-project mode where the facades can't change, they are provided in the tool's description) + if self._active_tools.contains_tool_class(SerenaReplTool) and not self.is_single_project(): + msg += f"\n\nAvailable facades for the `{SerenaReplTool.get_name_from_cls()}` tool:\n" + self.get_repl().entrypoint.overview() + self._project_prompt_status.mark_project_activation_message_as_provided(session_id) return msg @@ -1571,10 +1576,11 @@ class SerenaAgent: :param tool_class: the tool class :return: whether the function is available """ + is_active_tool = self._active_tools.contains_tool_class(tool_class) if self._agent_interface == AgentInterface.TOOLS: - return self._active_tools.contains_tool_class(tool_class) + return is_active_tool elif self._agent_interface == AgentInterface.REPL: - return self.get_repl().entrypoint.is_tool_function_available(tool_class) + return is_active_tool or self.get_repl().entrypoint.is_tool_function_available(tool_class) else: raise NotImplementedError diff --git a/src/serena/tools/repl_tools.py b/src/serena/tools/repl_tools.py index 04b6895d..8fe15c2d 100644 --- a/src/serena/tools/repl_tools.py +++ b/src/serena/tools/repl_tools.py @@ -13,25 +13,29 @@ class SerenaReplTool(Tool, ToolMarkerOptional, ToolMarkerBeta): """ def get_apply_docstring(self) -> str: - return self.get_apply_docstring_from_cls() + "\n\nAvailable facades:\n" + self.agent.get_repl().entrypoint.overview() + docs = self.get_apply_docstring_from_cls() + if self.agent.is_single_project(): + docs += "\n\nAvailable facades:\n" + self.agent.get_repl().entrypoint.overview() + else: + docs += "\n\nAvailable facades are provided at project activation" + return docs def apply(self, session: str, code: str) -> str: """ Executes the given Python code, which has access to Serena's functionality through the object `s`. - The functionality is organised in facades, which are attributes of `s` (e.g. `s.myfacade`); the available - facades and their methods are listed below. + The functionality is organised in facades, which are attributes of `s` (e.g. `s.myfacade`). Documentation: Use `s.info("")` when you will use a facade's functionality (it documents all common operations at once) and `s.info(".")` for a single or a rarely needed operation. Several items can be requested in one call, e.g. `s.info("lsp", "edit.replace_content")`. - `s.info("")` documents the facade's operations only, not their result types. Result types are given - in the method listing below (`method -> Type`); request their documentation via `s.info("")`, which + `s.info("")` documents the facade's operations only, not their result types. The facade listing + provides result types (`method -> Type`); request their documentation via `s.info("")`, which includes the types they contain, ONLY if you intend to process results in code (filter, aggregate, chain - calls). If you simply want the result, make it the last expression: results are rendered for you. + calls). The code is executed like a notebook cell: if its last statement is an expression, the expression's value is - the result (do not use `return`). Results are rendered in a form suitable for you; lists are rendered - element-wise, strings are passed through unchanged. + the result. Results are rendered in a form suitable for you; lists are rendered element-wise, + strings are passed through unchanged. Output size: methods with a `max_answer_chars` parameter limit the size of the rendered result (-1 uses the configured default). If the limit is exceeded, a shortened result (or no content) is rendered instead; diff --git a/test/serena/test_mcp.py b/test/serena/test_mcp.py index 66119e8a..1da10379 100644 --- a/test/serena/test_mcp.py +++ b/test/serena/test_mcp.py @@ -29,6 +29,10 @@ class MockAgent: def get_repl() -> SerenaRepl: return SerenaRepl([], ApiScope()) + @staticmethod + def is_single_project() -> bool: + return False + class BaseMockTool(Tool): """A mock Tool class for testing.""" diff --git a/test/serena/test_serena_agent.py b/test/serena/test_serena_agent.py index dece1f1a..625bd5a2 100644 --- a/test/serena/test_serena_agent.py +++ b/test/serena/test_serena_agent.py @@ -919,6 +919,13 @@ class TestSerenaAgent: assert exposed == expected assert "s.lsp" in agent.get_tool(SerenaReplTool).apply(agent.create_session().session_id, "s.info()") + # the facade listing is part of the (fixed) tool description in single-project sessions, + # and of the activation message otherwise (where the facades depend on the activated project) + tool_description = agent.get_tool(SerenaReplTool).get_apply_docstring() + activation_message = agent.get_project_activation_message("test_session") + assert ("s.lsp:" in tool_description) == context.single_project + assert ("s.lsp:" in activation_message) == (not context.single_project) + # prompts refer to operations by their qualified REPL names, e.g. `lsp.find_symbol` instead of the tool name system_prompt = agent.create_system_prompt() assert "`lsp.find_symbol`" in system_prompt