tunacode-cli 0.0.70__py3-none-any.whl → 0.0.78.6__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.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (90) hide show
  1. tunacode/cli/commands/__init__.py +0 -2
  2. tunacode/cli/commands/implementations/__init__.py +0 -3
  3. tunacode/cli/commands/implementations/debug.py +2 -2
  4. tunacode/cli/commands/implementations/development.py +10 -8
  5. tunacode/cli/commands/implementations/model.py +357 -29
  6. tunacode/cli/commands/implementations/system.py +3 -2
  7. tunacode/cli/commands/implementations/template.py +0 -2
  8. tunacode/cli/commands/registry.py +8 -7
  9. tunacode/cli/commands/slash/loader.py +2 -1
  10. tunacode/cli/commands/slash/validator.py +2 -1
  11. tunacode/cli/main.py +19 -1
  12. tunacode/cli/repl.py +90 -229
  13. tunacode/cli/repl_components/command_parser.py +2 -1
  14. tunacode/cli/repl_components/error_recovery.py +8 -5
  15. tunacode/cli/repl_components/output_display.py +1 -10
  16. tunacode/cli/repl_components/tool_executor.py +1 -13
  17. tunacode/configuration/defaults.py +2 -2
  18. tunacode/configuration/key_descriptions.py +284 -0
  19. tunacode/configuration/settings.py +0 -1
  20. tunacode/constants.py +6 -42
  21. tunacode/core/agents/__init__.py +43 -2
  22. tunacode/core/agents/agent_components/__init__.py +7 -0
  23. tunacode/core/agents/agent_components/agent_config.py +162 -158
  24. tunacode/core/agents/agent_components/agent_helpers.py +31 -2
  25. tunacode/core/agents/agent_components/node_processor.py +180 -146
  26. tunacode/core/agents/agent_components/response_state.py +123 -6
  27. tunacode/core/agents/agent_components/state_transition.py +116 -0
  28. tunacode/core/agents/agent_components/streaming.py +296 -0
  29. tunacode/core/agents/agent_components/task_completion.py +19 -6
  30. tunacode/core/agents/agent_components/tool_buffer.py +21 -1
  31. tunacode/core/agents/agent_components/tool_executor.py +10 -0
  32. tunacode/core/agents/main.py +522 -370
  33. tunacode/core/agents/main_legact.py +538 -0
  34. tunacode/core/agents/prompts.py +66 -0
  35. tunacode/core/agents/utils.py +29 -122
  36. tunacode/core/setup/__init__.py +0 -2
  37. tunacode/core/setup/config_setup.py +88 -227
  38. tunacode/core/setup/config_wizard.py +230 -0
  39. tunacode/core/setup/coordinator.py +2 -1
  40. tunacode/core/state.py +16 -64
  41. tunacode/core/token_usage/usage_tracker.py +3 -1
  42. tunacode/core/tool_authorization.py +352 -0
  43. tunacode/core/tool_handler.py +67 -60
  44. tunacode/prompts/system.xml +751 -0
  45. tunacode/services/mcp.py +97 -1
  46. tunacode/setup.py +0 -23
  47. tunacode/tools/base.py +54 -1
  48. tunacode/tools/bash.py +14 -0
  49. tunacode/tools/glob.py +4 -2
  50. tunacode/tools/grep.py +7 -17
  51. tunacode/tools/prompts/glob_prompt.xml +1 -1
  52. tunacode/tools/prompts/grep_prompt.xml +1 -0
  53. tunacode/tools/prompts/list_dir_prompt.xml +1 -1
  54. tunacode/tools/prompts/react_prompt.xml +23 -0
  55. tunacode/tools/prompts/read_file_prompt.xml +1 -1
  56. tunacode/tools/react.py +153 -0
  57. tunacode/tools/run_command.py +15 -0
  58. tunacode/types.py +14 -79
  59. tunacode/ui/completers.py +434 -50
  60. tunacode/ui/config_dashboard.py +585 -0
  61. tunacode/ui/console.py +63 -11
  62. tunacode/ui/input.py +8 -3
  63. tunacode/ui/keybindings.py +0 -18
  64. tunacode/ui/model_selector.py +395 -0
  65. tunacode/ui/output.py +40 -19
  66. tunacode/ui/panels.py +173 -49
  67. tunacode/ui/path_heuristics.py +91 -0
  68. tunacode/ui/prompt_manager.py +1 -20
  69. tunacode/ui/tool_ui.py +30 -8
  70. tunacode/utils/api_key_validation.py +93 -0
  71. tunacode/utils/config_comparator.py +340 -0
  72. tunacode/utils/models_registry.py +593 -0
  73. tunacode/utils/text_utils.py +18 -1
  74. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/METADATA +80 -12
  75. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/RECORD +78 -74
  76. tunacode/cli/commands/implementations/plan.py +0 -50
  77. tunacode/cli/commands/implementations/todo.py +0 -217
  78. tunacode/context.py +0 -71
  79. tunacode/core/setup/git_safety_setup.py +0 -186
  80. tunacode/prompts/system.md +0 -359
  81. tunacode/prompts/system.md.bak +0 -487
  82. tunacode/tools/exit_plan_mode.py +0 -273
  83. tunacode/tools/present_plan.py +0 -288
  84. tunacode/tools/prompts/exit_plan_mode_prompt.xml +0 -25
  85. tunacode/tools/prompts/present_plan_prompt.xml +0 -20
  86. tunacode/tools/prompts/todo_prompt.xml +0 -96
  87. tunacode/tools/todo.py +0 -456
  88. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +0 -0
  89. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
  90. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,352 @@
