minion-code 0.1.0__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.
Files changed (59) hide show
  1. examples/advance_tui.py +508 -0
  2. examples/agent_with_todos.py +165 -0
  3. examples/file_freshness_example.py +97 -0
  4. examples/file_watching_example.py +110 -0
  5. examples/interruptible_tui.py +5 -0
  6. examples/message_response_children_demo.py +226 -0
  7. examples/rich_example.py +4 -0
  8. examples/simple_file_watching.py +57 -0
  9. examples/simple_tui.py +267 -0
  10. examples/simple_usage.py +69 -0
  11. minion_code/__init__.py +16 -0
  12. minion_code/agents/__init__.py +11 -0
  13. minion_code/agents/code_agent.py +320 -0
  14. minion_code/cli.py +502 -0
  15. minion_code/commands/__init__.py +90 -0
  16. minion_code/commands/clear_command.py +70 -0
  17. minion_code/commands/help_command.py +90 -0
  18. minion_code/commands/history_command.py +104 -0
  19. minion_code/commands/quit_command.py +32 -0
  20. minion_code/commands/status_command.py +115 -0
  21. minion_code/commands/tools_command.py +86 -0
  22. minion_code/commands/version_command.py +104 -0
  23. minion_code/components/Message.py +304 -0
  24. minion_code/components/MessageResponse.py +188 -0
  25. minion_code/components/PromptInput.py +534 -0
  26. minion_code/components/__init__.py +29 -0
  27. minion_code/screens/REPL.py +925 -0
  28. minion_code/screens/__init__.py +4 -0
  29. minion_code/services/__init__.py +50 -0
  30. minion_code/services/event_system.py +108 -0
  31. minion_code/services/file_freshness_service.py +582 -0
  32. minion_code/tools/__init__.py +69 -0
  33. minion_code/tools/bash_tool.py +58 -0
  34. minion_code/tools/file_edit_tool.py +238 -0
  35. minion_code/tools/file_read_tool.py +73 -0
  36. minion_code/tools/file_write_tool.py +36 -0
  37. minion_code/tools/glob_tool.py +58 -0
  38. minion_code/tools/grep_tool.py +105 -0
  39. minion_code/tools/ls_tool.py +65 -0
  40. minion_code/tools/multi_edit_tool.py +271 -0
  41. minion_code/tools/python_interpreter_tool.py +105 -0
  42. minion_code/tools/todo_read_tool.py +100 -0
  43. minion_code/tools/todo_write_tool.py +234 -0
  44. minion_code/tools/user_input_tool.py +53 -0
  45. minion_code/types.py +88 -0
  46. minion_code/utils/__init__.py +44 -0
  47. minion_code/utils/mcp_loader.py +211 -0
  48. minion_code/utils/todo_file_utils.py +110 -0
  49. minion_code/utils/todo_storage.py +149 -0
  50. minion_code-0.1.0.dist-info/METADATA +350 -0
  51. minion_code-0.1.0.dist-info/RECORD +59 -0
  52. minion_code-0.1.0.dist-info/WHEEL +5 -0
  53. minion_code-0.1.0.dist-info/entry_points.txt +4 -0
  54. minion_code-0.1.0.dist-info/licenses/LICENSE +661 -0
  55. minion_code-0.1.0.dist-info/top_level.txt +3 -0
  56. tests/__init__.py +1 -0
  57. tests/test_basic.py +20 -0
  58. tests/test_readonly_tools.py +102 -0
  59. tests/test_tools.py +83 -0
