Skip to content

Commit 7a8b5fb

Browse files
authored
Merge pull request #1403 from oraios/jetbrains-debugger
Add JetBrainsDebugTool
2 parents fc27a52 + 1fd0fe2 commit 7a8b5fb

9 files changed

Lines changed: 158 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ Status of the `main` branch. Changes prior to the next official version change w
2929
- Serena's system prompt (a.k.a. the 'Serena Instructions Manual') is now provided lazily.
3030
At MCP connection time, only a one-sentence bootstrap prompt is provided.
3131
The `initial_instructions` tool provides the full prompt on demand, keeping the initial context lean.
32+
- Add `serena_info` tool for on-demand retrieval of usage information
3233

3334
* JetBrains:
34-
- `Move` and `SafeDelete` tools: transform empty string to None (counteracts client errors)
35+
- Add `debug` tool: The agent can set breakpoints, inspect variables, evaluate expressions and control execution flow
36+
by directly interacting with the IDE's debugger, using a REPL-style interface for maximum flexibility.
37+
- `move` and `safe_delete` tools: transform empty string to None (counteracts client errors)
3538

3639
* Dependencies:
3740
- `pywebview`: Switch back to official release (new version 6.2) #1253

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<br>
1515

1616

17-
* Serena provides essential **semantic code retrieval, editing and refactoring tools** that are akin to an IDE's capabilities,
17+
* Serena provides essential **semantic code retrieval, editing, refactoring and debugging tools** that are akin to an IDE's capabilities,
1818
operating at the symbol level and exploiting relational structure.
1919
* It integrates with any client/LLM via the model context protocol (**MCP**).
2020

@@ -171,6 +171,12 @@ Serena's symbolic editing tools are less error-prone and much more token-efficie
171171
| insert before symbol | yes | yes |
172172
| safe delete | yes | yes |
173173

174+
### Interactive Debugging
175+
176+
Exclusive to the JetBrains plugin, Serena supports a highly general debugging tool,
177+
which allows an agent to set breakpoints, inspect variables, evaluate expressions and control execution flow
178+
via a persistent REPL-style interface.
179+
174180
### Basic Features
175181

176182
Beyond its semantic capabilities, Serena includes a set of basic utilities for completeness.

docs/02-usage/025_jetbrains_plugin.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ There are multiple features that are only available when using the JetBrains plu
4343
* **Enhanced retrieval & refactoring capabilities**: The plugin adds additional [tools](../01-about/035_tools) (e.g. type
4444
hierarchy retrieval, move, find declaration, inline symbol, etc.)
4545
and transforms the underlying mechanisms of shared tools to build upon the IDE's capabilities.
46+
* **Interactive debugging**: The agent can set breakpoints, inspect variables, evaluate expressions and control execution flow
47+
by directly interacting with the IDE's debugger, using a REPL-style interface for maximum flexibility.
4648
* **Improved multi-agent support**: A single IDE instance naturally serves arbitrarily many agent sessions without requiring additional resources.
4749
* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration.
4850
* **Multi-language excellence** and **framework support**: First-class support for polyglot projects with multiple languages.

