diff --git a/CHANGELOG.md b/CHANGELOG.md index 0265b3c1..aea6203e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,9 +29,12 @@ Status of the `main` branch. Changes prior to the next official version change w - Serena's system prompt (a.k.a. the 'Serena Instructions Manual') is now provided lazily. At MCP connection time, only a one-sentence bootstrap prompt is provided. The `initial_instructions` tool provides the full prompt on demand, keeping the initial context lean. + - Add `serena_info` tool for on-demand retrieval of usage information * JetBrains: - - `Move` and `SafeDelete` tools: transform empty string to None (counteracts client errors) + - Add `debug` tool: The agent can set breakpoints, inspect variables, evaluate expressions and control execution flow + by directly interacting with the IDE's debugger, using a REPL-style interface for maximum flexibility. + - `move` and `safe_delete` tools: transform empty string to None (counteracts client errors) * Dependencies: - `pywebview`: Switch back to official release (new version 6.2) #1253 diff --git a/README.md b/README.md index 7ee19813..0c819fb4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@
-* Serena provides essential **semantic code retrieval, editing and refactoring tools** that are akin to an IDE's capabilities, +* Serena provides essential **semantic code retrieval, editing, refactoring and debugging tools** that are akin to an IDE's capabilities, operating at the symbol level and exploiting relational structure. * It integrates with any client/LLM via the model context protocol (**MCP**). @@ -171,6 +171,12 @@ Serena's symbolic editing tools are less error-prone and much more token-efficie | insert before symbol | yes | yes | | safe delete | yes | yes | +### Interactive Debugging + +Exclusive to the JetBrains plugin, Serena supports a highly general debugging tool, +which allows an agent to set breakpoints, inspect variables, evaluate expressions and control execution flow +via a persistent REPL-style interface. + ### Basic Features Beyond its semantic capabilities, Serena includes a set of basic utilities for completeness. diff --git a/docs/02-usage/025_jetbrains_plugin.md b/docs/02-usage/025_jetbrains_plugin.md index 1995ca34..4eb08688 100644 --- a/docs/02-usage/025_jetbrains_plugin.md +++ b/docs/02-usage/025_jetbrains_plugin.md @@ -43,6 +43,8 @@ There are multiple features that are only available when using the JetBrains plu * **Enhanced retrieval & refactoring capabilities**: The plugin adds additional [tools](../01-about/035_tools) (e.g. type hierarchy retrieval, move, find declaration, inline symbol, etc.) and transforms the underlying mechanisms of shared tools to build upon the IDE's capabilities. +* **Interactive debugging**: The agent can set breakpoints, inspect variables, evaluate expressions and control execution flow + by directly interacting with the IDE's debugger, using a REPL-style interface for maximum flexibility. * **Improved multi-agent support**: A single IDE instance naturally serves arbitrarily many agent sessions without requiring additional resources. * **Enhanced performance**: Faster tool execution thanks to optimized IDE integration. * **Multi-language excellence** and **framework support**: First-class support for polyglot projects with multiple languages. diff --git a/src/serena/generated/generated_prompt_factory.py b/src/serena/generated/generated_prompt_factory.py index 58070fd2..43f866d7 100644 --- a/src/serena/generated/generated_prompt_factory.py +++ b/src/serena/generated/generated_prompt_factory.py @@ -14,6 +14,9 @@ class PromptFactory(PromptFactoryBase): A class for retrieving and rendering prompt templates and prompt lists. """ + def create_info_jet_brains_debug_repl(self) -> str: + return self._render_prompt("info_jet_brains_debug_repl", locals()) + def create_onboarding_prompt(self, *, system: Any) -> str: return self._render_prompt("onboarding_prompt", locals()) diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index d65925ea..263bc8a9 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -633,6 +633,34 @@ class JetBrainsPluginClient(ToStringMixin): self._postprocess_symbol_collection_response(symbol_collection) return symbol_collection + def debug_eval(self, repl_key: str, expression: str) -> dict[str, Any]: + """ + Evaluates a Groovy expression in the persistent debug REPL. + + :param repl_key: the session key identifying the REPL instance + :param expression: the Groovy expression to evaluate + :return: the response containing REPL key and result + """ + self._require_version_at_least(2023, 2, 16) + request_data = { + "replKey": repl_key, + "expression": expression, + } + return self._make_request("POST", "/debugReplEval", request_data) + + def debug_close(self, repl_key: str) -> dict[str, Any]: + """ + Closes the debug REPL for the given session key, clearing all state. + + :param repl_key: the key identifying the REPL instance to close + :return: the status response + """ + self._require_version_at_least(2023, 2, 16) + request_data = { + "replKey": repl_key, + } + return self._make_request("POST", "/debugReplClose", request_data) + def close(self) -> None: self._session.close() diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index a08b189f..34baf6ce 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -23,3 +23,5 @@ included_optional_tools: - jet_brains_type_hierarchy - jet_brains_rename - jet_brains_safe_delete + - jet_brains_debug + - serena_info diff --git a/src/serena/resources/config/prompt_templates/info_prompts.yml b/src/serena/resources/config/prompt_templates/info_prompts.yml new file mode 100644 index 00000000..4c896429 --- /dev/null +++ b/src/serena/resources/config/prompt_templates/info_prompts.yml @@ -0,0 +1,64 @@ +# Defines prompts the SerenaInfoTool +prompts: + info_jet_brains_debug_repl: | + Note: The debugger uses 0-based line numbers like other Serena tools (while humans typically communicate 1-based ones). + You rely on the user to provide suitable run configurations for you to work with. + + State (objects, variables assigned) persists across calls within the same repl_key. + + Pre-bound variables: ``debug`` (debugger interface) + + Run configurations: + debug.listRunConfigs() + session = debug.startDebug("MyTestRun") # returns a DebugSession + debug.startRun("MyApp") # non-debug run, fire-and-forget + + Session discovery: + debug.sessions() + session = debug.currentSession() + session = debug.session(id) + + Breakpoints (project-global, set before or during debugging): + debug.addBreakpoint(file, line) + debug.addBreakpoint(file, line, condition) + debug.addLogpoint(file, line, logExpression) + debug.listBreakpoints() + debug.removeBreakpointAt(file, line) + + Execution control (on a DebugSession, e.g. ``session``):: + session.stepOver() | .stepInto() | .stepOut() + session.resume() | .pause() | .stop() + session.runToLine(file, line) + + Wait for pause (blocks until the session pauses or times out): + session.waitForPause(timeoutSeconds) + session.stepOverAndWait(timeoutSeconds) # step + wait in one call + session.resumeAndWait(timeoutSeconds) + + Inspection (when paused): + session.status() # location + source + variable values in one call + session.variables() # overview of locals/args (values may be truncated) + session.variable(name) # single variable (VariableInfo instance) + session.frames() | .frame() # stack trace | current frame + session.sourceContext(lines) # source around current line + session.threads() + + VariableInfo members: name, value (String as presented by the debugger), type + + Evaluation & modification: + session.eval("expr") # evaluate in current frame + session.setVar("x", "42") # set variable via assignment + + Example sequence of expressions one might execute in a REPL: + debug.addBreakpoint("src/main/java/Foo.java", 42) + session = debug.startDebug("MyTest") + session.waitForPause(30) + session.status() + + You can pass multiple statements at once; the result of the final expression is returned: + session = debug.startDebug("MyTest"); session.waitForPause(30); session.status() + + Control flow statements are also possible: + while (session.variable("count").value.equals("0")) { session.stepOverAndWait(5) } + + When done, close the REPL by passing an empty expression. diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 00d0adbd..0664fb03 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -565,3 +565,33 @@ class JetBrainsRenameTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): rename_in_text_occurrences=rename_in_text_occurrences, ) return self._to_json(result) + + +class JetBrainsDebugTool(Tool, ToolMarkerOptional, ToolMarkerBeta): + """ + Provides debugging functionality (run configs, breakpoints, stepping, inspection, and evaluation) + via a persistent debug REPL connected to the JetBrains IDE. + """ + + def apply( + self, + expression: str, + repl_key: str = "default", + ) -> str: + """ + Debug code interactively by evaluating Groovy expressions in a persistent REPL. + Important: Debugging should only be applied if the user has requested it! + + Use the `serena_info` tool with topic `jet_brains_debug_repl` for usage information. + + :param expression: a Groovy/Java expression/statement to evaluate in the REPL. + If empty/null, closes the REPL with the given key. + :param repl_key: identifier for the REPL instance. State persists across calls with the same key. + :return: string representation of the result + """ + with JetBrainsPluginClient.from_project(self.project) as client: + if expression: + response = client.debug_eval(repl_key=repl_key, expression=expression) + else: + response = client.debug_close(repl_key=repl_key) + return response.get("result", str(response)) diff --git a/src/serena/tools/workflow_tools.py b/src/serena/tools/workflow_tools.py index e50ec9b5..461da9ea 100644 --- a/src/serena/tools/workflow_tools.py +++ b/src/serena/tools/workflow_tools.py @@ -4,7 +4,7 @@ Tools supporting the general workflow of the agent import platform -from serena.tools import ReadMemoryTool, Tool, ToolMarkerDoesNotRequireActiveProject, WriteMemoryTool +from serena.tools import ReadMemoryTool, Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional, WriteMemoryTool class CheckOnboardingPerformedTool(Tool): @@ -72,3 +72,20 @@ class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject): as it will critically inform you! """ return self.agent.create_system_prompt(session_id=session_id) + + +class SerenaInfoTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveProject): + """ + Provides information about an advanced topic on demand, facilitating context-efficiency. + """ + + def apply(self, topic: str) -> str: + """ + Retrieves Serena-specific information + :param topic: the topic, which you must have been given explicitly + """ + match topic: + case "jet_brains_debug_repl": + return self.agent.prompt_factory.create_info_jet_brains_debug_repl() + case _: + raise ValueError("Invalid topic: " + topic)