examples/simple_tui.py ADDED
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Simple TUI using MinionCodeAgent
5
+
6
+ This example shows how the new MinionCodeAgent class simplifies
7
+ the TUI implementation by handling all the tool setup internally.
8
+
9
+ Compare this with minion_agent_tui.py to see the reduction in boilerplate code.
10
+ """
11
+
12
+ import asyncio
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # Add project root to path
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
19
+ from minion_code import MinionCodeAgent
20
+ from minion_code.commands import command_registry
21
+ from rich.console import Console
22
+ from rich.panel import Panel
23
+ from rich.text import Text
24
+ from rich.markdown import Markdown
25
+ from rich.table import Table
26
+ from rich.progress import Progress, SpinnerColumn, TextColumn
27
+ from rich.prompt import Prompt
28
+
29
+
30
+ class SimpleTUI:
31
+ """Simplified TUI using MinionCodeAgent."""
32
+
33
+ def __init__(self):
34
+ self.agent = None
35
+ self.running = True
36
+ self.console = Console()
37
+
38
+ async def setup(self):
39
+ """Setup the agent."""
40
+ with Progress(
41
+ SpinnerColumn(),
42
+ TextColumn("[progress.description]{task.description}"),
43
+ console=self.console,
44
+ ) as progress:
45
+ task = progress.add_task("šŸ”§ Setting up MinionCodeAgent...", total=None)
46
+
47
+ # Much simpler setup - no manual tool configuration needed
48
+ self.agent = await MinionCodeAgent.create(
49
+ name="Simple TUI Assistant",
50
+ llm="gpt-4o-mini",
51
+ )
52
+
53
+ progress.update(task, completed=True)
54
+
55
+ success_panel = Panel(
56
+ f"āœ… Agent ready with [bold green]{len(self.agent.tools)}[/bold green] tools!",
57
+ title="[bold green]Setup Complete[/bold green]",
58
+ border_style="green"
59
+ )
60
+ self.console.print(success_panel)
61
+
62
+ def show_help(self):
63
+ """Show help information."""
64
+ help_table = Table(title="šŸ“š Simple TUI Help", show_header=True, header_style="bold blue")
65
+ help_table.add_column("Command", style="cyan", no_wrap=True)
66
+ help_table.add_column("Description", style="white")
67
+
68
+ help_table.add_row("help", "Show this help")
69
+ help_table.add_row("tools", "List available tools")
70
+ help_table.add_row("history", "Show conversation history")
71
+ help_table.add_row("clear", "Clear history")
72
+ help_table.add_row("quit", "Exit")
73
+
74
+ self.console.print(help_table)
75
+ self.console.print("\nšŸ’” [italic]Just type your message to chat with the AI agent![/italic]")
76
+
77
+ async def process_input(self, user_input: str):
78
+ """Process user input."""
79
+ user_input = user_input.strip()
80
+
81
+ # Check if it's a command (starts with /)
82
+ if user_input.startswith('/'):
83
+ await self.process_command(user_input)
84
+ return
85
+
86
+ # Process with agent
87
+ try:
88
+ with Progress(
89
+ SpinnerColumn(),
90
+ TextColumn("[progress.description]{task.description}"),
91
+ console=self.console,
92
+ ) as progress:
93
+ task = progress.add_task("šŸ¤– Processing...", total=None)
94
+ response = await self.agent.run_async(user_input)
95
+ progress.update(task, completed=True)
96
+
97
+ # Display agent response with rich formatting
98
+ if "```" in response.answer:
99
+ # If response contains code blocks, render as markdown
100
+ agent_content = Markdown(response.answer)
101
+ else:
102
+ agent_content = response.answer
103
+
104
+ response_panel = Panel(
105
+ agent_content,
106
+ title="šŸ¤– [bold green]Agent Response[/bold green]",
107
+ border_style="green"
108
+ )
109
+ self.console.print(response_panel)
110
+
111
+ except Exception as e:
112
+ error_panel = Panel(
113
+ f"āŒ [bold red]Error: {e}[/bold red]",
114
+ title="[bold red]Error[/bold red]",
115
+ border_style="red"
116
+ )
117
+ self.console.print(error_panel)
118
+
119
+ async def process_command(self, command_input: str):
120
+ """Process a command input."""
121
+ # Remove the leading /
122
+ command_input = command_input[1:] if command_input.startswith('/') else command_input
123
+
124
+ # Split command and arguments
125
+ parts = command_input.split(' ', 1)
126
+ command_name = parts[0].lower()
127
+ args = parts[1] if len(parts) > 1 else ""
128
+
129
+ # Get command class
130
+ command_class = command_registry.get_command(command_name)
131
+ if not command_class:
132
+ error_panel = Panel(
133
+ f"āŒ [bold red]Unknown command: /{command_name}[/bold red]\n"
134
+ f"šŸ’” [italic]Use '/help' to see available commands[/italic]",
135
+ title="[bold red]Error[/bold red]",
136
+ border_style="red"
137
+ )
138
+ self.console.print(error_panel)
139
+ return
140
+
141
+ # Create and execute command
142
+ try:
143
+ command_instance = command_class(self.console, self.agent)
144
+
145
+ # Special handling for quit command
146
+ if command_name in ["quit", "exit", "q", "bye"]:
147
+ command_instance._tui_instance = self
148
+
149
+ await command_instance.execute(args)
150
+
151
+ except Exception as e:
152
+ error_panel = Panel(
153
+ f"āŒ [bold red]Error executing command /{command_name}: {e}[/bold red]",
154
+ title="[bold red]Command Error[/bold red]",
155
+ border_style="red"
156
+ )
157
+ self.console.print(error_panel)
158
+
159
+ def show_tools(self):
160
+ """Show available tools in a beautiful table."""
161
+ if not self.agent or not self.agent.tools:
162
+ self.console.print("āŒ No tools available")
163
+ return
164
+
165
+ tools_table = Table(title="šŸ› ļø Available Tools", show_header=True, header_style="bold magenta")
166
+ tools_table.add_column("Tool Name", style="cyan", no_wrap=True)
167
+ tools_table.add_column("Description", style="white")
168
+ tools_table.add_column("Type", style="yellow")
169
+
170
+ for tool in self.agent.tools:
171
+ tool_type = "Read-only" if getattr(tool, 'readonly', False) else "Read-write"
172
+ tools_table.add_row(
173
+ tool.name,
174
+ tool.description[:60] + "..." if len(tool.description) > 60 else tool.description,
175
+ tool_type
176
+ )
177
+
178
+ self.console.print(tools_table)
179
+
180
+ def show_history(self):
181
+ """Show conversation history in a beautiful format."""
182
+ if not self.agent:
183
+ return
184
+
185
+ history = self.agent.get_conversation_history()
186
+ if not history:
187
+ no_history_panel = Panel(
188
+ "šŸ“ [italic]No conversation history yet.[/italic]",
189
+ title="[bold blue]History[/bold blue]",
190
+ border_style="blue"
191
+ )
192
+ self.console.print(no_history_panel)
193
+ return
194
+
195
+ history_panel = Panel(
196
+ f"šŸ“ [bold blue]Conversation History ({len(history)} messages)[/bold blue]",
197
+ border_style="blue"
198
+ )
199
+ self.console.print(history_panel)
200
+
201
+ for i, entry in enumerate(history[-5:], 1): # Show last 5 messages
202
+ # User message
203
+ user_panel = Panel(
204
+ entry['user_message'][:200] + "..." if len(entry['user_message']) > 200 else entry['user_message'],
205
+ title=f"šŸ‘¤ [bold cyan]You (Message {len(history)-5+i})[/bold cyan]",
206
+ border_style="cyan"
207
+ )
208
+ self.console.print(user_panel)
209
+
210
+ # Agent response
211
+ agent_response = entry['agent_response'][:200] + "..." if len(entry['agent_response']) > 200 else entry['agent_response']
212
+ agent_panel = Panel(
213
+ agent_response,
214
+ title="šŸ¤– [bold green]Agent[/bold green]",
215
+ border_style="green"
216
+ )
217
+ self.console.print(agent_panel)
218
+ self.console.print() # Add spacing
219
+
220
+ async def run(self):
221
+ """Run the TUI."""
222
+ # Welcome banner
223
+ welcome_panel = Panel(
224
+ "šŸš€ [bold blue]Simple MinionCodeAgent TUI[/bold blue]\n"
225
+ "šŸ’” [italic]Use '/help' for commands or just chat with the agent![/italic]\n"
226
+ "šŸ›‘ [italic]Type '/quit' to exit[/italic]",
227
+ title="[bold magenta]Welcome[/bold magenta]",
228
+ border_style="magenta"
229
+ )
230
+ self.console.print(welcome_panel)
231
+
232
+ await self.setup()
233
+
234
+ while self.running:
235
+ try:
236
+ # Use rich prompt for better input experience
237
+ user_input = Prompt.ask(
238
+ "\n[bold cyan]šŸ‘¤ You[/bold cyan]",
239
+ console=self.console
240
+ ).strip()
241
+
242
+ if user_input:
243
+ await self.process_input(user_input)
244
+
245
+ except (EOFError, KeyboardInterrupt):
246
+ goodbye_panel = Panel(
247
+ "\nšŸ‘‹ [bold yellow]Goodbye![/bold yellow]",
248
+ title="[bold red]Exit[/bold red]",
249
+ border_style="red"
250
+ )
251
+ self.console.print(goodbye_panel)
252
+ break
253
+
254
+
255
+ async def main():
256
+ """Main function."""
257
+ tui = SimpleTUI()
258
+ await tui.run()
259
+
260
+
261
+ def run():
262
+ """Synchronous entry point."""
263
+ asyncio.run(main())
264
+
265
+
266
+ if __name__ == "__main__":
267
+ run()
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """Simple usage example of FileFreshnessService."""
3
+
4
+ import os
5
+ import sys
6
+
7
+ # Add parent directory to path for imports
8
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+ from minion_code.services import (
11
+ record_file_read,
12
+ record_file_edit,
13
+ check_file_freshness,
14
+ generate_file_modification_reminder,
15
+ add_event_listener,
16
+ emit_event,
17
+ )
18
+
19
+
20
+ def main():
21
+ """Simple usage demonstration."""
22
+
23
+ # Set up a simple event listener
24
+ def on_conflict(context):
25
+ print(f"āš ļø Conflict: {context.data['file_path']}")
26
+
27
+ add_event_listener('file:conflict', on_conflict)
28
+
29
+ # Create test file
30
+ test_file = "example.py"
31
+ with open(test_file, "w") as f:
32
+ f.write("print('Hello, World!')")
33
+
34
+ # Record reading the file
35
+ record_file_read(test_file)
36
+ print(f"āœ… Recorded reading {test_file}")
37
+
38
+ # Check freshness (should be fresh)
39
+ result = check_file_freshness(test_file)
40
+ print(f"šŸ“Š File is fresh: {result.is_fresh}")
41
+
42
+ # Simulate external modification
43
+ with open(test_file, "w") as f:
44
+ f.write("print('Modified externally!')")
45
+
46
+ # Check freshness again (should detect conflict)
47
+ result = check_file_freshness(test_file)
48
+ print(f"šŸ“Š After external change - Fresh: {result.is_fresh}, Conflict: {result.conflict}")
49
+
50
+ # Generate reminder for external modification
51
+ reminder = generate_file_modification_reminder(test_file)
52
+ if reminder:
53
+ print(f"šŸ’” Reminder: {reminder}")
54
+
55
+ # Record agent edit (resolves conflict)
56
+ record_file_edit(test_file, "print('Agent fixed this!')")
57
+ print(f"āœ… Agent edited {test_file}")
58
+
59
+ # Check freshness after agent edit
60
+ result = check_file_freshness(test_file)
61
+ print(f"šŸ“Š After agent edit - Fresh: {result.is_fresh}, Conflict: {result.conflict}")
62
+
63
+ # Cleanup
64
+ os.remove(test_file)
65
+ print("🧹 Cleaned up test file")
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Minion Code Tools Package
5
+
6
+ A collection of tools and enhanced agents for the Minion framework including
7
+ file operations, system commands, web interactions, and specialized agents
8
+ with dynamic system prompts and state management.
9
+ """
10
+
11
+ from . import tools
12
+ from . import agents
13
+ from .agents import MinionCodeAgent, create_minion_code_agent
14
+
15
+ __version__ = "0.1.0"
16
+ __all__ = ["tools", "agents", "MinionCodeAgent", "create_minion_code_agent"]
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Minion Code Agents Package
5
+
6
+ Enhanced agents with specialized capabilities for code development tasks.
7
+ """
8
+
9
+ from .code_agent import MinionCodeAgent,create_minion_code_agent
10
+
11
+ __all__ = ["MinionCodeAgent","create_minion_code_agent"]
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ MinionCodeAgent - Enhanced CodeAgent with minion_code tools
5
+
6
+ This module provides a specialized CodeAgent subclass that comes pre-configured
7
+ with all minion_code tools and optimized system prompts for code development tasks.
8
+
9
+ Key features:
10
+ - Pre-configured with all minion_code tools
11
+ - Optimized system prompt for coding tasks
12
+ - Async/sync support
13
+ - Easy setup and initialization
14
+ - Conversation history management
15
+ """
16
+
17
+ import asyncio
18
+ import logging
19
+ import traceback
20
+ from dataclasses import dataclass
21
+ from pathlib import Path
22
+ from typing import List, Optional, Union, Any
23
+ import sys
24
+
25
+ from minion.agents import CodeAgent
26
+
27
+ # Import all minion_code tools
28
+ from ..tools import (
29
+ FileReadTool,
30
+ FileWriteTool,
31
+ FileEditTool,
32
+ MultiEditTool,
33
+ BashTool,
34
+ GrepTool,
35
+ GlobTool,
36
+ LsTool,
37
+ PythonInterpreterTool,
38
+ UserInputTool,
39
+
40
+ TodoWriteTool,
41
+ TodoReadTool,
42
+ TOOL_MAPPING,
43
+ )
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+ # Todo reminder constants
48
+ INITIAL_REMINDER = (
49
+ '<reminder source="system" topic="todos">'
50
+ "System message: complex work should be tracked with the Todo tool. "
51
+ "Do not respond to this reminder and do not mention it to the user."
52
+ '</reminder>'
53
+ )
54
+
55
+ NAG_REMINDER = (
56
+ '<reminder source="system" topic="todos">'
57
+ "System notice: more than ten rounds passed without Todo usage. "
58
+ "Update the Todo board if the task still requires multiple steps. "
59
+ "Do not reply to or mention this reminder to the user."
60
+ '</reminder>'
61
+ )
62
+
63
+ @dataclass
64
+ class MinionCodeAgent(CodeAgent):
65
+ """
66
+ Enhanced CodeAgent with pre-configured minion_code tools.
67
+
68
+ This class wraps the Minion CodeAgent with all minion_code tools
69
+ and provides optimized system prompts for coding tasks.
70
+ """
71
+
72
+ DEFAULT_SYSTEM_PROMPT = (
73
+ "You are a coding agent operating INSIDE the user's repository at {workdir}.\n"
74
+ "Follow this loop strictly: plan briefly → use TOOLS to act directly on files/shell → report concise results.\n"
75
+ "Rules:\n"
76
+ "- Prefer taking actions with tools (read/write/edit/bash) over long prose.\n"
77
+ "- Keep outputs terse. Use bullet lists / checklists when summarizing.\n"
78
+ "- Never invent file paths. Ask via reads or list directories first if unsure.\n"
79
+ "- For edits, choose the right tool: string_edit for single string replacements, multi_edit for multiple changes to same file, file_edit for advanced operations.\n"
80
+ "- Always read files before editing to establish freshness tracking.\n"
81
+ "- For bash, avoid destructive or privileged commands; stay inside the workspace.\n"
82
+ "- Use the Todo tool to maintain multi-step plans when needed.\n"
83
+ "- After finishing, summarize what changed and how to run or test."
84
+ )
85
+
86
+ def __post_init__(self):
87
+ """Initialize the CodeAgent with thinking capabilities and optional state tracking."""
88
+ super().__post_init__()
89
+ self.conversation_history = []
90
+
91
+ async def pre_step(self, input_data, kwargs):
92
+ """Override pre_step to track iterations without todo usage."""
93
+ # Call parent pre_step first
94
+ result = await super().pre_step(input_data, kwargs)
95
+
96
+ # Initialize metadata if not exists
97
+ if not hasattr(self.state, 'metadata'):
98
+ self.state.metadata = {}
99
+ if "iteration_without_todos" not in self.state.metadata:
100
+ self.state.metadata["iteration_without_todos"] = 0
101
+
102
+ # Increment iteration counter
103
+ self.state.metadata["iteration_without_todos"] += 1
104
+
105
+ # Add nag reminder if more than 10 iterations without todo usage
106
+ if self.state.metadata["iteration_without_todos"] > 10:
107
+ self.state.history.append({
108
+ 'role': 'user',
109
+ 'content': NAG_REMINDER
110
+ })
111
+ # Reset counter to avoid spamming reminders
112
+ self.state.metadata["iteration_without_todos"] = 0
113
+
114
+ return result
115
+
116
+ return result
117
+
118
+ @classmethod
119
+ async def create(
120
+ cls,
121
+ name: str = "Minion Code Assistant",
122
+ llm: str = "sonnet",
123
+ system_prompt: Optional[str] = None,
124
+ workdir: Optional[Union[str, Path]] = None,
125
+ additional_tools: Optional[List[Any]] = None,
126
+ **kwargs
127
+ ) -> "MinionCodeAgent":
128
+ """
129
+ Create a new MinionCodeAgent with all minion_code tools.
130
+
131
+ Args:
132
+ name: Agent name
133
+ llm: LLM model to use
134
+ system_prompt: Custom system prompt (uses default if None)
135
+ workdir: Working directory (uses current if None)
136
+ additional_tools: Extra tools to add beyond minion_code tools
137
+ **kwargs: Additional arguments passed to CodeAgent.create()
138
+
139
+ Returns:
140
+ Configured MinionCodeAgent instance
141
+ """
142
+ if workdir is None:
143
+ workdir = Path.cwd()
144
+ else:
145
+ workdir = Path(workdir)
146
+
147
+ # Use default system prompt if none provided
148
+ if system_prompt is None:
149
+ system_prompt = cls.DEFAULT_SYSTEM_PROMPT.format(workdir=workdir)
150
+
151
+ # Get all minion_code tools
152
+ minion_tools = [
153
+ FileReadTool(),
154
+ FileWriteTool(),
155
+ FileEditTool(),
156
+ MultiEditTool(),
157
+ BashTool(),
158
+ GrepTool(),
159
+ GlobTool(),
160
+ LsTool(),
161
+ PythonInterpreterTool(),
162
+ UserInputTool(),
163
+ TodoWriteTool(),
164
+ TodoReadTool(),
165
+ ]
166
+
167
+ # Add any additional tools
168
+ all_tools = minion_tools[:]
169
+ if additional_tools:
170
+ all_tools.extend(additional_tools)
171
+
172
+ logger.info(f"Creating MinionCodeAgent with {len(all_tools)} tools")
173
+
174
+ # Create the underlying CodeAgent
175
+ agent = await super().create(
176
+ name=name,
177
+ llm=llm,
178
+ system_prompt=system_prompt,
179
+ tools=all_tools,
180
+ **kwargs
181
+ )
182
+
183
+ # Initialize todo tracking metadata
184
+ if not hasattr(agent.state, 'metadata'):
185
+ agent.state.metadata = {}
186
+ agent.state.metadata["iteration_without_todos"] = 0
187
+
188
+ # Add initial todo reminder to history
189
+ agent.state.history.append({
190
+ 'role': 'user',
191
+ 'content': INITIAL_REMINDER
192
+ })
193
+
194
+ return agent
195
+
196
+ async def run_async(self, message: str, **kwargs) -> Any:
197
+ """
198
+ Run agent asynchronously and track conversation history.
199
+
200
+ Args:
201
+ message: User message
202
+ **kwargs: Additional arguments passed to agent.run_async()
203
+
204
+ Returns:
205
+ Agent response
206
+ """
207
+ try:
208
+ response = await super().run_async(message, **kwargs)
209
+
210
+ # Track conversation history
211
+ self.conversation_history.append({
212
+ 'user_message': message,
213
+ 'agent_response': response.answer if hasattr(response, 'answer') else str(response),
214
+ 'timestamp': asyncio.get_event_loop().time()
215
+ })
216
+
217
+ return response
218
+
219
+ except Exception as e:
220
+ logger.error(f"Error in run_async: {e}")
221
+ traceback.print_exc()
222
+ raise
223
+
224
+ def run(self, message: str, **kwargs) -> Any:
225
+ """
226
+ Run agent synchronously.
227
+
228
+ Args:
229
+ message: User message
230
+ **kwargs: Additional arguments
231
+
232
+ Returns:
233
+ Agent response
234
+ """
235
+ return asyncio.run(self.run_async(message, **kwargs))
236
+
237
+ def get_conversation_history(self) -> List[dict]:
238
+ """Get conversation history."""
239
+ return self.conversation_history.copy()
240
+
241
+ def clear_conversation_history(self):
242
+ """Clear conversation history."""
243
+ self.conversation_history.clear()
244
+
245
+ def get_tools_info(self) -> List[dict]:
246
+ """
247
+ Get information about available tools.
248
+
249
+ Returns:
250
+ List of tool information dictionaries
251
+ """
252
+ tools_info = []
253
+ for tool in self.tools:
254
+ readonly_status = getattr(tool, "readonly", None)
255
+ tools_info.append({
256
+ 'name': tool.name,
257
+ 'description': tool.description,
258
+ 'readonly': readonly_status,
259
+ 'type': type(tool).__name__
260
+ })
261
+ return tools_info
262
+
263
+ def print_tools_summary(self):
264
+ """Print a summary of available tools."""
265
+ tools_info = self.get_tools_info()
266
+
267
+ print(f"\nšŸ› ļø Available Tools ({len(tools_info)} total):")
268
+
269
+ # Group tools by category
270
+ categories = {
271
+ 'File & Directory': ['file', 'read', 'write', 'grep', 'glob', 'ls'],
272
+ 'System & Execution': ['bash', 'python', 'calc', 'system'],
273
+ 'Web & Search': ['web', 'search', 'wikipedia', 'visit'],
274
+ 'Other': []
275
+ }
276
+
277
+ categorized_tools = {cat: [] for cat in categories}
278
+
279
+ for tool in tools_info:
280
+ categorized = False
281
+ for category, keywords in categories.items():
282
+ if category == 'Other':
283
+ continue
284
+ if any(keyword in tool['name'].lower() for keyword in keywords):
285
+ categorized_tools[category].append(tool)
286
+ categorized = True
287
+ break
288
+
289
+ if not categorized:
290
+ categorized_tools['Other'].append(tool)
291
+
292
+ # Print categorized tools
293
+ for category, tools in categorized_tools.items():
294
+ if tools:
295
+ print(f"\nšŸ“ {category} Tools:")
296
+ for tool in tools:
297
+ readonly_icon = "šŸ”’" if tool['readonly'] else "āœļø"
298
+ print(f" {readonly_icon} {tool['name']}: {tool['description']}")
299
+
300
+ print(f"\nšŸ”’ = readonly tool, āœļø = read/write tool")
301
+
302
+
303
+ # Convenience function for quick setup
304
+ async def create_minion_code_agent(
305
+ name: str = "Minion Code Assistant",
306
+ llm: str = "gpt-4o-mini",
307
+ **kwargs
308
+ ) -> MinionCodeAgent:
309
+ """
310
+ Convenience function to create a MinionCodeAgent.
311
+
312
+ Args:
313
+ name: Agent name
314
+ llm: LLM model to use
315
+ **kwargs: Additional arguments passed to MinionCodeAgent.create()
316
+
317
+ Returns:
318
+ Configured MinionCodeAgent instance
319
+ """
320
+ return await MinionCodeAgent.create(name=name, llm=llm, **kwargs)