ripperdoc 0.2.7__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 +33 -115
- ripperdoc/cli/commands/__init__.py +70 -6
- 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/help_cmd.py +11 -1
- ripperdoc/cli/commands/hooks_cmd.py +610 -0
- ripperdoc/cli/commands/models_cmd.py +26 -9
- ripperdoc/cli/commands/permissions_cmd.py +57 -37
- ripperdoc/cli/commands/resume_cmd.py +6 -4
- ripperdoc/cli/commands/status_cmd.py +4 -4
- ripperdoc/cli/commands/tasks_cmd.py +8 -4
- ripperdoc/cli/ui/file_mention_completer.py +64 -8
- ripperdoc/cli/ui/interrupt_handler.py +3 -4
- ripperdoc/cli/ui/message_display.py +5 -3
- ripperdoc/cli/ui/panels.py +13 -10
- ripperdoc/cli/ui/provider_options.py +247 -0
- ripperdoc/cli/ui/rich_ui.py +196 -77
- 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 +412 -0
- ripperdoc/core/default_tools.py +11 -2
- ripperdoc/core/hooks/__init__.py +99 -0
- ripperdoc/core/hooks/config.py +301 -0
- ripperdoc/core/hooks/events.py +535 -0
- ripperdoc/core/hooks/executor.py +496 -0
- ripperdoc/core/hooks/integration.py +344 -0
- ripperdoc/core/hooks/manager.py +745 -0
- ripperdoc/core/permissions.py +40 -8
- ripperdoc/core/providers/anthropic.py +548 -68
- ripperdoc/core/providers/gemini.py +70 -5
- ripperdoc/core/providers/openai.py +60 -5
- ripperdoc/core/query.py +140 -39
- 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 +30 -20
- 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 +9 -5
- 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 +11 -7
- 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 +38 -12
- 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.7.dist-info → ripperdoc-0.2.9.dist-info}/METADATA +24 -3
- ripperdoc-0.2.9.dist-info/RECORD +123 -0
- ripperdoc-0.2.7.dist-info/RECORD +0 -113
- {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/WHEEL +0 -0
- {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/top_level.txt +0 -0
ripperdoc/core/skills.py
CHANGED
|
@@ -84,10 +84,15 @@ def _split_frontmatter(raw_text: str) -> Tuple[Dict[str, Any], str]:
|
|
|
84
84
|
body = "\n".join(lines[idx + 1 :])
|
|
85
85
|
try:
|
|
86
86
|
frontmatter = yaml.safe_load(frontmatter_text) or {}
|
|
87
|
-
except (
|
|
87
|
+
except (
|
|
88
|
+
yaml.YAMLError,
|
|
89
|
+
ValueError,
|
|
90
|
+
TypeError,
|
|
91
|
+
) as exc: # pragma: no cover - defensive
|
|
88
92
|
logger.warning(
|
|
89
93
|
"[skills] Invalid frontmatter in SKILL.md: %s: %s",
|
|
90
|
-
type(exc).__name__,
|
|
94
|
+
type(exc).__name__,
|
|
95
|
+
exc,
|
|
91
96
|
)
|
|
92
97
|
return {"__error__": f"Invalid frontmatter: {exc}"}, body
|
|
93
98
|
return frontmatter, body
|
|
@@ -118,7 +123,8 @@ def _load_skill_file(
|
|
|
118
123
|
except (OSError, IOError, UnicodeDecodeError) as exc:
|
|
119
124
|
logger.warning(
|
|
120
125
|
"[skills] Failed to read skill file: %s: %s",
|
|
121
|
-
type(exc).__name__,
|
|
126
|
+
type(exc).__name__,
|
|
127
|
+
exc,
|
|
122
128
|
extra={"path": str(path)},
|
|
123
129
|
)
|
|
124
130
|
return None, SkillLoadError(path=path, reason=f"Failed to read file: {exc}")
|
ripperdoc/core/system_prompt.py
CHANGED
|
@@ -49,7 +49,8 @@ def _detect_git_repo(cwd: Path) -> bool:
|
|
|
49
49
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
50
50
|
logger.warning(
|
|
51
51
|
"[system_prompt] Failed to detect git repository: %s: %s",
|
|
52
|
-
type(exc).__name__,
|
|
52
|
+
type(exc).__name__,
|
|
53
|
+
exc,
|
|
53
54
|
extra={"cwd": str(cwd)},
|
|
54
55
|
)
|
|
55
56
|
return False
|
|
@@ -393,7 +394,8 @@ def build_system_prompt(
|
|
|
393
394
|
except (OSError, ValueError, RuntimeError) as exc:
|
|
394
395
|
logger.warning(
|
|
395
396
|
"Failed to load agent definitions: %s: %s",
|
|
396
|
-
type(exc).__name__,
|
|
397
|
+
type(exc).__name__,
|
|
398
|
+
exc,
|
|
397
399
|
)
|
|
398
400
|
agent_section = (
|
|
399
401
|
"# Subagents\nTask tool available, but agent definitions could not be loaded."
|
ripperdoc/core/tool.py
CHANGED
|
@@ -37,13 +37,15 @@ class ToolUseContext(BaseModel):
|
|
|
37
37
|
|
|
38
38
|
message_id: Optional[str] = None
|
|
39
39
|
agent_id: Optional[str] = None
|
|
40
|
-
|
|
40
|
+
yolo_mode: bool = False
|
|
41
41
|
verbose: bool = False
|
|
42
42
|
permission_checker: Optional[Any] = None
|
|
43
43
|
read_file_timestamps: Dict[str, float] = Field(default_factory=dict)
|
|
44
44
|
# SkipValidation prevents Pydantic from copying the dict during validation,
|
|
45
45
|
# ensuring Read and Edit tools share the same cache instance
|
|
46
|
-
file_state_cache: Annotated[Dict[str, FileSnapshot], SkipValidation] = Field(
|
|
46
|
+
file_state_cache: Annotated[Dict[str, FileSnapshot], SkipValidation] = Field(
|
|
47
|
+
default_factory=dict
|
|
48
|
+
)
|
|
47
49
|
tool_registry: Optional[Any] = None
|
|
48
50
|
abort_signal: Optional[Any] = None
|
|
49
51
|
# UI control callbacks for tools that need user interaction
|
|
@@ -110,7 +112,7 @@ class Tool(ABC, Generic[TInput, TOutput]):
|
|
|
110
112
|
pass
|
|
111
113
|
|
|
112
114
|
@abstractmethod
|
|
113
|
-
async def prompt(self,
|
|
115
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
114
116
|
"""Get the system prompt for this tool."""
|
|
115
117
|
pass
|
|
116
118
|
|
|
@@ -213,7 +215,8 @@ async def build_tool_description(
|
|
|
213
215
|
except (TypeError, ValueError, AttributeError, KeyError) as exc:
|
|
214
216
|
logger.warning(
|
|
215
217
|
"[tool] Failed to build input example section: %s: %s",
|
|
216
|
-
type(exc).__name__,
|
|
218
|
+
type(exc).__name__,
|
|
219
|
+
exc,
|
|
217
220
|
extra={"tool": getattr(tool, "name", None)},
|
|
218
221
|
)
|
|
219
222
|
return description_text
|
|
@@ -233,7 +236,8 @@ def tool_input_examples(tool: Tool[Any, Any], limit: int = 5) -> List[Dict[str,
|
|
|
233
236
|
except (TypeError, ValueError, AttributeError) as exc:
|
|
234
237
|
logger.warning(
|
|
235
238
|
"[tool] Failed to format tool input example: %s: %s",
|
|
236
|
-
type(exc).__name__,
|
|
239
|
+
type(exc).__name__,
|
|
240
|
+
exc,
|
|
237
241
|
extra={"tool": getattr(tool, "name", None)},
|
|
238
242
|
)
|
|
239
243
|
continue
|
ripperdoc/sdk/client.py
CHANGED
|
@@ -83,7 +83,7 @@ class RipperdocOptions:
|
|
|
83
83
|
tools: Optional[Sequence[Tool[Any, Any]]] = None
|
|
84
84
|
allowed_tools: Optional[Sequence[str]] = None
|
|
85
85
|
disallowed_tools: Optional[Sequence[str]] = None
|
|
86
|
-
|
|
86
|
+
yolo_mode: bool = False
|
|
87
87
|
verbose: bool = False
|
|
88
88
|
model: str = "main"
|
|
89
89
|
max_thinking_tokens: int = 0
|
|
@@ -227,7 +227,7 @@ class RipperdocClient:
|
|
|
227
227
|
query_context = QueryContext(
|
|
228
228
|
tools=self._tools,
|
|
229
229
|
max_thinking_tokens=self.options.max_thinking_tokens,
|
|
230
|
-
|
|
230
|
+
yolo_mode=self.options.yolo_mode,
|
|
231
231
|
model=self.options.model,
|
|
232
232
|
verbose=self.options.verbose,
|
|
233
233
|
)
|
|
@@ -228,7 +228,8 @@ async def prompt_user_for_answer(
|
|
|
228
228
|
except (OSError, RuntimeError, ValueError) as e:
|
|
229
229
|
logger.warning(
|
|
230
230
|
"[ask_user_question_tool] Error during prompt: %s: %s",
|
|
231
|
-
type(e).__name__,
|
|
231
|
+
type(e).__name__,
|
|
232
|
+
e,
|
|
232
233
|
)
|
|
233
234
|
return None
|
|
234
235
|
|
|
@@ -275,7 +276,7 @@ class AskUserQuestionTool(Tool[AskUserQuestionToolInput, AskUserQuestionToolOutp
|
|
|
275
276
|
def input_schema(self) -> type[AskUserQuestionToolInput]:
|
|
276
277
|
return AskUserQuestionToolInput
|
|
277
278
|
|
|
278
|
-
async def prompt(self,
|
|
279
|
+
async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
|
|
279
280
|
return ASK_USER_QUESTION_PROMPT
|
|
280
281
|
|
|
281
282
|
def user_facing_name(self) -> str:
|
|
@@ -410,7 +411,8 @@ class AskUserQuestionTool(Tool[AskUserQuestionToolInput, AskUserQuestionToolOutp
|
|
|
410
411
|
except (OSError, RuntimeError, ValueError, KeyError) as exc:
|
|
411
412
|
logger.warning(
|
|
412
413
|
"[ask_user_question_tool] Error collecting answers: %s: %s",
|
|
413
|
-
type(exc).__name__,
|
|
414
|
+
type(exc).__name__,
|
|
415
|
+
exc,
|
|
414
416
|
)
|
|
415
417
|
output = AskUserQuestionToolOutput(
|
|
416
418
|
questions=questions,
|
|
@@ -162,7 +162,8 @@ async def _monitor_task(task: BackgroundTask) -> None:
|
|
|
162
162
|
except (OSError, RuntimeError, ProcessLookupError) as exc:
|
|
163
163
|
logger.warning(
|
|
164
164
|
"Error monitoring background task: %s: %s",
|
|
165
|
-
type(exc).__name__,
|
|
165
|
+
type(exc).__name__,
|
|
166
|
+
exc,
|
|
166
167
|
extra={"task_id": task.id, "command": task.command},
|
|
167
168
|
)
|
|
168
169
|
with _tasks_lock:
|
|
@@ -40,7 +40,7 @@ class BashOutputTool(Tool[BashOutputInput, BashOutputData]):
|
|
|
40
40
|
async def description(self) -> str:
|
|
41
41
|
return "Read output and status from a background bash command started with BashTool(run_in_background=True)."
|
|
42
42
|
|
|
43
|
-
async def prompt(self,
|
|
43
|
+
async def prompt(self, yolo_mode: bool = False) -> str:
|
|
44
44
|
return "Fetch buffered output and status for a background bash task by id."
|
|
45
45
|
|
|
46
46
|
@property
|
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(
|
|
@@ -652,7 +663,7 @@ build projects, run tests, and interact with the file system."""
|
|
|
652
663
|
# Emit progress updates for newly received output chunks
|
|
653
664
|
while not queue.empty():
|
|
654
665
|
label, text = queue.get_nowait()
|
|
655
|
-
yield ToolProgress(content=f"{label}: {text}")
|
|
666
|
+
yield ToolProgress(content=f"{label}: {text}") # type: ignore[misc]
|
|
656
667
|
|
|
657
668
|
# Report progress at intervals
|
|
658
669
|
if now - last_progress_time >= PROGRESS_INTERVAL_SECONDS:
|
|
@@ -660,7 +671,7 @@ build projects, run tests, and interact with the file system."""
|
|
|
660
671
|
if combined_output:
|
|
661
672
|
preview = get_last_n_lines(combined_output, 5)
|
|
662
673
|
elapsed = format_duration((now - start_time) * 1000)
|
|
663
|
-
yield ToolProgress(content=f"Running... ({elapsed})\n{preview}")
|
|
674
|
+
yield ToolProgress(content=f"Running... ({elapsed})\n{preview}") # type: ignore[misc]
|
|
664
675
|
last_progress_time = now
|
|
665
676
|
|
|
666
677
|
# Check timeout
|
|
@@ -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
|
|
@@ -907,14 +916,14 @@ build projects, run tests, and interact with the file system."""
|
|
|
907
916
|
|
|
908
917
|
while not queue.empty():
|
|
909
918
|
label, text = queue.get_nowait()
|
|
910
|
-
yield ToolProgress(content=f"{label}: {text}")
|
|
919
|
+
yield ToolProgress(content=f"{label}: {text}") # type: ignore[misc]
|
|
911
920
|
|
|
912
921
|
if now - last_progress_time >= PROGRESS_INTERVAL_SECONDS:
|
|
913
922
|
combined_output = "".join(stdout_lines + stderr_lines)
|
|
914
923
|
if combined_output:
|
|
915
924
|
preview = get_last_n_lines(combined_output, 5)
|
|
916
925
|
elapsed = format_duration((now - start) * 1000)
|
|
917
|
-
yield ToolProgress(content=f"Running... ({elapsed})\n{preview}")
|
|
926
|
+
yield ToolProgress(content=f"Running... ({elapsed})\n{preview}") # type: ignore[misc]
|
|
918
927
|
last_progress_time = now
|
|
919
928
|
|
|
920
929
|
if deadline is not None and now >= deadline:
|
|
@@ -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(
|
|
@@ -18,7 +18,7 @@ from ripperdoc.core.tool import (
|
|
|
18
18
|
)
|
|
19
19
|
from ripperdoc.utils.log import get_logger
|
|
20
20
|
from ripperdoc.utils.file_watch import record_snapshot
|
|
21
|
-
from ripperdoc.utils.path_ignore import check_path_for_tool
|
|
21
|
+
from ripperdoc.utils.path_ignore import check_path_for_tool
|
|
22
22
|
|
|
23
23
|
logger = get_logger()
|
|
24
24
|
|
|
@@ -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 "
|