1
+ """
2
+ Tool authorization system with separated concerns.
3
+
4
+ This module implements a declarative, rule-based authorization system for
5
+ tool execution confirmation. It replaces the complex nested conditionals in
6
+ the original ToolHandler with composable, testable authorization rules.
7
+
8
+ Architecture:
9
+ - AuthorizationRule (Protocol): Interface for authorization rules
10
+ - AuthContext: Immutable context for authorization decisions
11
+ - Concrete Rules: Specific authorization rules (ReadOnly, Template, YOLO, etc.)
12
+ - AuthorizationPolicy: Orchestrates multiple rules in priority order
13
+ - ToolRejectionNotifier: Handles agent communication on rejection
14
+ - ConfirmationRequestFactory: Creates confirmation requests for UI
15
+
16
+ Design Principles:
17
+ - Single Responsibility: Each class has one focused purpose
18
+ - Open/Closed: New rules can be added without modifying existing code
19
+ - Dependency Injection: Clear dependencies through constructors
20
+ - Testability: Each component can be tested in isolation
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass
26
+ from typing import TYPE_CHECKING, Optional, Protocol
27
+
28
+ from tunacode.constants import READ_ONLY_TOOLS, ToolName
29
+ from tunacode.types import ToolArgs, ToolConfirmationRequest, ToolConfirmationResponse
30
+
31
+ if TYPE_CHECKING:
32
+ from tunacode.core.state import StateManager
33
+ from tunacode.templates.loader import Template
34
+
35
+
36
+ # =============================================================================
37
+ # Authorization Context
38
+ # =============================================================================
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class AuthContext:
43
+ """Immutable context for authorization decisions.
44
+
45
+ Replaces mutable state access with explicit, immutable context. Makes all
46
+ authorization inputs visible and testable.
47
+
48
+ Attributes:
49
+ yolo_mode: Whether YOLO mode is active (skip all confirmations)
50
+ tool_ignore_list: Tools user has chosen to skip confirmation for
51
+ active_template: Currently active template with allowed tools
52
+ """
53
+
54
+ yolo_mode: bool
55
+ tool_ignore_list: tuple[ToolName, ...] # Immutable tuple
56
+ active_template: Optional[Template]
57
+
58
+ @classmethod
59
+ def from_state(cls, state: StateManager) -> AuthContext:
60
+ """Create authorization context from current state.
61
+
62
+ Args:
63
+ state: State manager with current session state
64
+
65
+ Returns:
66
+ Immutable authorization context
67
+ """
68
+ # Get active_template from tool_handler if it exists
69
+ active_template = None
70
+ if hasattr(state, "tool_handler") and state.tool_handler is not None:
71
+ active_template = getattr(state.tool_handler, "active_template", None)
72
+
73
+ return cls(
74
+ yolo_mode=state.session.yolo,
75
+ tool_ignore_list=tuple(state.session.tool_ignore),
76
+ active_template=active_template,
77
+ )
78
+
79
+
80
+ # =============================================================================
81
+ # Authorization Rule Protocol
82
+ # =============================================================================
83
+
84
+
85
+ class AuthorizationRule(Protocol):
86
+ """Protocol for authorization rules.
87
+
88
+ Each rule encapsulates a single authorization concern. Rules are evaluated
89
+ in priority order by AuthorizationPolicy.
90
+
91
+ Priority Ranges:
92
+ 200-299: Allowlist rules (read-only, templates)
93
+ 300-399: User preference rules (YOLO, ignore list)
94
+ """
95
+
96
+ def should_allow_without_confirmation(self, tool_name: ToolName, context: AuthContext) -> bool:
97
+ """Check if this rule allows the tool without confirmation.
98
+
99
+ Args:
100
+ tool_name: Tool being authorized
101
+ context: Current authorization context
102
+
103
+ Returns:
104
+ True if this rule permits tool without confirmation
105
+ """
106
+ ...
107
+
108
+ def priority(self) -> int:
109
+ """Return priority for rule evaluation.
110
+
111
+ Returns:
112
+ Priority (lower number = higher priority)
113
+ """
114
+ ...
115
+
116
+
117
+ # =============================================================================
118
+ # Concrete Authorization Rules
119
+ # =============================================================================
120
+
121
+
122
+ class ReadOnlyToolRule:
123
+ """Read-only tools are always safe to execute.
124
+
125
+ Read-only tools (read_file, grep, list_dir, etc.) don't modify state,
126
+ so they never require confirmation.
127
+
128
+ Priority: 200 (allowlist rule)
129
+ """
130
+
131
+ def priority(self) -> int:
132
+ return 200
133
+
134
+ def should_allow_without_confirmation(self, tool_name: ToolName, context: AuthContext) -> bool:
135
+ return is_read_only_tool(tool_name)
136
+
137
+
138
+ class TemplateAllowedToolsRule:
139
+ """Tools allowed by active template don't require confirmation.
140
+
141
+ Templates can pre-approve specific tools for workflow-specific operations.
142
+ If a tool is in the active template's allowed_tools list, it can execute
143
+ without confirmation.
144
+
145
+ Priority: 210 (allowlist rule, after read-only)
146
+ """
147
+
148
+ def priority(self) -> int:
149
+ return 210
150
+
151
+ def should_allow_without_confirmation(self, tool_name: ToolName, context: AuthContext) -> bool:
152
+ if context.active_template is None:
153
+ return False
154
+
155
+ if context.active_template.allowed_tools is None:
156
+ return False
157
+
158
+ return tool_name in context.active_template.allowed_tools
159
+
160
+
161
+ class YoloModeRule:
162
+ """YOLO mode bypasses all confirmations.
163
+
164
+ When YOLO mode is active, all tools can execute without confirmation.
165
+ This is useful for batch operations or when the user trusts the agent.
166
+
167
+ Priority: 300 (user preference rule)
168
+ """
169
+
170
+ def priority(self) -> int:
171
+ return 300
172
+
173
+ def should_allow_without_confirmation(self, tool_name: ToolName, context: AuthContext) -> bool:
174
+ return context.yolo_mode
175
+
176
+
177
+ class ToolIgnoreListRule:
178
+ """User-configured ignore list bypasses confirmations.
179
+
180
+ Users can choose to skip confirmation for specific tools by adding them
181
+ to the ignore list (via "skip future" in confirmation dialog).
182
+
183
+ Priority: 310 (user preference rule, after YOLO)
184
+ """
185
+
186
+ def priority(self) -> int:
187
+ return 310
188
+
189
+ def should_allow_without_confirmation(self, tool_name: ToolName, context: AuthContext) -> bool:
190
+ return tool_name in context.tool_ignore_list
191
+
192
+
193
+ # =============================================================================
194
+ # Authorization Policy
195
+ # =============================================================================
196
+
197
+
198
+ class AuthorizationPolicy:
199
+ """Orchestrates authorization rules to determine if tools require confirmation.
200
+
201
+ Uses Strategy pattern to compose multiple authorization rules.
202
+ Evaluates allowlist rules in priority order.
203
+
204
+ This replaces the complex nested conditionals in the original ToolHandler
205
+ with a declarative, testable approach.
206
+ """
207
+
208
+ def __init__(self, rules: list[AuthorizationRule]):
209
+ """Initialize with authorization rules.
210
+
211
+ Args:
212
+ rules: List of authorization rules to evaluate
213
+ """
214
+ # Sort rules by priority (lower number = higher priority)
215
+ self._rules = sorted(rules, key=lambda r: r.priority())
216
+
217
+ def should_confirm(self, tool_name: ToolName, context: AuthContext) -> bool:
218
+ """Determine if tool requires user confirmation.
219
+
220
+ Evaluates allowlist rules in priority order.
221
+
222
+ Args:
223
+ tool_name: Tool to authorize
224
+ context: Current authorization context
225
+
226
+ Returns:
227
+ True if confirmation required, False otherwise
228
+ """
229
+ # Evaluate allowlist rules in priority order
230
+ for rule in self._rules:
231
+ if rule.should_allow_without_confirmation(tool_name, context):
232
+ return False # Rule allows tool without confirmation
233
+
234
+ # No rule allowed it, require confirmation
235
+ return True
236
+
237
+ def is_tool_blocked(self, tool_name: ToolName, context: AuthContext) -> bool:
238
+ """Check if tool is blocked.
239
+
240
+ Returns:
241
+ Always False (no tools are blocked)
242
+ """
243
+ return False
244
+
245
+
246
+ # =============================================================================
247
+ # Tool Rejection Notifier
248
+ # =============================================================================
249
+
250
+
251
+ class ToolRejectionNotifier:
252
+ """Handles communication with agents when tools are rejected.
253
+
254
+ Separates agent communication concerns from authorization logic. This
255
+ eliminates the hidden dependency on the agent messaging system in the
256
+ original ToolHandler.
257
+ """
258
+
259
+ def notify_rejection(
260
+ self,
261
+ tool_name: ToolName,
262
+ response: ToolConfirmationResponse,
263
+ state: StateManager,
264
+ ) -> None:
265
+ """Notify agent that tool execution was rejected.
266
+
267
+ Routes user guidance back to agent system to maintain conversation
268
+ context and enable the agent to respond appropriately.
269
+
270
+ Args:
271
+ tool_name: Tool that was rejected
272
+ response: User's confirmation response with optional guidance
273
+ state: State manager for routing message to agent
274
+ """
275
+ from tunacode.core.agents.agent_components.agent_helpers import create_user_message
276
+
277
+ guidance = getattr(response, "instructions", "").strip()
278
+
279
+ if guidance:
280
+ guidance_section = f"User guidance:\n{guidance}"
281
+ else:
282
+ guidance_section = "User cancelled without additional instructions."
283
+
284
+ message = (
285
+ f"Tool '{tool_name}' execution cancelled before running.\n"
286
+ f"{guidance_section}\n"
287
+ "Do not assume the operation succeeded; "
288
+ "request updated guidance or offer alternatives."
289
+ )
290
+
291
+ create_user_message(message, state)
292
+
293
+
294
+ # =============================================================================
295
+ # Confirmation Request Factory
296
+ # =============================================================================
297
+
298
+
299
+ class ConfirmationRequestFactory:
300
+ """Creates confirmation requests from tool information.
301
+
302
+ Separates UI concern (confirmation dialog creation) from business logic.
303
+ Makes confirmation request creation testable in isolation.
304
+ """
305
+
306
+ def create(self, tool_name: ToolName, args: ToolArgs) -> ToolConfirmationRequest:
307
+ """Create a confirmation request from tool information.
308
+
309
+ Extracts relevant context (like filepath) for display in confirmation
310
+ dialog.
311
+
312
+ Args:
313
+ tool_name: Name of tool requiring confirmation
314
+ args: Tool arguments (may contain filepath, command, etc.)
315
+
316
+ Returns:
317
+ Structured confirmation request for UI
318
+ """
319
+ filepath = args.get("filepath")
320
+ return ToolConfirmationRequest(tool_name=tool_name, args=args, filepath=filepath)
321
+
322
+
323
+ # =============================================================================
324
+ # Helper Functions
325
+ # =============================================================================
326
+
327
+
328
+ def is_read_only_tool(tool_name: str) -> bool:
329
+ """Check if a tool is read-only (safe to execute without confirmation).
330
+
331
+ Args:
332
+ tool_name: Name of the tool to check
333
+
334
+ Returns:
335
+ True if the tool is read-only, False otherwise
336
+ """
337
+ return tool_name in READ_ONLY_TOOLS
338
+
339
+
340
+ def create_default_authorization_policy() -> AuthorizationPolicy:
341
+ """Create default authorization policy with all standard rules.
342
+
343
+ Returns:
344
+ Authorization policy with standard rules in correct priority order
345
+ """
346
+ rules: list[AuthorizationRule] = [
347
+ ReadOnlyToolRule(),
348
+ TemplateAllowedToolsRule(),
349
+ YoloModeRule(),
350
+ ToolIgnoreListRule(),
351
+ ]
352
+ return AuthorizationPolicy(rules)
@@ -1,11 +1,24 @@
1
1
  """
