ripperdoc 0.2.8__py3-none-any.whl → 0.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ripperdoc/__init__.py +1 -1
- ripperdoc/cli/cli.py +28 -115
- ripperdoc/cli/commands/__init__.py +0 -1
- ripperdoc/cli/commands/agents_cmd.py +6 -3
- ripperdoc/cli/commands/clear_cmd.py +1 -4
- ripperdoc/cli/commands/config_cmd.py +1 -1
- ripperdoc/cli/commands/context_cmd.py +3 -2
- ripperdoc/cli/commands/doctor_cmd.py +18 -4
- ripperdoc/cli/commands/hooks_cmd.py +27 -53
- ripperdoc/cli/commands/models_cmd.py +26 -9
- ripperdoc/cli/commands/permissions_cmd.py +27 -9
- ripperdoc/cli/commands/resume_cmd.py +5 -3
- ripperdoc/cli/commands/status_cmd.py +4 -4
- ripperdoc/cli/commands/tasks_cmd.py +8 -4
- ripperdoc/cli/ui/file_mention_completer.py +2 -1
- ripperdoc/cli/ui/interrupt_handler.py +2 -3
- ripperdoc/cli/ui/message_display.py +4 -2
- ripperdoc/cli/ui/provider_options.py +247 -0
- ripperdoc/cli/ui/rich_ui.py +110 -59
- ripperdoc/cli/ui/spinner.py +25 -1
- ripperdoc/cli/ui/tool_renderers.py +8 -2
- ripperdoc/cli/ui/wizard.py +215 -0
- ripperdoc/core/agents.py +9 -3
- ripperdoc/core/config.py +49 -12
- ripperdoc/core/custom_commands.py +7 -6
- ripperdoc/core/default_tools.py +11 -2
- ripperdoc/core/hooks/config.py +1 -3
- ripperdoc/core/hooks/events.py +23 -28
- ripperdoc/core/hooks/executor.py +4 -6
- ripperdoc/core/hooks/integration.py +12 -21
- ripperdoc/core/hooks/manager.py +40 -15
- ripperdoc/core/permissions.py +40 -8
- ripperdoc/core/providers/anthropic.py +109 -36
- ripperdoc/core/providers/gemini.py +70 -5
- ripperdoc/core/providers/openai.py +60 -5
- ripperdoc/core/query.py +82 -38
- ripperdoc/core/query_utils.py +2 -0
- ripperdoc/core/skills.py +9 -3
- ripperdoc/core/system_prompt.py +4 -2
- ripperdoc/core/tool.py +9 -5
- ripperdoc/sdk/client.py +2 -2
- ripperdoc/tools/ask_user_question_tool.py +5 -3
- ripperdoc/tools/background_shell.py +2 -1
- ripperdoc/tools/bash_output_tool.py +1 -1
- ripperdoc/tools/bash_tool.py +26 -16
- ripperdoc/tools/dynamic_mcp_tool.py +29 -8
- ripperdoc/tools/enter_plan_mode_tool.py +1 -1
- ripperdoc/tools/exit_plan_mode_tool.py +1 -1
- ripperdoc/tools/file_edit_tool.py +8 -4
- ripperdoc/tools/file_read_tool.py +8 -4
- ripperdoc/tools/file_write_tool.py +9 -5
- ripperdoc/tools/glob_tool.py +3 -2
- ripperdoc/tools/grep_tool.py +3 -2
- ripperdoc/tools/kill_bash_tool.py +1 -1
- ripperdoc/tools/ls_tool.py +1 -1
- ripperdoc/tools/mcp_tools.py +13 -10
- ripperdoc/tools/multi_edit_tool.py +8 -7
- ripperdoc/tools/notebook_edit_tool.py +7 -4
- ripperdoc/tools/skill_tool.py +1 -1
- ripperdoc/tools/task_tool.py +5 -4
- ripperdoc/tools/todo_tool.py +2 -2
- ripperdoc/tools/tool_search_tool.py +3 -2
- ripperdoc/utils/conversation_compaction.py +8 -4
- ripperdoc/utils/file_watch.py +8 -2
- ripperdoc/utils/json_utils.py +2 -1
- ripperdoc/utils/mcp.py +11 -3
- ripperdoc/utils/memory.py +4 -2
- ripperdoc/utils/message_compaction.py +21 -7
- ripperdoc/utils/message_formatting.py +11 -7
- ripperdoc/utils/messages.py +105 -66
- ripperdoc/utils/path_ignore.py +35 -8
- ripperdoc/utils/permissions/path_validation_utils.py +2 -1
- ripperdoc/utils/permissions/shell_command_validation.py +427 -91
- ripperdoc/utils/safe_get_cwd.py +2 -1
- ripperdoc/utils/session_history.py +13 -6
- ripperdoc/utils/todo.py +2 -1
- ripperdoc/utils/token_estimation.py +6 -1
- {ripperdoc-0.2.8.dist-info → ripperdoc-0.2.9.dist-info}/METADATA +1 -1
- ripperdoc-0.2.9.dist-info/RECORD +123 -0
- ripperdoc-0.2.8.dist-info/RECORD +0 -121
- {ripperdoc-0.2.8.dist-info → ripperdoc-0.2.9.dist-info}/WHEEL +0 -0
- {ripperdoc-0.2.8.dist-info → ripperdoc-0.2.9.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.2.8.dist-info → ripperdoc-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.2.8.dist-info → ripperdoc-0.2.9.dist-info}/top_level.txt +0 -0
ripperdoc/tools/bash_tool.py
CHANGED
|
@@ -150,7 +150,7 @@ build projects, run tests, and interact with the file system."""
|
|
|
150
150
|
),
|
|
151
151
|
]
|
|
152
152
|
|
|
153
|
-
async def prompt(self,
|
|
153
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
154
154
|
sandbox_available = is_sandbox_available()
|
|
155
155
|
try:
|
|
156
156
|
current_shell = find_suitable_shell()
|
|
@@ -398,6 +398,11 @@ build projects, run tests, and interact with the file system."""
|
|
|
398
398
|
|
|
399
399
|
validation = validate_shell_command(input_data.command)
|
|
400
400
|
if validation.behavior == "ask":
|
|
401
|
+
# In yolo mode, allow shell metacharacters
|
|
402
|
+
if context and hasattr(context, 'yolo_mode') and context.yolo_mode \
|
|
403
|
+
and "shell metacharacters" in validation.message:
|
|
404
|
+
# Allow commands with shell metacharacters in yolo mode
|
|
405
|
+
return ValidationResult(result=True)
|
|
401
406
|
return ValidationResult(result=False, message=validation.message)
|
|
402
407
|
|
|
403
408
|
return ValidationResult(result=True)
|
|
@@ -505,9 +510,7 @@ build projects, run tests, and interact with the file system."""
|
|
|
505
510
|
|
|
506
511
|
return command, False
|
|
507
512
|
|
|
508
|
-
def _create_error_output(
|
|
509
|
-
self, command: str, stderr: str, sandbox: bool
|
|
510
|
-
) -> BashToolOutput:
|
|
513
|
+
def _create_error_output(self, command: str, stderr: str, sandbox: bool) -> BashToolOutput:
|
|
511
514
|
"""Create a standardized error output."""
|
|
512
515
|
return BashToolOutput(
|
|
513
516
|
stdout="",
|
|
@@ -531,9 +534,13 @@ build projects, run tests, and interact with the file system."""
|
|
|
531
534
|
return command, None, None
|
|
532
535
|
|
|
533
536
|
if not is_sandbox_available():
|
|
534
|
-
return
|
|
535
|
-
|
|
536
|
-
|
|
537
|
+
return (
|
|
538
|
+
None,
|
|
539
|
+
self._create_error_output(
|
|
540
|
+
command, "Sandbox mode requested but not available on this system", True
|
|
541
|
+
),
|
|
542
|
+
None,
|
|
543
|
+
)
|
|
537
544
|
|
|
538
545
|
try:
|
|
539
546
|
wrapper = create_sandbox_wrapper(command)
|
|
@@ -541,12 +548,15 @@ build projects, run tests, and interact with the file system."""
|
|
|
541
548
|
except (OSError, RuntimeError, ValueError) as exc:
|
|
542
549
|
logger.warning(
|
|
543
550
|
"[bash_tool] Failed to enable sandbox: %s: %s",
|
|
544
|
-
type(exc).__name__,
|
|
551
|
+
type(exc).__name__,
|
|
552
|
+
exc,
|
|
545
553
|
extra={"command": command},
|
|
546
554
|
)
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
|
|
555
|
+
return (
|
|
556
|
+
None,
|
|
557
|
+
self._create_error_output(command, f"Failed to enable sandbox: {exc}", True),
|
|
558
|
+
None,
|
|
559
|
+
)
|
|
550
560
|
|
|
551
561
|
async def _run_background_command(
|
|
552
562
|
self,
|
|
@@ -570,7 +580,8 @@ build projects, run tests, and interact with the file system."""
|
|
|
570
580
|
# pragma: no cover - defensive import
|
|
571
581
|
logger.warning(
|
|
572
582
|
"[bash_tool] Failed to import background shell runner: %s: %s",
|
|
573
|
-
type(e).__name__,
|
|
583
|
+
type(e).__name__,
|
|
584
|
+
e,
|
|
574
585
|
extra={"command": effective_command},
|
|
575
586
|
)
|
|
576
587
|
return self._create_error_output(
|
|
@@ -696,9 +707,7 @@ build projects, run tests, and interact with the file system."""
|
|
|
696
707
|
# Store results in a way that the caller can access
|
|
697
708
|
self._last_execution_result = (stdout_lines, stderr_lines, timed_out)
|
|
698
709
|
|
|
699
|
-
async def _drain_stream(
|
|
700
|
-
self, stream: Optional[asyncio.StreamReader], sink: list[str]
|
|
701
|
-
) -> None:
|
|
710
|
+
async def _drain_stream(self, stream: Optional[asyncio.StreamReader], sink: list[str]) -> None:
|
|
702
711
|
"""Drain any remaining data from a stream."""
|
|
703
712
|
if not stream:
|
|
704
713
|
return
|
|
@@ -969,7 +978,8 @@ build projects, run tests, and interact with the file system."""
|
|
|
969
978
|
raise # Re-raise cancellation
|
|
970
979
|
logger.warning(
|
|
971
980
|
"[bash_tool] Error executing command: %s: %s",
|
|
972
|
-
type(e).__name__,
|
|
981
|
+
type(e).__name__,
|
|
982
|
+
e,
|
|
973
983
|
extra={"command": effective_command},
|
|
974
984
|
)
|
|
975
985
|
error_output = self._create_error_output(
|
|
@@ -74,7 +74,8 @@ def _annotation_flag(tool_info: Any, key: str) -> bool:
|
|
|
74
74
|
except (AttributeError, TypeError, KeyError) as exc:
|
|
75
75
|
logger.debug(
|
|
76
76
|
"[mcp_tools] Failed to read annotation flag: %s: %s",
|
|
77
|
-
type(exc).__name__,
|
|
77
|
+
type(exc).__name__,
|
|
78
|
+
exc,
|
|
78
79
|
)
|
|
79
80
|
return False
|
|
80
81
|
return False
|
|
@@ -204,7 +205,7 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
|
|
|
204
205
|
def input_schema(self) -> type[BaseModel]:
|
|
205
206
|
return self._input_model
|
|
206
207
|
|
|
207
|
-
async def prompt(self,
|
|
208
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
208
209
|
return await self.description()
|
|
209
210
|
|
|
210
211
|
def is_read_only(self) -> bool:
|
|
@@ -321,7 +322,14 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
|
|
|
321
322
|
data=annotated_output,
|
|
322
323
|
result_for_assistant=final_text,
|
|
323
324
|
)
|
|
324
|
-
except (
|
|
325
|
+
except (
|
|
326
|
+
OSError,
|
|
327
|
+
RuntimeError,
|
|
328
|
+
ConnectionError,
|
|
329
|
+
ValueError,
|
|
330
|
+
KeyError,
|
|
331
|
+
TypeError,
|
|
332
|
+
) as exc: # pragma: no cover - runtime errors
|
|
325
333
|
output = McpToolCallOutput(
|
|
326
334
|
server=self.server_name,
|
|
327
335
|
tool=self.tool_info.name,
|
|
@@ -333,7 +341,8 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
|
|
|
333
341
|
)
|
|
334
342
|
logger.warning(
|
|
335
343
|
"Error calling MCP tool: %s: %s",
|
|
336
|
-
type(exc).__name__,
|
|
344
|
+
type(exc).__name__,
|
|
345
|
+
exc,
|
|
337
346
|
extra={
|
|
338
347
|
"server": self.server_name,
|
|
339
348
|
"tool": self.tool_info.name,
|
|
@@ -381,10 +390,16 @@ def load_dynamic_mcp_tools_sync(project_path: Optional[Path] = None) -> List[Dyn
|
|
|
381
390
|
|
|
382
391
|
try:
|
|
383
392
|
return asyncio.run(_load_and_keep())
|
|
384
|
-
except (
|
|
393
|
+
except (
|
|
394
|
+
OSError,
|
|
395
|
+
RuntimeError,
|
|
396
|
+
ConnectionError,
|
|
397
|
+
ValueError,
|
|
398
|
+
) as exc: # pragma: no cover - SDK/runtime failures
|
|
385
399
|
logger.warning(
|
|
386
400
|
"Failed to initialize MCP runtime for dynamic tools (sync): %s: %s",
|
|
387
|
-
type(exc).__name__,
|
|
401
|
+
type(exc).__name__,
|
|
402
|
+
exc,
|
|
388
403
|
)
|
|
389
404
|
return []
|
|
390
405
|
|
|
@@ -393,10 +408,16 @@ async def load_dynamic_mcp_tools_async(project_path: Optional[Path] = None) -> L
|
|
|
393
408
|
"""Async loader for MCP tools when already in an event loop."""
|
|
394
409
|
try:
|
|
395
410
|
runtime = await ensure_mcp_runtime(project_path)
|
|
396
|
-
except (
|
|
411
|
+
except (
|
|
412
|
+
OSError,
|
|
413
|
+
RuntimeError,
|
|
414
|
+
ConnectionError,
|
|
415
|
+
ValueError,
|
|
416
|
+
) as exc: # pragma: no cover - SDK/runtime failures
|
|
397
417
|
logger.warning(
|
|
398
418
|
"Failed to initialize MCP runtime for dynamic tools (async): %s: %s",
|
|
399
|
-
type(exc).__name__,
|
|
419
|
+
type(exc).__name__,
|
|
420
|
+
exc,
|
|
400
421
|
)
|
|
401
422
|
return []
|
|
402
423
|
return _build_dynamic_mcp_tools(runtime)
|
|
@@ -151,7 +151,7 @@ class EnterPlanModeTool(Tool[EnterPlanModeToolInput, EnterPlanModeToolOutput]):
|
|
|
151
151
|
def input_schema(self) -> type[EnterPlanModeToolInput]:
|
|
152
152
|
return EnterPlanModeToolInput
|
|
153
153
|
|
|
154
|
-
async def prompt(self,
|
|
154
|
+
async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
|
|
155
155
|
return ENTER_PLAN_MODE_PROMPT
|
|
156
156
|
|
|
157
157
|
def user_facing_name(self) -> str:
|
|
@@ -84,7 +84,7 @@ class ExitPlanModeTool(Tool[ExitPlanModeToolInput, ExitPlanModeToolOutput]):
|
|
|
84
84
|
def input_schema(self) -> type[ExitPlanModeToolInput]:
|
|
85
85
|
return ExitPlanModeToolInput
|
|
86
86
|
|
|
87
|
-
async def prompt(self,
|
|
87
|
+
async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
|
|
88
88
|
return EXIT_PLAN_MODE_PROMPT
|
|
89
89
|
|
|
90
90
|
def user_facing_name(self) -> str:
|
|
@@ -84,7 +84,7 @@ match exactly (including whitespace and indentation)."""
|
|
|
84
84
|
),
|
|
85
85
|
]
|
|
86
86
|
|
|
87
|
-
async def prompt(self,
|
|
87
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
88
88
|
return (
|
|
89
89
|
"Performs exact string replacements in files.\n\n"
|
|
90
90
|
"Usage:\n"
|
|
@@ -159,7 +159,9 @@ match exactly (including whitespace and indentation)."""
|
|
|
159
159
|
|
|
160
160
|
# Check if path is ignored (warning for edit operations)
|
|
161
161
|
file_path_obj = Path(file_path)
|
|
162
|
-
should_proceed, warning_msg = check_path_for_tool(
|
|
162
|
+
should_proceed, warning_msg = check_path_for_tool(
|
|
163
|
+
file_path_obj, tool_name="Edit", warn_only=True
|
|
164
|
+
)
|
|
163
165
|
if warning_msg:
|
|
164
166
|
logger.warning("[file_edit_tool] %s", warning_msg)
|
|
165
167
|
|
|
@@ -238,7 +240,8 @@ match exactly (including whitespace and indentation)."""
|
|
|
238
240
|
except (OSError, IOError, RuntimeError) as exc:
|
|
239
241
|
logger.warning(
|
|
240
242
|
"[file_edit_tool] Failed to record file snapshot: %s: %s",
|
|
241
|
-
type(exc).__name__,
|
|
243
|
+
type(exc).__name__,
|
|
244
|
+
exc,
|
|
242
245
|
extra={"file_path": abs_file_path},
|
|
243
246
|
)
|
|
244
247
|
|
|
@@ -330,7 +333,8 @@ match exactly (including whitespace and indentation)."""
|
|
|
330
333
|
except (OSError, IOError, PermissionError, UnicodeDecodeError, ValueError) as e:
|
|
331
334
|
logger.warning(
|
|
332
335
|
"[file_edit_tool] Error editing file: %s: %s",
|
|
333
|
-
type(e).__name__,
|
|
336
|
+
type(e).__name__,
|
|
337
|
+
e,
|
|
334
338
|
extra={"file_path": input_data.file_path},
|
|
335
339
|
)
|
|
336
340
|
error_output = FileEditToolOutput(
|
|
@@ -70,7 +70,7 @@ and limit to read only a portion of the file."""
|
|
|
70
70
|
),
|
|
71
71
|
]
|
|
72
72
|
|
|
73
|
-
async def prompt(self,
|
|
73
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
74
74
|
return (
|
|
75
75
|
"Read a file from the local filesystem.\n\n"
|
|
76
76
|
"Usage:\n"
|
|
@@ -106,7 +106,9 @@ and limit to read only a portion of the file."""
|
|
|
106
106
|
|
|
107
107
|
# Check if path is ignored (warning only for read operations)
|
|
108
108
|
file_path = Path(input_data.file_path)
|
|
109
|
-
should_proceed, warning_msg = check_path_for_tool(
|
|
109
|
+
should_proceed, warning_msg = check_path_for_tool(
|
|
110
|
+
file_path, tool_name="Read", warn_only=True
|
|
111
|
+
)
|
|
110
112
|
if warning_msg:
|
|
111
113
|
logger.info("[file_read_tool] %s", warning_msg)
|
|
112
114
|
|
|
@@ -166,7 +168,8 @@ and limit to read only a portion of the file."""
|
|
|
166
168
|
except (OSError, IOError, RuntimeError) as exc:
|
|
167
169
|
logger.warning(
|
|
168
170
|
"[file_read_tool] Failed to record file snapshot: %s: %s",
|
|
169
|
-
type(exc).__name__,
|
|
171
|
+
type(exc).__name__,
|
|
172
|
+
exc,
|
|
170
173
|
extra={"file_path": input_data.file_path},
|
|
171
174
|
)
|
|
172
175
|
|
|
@@ -185,7 +188,8 @@ and limit to read only a portion of the file."""
|
|
|
185
188
|
except (OSError, IOError, UnicodeDecodeError, ValueError) as e:
|
|
186
189
|
logger.warning(
|
|
187
190
|
"[file_read_tool] Error reading file: %s: %s",
|
|
188
|
-
type(e).__name__,
|
|
191
|
+
type(e).__name__,
|
|
192
|
+
e,
|
|
189
193
|
extra={"file_path": input_data.file_path},
|
|
190
194
|
)
|
|
191
195
|
# Create an error output
|
|
@@ -72,10 +72,10 @@ the file if it already exists."""
|
|
|
72
72
|
),
|
|
73
73
|
]
|
|
74
74
|
|
|
75
|
-
async def prompt(self,
|
|
75
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
76
76
|
prompt = """Use the Write tool to create new files. """
|
|
77
77
|
|
|
78
|
-
if
|
|
78
|
+
if not yolo_mode:
|
|
79
79
|
prompt += """IMPORTANT: You must ALWAYS prefer editing existing files.
|
|
80
80
|
NEVER write new files unless explicitly required by the user."""
|
|
81
81
|
|
|
@@ -134,7 +134,9 @@ NEVER write new files unless explicitly required by the user."""
|
|
|
134
134
|
|
|
135
135
|
# Check if path is ignored (warning for write operations)
|
|
136
136
|
file_path_obj = Path(file_path)
|
|
137
|
-
should_proceed, warning_msg = check_path_for_tool(
|
|
137
|
+
should_proceed, warning_msg = check_path_for_tool(
|
|
138
|
+
file_path_obj, tool_name="Write", warn_only=True
|
|
139
|
+
)
|
|
138
140
|
if warning_msg:
|
|
139
141
|
logger.warning("[file_write_tool] %s", warning_msg)
|
|
140
142
|
|
|
@@ -171,7 +173,8 @@ NEVER write new files unless explicitly required by the user."""
|
|
|
171
173
|
except (OSError, IOError, RuntimeError) as exc:
|
|
172
174
|
logger.warning(
|
|
173
175
|
"[file_write_tool] Failed to record file snapshot: %s: %s",
|
|
174
|
-
type(exc).__name__,
|
|
176
|
+
type(exc).__name__,
|
|
177
|
+
exc,
|
|
175
178
|
extra={"file_path": abs_file_path},
|
|
176
179
|
)
|
|
177
180
|
|
|
@@ -189,7 +192,8 @@ NEVER write new files unless explicitly required by the user."""
|
|
|
189
192
|
except (OSError, IOError, PermissionError, UnicodeEncodeError) as e:
|
|
190
193
|
logger.warning(
|
|
191
194
|
"[file_write_tool] Error writing file: %s: %s",
|
|
192
|
-
type(e).__name__,
|
|
195
|
+
type(e).__name__,
|
|
196
|
+
e,
|
|
193
197
|
extra={"file_path": input_data.file_path},
|
|
194
198
|
)
|
|
195
199
|
error_output = FileWriteToolOutput(
|
ripperdoc/tools/glob_tool.py
CHANGED
|
@@ -76,7 +76,7 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
|
|
|
76
76
|
),
|
|
77
77
|
]
|
|
78
78
|
|
|
79
|
-
async def prompt(self,
|
|
79
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
80
80
|
return GLOB_USAGE
|
|
81
81
|
|
|
82
82
|
def is_read_only(self) -> bool:
|
|
@@ -169,7 +169,8 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
|
|
|
169
169
|
except (OSError, RuntimeError, ValueError) as e:
|
|
170
170
|
logger.warning(
|
|
171
171
|
"[glob_tool] Error executing glob: %s: %s",
|
|
172
|
-
type(e).__name__,
|
|
172
|
+
type(e).__name__,
|
|
173
|
+
e,
|
|
173
174
|
extra={"pattern": input_data.pattern, "path": input_data.path},
|
|
174
175
|
)
|
|
175
176
|
error_output = GlobToolOutput(matches=[], pattern=input_data.pattern, count=0)
|
ripperdoc/tools/grep_tool.py
CHANGED
|
@@ -148,7 +148,7 @@ class GrepTool(Tool[GrepToolInput, GrepToolOutput]):
|
|
|
148
148
|
),
|
|
149
149
|
]
|
|
150
150
|
|
|
151
|
-
async def prompt(self,
|
|
151
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
152
152
|
return GREP_USAGE
|
|
153
153
|
|
|
154
154
|
def is_read_only(self) -> bool:
|
|
@@ -358,7 +358,8 @@ class GrepTool(Tool[GrepToolInput, GrepToolOutput]):
|
|
|
358
358
|
except (OSError, RuntimeError, ValueError, subprocess.SubprocessError) as e:
|
|
359
359
|
logger.warning(
|
|
360
360
|
"[grep_tool] Error executing grep: %s: %s",
|
|
361
|
-
type(e).__name__,
|
|
361
|
+
type(e).__name__,
|
|
362
|
+
e,
|
|
362
363
|
extra={"pattern": input_data.pattern, "path": input_data.path},
|
|
363
364
|
)
|
|
364
365
|
error_output = GrepToolOutput(
|
|
@@ -53,7 +53,7 @@ class KillBashTool(Tool[KillBashInput, KillBashOutput]):
|
|
|
53
53
|
async def description(self) -> str:
|
|
54
54
|
return "Kill a background bash shell by ID"
|
|
55
55
|
|
|
56
|
-
async def prompt(self,
|
|
56
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
57
57
|
return KILL_BASH_PROMPT
|
|
58
58
|
|
|
59
59
|
@property
|
ripperdoc/tools/ls_tool.py
CHANGED
|
@@ -320,7 +320,7 @@ class LSTool(Tool[LSToolInput, LSToolOutput]):
|
|
|
320
320
|
),
|
|
321
321
|
]
|
|
322
322
|
|
|
323
|
-
async def prompt(self,
|
|
323
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
324
324
|
return (
|
|
325
325
|
"Lists files and directories in a given path. The path parameter must be an absolute path, "
|
|
326
326
|
"not a relative path. You can optionally provide an array of glob patterns to ignore with "
|
ripperdoc/tools/mcp_tools.py
CHANGED
|
@@ -47,6 +47,7 @@ DEFAULT_MCP_WARNING_FRACTION = 0.8
|
|
|
47
47
|
# Base class for MCP tools to reduce code duplication
|
|
48
48
|
# =============================================================================
|
|
49
49
|
|
|
50
|
+
|
|
50
51
|
class BaseMcpTool(Tool): # type: ignore[type-arg]
|
|
51
52
|
"""Base class for MCP tools with common default implementations.
|
|
52
53
|
|
|
@@ -76,9 +77,7 @@ class BaseMcpTool(Tool): # type: ignore[type-arg]
|
|
|
76
77
|
runtime = await ensure_mcp_runtime()
|
|
77
78
|
server_names = {s.name for s in runtime.servers}
|
|
78
79
|
if server_name not in server_names:
|
|
79
|
-
return ValidationResult(
|
|
80
|
-
result=False, message=f"Unknown MCP server '{server_name}'."
|
|
81
|
-
)
|
|
80
|
+
return ValidationResult(result=False, message=f"Unknown MCP server '{server_name}'.")
|
|
82
81
|
return ValidationResult(result=True)
|
|
83
82
|
|
|
84
83
|
|
|
@@ -160,7 +159,7 @@ class ListMcpServersTool(BaseMcpTool, Tool[ListMcpServersInput, ListMcpServersOu
|
|
|
160
159
|
def input_schema(self) -> type[ListMcpServersInput]:
|
|
161
160
|
return ListMcpServersInput
|
|
162
161
|
|
|
163
|
-
async def prompt(self,
|
|
162
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
164
163
|
servers = await load_mcp_servers_async()
|
|
165
164
|
return format_mcp_instructions(servers)
|
|
166
165
|
|
|
@@ -243,7 +242,7 @@ class ListMcpResourcesTool(BaseMcpTool, Tool[ListMcpResourcesInput, ListMcpResou
|
|
|
243
242
|
def input_schema(self) -> type[ListMcpResourcesInput]:
|
|
244
243
|
return ListMcpResourcesInput
|
|
245
244
|
|
|
246
|
-
async def prompt(self,
|
|
245
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
247
246
|
return (
|
|
248
247
|
"List available resources from configured MCP servers.\n"
|
|
249
248
|
"Each returned resource will include all standard MCP resource fields plus a 'server' field\n"
|
|
@@ -268,7 +267,8 @@ class ListMcpResourcesTool(BaseMcpTool, Tool[ListMcpResourcesInput, ListMcpResou
|
|
|
268
267
|
except (TypeError, ValueError) as exc:
|
|
269
268
|
logger.warning(
|
|
270
269
|
"[mcp_tools] Failed to serialize MCP resources for assistant output: %s: %s",
|
|
271
|
-
type(exc).__name__,
|
|
270
|
+
type(exc).__name__,
|
|
271
|
+
exc,
|
|
272
272
|
)
|
|
273
273
|
return str(output.resources)
|
|
274
274
|
|
|
@@ -314,7 +314,8 @@ class ListMcpResourcesTool(BaseMcpTool, Tool[ListMcpResourcesInput, ListMcpResou
|
|
|
314
314
|
# pragma: no cover - runtime errors
|
|
315
315
|
logger.warning(
|
|
316
316
|
"Failed to fetch resources from MCP server: %s: %s",
|
|
317
|
-
type(exc).__name__,
|
|
317
|
+
type(exc).__name__,
|
|
318
|
+
exc,
|
|
318
319
|
extra={"server": server.name},
|
|
319
320
|
)
|
|
320
321
|
fetched = []
|
|
@@ -394,7 +395,7 @@ class ReadMcpResourceTool(BaseMcpTool, Tool[ReadMcpResourceInput, ReadMcpResourc
|
|
|
394
395
|
def input_schema(self) -> type[ReadMcpResourceInput]:
|
|
395
396
|
return ReadMcpResourceInput
|
|
396
397
|
|
|
397
|
-
async def prompt(self,
|
|
398
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
398
399
|
return (
|
|
399
400
|
"Reads a specific resource from an MCP server, identified by server name and resource URI.\n\n"
|
|
400
401
|
"Parameters:\n"
|
|
@@ -482,7 +483,8 @@ class ReadMcpResourceTool(BaseMcpTool, Tool[ReadMcpResourceInput, ReadMcpResourc
|
|
|
482
483
|
except (ValueError, binascii.Error) as exc:
|
|
483
484
|
logger.warning(
|
|
484
485
|
"[mcp_tools] Failed to decode base64 blob content: %s: %s",
|
|
485
|
-
type(exc).__name__,
|
|
486
|
+
type(exc).__name__,
|
|
487
|
+
exc,
|
|
486
488
|
extra={"server": input_data.server, "uri": input_data.uri},
|
|
487
489
|
)
|
|
488
490
|
raw_bytes = None
|
|
@@ -515,7 +517,8 @@ class ReadMcpResourceTool(BaseMcpTool, Tool[ReadMcpResourceInput, ReadMcpResourc
|
|
|
515
517
|
# pragma: no cover - runtime errors
|
|
516
518
|
logger.warning(
|
|
517
519
|
"Error reading MCP resource: %s: %s",
|
|
518
|
-
type(exc).__name__,
|
|
520
|
+
type(exc).__name__,
|
|
521
|
+
exc,
|
|
519
522
|
extra={"server": input_data.server, "uri": input_data.uri},
|
|
520
523
|
)
|
|
521
524
|
content_text = f"Error reading MCP resource: {exc}"
|
|
@@ -149,7 +149,7 @@ class MultiEditTool(Tool[MultiEditToolInput, MultiEditToolOutput]):
|
|
|
149
149
|
),
|
|
150
150
|
]
|
|
151
151
|
|
|
152
|
-
async def prompt(self,
|
|
152
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
153
153
|
return MULTI_EDIT_DESCRIPTION
|
|
154
154
|
|
|
155
155
|
def is_read_only(self) -> bool:
|
|
@@ -190,9 +190,7 @@ class MultiEditTool(Tool[MultiEditToolInput, MultiEditToolOutput]):
|
|
|
190
190
|
|
|
191
191
|
# Check if this is a file creation (first edit has empty old_string)
|
|
192
192
|
is_creation = (
|
|
193
|
-
not path.exists()
|
|
194
|
-
and len(input_data.edits) > 0
|
|
195
|
-
and input_data.edits[0].old_string == ""
|
|
193
|
+
not path.exists() and len(input_data.edits) > 0 and input_data.edits[0].old_string == ""
|
|
196
194
|
)
|
|
197
195
|
|
|
198
196
|
# If file exists, check if it has been read before editing
|
|
@@ -350,7 +348,8 @@ class MultiEditTool(Tool[MultiEditToolInput, MultiEditToolOutput]):
|
|
|
350
348
|
# pragma: no cover - unlikely permission issue
|
|
351
349
|
logger.warning(
|
|
352
350
|
"[multi_edit_tool] Error reading file before edits: %s: %s",
|
|
353
|
-
type(exc).__name__,
|
|
351
|
+
type(exc).__name__,
|
|
352
|
+
exc,
|
|
354
353
|
extra={"file_path": str(file_path)},
|
|
355
354
|
)
|
|
356
355
|
output = MultiEditToolOutput(
|
|
@@ -408,13 +407,15 @@ class MultiEditTool(Tool[MultiEditToolInput, MultiEditToolOutput]):
|
|
|
408
407
|
except (OSError, IOError, RuntimeError) as exc:
|
|
409
408
|
logger.warning(
|
|
410
409
|
"[multi_edit_tool] Failed to record file snapshot: %s: %s",
|
|
411
|
-
type(exc).__name__,
|
|
410
|
+
type(exc).__name__,
|
|
411
|
+
exc,
|
|
412
412
|
extra={"file_path": str(file_path)},
|
|
413
413
|
)
|
|
414
414
|
except (OSError, IOError, PermissionError, UnicodeDecodeError) as exc:
|
|
415
415
|
logger.warning(
|
|
416
416
|
"[multi_edit_tool] Error writing edited file: %s: %s",
|
|
417
|
-
type(exc).__name__,
|
|
417
|
+
type(exc).__name__,
|
|
418
|
+
exc,
|
|
418
419
|
extra={"file_path": str(file_path)},
|
|
419
420
|
)
|
|
420
421
|
output = MultiEditToolOutput(
|
|
@@ -122,7 +122,7 @@ class NotebookEditTool(Tool[NotebookEditInput, NotebookEditOutput]):
|
|
|
122
122
|
),
|
|
123
123
|
]
|
|
124
124
|
|
|
125
|
-
async def prompt(self,
|
|
125
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
126
126
|
return NOTEBOOK_EDIT_DESCRIPTION
|
|
127
127
|
|
|
128
128
|
def is_read_only(self) -> bool:
|
|
@@ -204,7 +204,8 @@ class NotebookEditTool(Tool[NotebookEditInput, NotebookEditOutput]):
|
|
|
204
204
|
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as exc:
|
|
205
205
|
logger.warning(
|
|
206
206
|
"Failed to parse notebook: %s: %s",
|
|
207
|
-
type(exc).__name__,
|
|
207
|
+
type(exc).__name__,
|
|
208
|
+
exc,
|
|
208
209
|
extra={"path": str(path)},
|
|
209
210
|
)
|
|
210
211
|
return ValidationResult(
|
|
@@ -325,7 +326,8 @@ class NotebookEditTool(Tool[NotebookEditInput, NotebookEditOutput]):
|
|
|
325
326
|
except (OSError, IOError, RuntimeError) as exc:
|
|
326
327
|
logger.warning(
|
|
327
328
|
"[notebook_edit_tool] Failed to record file snapshot: %s: %s",
|
|
328
|
-
type(exc).__name__,
|
|
329
|
+
type(exc).__name__,
|
|
330
|
+
exc,
|
|
329
331
|
extra={"file_path": abs_notebook_path},
|
|
330
332
|
)
|
|
331
333
|
|
|
@@ -344,7 +346,8 @@ class NotebookEditTool(Tool[NotebookEditInput, NotebookEditOutput]):
|
|
|
344
346
|
# pragma: no cover - error path
|
|
345
347
|
logger.warning(
|
|
346
348
|
"Error editing notebook: %s: %s",
|
|
347
|
-
type(exc).__name__,
|
|
349
|
+
type(exc).__name__,
|
|
350
|
+
exc,
|
|
348
351
|
extra={"path": input_data.notebook_path},
|
|
349
352
|
)
|
|
350
353
|
output = NotebookEditOutput(
|
ripperdoc/tools/skill_tool.py
CHANGED
|
@@ -82,7 +82,7 @@ class SkillTool(Tool[SkillToolInput, SkillToolOutput]):
|
|
|
82
82
|
),
|
|
83
83
|
]
|
|
84
84
|
|
|
85
|
-
async def prompt(self,
|
|
85
|
+
async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
|
|
86
86
|
return (
|
|
87
87
|
"Load a skill by name to read its SKILL.md content. "
|
|
88
88
|
"Only call this when the skill description is clearly relevant. "
|
ripperdoc/tools/task_tool.py
CHANGED
|
@@ -69,8 +69,8 @@ class TaskTool(Tool[TaskToolInput, TaskToolOutput]):
|
|
|
69
69
|
def input_schema(self) -> type[TaskToolInput]:
|
|
70
70
|
return TaskToolInput
|
|
71
71
|
|
|
72
|
-
async def prompt(self,
|
|
73
|
-
del
|
|
72
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
73
|
+
del yolo_mode
|
|
74
74
|
clear_agent_cache()
|
|
75
75
|
agents: AgentLoadResult = load_agent_definitions()
|
|
76
76
|
|
|
@@ -221,7 +221,7 @@ class TaskTool(Tool[TaskToolInput, TaskToolOutput]):
|
|
|
221
221
|
|
|
222
222
|
subagent_context = QueryContext(
|
|
223
223
|
tools=typed_agent_tools,
|
|
224
|
-
|
|
224
|
+
yolo_mode=context.yolo_mode,
|
|
225
225
|
verbose=context.verbose,
|
|
226
226
|
model=target_agent.model or "task",
|
|
227
227
|
)
|
|
@@ -370,7 +370,8 @@ class TaskTool(Tool[TaskToolInput, TaskToolOutput]):
|
|
|
370
370
|
except (TypeError, ValueError) as exc:
|
|
371
371
|
logger.warning(
|
|
372
372
|
"[task_tool] Failed to serialize tool_use input: %s: %s",
|
|
373
|
-
type(exc).__name__,
|
|
373
|
+
type(exc).__name__,
|
|
374
|
+
exc,
|
|
374
375
|
extra={"tool_use_input": str(inp)[:200]},
|
|
375
376
|
)
|
|
376
377
|
serialized = str(inp)
|
ripperdoc/tools/todo_tool.py
CHANGED
|
@@ -309,7 +309,7 @@ class TodoWriteTool(Tool[TodoWriteToolInput, TodoToolOutput]):
|
|
|
309
309
|
),
|
|
310
310
|
]
|
|
311
311
|
|
|
312
|
-
async def prompt(self,
|
|
312
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
313
313
|
return TODO_WRITE_PROMPT
|
|
314
314
|
|
|
315
315
|
def is_read_only(self) -> bool:
|
|
@@ -403,7 +403,7 @@ class TodoReadTool(Tool[TodoReadToolInput, TodoToolOutput]):
|
|
|
403
403
|
),
|
|
404
404
|
]
|
|
405
405
|
|
|
406
|
-
async def prompt(self,
|
|
406
|
+
async def prompt(self, _yolo_mode: bool = False) -> str:
|
|
407
407
|
return (
|
|
408
408
|
"Use TodoRead to fetch the current todo list before making progress or when you need "
|
|
409
409
|
"to confirm the next action. You can request only the next actionable item or filter "
|
|
@@ -106,7 +106,7 @@ class ToolSearchTool(Tool[ToolSearchInput, ToolSearchOutput]):
|
|
|
106
106
|
),
|
|
107
107
|
]
|
|
108
108
|
|
|
109
|
-
async def prompt(self,
|
|
109
|
+
async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
|
|
110
110
|
return (
|
|
111
111
|
"Search for a tool by providing a short description (e.g., 'query database', 'render notebook'). "
|
|
112
112
|
"Use names to activate tools you've already discovered. "
|
|
@@ -193,7 +193,8 @@ class ToolSearchTool(Tool[ToolSearchInput, ToolSearchOutput]):
|
|
|
193
193
|
description = ""
|
|
194
194
|
logger.warning(
|
|
195
195
|
"[tool_search] Failed to build tool description: %s: %s",
|
|
196
|
-
type(exc).__name__,
|
|
196
|
+
type(exc).__name__,
|
|
197
|
+
exc,
|
|
197
198
|
extra={"tool_name": getattr(tool, "name", None)},
|
|
198
199
|
)
|
|
199
200
|
doc_text = " ".join([name, tool.user_facing_name(), description])
|