src/serena/generated/generated_prompt_factory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class PromptFactory(PromptFactoryBase):
1414
A class for retrieving and rendering prompt templates and prompt lists.
1515
"""
1616

17+
def create_info_jet_brains_debug_repl(self) -> str:
18+
return self._render_prompt("info_jet_brains_debug_repl", locals())
19+
1720
def create_onboarding_prompt(self, *, system: Any) -> str:
1821
return self._render_prompt("onboarding_prompt", locals())
1922

src/serena/jetbrains/jetbrains_plugin_client.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,34 @@ def find_implementations(self, relative_path: str, name_path: str, include_quick
633633
self._postprocess_symbol_collection_response(symbol_collection)
634634
return symbol_collection
635635

636+
def debug_eval(self, repl_key: str, expression: str) -> dict[str, Any]:
637+
"""
638+
Evaluates a Groovy expression in the persistent debug REPL.
639+
640+
:param repl_key: the session key identifying the REPL instance
641+
:param expression: the Groovy expression to evaluate
642+
:return: the response containing REPL key and result
643+
"""
644+
self._require_version_at_least(2023, 2, 16)
645+
request_data = {
646+
"replKey": repl_key,
647+
"expression": expression,
648+
}
649+
return self._make_request("POST", "/debugReplEval", request_data)
650+
651+
def debug_close(self, repl_key: str) -> dict[str, Any]:
652+
"""
653+
Closes the debug REPL for the given session key, clearing all state.
654+
655+
:param repl_key: the key identifying the REPL instance to close
656+
:return: the status response
657+
"""
658+
self._require_version_at_least(2023, 2, 16)
659+
request_data = {
660+
"replKey": repl_key,
661+
}
662+
return self._make_request("POST", "/debugReplClose", request_data)
663+
636664
def close(self) -> None:
637665
self._session.close()
638666

src/serena/resources/config/internal_modes/jetbrains.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ included_optional_tools:
2323
- jet_brains_type_hierarchy
2424
- jet_brains_rename
2525
- jet_brains_safe_delete
26+
- jet_brains_debug
27+
- serena_info
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Defines prompts the SerenaInfoTool
2+
prompts:
3+
info_jet_brains_debug_repl: |
4+
Note: The debugger uses 0-based line numbers like other Serena tools (while humans typically communicate 1-based ones).
5+
You rely on the user to provide suitable run configurations for you to work with.
6+
7+
State (objects, variables assigned) persists across calls within the same repl_key.
8+
9+
Pre-bound variables: ``debug`` (debugger interface)
10+
11+
Run configurations:
12+
debug.listRunConfigs()
13+
session = debug.startDebug("MyTestRun") # returns a DebugSession
14+
debug.startRun("MyApp") # non-debug run, fire-and-forget
15+
16+
Session discovery:
17+
debug.sessions()
18+
session = debug.currentSession()
19+
session = debug.session(id)
20+
21+
Breakpoints (project-global, set before or during debugging):
22+
debug.addBreakpoint(file, line)
23+
debug.addBreakpoint(file, line, condition)
24+
debug.addLogpoint(file, line, logExpression)
25+
debug.listBreakpoints()
26+
debug.removeBreakpointAt(file, line)
27+
28+
Execution control (on a DebugSession, e.g. ``session``)::
29+
session.stepOver() | .stepInto() | .stepOut()
30+
session.resume() | .pause() | .stop()
31+
session.runToLine(file, line)
32+
33+
Wait for pause (blocks until the session pauses or times out):
34+
session.waitForPause(timeoutSeconds)
35+
session.stepOverAndWait(timeoutSeconds) # step + wait in one call
36+
session.resumeAndWait(timeoutSeconds)
37+
38+
Inspection (when paused):
39+
session.status() # location + source + variable values in one call
40+
session.variables() # overview of locals/args (values may be truncated)
41+
session.variable(name) # single variable (VariableInfo instance)
42+
session.frames() | .frame() # stack trace | current frame
43+
session.sourceContext(lines) # source around current line
44+
session.threads()
45+
46+
VariableInfo members: name, value (String as presented by the debugger), type
47+
48+
Evaluation & modification:
49+
session.eval("expr") # evaluate in current frame
50+
session.setVar("x", "42") # set variable via assignment
51+
52+
Example sequence of expressions one might execute in a REPL:
53+
debug.addBreakpoint("src/main/java/Foo.java", 42)
54+
session = debug.startDebug("MyTest")
55+
session.waitForPause(30)
56+
session.status()
57+
58+
You can pass multiple statements at once; the result of the final expression is returned:
59+
session = debug.startDebug("MyTest"); session.waitForPause(30); session.status()
60+
61+
Control flow statements are also possible:
62+
while (session.variable("count").value.equals("0")) { session.stepOverAndWait(5) }
63+
64+
When done, close the REPL by passing an empty expression.

src/serena/tools/jetbrains_tools.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,33 @@ def apply(
565565
rename_in_text_occurrences=rename_in_text_occurrences,
566566
)
567567
return self._to_json(result)
568+
569+
570+
class JetBrainsDebugTool(Tool, ToolMarkerOptional, ToolMarkerBeta):
571+
"""
572+
Provides debugging functionality (run configs, breakpoints, stepping, inspection, and evaluation)
573+
via a persistent debug REPL connected to the JetBrains IDE.
574+
"""
575+
576+
def apply(
577+
self,
578+
expression: str,
579+
repl_key: str = "default",
580+
) -> str:
581+
"""
582+
Debug code interactively by evaluating Groovy expressions in a persistent REPL.
583+
Important: Debugging should only be applied if the user has requested it!
584+
585+
Use the `serena_info` tool with topic `jet_brains_debug_repl` for usage information.
586+
587+
:param expression: a Groovy/Java expression/statement to evaluate in the REPL.
588+
If empty/null, closes the REPL with the given key.
589+
:param repl_key: identifier for the REPL instance. State persists across calls with the same key.
590+
:return: string representation of the result
591+
"""
592+
with JetBrainsPluginClient.from_project(self.project) as client:
593+
if expression:
594+
response = client.debug_eval(repl_key=repl_key, expression=expression)
595+
else:
596+
response = client.debug_close(repl_key=repl_key)
597+
return response.get("result", str(response))

src/serena/tools/workflow_tools.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import platform
66

7-
from serena.tools import ReadMemoryTool, Tool, ToolMarkerDoesNotRequireActiveProject, WriteMemoryTool
7+
from serena.tools import ReadMemoryTool, Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional, WriteMemoryTool
88

99

1010
class CheckOnboardingPerformedTool(Tool):
@@ -72,3 +72,20 @@ def apply(self, session_id: str) -> str:
7272
as it will critically inform you!
7373
"""
7474
return self.agent.create_system_prompt(session_id=session_id)
75+
76+
77+
class SerenaInfoTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveProject):
78+
"""
79+
Provides information about an advanced topic on demand, facilitating context-efficiency.
80+
"""
81+
82+
def apply(self, topic: str) -> str:
83+
"""
84+
Retrieves Serena-specific information
85+
:param topic: the topic, which you must have been given explicitly
86+
"""
87+
match topic:
88+
case "jet_brains_debug_repl":
89+
return self.agent.prompt_factory.create_info_jet_brains_debug_repl()
90+
case _:
91+
raise ValueError("Invalid topic: " + topic)

0 commit comments

Comments
 (0)