2
2
  Tool handling business logic, separated from UI concerns.
3
+
4
+ Refactored to use composition with specialized authorization components:
5
+ - AuthorizationPolicy: Determines if tools require confirmation
6
+ - ToolRejectionNotifier: Handles agent communication on rejection
7
+ - ConfirmationRequestFactory: Creates confirmation requests for UI
8
+
9
+ This eliminates the "God Object" anti-pattern by delegating to focused components.
3
10
  """
4
11
 
5
12
  from typing import Optional
6
13
 
7
- from tunacode.constants import READ_ONLY_TOOLS
8
14
  from tunacode.core.state import StateManager
15
+ from tunacode.core.tool_authorization import (
16
+ AuthContext,
17
+ AuthorizationPolicy,
18
+ ConfirmationRequestFactory,
19
+ ToolRejectionNotifier,
20
+ create_default_authorization_policy,
21
+ )
9
22
  from tunacode.templates.loader import Template
10
23
  from tunacode.types import (
11
24
  ToolArgs,
@@ -16,15 +29,49 @@ from tunacode.types import (
16
29
 
17
30
 
18
31
  class ToolHandler:
19
- """Handles tool confirmation logic separate from UI."""
32
+ """Coordinates tool authorization, confirmation, and rejection handling.
33
+
34
+ Refactored to use Facade pattern, delegating to specialized components:
35
+ - AuthorizationPolicy: Determines if confirmation is needed
36
+ - ToolRejectionNotifier: Handles agent communication
37
+ - ConfirmationRequestFactory: Creates confirmation requests
38
+
39
+ This eliminates the "God Object" anti-pattern by separating concerns.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ state_manager: StateManager,
45
+ policy: Optional[AuthorizationPolicy] = None,
46
+ notifier: Optional[ToolRejectionNotifier] = None,
47
+ factory: Optional[ConfirmationRequestFactory] = None,
48
+ ):
49
+ """Initialize tool handler with injected dependencies.
50
+
51
+ Dependencies are optional to maintain backward compatibility during
52
+ refactoring. Defaults are provided for gradual migration.
20
53
 
21
- def __init__(self, state_manager: StateManager):
54
+ Args:
55
+ state_manager: State manager for session state access
56
+ policy: Authorization policy (default: create standard policy)
57
+ notifier: Rejection notifier (default: create standard notifier)
58
+ factory: Confirmation factory (default: create standard factory)
59
+ """
22
60
  self.state = state_manager
