kollabor 0.4.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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- system_prompt/example_with_trender.md +47 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"""Tool execution engine for terminal commands, MCP tools, and file operations.
|
|
2
|
+
|
|
3
|
+
Provides unified execution interface for terminal commands, MCP tool calls, and
|
|
4
|
+
file operations with proper error handling, logging, and result processing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import subprocess
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from .mcp_integration import MCPIntegration
|
|
15
|
+
from .file_operations_executor import FileOperationsExecutor
|
|
16
|
+
from ..events.models import EventType
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ToolExecutionResult:
|
|
22
|
+
"""Result of tool execution."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, tool_id: str, tool_type: str, success: bool,
|
|
25
|
+
output: str = "", error: str = "", execution_time: float = 0.0,
|
|
26
|
+
metadata: Dict[str, Any] = None):
|
|
27
|
+
"""Initialize tool execution result.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
tool_id: Unique identifier for the tool
|
|
31
|
+
tool_type: Type of tool (terminal, mcp_tool, file_edit, etc.)
|
|
32
|
+
success: Whether execution was successful
|
|
33
|
+
output: Tool output/result
|
|
34
|
+
error: Error message if failed
|
|
35
|
+
execution_time: Execution time in seconds
|
|
36
|
+
metadata: Additional metadata (e.g., diff_info for file edits)
|
|
37
|
+
"""
|
|
38
|
+
self.tool_id = tool_id
|
|
39
|
+
self.tool_type = tool_type
|
|
40
|
+
self.success = success
|
|
41
|
+
self.output = output
|
|
42
|
+
self.error = error
|
|
43
|
+
self.execution_time = execution_time
|
|
44
|
+
self.metadata = metadata or {}
|
|
45
|
+
self.timestamp = time.time()
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
48
|
+
"""Convert result to dictionary."""
|
|
49
|
+
return {
|
|
50
|
+
"tool_id": self.tool_id,
|
|
51
|
+
"tool_type": self.tool_type,
|
|
52
|
+
"success": self.success,
|
|
53
|
+
"output": self.output,
|
|
54
|
+
"error": self.error,
|
|
55
|
+
"execution_time": self.execution_time,
|
|
56
|
+
"timestamp": self.timestamp
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
"""String representation of result."""
|
|
61
|
+
status = "SUCCESS" if self.success else "FAILED"
|
|
62
|
+
return f"[{status}] {self.tool_type}:{self.tool_id} ({self.execution_time:.2f}s)"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ToolExecutor:
|
|
66
|
+
"""Execute tools with unified interface for terminal, MCP, and file operations.
|
|
67
|
+
|
|
68
|
+
Handles execution of terminal commands, MCP tool calls, and file operations
|
|
69
|
+
with proper error handling, timeouts, and result logging.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, mcp_integration: MCPIntegration, event_bus,
|
|
73
|
+
terminal_timeout: int = 90, mcp_timeout: int = 180, config=None):
|
|
74
|
+
"""Initialize tool executor.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
mcp_integration: MCP integration instance
|
|
78
|
+
event_bus: Event bus for hook emissions
|
|
79
|
+
terminal_timeout: Timeout for terminal commands in seconds
|
|
80
|
+
mcp_timeout: Timeout for MCP tool calls in seconds
|
|
81
|
+
config: Configuration manager (optional)
|
|
82
|
+
"""
|
|
83
|
+
self.mcp_integration = mcp_integration
|
|
84
|
+
self.event_bus = event_bus
|
|
85
|
+
self.terminal_timeout = terminal_timeout
|
|
86
|
+
self.mcp_timeout = mcp_timeout
|
|
87
|
+
|
|
88
|
+
# File operations executor
|
|
89
|
+
self.file_ops_executor = FileOperationsExecutor(config=config)
|
|
90
|
+
|
|
91
|
+
# Execution statistics
|
|
92
|
+
self.stats = {
|
|
93
|
+
"total_executions": 0,
|
|
94
|
+
"successful_executions": 0,
|
|
95
|
+
"failed_executions": 0,
|
|
96
|
+
"terminal_executions": 0,
|
|
97
|
+
"mcp_executions": 0,
|
|
98
|
+
"file_op_executions": 0,
|
|
99
|
+
"total_execution_time": 0.0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
logger.info("Tool executor initialized with terminal, MCP, and file operations support")
|
|
103
|
+
|
|
104
|
+
async def execute_tool(self, tool_data: Dict[str, Any]) -> ToolExecutionResult:
|
|
105
|
+
"""Execute a single tool (terminal, MCP, or file operation).
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
tool_data: Tool information from ResponseParser
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Tool execution result
|
|
112
|
+
"""
|
|
113
|
+
tool_type = tool_data.get("type", "unknown")
|
|
114
|
+
tool_id = tool_data.get("id", "unknown")
|
|
115
|
+
|
|
116
|
+
start_time = time.time()
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
# Emit pre-execution hook
|
|
120
|
+
await self.event_bus.emit_with_hooks(
|
|
121
|
+
EventType.TOOL_CALL_PRE,
|
|
122
|
+
{"tool_data": tool_data},
|
|
123
|
+
"tool_executor"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Execute based on tool type
|
|
127
|
+
try:
|
|
128
|
+
logger.debug(f"Executing tool {tool_id} of type {tool_type}")
|
|
129
|
+
try:
|
|
130
|
+
if tool_type == "terminal":
|
|
131
|
+
logger.debug(f"About to call _execute_terminal_command for {tool_id}")
|
|
132
|
+
result = await self._execute_terminal_command(tool_data)
|
|
133
|
+
logger.debug(f"_execute_terminal_command completed for {tool_id}")
|
|
134
|
+
elif tool_type == "mcp_tool":
|
|
135
|
+
logger.debug(f"About to call _execute_mcp_tool for {tool_id}")
|
|
136
|
+
result = await self._execute_mcp_tool(tool_data)
|
|
137
|
+
logger.debug(f"_execute_mcp_tool completed for {tool_id}")
|
|
138
|
+
elif tool_type.startswith("file_"):
|
|
139
|
+
# File operation
|
|
140
|
+
logger.debug(f"About to call _execute_file_operation for {tool_id}")
|
|
141
|
+
result = await self._execute_file_operation(tool_data)
|
|
142
|
+
logger.debug(f"_execute_file_operation completed for {tool_id}")
|
|
143
|
+
else:
|
|
144
|
+
result = ToolExecutionResult(
|
|
145
|
+
tool_id=tool_id,
|
|
146
|
+
tool_type=tool_type,
|
|
147
|
+
success=False,
|
|
148
|
+
error=f"Unknown tool type: {tool_type}"
|
|
149
|
+
)
|
|
150
|
+
logger.debug(f"Tool {tool_id} execution result: success={result.success}")
|
|
151
|
+
except Exception as inner_e:
|
|
152
|
+
import traceback
|
|
153
|
+
inner_trace = traceback.format_exc()
|
|
154
|
+
logger.error(f"Inner execution error for {tool_id}: {str(inner_e)}")
|
|
155
|
+
logger.error(f"Inner execution traceback for {tool_id}: {inner_trace}")
|
|
156
|
+
raise # Re-raise for outer handler
|
|
157
|
+
except Exception as e:
|
|
158
|
+
import traceback
|
|
159
|
+
error_details = f"Tool execution exception: {str(e)}\nTraceback: {traceback.format_exc()}"
|
|
160
|
+
logger.error(f"Critical error during tool {tool_id} execution: {error_details}")
|
|
161
|
+
result = ToolExecutionResult(
|
|
162
|
+
tool_id=tool_id,
|
|
163
|
+
tool_type=tool_type,
|
|
164
|
+
success=False,
|
|
165
|
+
error=f"Tool execution error: {str(e)}"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Update execution time
|
|
169
|
+
result.execution_time = time.time() - start_time
|
|
170
|
+
|
|
171
|
+
# Emit post-execution hook
|
|
172
|
+
await self.event_bus.emit_with_hooks(
|
|
173
|
+
EventType.TOOL_CALL_POST,
|
|
174
|
+
{
|
|
175
|
+
"tool_data": tool_data,
|
|
176
|
+
"result": result.to_dict()
|
|
177
|
+
},
|
|
178
|
+
"tool_executor"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Update statistics
|
|
182
|
+
self._update_stats(result)
|
|
183
|
+
|
|
184
|
+
logger.info(f"Tool execution completed: {result}")
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
execution_time = time.time() - start_time
|
|
189
|
+
error_result = ToolExecutionResult(
|
|
190
|
+
tool_id=tool_id,
|
|
191
|
+
tool_type=tool_type,
|
|
192
|
+
success=False,
|
|
193
|
+
error=f"Execution error: {str(e)}",
|
|
194
|
+
execution_time=execution_time
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self._update_stats(error_result)
|
|
198
|
+
logger.error(f"Tool execution failed: {e}")
|
|
199
|
+
return error_result
|
|
200
|
+
|
|
201
|
+
async def execute_all_tools(self, tools: List[Dict[str, Any]]) -> List[ToolExecutionResult]:
|
|
202
|
+
"""Execute multiple tools in sequence.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
tools: List of tool data from ResponseParser
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List of execution results in order
|
|
209
|
+
"""
|
|
210
|
+
if not tools:
|
|
211
|
+
return []
|
|
212
|
+
|
|
213
|
+
logger.info(f"Executing {len(tools)} tools in sequence")
|
|
214
|
+
results = []
|
|
215
|
+
|
|
216
|
+
for i, tool_data in enumerate(tools):
|
|
217
|
+
logger.debug(f"Executing tool {i+1}/{len(tools)}: {tool_data.get('id', 'unknown')}")
|
|
218
|
+
|
|
219
|
+
result = await self.execute_tool(tool_data)
|
|
220
|
+
results.append(result)
|
|
221
|
+
|
|
222
|
+
# Log intermediate result
|
|
223
|
+
if result.success:
|
|
224
|
+
logger.debug(f"Tool {i+1} succeeded: {len(result.output)} chars output")
|
|
225
|
+
else:
|
|
226
|
+
logger.warning(f"Tool {i+1} failed: {result.error}")
|
|
227
|
+
# Continue executing remaining tools even if one fails
|
|
228
|
+
|
|
229
|
+
logger.info(f"Tool execution batch completed: "
|
|
230
|
+
f"{sum(1 for r in results if r.success)}/{len(results)} successful")
|
|
231
|
+
|
|
232
|
+
return results
|
|
233
|
+
|
|
234
|
+
async def _execute_terminal_command(self, tool_data: Dict[str, Any]) -> ToolExecutionResult:
|
|
235
|
+
"""Execute a terminal command.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
tool_data: Terminal tool data with command
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Execution result
|
|
242
|
+
"""
|
|
243
|
+
command = tool_data.get("command", "").strip()
|
|
244
|
+
tool_id = tool_data.get("id", "unknown")
|
|
245
|
+
|
|
246
|
+
if not command:
|
|
247
|
+
return ToolExecutionResult(
|
|
248
|
+
tool_id=tool_id,
|
|
249
|
+
tool_type="terminal",
|
|
250
|
+
success=False,
|
|
251
|
+
error="Empty command"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
logger.debug(f"Executing terminal command: {command[:100]}...")
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Execute command with timeout
|
|
258
|
+
process = await asyncio.create_subprocess_shell(
|
|
259
|
+
command,
|
|
260
|
+
stdout=asyncio.subprocess.PIPE,
|
|
261
|
+
stderr=asyncio.subprocess.PIPE,
|
|
262
|
+
cwd=Path.cwd()
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
stdout, stderr = await asyncio.wait_for(
|
|
267
|
+
process.communicate(),
|
|
268
|
+
timeout=self.terminal_timeout
|
|
269
|
+
)
|
|
270
|
+
except asyncio.TimeoutError:
|
|
271
|
+
process.kill()
|
|
272
|
+
await process.wait()
|
|
273
|
+
return ToolExecutionResult(
|
|
274
|
+
tool_id=tool_id,
|
|
275
|
+
tool_type="terminal",
|
|
276
|
+
success=False,
|
|
277
|
+
error=f"Command timed out after {self.terminal_timeout} seconds"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Process results
|
|
281
|
+
stdout_text = stdout.decode('utf-8', errors='replace')
|
|
282
|
+
stderr_text = stderr.decode('utf-8', errors='replace')
|
|
283
|
+
|
|
284
|
+
success = process.returncode == 0
|
|
285
|
+
output = stdout_text if success else stderr_text
|
|
286
|
+
error = "" if success else f"Exit code {process.returncode}: {stderr_text}"
|
|
287
|
+
|
|
288
|
+
return ToolExecutionResult(
|
|
289
|
+
tool_id=tool_id,
|
|
290
|
+
tool_type="terminal",
|
|
291
|
+
success=success,
|
|
292
|
+
output=output,
|
|
293
|
+
error=error
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
except Exception as e:
|
|
297
|
+
import traceback
|
|
298
|
+
error_details = f"Execution exception: {str(e)}\nTraceback: {traceback.format_exc()}"
|
|
299
|
+
logger.error(f"Terminal execution failed for command '{command}': {error_details}")
|
|
300
|
+
return ToolExecutionResult(
|
|
301
|
+
tool_id=tool_id,
|
|
302
|
+
tool_type="terminal",
|
|
303
|
+
success=False,
|
|
304
|
+
error=f"Execution error: {str(e)}"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
async def _execute_mcp_tool(self, tool_data: Dict[str, Any]) -> ToolExecutionResult:
|
|
308
|
+
"""Execute an MCP tool call.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
tool_data: MCP tool data with name and arguments
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Execution result
|
|
315
|
+
"""
|
|
316
|
+
tool_name = tool_data.get("name", "")
|
|
317
|
+
tool_arguments = tool_data.get("arguments", {})
|
|
318
|
+
tool_id = tool_data.get("id", "unknown")
|
|
319
|
+
|
|
320
|
+
if not tool_name:
|
|
321
|
+
return ToolExecutionResult(
|
|
322
|
+
tool_id=tool_id,
|
|
323
|
+
tool_type="mcp_tool",
|
|
324
|
+
success=False,
|
|
325
|
+
error="Missing tool name"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
logger.debug(f"Executing MCP tool: {tool_name} with args {tool_arguments}")
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
# Call MCP tool with timeout
|
|
332
|
+
mcp_result = await asyncio.wait_for(
|
|
333
|
+
self.mcp_integration.call_mcp_tool(tool_name, tool_arguments),
|
|
334
|
+
timeout=self.mcp_timeout
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Process MCP result
|
|
338
|
+
if "error" in mcp_result:
|
|
339
|
+
return ToolExecutionResult(
|
|
340
|
+
tool_id=tool_id,
|
|
341
|
+
tool_type="mcp_tool",
|
|
342
|
+
success=False,
|
|
343
|
+
error=mcp_result["error"]
|
|
344
|
+
)
|
|
345
|
+
else:
|
|
346
|
+
# Format MCP output for display
|
|
347
|
+
output = self._format_mcp_output(mcp_result)
|
|
348
|
+
|
|
349
|
+
return ToolExecutionResult(
|
|
350
|
+
tool_id=tool_id,
|
|
351
|
+
tool_type="mcp_tool",
|
|
352
|
+
success=True,
|
|
353
|
+
output=output
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
except asyncio.TimeoutError:
|
|
357
|
+
return ToolExecutionResult(
|
|
358
|
+
tool_id=tool_id,
|
|
359
|
+
tool_type="mcp_tool",
|
|
360
|
+
success=False,
|
|
361
|
+
error=f"MCP tool timed out after {self.mcp_timeout} seconds"
|
|
362
|
+
)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
return ToolExecutionResult(
|
|
365
|
+
tool_id=tool_id,
|
|
366
|
+
tool_type="mcp_tool",
|
|
367
|
+
success=False,
|
|
368
|
+
error=f"MCP execution error: {str(e)}"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def _format_mcp_output(self, mcp_result: Dict[str, Any]) -> str:
|
|
372
|
+
"""Format MCP tool result for display.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
mcp_result: Raw MCP result dictionary
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Formatted output string
|
|
379
|
+
"""
|
|
380
|
+
# Handle different MCP result formats
|
|
381
|
+
if "content" in mcp_result:
|
|
382
|
+
# Standard MCP content format
|
|
383
|
+
content = mcp_result["content"]
|
|
384
|
+
if isinstance(content, list) and content:
|
|
385
|
+
# Multiple content blocks
|
|
386
|
+
parts = []
|
|
387
|
+
for block in content:
|
|
388
|
+
if isinstance(block, dict):
|
|
389
|
+
if block.get("type") == "text":
|
|
390
|
+
parts.append(block.get("text", ""))
|
|
391
|
+
else:
|
|
392
|
+
parts.append(str(block))
|
|
393
|
+
else:
|
|
394
|
+
parts.append(str(block))
|
|
395
|
+
return "\n".join(parts)
|
|
396
|
+
else:
|
|
397
|
+
return str(content)
|
|
398
|
+
|
|
399
|
+
elif "output" in mcp_result:
|
|
400
|
+
# Simple output format
|
|
401
|
+
return str(mcp_result["output"])
|
|
402
|
+
|
|
403
|
+
elif "result" in mcp_result:
|
|
404
|
+
# JSON-RPC result format
|
|
405
|
+
return str(mcp_result["result"])
|
|
406
|
+
|
|
407
|
+
else:
|
|
408
|
+
# Fallback: stringify entire result
|
|
409
|
+
return str(mcp_result)
|
|
410
|
+
|
|
411
|
+
async def _execute_file_operation(self, tool_data: Dict[str, Any]) -> ToolExecutionResult:
|
|
412
|
+
"""Execute a file operation.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
tool_data: File operation data from parser
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Tool execution result
|
|
419
|
+
"""
|
|
420
|
+
tool_id = tool_data.get("id", "unknown")
|
|
421
|
+
tool_type = tool_data.get("type", "unknown")
|
|
422
|
+
|
|
423
|
+
logger.debug(f"Executing file operation: {tool_type}")
|
|
424
|
+
|
|
425
|
+
# Run file operation synchronously (file I/O is blocking anyway)
|
|
426
|
+
# Use asyncio.to_thread to avoid blocking the event loop
|
|
427
|
+
try:
|
|
428
|
+
result_dict = await asyncio.to_thread(
|
|
429
|
+
self.file_ops_executor.execute_operation,
|
|
430
|
+
tool_data
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Convert to ToolExecutionResult, preserving metadata (e.g., diff_info)
|
|
434
|
+
metadata = {}
|
|
435
|
+
if "diff_info" in result_dict:
|
|
436
|
+
metadata["diff_info"] = result_dict["diff_info"]
|
|
437
|
+
|
|
438
|
+
return ToolExecutionResult(
|
|
439
|
+
tool_id=tool_id,
|
|
440
|
+
tool_type=tool_type,
|
|
441
|
+
success=result_dict.get("success", False),
|
|
442
|
+
output=result_dict.get("output", ""),
|
|
443
|
+
error=result_dict.get("error", ""),
|
|
444
|
+
metadata=metadata
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
except Exception as e:
|
|
448
|
+
import traceback
|
|
449
|
+
error_trace = traceback.format_exc()
|
|
450
|
+
logger.error(f"File operation execution failed: {error_trace}")
|
|
451
|
+
return ToolExecutionResult(
|
|
452
|
+
tool_id=tool_id,
|
|
453
|
+
tool_type=tool_type,
|
|
454
|
+
success=False,
|
|
455
|
+
error=f"File operation error: {str(e)}"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def _update_stats(self, result: ToolExecutionResult):
|
|
459
|
+
"""Update execution statistics.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
result: Tool execution result
|
|
463
|
+
"""
|
|
464
|
+
self.stats["total_executions"] += 1
|
|
465
|
+
self.stats["total_execution_time"] += result.execution_time
|
|
466
|
+
|
|
467
|
+
if result.success:
|
|
468
|
+
self.stats["successful_executions"] += 1
|
|
469
|
+
else:
|
|
470
|
+
self.stats["failed_executions"] += 1
|
|
471
|
+
|
|
472
|
+
if result.tool_type == "terminal":
|
|
473
|
+
self.stats["terminal_executions"] += 1
|
|
474
|
+
elif result.tool_type == "mcp_tool":
|
|
475
|
+
self.stats["mcp_executions"] += 1
|
|
476
|
+
elif result.tool_type.startswith("file_"):
|
|
477
|
+
self.stats["file_op_executions"] += 1
|
|
478
|
+
|
|
479
|
+
def get_execution_stats(self) -> Dict[str, Any]:
|
|
480
|
+
"""Get execution statistics.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Dictionary of execution statistics
|
|
484
|
+
"""
|
|
485
|
+
total = self.stats["total_executions"]
|
|
486
|
+
if total == 0:
|
|
487
|
+
return {**self.stats, "success_rate": 0.0, "average_time": 0.0}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
**self.stats,
|
|
491
|
+
"success_rate": self.stats["successful_executions"] / total,
|
|
492
|
+
"average_time": self.stats["total_execution_time"] / total
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
def format_result_for_conversation(self, result: ToolExecutionResult) -> str:
|
|
496
|
+
"""Format tool result for conversation history.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
result: Tool execution result
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
Formatted string for conversation logging
|
|
503
|
+
"""
|
|
504
|
+
if result.success:
|
|
505
|
+
return f"[{result.tool_type}] {result.output}"
|
|
506
|
+
else:
|
|
507
|
+
return f"[{result.tool_type}] ERROR: {result.error}"
|
|
508
|
+
|
|
509
|
+
def reset_stats(self):
|
|
510
|
+
"""Reset execution statistics."""
|
|
511
|
+
self.stats = {
|
|
512
|
+
"total_executions": 0,
|
|
513
|
+
"successful_executions": 0,
|
|
514
|
+
"failed_executions": 0,
|
|
515
|
+
"terminal_executions": 0,
|
|
516
|
+
"mcp_executions": 0,
|
|
517
|
+
"file_op_executions": 0,
|
|
518
|
+
"total_execution_time": 0.0
|
|
519
|
+
}
|
|
520
|
+
logger.info("Tool execution statistics reset")
|
core/logging/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Logging module for centralized logging configuration."""
|
|
2
|
+
|
|
3
|
+
from .setup import (
|
|
4
|
+
setup_bootstrap_logging,
|
|
5
|
+
setup_from_config,
|
|
6
|
+
get_current_config,
|
|
7
|
+
is_configured,
|
|
8
|
+
CompactFormatter,
|
|
9
|
+
LoggingSetup
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'setup_bootstrap_logging',
|
|
14
|
+
'setup_from_config',
|
|
15
|
+
'get_current_config',
|
|
16
|
+
'is_configured',
|
|
17
|
+
'CompactFormatter',
|
|
18
|
+
'LoggingSetup'
|
|
19
|
+
]
|