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.
- tunacode/cli/commands/__init__.py +0 -2
- tunacode/cli/commands/implementations/__init__.py +0 -3
- tunacode/cli/commands/implementations/debug.py +2 -2
- tunacode/cli/commands/implementations/development.py +10 -8
- tunacode/cli/commands/implementations/model.py +357 -29
- tunacode/cli/commands/implementations/system.py +3 -2
- tunacode/cli/commands/implementations/template.py +0 -2
- tunacode/cli/commands/registry.py +8 -7
- tunacode/cli/commands/slash/loader.py +2 -1
- tunacode/cli/commands/slash/validator.py +2 -1
- tunacode/cli/main.py +19 -1
- tunacode/cli/repl.py +90 -229
- tunacode/cli/repl_components/command_parser.py +2 -1
- tunacode/cli/repl_components/error_recovery.py +8 -5
- tunacode/cli/repl_components/output_display.py +1 -10
- tunacode/cli/repl_components/tool_executor.py +1 -13
- tunacode/configuration/defaults.py +2 -2
- tunacode/configuration/key_descriptions.py +284 -0
- tunacode/configuration/settings.py +0 -1
- tunacode/constants.py +6 -42
- tunacode/core/agents/__init__.py +43 -2
- tunacode/core/agents/agent_components/__init__.py +7 -0
- tunacode/core/agents/agent_components/agent_config.py +162 -158
- tunacode/core/agents/agent_components/agent_helpers.py +31 -2
- tunacode/core/agents/agent_components/node_processor.py +180 -146
- tunacode/core/agents/agent_components/response_state.py +123 -6
- tunacode/core/agents/agent_components/state_transition.py +116 -0
- tunacode/core/agents/agent_components/streaming.py +296 -0
- tunacode/core/agents/agent_components/task_completion.py +19 -6
- tunacode/core/agents/agent_components/tool_buffer.py +21 -1
- tunacode/core/agents/agent_components/tool_executor.py +10 -0
- tunacode/core/agents/main.py +522 -370
- tunacode/core/agents/main_legact.py +538 -0
- tunacode/core/agents/prompts.py +66 -0
- tunacode/core/agents/utils.py +29 -122
- tunacode/core/setup/__init__.py +0 -2
- tunacode/core/setup/config_setup.py +88 -227
- tunacode/core/setup/config_wizard.py +230 -0
- tunacode/core/setup/coordinator.py +2 -1
- tunacode/core/state.py +16 -64
- tunacode/core/token_usage/usage_tracker.py +3 -1
- tunacode/core/tool_authorization.py +352 -0
- tunacode/core/tool_handler.py +67 -60
- tunacode/prompts/system.xml +751 -0
- tunacode/services/mcp.py +97 -1
- tunacode/setup.py +0 -23
- tunacode/tools/base.py +54 -1
- tunacode/tools/bash.py +14 -0
- tunacode/tools/glob.py +4 -2
- tunacode/tools/grep.py +7 -17
- tunacode/tools/prompts/glob_prompt.xml +1 -1
- tunacode/tools/prompts/grep_prompt.xml +1 -0
- tunacode/tools/prompts/list_dir_prompt.xml +1 -1
- tunacode/tools/prompts/react_prompt.xml +23 -0
- tunacode/tools/prompts/read_file_prompt.xml +1 -1
- tunacode/tools/react.py +153 -0
- tunacode/tools/run_command.py +15 -0
- tunacode/types.py +14 -79
- tunacode/ui/completers.py +434 -50
- tunacode/ui/config_dashboard.py +585 -0
- tunacode/ui/console.py +63 -11
- tunacode/ui/input.py +8 -3
- tunacode/ui/keybindings.py +0 -18
- tunacode/ui/model_selector.py +395 -0
- tunacode/ui/output.py +40 -19
- tunacode/ui/panels.py +173 -49
- tunacode/ui/path_heuristics.py +91 -0
- tunacode/ui/prompt_manager.py +1 -20
- tunacode/ui/tool_ui.py +30 -8
- tunacode/utils/api_key_validation.py +93 -0
- tunacode/utils/config_comparator.py +340 -0
- tunacode/utils/models_registry.py +593 -0
- tunacode/utils/text_utils.py +18 -1
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/METADATA +80 -12
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/RECORD +78 -74
- tunacode/cli/commands/implementations/plan.py +0 -50
- tunacode/cli/commands/implementations/todo.py +0 -217
- tunacode/context.py +0 -71
- tunacode/core/setup/git_safety_setup.py +0 -186
- tunacode/prompts/system.md +0 -359
- tunacode/prompts/system.md.bak +0 -487
- tunacode/tools/exit_plan_mode.py +0 -273
- tunacode/tools/present_plan.py +0 -288
- tunacode/tools/prompts/exit_plan_mode_prompt.xml +0 -25
- tunacode/tools/prompts/present_plan_prompt.xml +0 -20
- tunacode/tools/prompts/todo_prompt.xml +0 -96
- tunacode/tools/todo.py +0 -456
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
- {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)
|
tunacode/core/tool_handler.py
CHANGED
|
@@ -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
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
True if confirmation is required, False otherwise.
|
|
43
89
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
+
Structured confirmation request for UI.
|
|
103
124
|
"""
|
|
104
|
-
|
|
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)
|