23
- self.active_template: Optional[Template] = None
61
+ self.active_template: Optional[Template] = None # Kept for compatibility
62
+
63
+ # Inject dependencies with defaults
64
+ self._policy = policy or create_default_authorization_policy()
65
+ self._notifier = notifier or ToolRejectionNotifier()
66
+ self._factory = factory or ConfirmationRequestFactory()
67
+
68
+ # Register self with state manager if not already set
69
+ # This ensures AuthContext can read active_template
70
+ if state_manager.tool_handler is None:
71
+ state_manager.set_tool_handler(self)
24
72
 
25
73
  def set_active_template(self, template: Optional[Template]) -> None:
26
- """
27
- Set the currently active template.
74
+ """Set the currently active template.
28
75
 
29
76
  Args:
30
77
  template: The template to activate, or None to clear the active template.
@@ -32,87 +79,47 @@ class ToolHandler:
32
79
  self.active_template = template
33
80
 
34
81
  def should_confirm(self, tool_name: ToolName) -> bool:
35
- """
36
- Determine if a tool requires confirmation.
82
+ """Determine if a tool requires confirmation.
37
83
 
38
84
  Args:
39
85
  tool_name: Name of the tool to check.
40
86
 
41
87
  Returns:
42
- bool: True if confirmation is required, False otherwise.
88
+ True if confirmation is required, False otherwise.
43
89
  """
44
- # Never confirm present_plan - it has its own approval flow
45
- if tool_name == "present_plan":
46
- return False
47
-
48
- # Block write tools in plan mode
49
- if self.is_tool_blocked_in_plan_mode(tool_name):
50
- return True # Force confirmation for blocked tools
51
-
52
- # Skip confirmation for read-only tools
53
- if is_read_only_tool(tool_name):
54
- return False
55
-
56
- # Check if tool is allowed by active template
57
- if self.active_template and self.active_template.allowed_tools:
58
- if tool_name in self.active_template.allowed_tools:
59
- return False
60
-
61
- return not (self.state.session.yolo or tool_name in self.state.session.tool_ignore)
62
-
63
- def is_tool_blocked_in_plan_mode(self, tool_name: ToolName) -> bool:
64
- """Check if tool is blocked in plan mode."""
65
- if not self.state.is_plan_mode():
66
- return False
67
-
68
- # Allow present_plan tool to end planning phase
69
- if tool_name == "present_plan":
70
- return False
71
-
72
- # Allow read-only tools
73
- return not is_read_only_tool(tool_name)
90
+ context = AuthContext.from_state(self.state)
91
+ return self._policy.should_confirm(tool_name, context)
74
92
 
75
93
  def process_confirmation(self, response: ToolConfirmationResponse, tool_name: ToolName) -> bool:
76
- """
77
- Process the confirmation response.
94
+ """Process the confirmation response.
95
+
96
+ Handles skip_future preference and rejection notification.
78
97
 
79
98
  Args:
80
99
  response: The confirmation response from the user.
81
100
  tool_name: Name of the tool being confirmed.
82
101
 
83
102
  Returns:
84
- bool: True if tool should proceed, False if aborted.
103
+ True if tool should proceed, False if aborted.
85
104
  """
86
105
  if response.skip_future:
87
106
  self.state.session.tool_ignore.append(tool_name)
88
107
 
108
+ if not response.approved or response.abort:
109
+ self._notifier.notify_rejection(tool_name, response, self.state)
110
+
89
111
  return response.approved and not response.abort
90
112
 
91
113
  def create_confirmation_request(
92
114
  self, tool_name: ToolName, args: ToolArgs
93
115
  ) -> ToolConfirmationRequest:
94
- """
95
- Create a confirmation request from tool information.
116
+ """Create a confirmation request from tool information.
96
117
 
97
118
  Args:
98
119
  tool_name: Name of the tool.
99
120
  args: Tool arguments.
100
121
 
101
122
  Returns:
102
- ToolConfirmationRequest: The confirmation request.
123
+ Structured confirmation request for UI.
103
124
  """
104
- filepath = args.get("filepath")
105
- return ToolConfirmationRequest(tool_name=tool_name, args=args, filepath=filepath)
106
-
107
-
108
- def is_read_only_tool(tool_name: str) -> bool:
109
- """
110
- Check if a tool is read-only (safe to execute without confirmation).
111
-
112
- Args:
113
- tool_name: Name of the tool to check.
114
-
115
- Returns:
116
- bool: True if the tool is read-only, False otherwise.
117
- """
118
- return tool_name in READ_ONLY_TOOLS
125
+ return self._factory.create(tool_name, args)