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.
- examples/advance_tui.py +508 -0
- examples/agent_with_todos.py +165 -0
- examples/file_freshness_example.py +97 -0
- examples/file_watching_example.py +110 -0
- examples/interruptible_tui.py +5 -0
- examples/message_response_children_demo.py +226 -0
- examples/rich_example.py +4 -0
- examples/simple_file_watching.py +57 -0
- examples/simple_tui.py +267 -0
- examples/simple_usage.py +69 -0
- minion_code/__init__.py +16 -0
- minion_code/agents/__init__.py +11 -0
- minion_code/agents/code_agent.py +320 -0
- minion_code/cli.py +502 -0
- minion_code/commands/__init__.py +90 -0
- minion_code/commands/clear_command.py +70 -0
- minion_code/commands/help_command.py +90 -0
- minion_code/commands/history_command.py +104 -0
- minion_code/commands/quit_command.py +32 -0
- minion_code/commands/status_command.py +115 -0
- minion_code/commands/tools_command.py +86 -0
- minion_code/commands/version_command.py +104 -0
- minion_code/components/Message.py +304 -0
- minion_code/components/MessageResponse.py +188 -0
- minion_code/components/PromptInput.py +534 -0
- minion_code/components/__init__.py +29 -0
- minion_code/screens/REPL.py +925 -0
- minion_code/screens/__init__.py +4 -0
- minion_code/services/__init__.py +50 -0
- minion_code/services/event_system.py +108 -0
- minion_code/services/file_freshness_service.py +582 -0
- minion_code/tools/__init__.py +69 -0
- minion_code/tools/bash_tool.py +58 -0
- minion_code/tools/file_edit_tool.py +238 -0
- minion_code/tools/file_read_tool.py +73 -0
- minion_code/tools/file_write_tool.py +36 -0
- minion_code/tools/glob_tool.py +58 -0
- minion_code/tools/grep_tool.py +105 -0
- minion_code/tools/ls_tool.py +65 -0
- minion_code/tools/multi_edit_tool.py +271 -0
- minion_code/tools/python_interpreter_tool.py +105 -0
- minion_code/tools/todo_read_tool.py +100 -0
- minion_code/tools/todo_write_tool.py +234 -0
- minion_code/tools/user_input_tool.py +53 -0
- minion_code/types.py +88 -0
- minion_code/utils/__init__.py +44 -0
- minion_code/utils/mcp_loader.py +211 -0
- minion_code/utils/todo_file_utils.py +110 -0
- minion_code/utils/todo_storage.py +149 -0
- minion_code-0.1.0.dist-info/METADATA +350 -0
- minion_code-0.1.0.dist-info/RECORD +59 -0
- minion_code-0.1.0.dist-info/WHEEL +5 -0
- minion_code-0.1.0.dist-info/entry_points.txt +4 -0
- minion_code-0.1.0.dist-info/licenses/LICENSE +661 -0
- minion_code-0.1.0.dist-info/top_level.txt +3 -0
- tests/__init__.py +1 -0
- tests/test_basic.py +20 -0
- tests/test_readonly_tools.py +102 -0
- tests/test_tools.py +83 -0
examples/advance_tui.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Textual UI for MinionCodeAgent
|
|
5
|
+
|
|
6
|
+
A modern terminal user interface using the Textual library for the MinionCodeAgent.
|
|
7
|
+
Features include:
|
|
8
|
+
- Real-time chat interface
|
|
9
|
+
- Tool management
|
|
10
|
+
- Conversation history
|
|
11
|
+
- Task interruption support
|
|
12
|
+
- Rich text formatting
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional, List
|
|
19
|
+
|
|
20
|
+
# Add project root to path
|
|
21
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
22
|
+
|
|
23
|
+
from textual.app import App, ComposeResult
|
|
24
|
+
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
|
|
25
|
+
from textual.widgets import (
|
|
26
|
+
Header, Footer, Input, Button, Static, DataTable,
|
|
27
|
+
TabbedContent, TabPane, Log, ProgressBar, Label
|
|
28
|
+
)
|
|
29
|
+
from textual.reactive import reactive
|
|
30
|
+
from textual.message import Message
|
|
31
|
+
from textual.binding import Binding
|
|
32
|
+
from textual import events
|
|
33
|
+
from textual.screen import ModalScreen
|
|
34
|
+
from rich.text import Text
|
|
35
|
+
from rich.markdown import Markdown
|
|
36
|
+
from rich.console import Console
|
|
37
|
+
from rich.panel import Panel
|
|
38
|
+
|
|
39
|
+
from minion_code import MinionCodeAgent
|
|
40
|
+
from minion_code.utils.mcp_loader import MCPToolsLoader
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ToolsModal(ModalScreen):
|
|
44
|
+
"""Modal screen to display available tools."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, tools: List):
|
|
47
|
+
super().__init__()
|
|
48
|
+
self.tools = tools
|
|
49
|
+
|
|
50
|
+
def compose(self) -> ComposeResult:
|
|
51
|
+
with Container(id="tools-modal"):
|
|
52
|
+
yield Static("🛠️ Available Tools", classes="modal-title")
|
|
53
|
+
|
|
54
|
+
table = DataTable()
|
|
55
|
+
table.add_columns("Name", "Description", "Type")
|
|
56
|
+
|
|
57
|
+
for tool in self.tools:
|
|
58
|
+
tool_type = "Read-only" if getattr(tool, 'readonly', False) else "Read-write"
|
|
59
|
+
description = tool.description[:60] + "..." if len(tool.description) > 60 else tool.description
|
|
60
|
+
table.add_row(tool.name, description, tool_type)
|
|
61
|
+
|
|
62
|
+
yield table
|
|
63
|
+
yield Button("Close", id="close-tools", variant="primary")
|
|
64
|
+
|
|
65
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
66
|
+
if event.button.id == "close-tools":
|
|
67
|
+
self.dismiss()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class HistoryModal(ModalScreen):
|
|
71
|
+
"""Modal screen to display conversation history."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, history: List):
|
|
74
|
+
super().__init__()
|
|
75
|
+
self.history = history
|
|
76
|
+
|
|
77
|
+
def compose(self) -> ComposeResult:
|
|
78
|
+
with Container(id="history-modal"):
|
|
79
|
+
yield Static("📝 Conversation History", classes="modal-title")
|
|
80
|
+
|
|
81
|
+
with ScrollableContainer():
|
|
82
|
+
if not self.history:
|
|
83
|
+
yield Static("No conversation history yet.", classes="empty-history")
|
|
84
|
+
else:
|
|
85
|
+
for i, entry in enumerate(self.history[-10:], 1): # Show last 10
|
|
86
|
+
yield Static(f"👤 You (Message {len(self.history)-10+i}):", classes="user-label")
|
|
87
|
+
yield Static(entry['user_message'][:200] + "..." if len(entry['user_message']) > 200 else entry['user_message'], classes="user-message")
|
|
88
|
+
yield Static("🤖 Agent:", classes="agent-label")
|
|
89
|
+
yield Static(entry['agent_response'][:200] + "..." if len(entry['agent_response']) > 200 else entry['agent_response'], classes="agent-message")
|
|
90
|
+
yield Static("", classes="message-separator")
|
|
91
|
+
|
|
92
|
+
yield Button("Close", id="close-history", variant="primary")
|
|
93
|
+
|
|
94
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
95
|
+
if event.button.id == "close-history":
|
|
96
|
+
self.dismiss()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class MinionCodeTUI(App):
|
|
100
|
+
"""Textual UI for MinionCodeAgent."""
|
|
101
|
+
|
|
102
|
+
CSS = """
|
|
103
|
+
#main-container {
|
|
104
|
+
layout: vertical;
|
|
105
|
+
height: 100%;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#chat-container {
|
|
109
|
+
layout: vertical;
|
|
110
|
+
height: 1fr;
|
|
111
|
+
border: solid $primary;
|
|
112
|
+
margin: 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#chat-log {
|
|
116
|
+
height: 1fr;
|
|
117
|
+
scrollbar-gutter: stable;
|
|
118
|
+
border: solid $accent;
|
|
119
|
+
margin: 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#input-container {
|
|
123
|
+
layout: horizontal;
|
|
124
|
+
height: 3;
|
|
125
|
+
margin: 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#user-input {
|
|
129
|
+
width: 1fr;
|
|
130
|
+
margin-right: 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#send-button {
|
|
134
|
+
width: 10;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#status-bar {
|
|
138
|
+
height: 3;
|
|
139
|
+
background: $surface;
|
|
140
|
+
border: solid $primary;
|
|
141
|
+
margin: 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#tools-modal, #history-modal {
|
|
145
|
+
align: center middle;
|
|
146
|
+
width: 80%;
|
|
147
|
+
height: 80%;
|
|
148
|
+
background: $surface;
|
|
149
|
+
border: solid $primary;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.modal-title {
|
|
153
|
+
text-align: center;
|
|
154
|
+
text-style: bold;
|
|
155
|
+
background: $primary;
|
|
156
|
+
color: $text;
|
|
157
|
+
height: 3;
|
|
158
|
+
content-align: center middle;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.user-message {
|
|
162
|
+
background: $primary 20%;
|
|
163
|
+
margin: 1;
|
|
164
|
+
padding: 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.agent-message {
|
|
168
|
+
background: $success 20%;
|
|
169
|
+
margin: 1;
|
|
170
|
+
padding: 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.user-label {
|
|
174
|
+
text-style: bold;
|
|
175
|
+
color: $primary;
|
|
176
|
+
margin-top: 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.agent-label {
|
|
180
|
+
text-style: bold;
|
|
181
|
+
color: $success;
|
|
182
|
+
margin-top: 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.message-separator {
|
|
186
|
+
height: 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.empty-history {
|
|
190
|
+
text-align: center;
|
|
191
|
+
text-style: italic;
|
|
192
|
+
margin: 2;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.status-text {
|
|
196
|
+
text-align: center;
|
|
197
|
+
content-align: center middle;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.error-message {
|
|
201
|
+
color: $error;
|
|
202
|
+
text-style: bold;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.success-message {
|
|
206
|
+
color: $success;
|
|
207
|
+
text-style: bold;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.processing-message {
|
|
211
|
+
color: $warning;
|
|
212
|
+
text-style: bold;
|
|
213
|
+
}
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
BINDINGS = [
|
|
217
|
+
Binding("ctrl+q", "quit", "Quit"),
|
|
218
|
+
Binding("ctrl+t", "show_tools", "Tools"),
|
|
219
|
+
Binding("ctrl+h", "show_history", "History"),
|
|
220
|
+
Binding("ctrl+c", "interrupt", "Interrupt"),
|
|
221
|
+
Binding("ctrl+l", "clear_chat", "Clear Chat"),
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
agent_status = reactive("Initializing...")
|
|
225
|
+
processing = reactive(False)
|
|
226
|
+
|
|
227
|
+
def __init__(self, mcp_config: Optional[Path] = None, verbose: bool = False):
|
|
228
|
+
super().__init__()
|
|
229
|
+
self.agent = None
|
|
230
|
+
self.mcp_config = mcp_config
|
|
231
|
+
self.verbose = verbose
|
|
232
|
+
self.mcp_tools = []
|
|
233
|
+
self.mcp_loader = None
|
|
234
|
+
self.current_task = None
|
|
235
|
+
self.interrupt_requested = False
|
|
236
|
+
|
|
237
|
+
def compose(self) -> ComposeResult:
|
|
238
|
+
"""Create the UI layout."""
|
|
239
|
+
yield Header()
|
|
240
|
+
|
|
241
|
+
with Container(id="main-container"):
|
|
242
|
+
with Container(id="chat-container"):
|
|
243
|
+
yield Log(id="chat-log", auto_scroll=True)
|
|
244
|
+
|
|
245
|
+
with Horizontal(id="input-container"):
|
|
246
|
+
yield Input(placeholder="Type your message here...", id="user-input")
|
|
247
|
+
yield Button("Send", id="send-button", variant="primary")
|
|
248
|
+
|
|
249
|
+
yield Static(self.agent_status, id="status-bar", classes="status-text")
|
|
250
|
+
|
|
251
|
+
yield Footer()
|
|
252
|
+
|
|
253
|
+
async def on_mount(self) -> None:
|
|
254
|
+
"""Initialize the application."""
|
|
255
|
+
self.title = "🤖 MinionCodeAgent TUI"
|
|
256
|
+
self.sub_title = "AI-powered code assistant"
|
|
257
|
+
|
|
258
|
+
# Start agent setup
|
|
259
|
+
await self.setup_agent()
|
|
260
|
+
|
|
261
|
+
# Focus on input
|
|
262
|
+
self.query_one("#user-input").focus()
|
|
263
|
+
|
|
264
|
+
async def setup_agent(self) -> None:
|
|
265
|
+
"""Setup the MinionCodeAgent."""
|
|
266
|
+
try:
|
|
267
|
+
self.agent_status = "🔧 Setting up MinionCodeAgent..."
|
|
268
|
+
|
|
269
|
+
# Load MCP tools if config provided
|
|
270
|
+
if self.mcp_config:
|
|
271
|
+
self.agent_status = "🔌 Loading MCP tools..."
|
|
272
|
+
try:
|
|
273
|
+
self.mcp_loader = MCPToolsLoader(self.mcp_config)
|
|
274
|
+
self.mcp_loader.load_config()
|
|
275
|
+
self.mcp_tools = await self.mcp_loader.load_all_tools()
|
|
276
|
+
|
|
277
|
+
if self.mcp_tools:
|
|
278
|
+
self.log_message(f"✅ Loaded {len(self.mcp_tools)} MCP tools", "success")
|
|
279
|
+
else:
|
|
280
|
+
self.log_message("⚠️ No MCP tools loaded", "warning")
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
self.log_message(f"❌ Failed to load MCP tools: {e}", "error")
|
|
284
|
+
|
|
285
|
+
# Create agent
|
|
286
|
+
self.agent_status = "🤖 Creating agent..."
|
|
287
|
+
self.agent = await MinionCodeAgent.create(
|
|
288
|
+
name="TUI Code Assistant",
|
|
289
|
+
llm="sonnet",
|
|
290
|
+
additional_tools=self.mcp_tools if self.mcp_tools else None
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Update status
|
|
294
|
+
total_tools = len(self.agent.tools)
|
|
295
|
+
mcp_count = len(self.mcp_tools)
|
|
296
|
+
builtin_count = total_tools - mcp_count
|
|
297
|
+
|
|
298
|
+
self.agent_status = f"✅ Ready! {total_tools} tools available"
|
|
299
|
+
|
|
300
|
+
# Log welcome message
|
|
301
|
+
welcome_msg = f"🚀 MinionCodeAgent TUI Ready!\n"
|
|
302
|
+
welcome_msg += f"🛠️ Total tools: {total_tools}"
|
|
303
|
+
if mcp_count > 0:
|
|
304
|
+
welcome_msg += f" (Built-in: {builtin_count}, MCP: {mcp_count})"
|
|
305
|
+
welcome_msg += f"\n💡 Type your message and press Enter or click Send"
|
|
306
|
+
welcome_msg += f"\n⚠️ Use Ctrl+C to interrupt tasks, Ctrl+Q to quit"
|
|
307
|
+
|
|
308
|
+
self.log_message(welcome_msg, "success")
|
|
309
|
+
|
|
310
|
+
except Exception as e:
|
|
311
|
+
self.agent_status = f"❌ Setup failed: {e}"
|
|
312
|
+
self.log_message(f"❌ Failed to setup agent: {e}", "error")
|
|
313
|
+
|
|
314
|
+
def log_message(self, message: str, msg_type: str = "info") -> None:
|
|
315
|
+
"""Add a message to the chat log."""
|
|
316
|
+
chat_log = self.query_one("#chat-log", Log)
|
|
317
|
+
|
|
318
|
+
# Format messages with simple text prefixes since Log only supports plain text
|
|
319
|
+
if msg_type == "error":
|
|
320
|
+
chat_log.write(f"❌ {message}")
|
|
321
|
+
elif msg_type == "success":
|
|
322
|
+
chat_log.write(f"✅ {message}")
|
|
323
|
+
elif msg_type == "warning":
|
|
324
|
+
chat_log.write(f"⚠️ {message}")
|
|
325
|
+
elif msg_type == "user":
|
|
326
|
+
chat_log.write(f"� You: {message}")
|
|
327
|
+
elif msg_type == "agent":
|
|
328
|
+
# Handle markdown in agent responses
|
|
329
|
+
if "```" in message:
|
|
330
|
+
chat_log.write("🤖 Agent:")
|
|
331
|
+
chat_log.write(message)
|
|
332
|
+
else:
|
|
333
|
+
chat_log.write(f"🤖 Agent: {message}")
|
|
334
|
+
else:
|
|
335
|
+
chat_log.write(message)
|
|
336
|
+
|
|
337
|
+
async def process_user_input(self, message: str) -> None:
|
|
338
|
+
"""Process user input with the agent."""
|
|
339
|
+
if not self.agent:
|
|
340
|
+
self.log_message("❌ Agent not ready yet", "error")
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if not message.strip():
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
# Log user message
|
|
347
|
+
self.log_message(message, "user")
|
|
348
|
+
|
|
349
|
+
# Check for commands
|
|
350
|
+
if message.startswith('/'):
|
|
351
|
+
await self.process_command(message)
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
# Process with agent
|
|
355
|
+
try:
|
|
356
|
+
self.processing = True
|
|
357
|
+
self.agent_status = "🤖 Processing... (Ctrl+C to interrupt)"
|
|
358
|
+
self.interrupt_requested = False
|
|
359
|
+
|
|
360
|
+
# Create processing task
|
|
361
|
+
async def processing_task():
|
|
362
|
+
response = await self.agent.run_async(message)
|
|
363
|
+
return response
|
|
364
|
+
|
|
365
|
+
self.current_task = asyncio.create_task(processing_task())
|
|
366
|
+
|
|
367
|
+
# Monitor for interruption
|
|
368
|
+
while not self.current_task.done():
|
|
369
|
+
if self.interrupt_requested:
|
|
370
|
+
self.current_task.cancel()
|
|
371
|
+
try:
|
|
372
|
+
await self.current_task
|
|
373
|
+
except asyncio.CancelledError:
|
|
374
|
+
pass
|
|
375
|
+
self.log_message("⚠️ Task interrupted by user", "warning")
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
await asyncio.sleep(0.1)
|
|
379
|
+
|
|
380
|
+
# Get result
|
|
381
|
+
response = await self.current_task
|
|
382
|
+
self.log_message(response.answer, "agent")
|
|
383
|
+
|
|
384
|
+
except asyncio.CancelledError:
|
|
385
|
+
self.log_message("⚠️ Task was cancelled", "warning")
|
|
386
|
+
except Exception as e:
|
|
387
|
+
self.log_message(f"❌ Error: {e}", "error")
|
|
388
|
+
finally:
|
|
389
|
+
self.processing = False
|
|
390
|
+
self.current_task = None
|
|
391
|
+
total_tools = len(self.agent.tools) if self.agent else 0
|
|
392
|
+
self.agent_status = f"✅ Ready! {total_tools} tools available"
|
|
393
|
+
|
|
394
|
+
async def process_command(self, command: str) -> None:
|
|
395
|
+
"""Process a command."""
|
|
396
|
+
command = command[1:].lower().strip() # Remove leading /
|
|
397
|
+
|
|
398
|
+
if command == "help":
|
|
399
|
+
help_msg = """📚 Available Commands:
|
|
400
|
+
/help - Show this help
|
|
401
|
+
/tools - Show available tools
|
|
402
|
+
/history - Show conversation history
|
|
403
|
+
/clear - Clear chat log
|
|
404
|
+
/quit - Exit application
|
|
405
|
+
|
|
406
|
+
🔧 Keyboard Shortcuts:
|
|
407
|
+
Ctrl+T - Show tools
|
|
408
|
+
Ctrl+H - Show history
|
|
409
|
+
Ctrl+L - Clear chat
|
|
410
|
+
Ctrl+C - Interrupt current task
|
|
411
|
+
Ctrl+Q - Quit application"""
|
|
412
|
+
self.log_message(help_msg, "info")
|
|
413
|
+
|
|
414
|
+
elif command == "tools":
|
|
415
|
+
await self.action_show_tools()
|
|
416
|
+
|
|
417
|
+
elif command == "history":
|
|
418
|
+
await self.action_show_history()
|
|
419
|
+
|
|
420
|
+
elif command == "clear":
|
|
421
|
+
await self.action_clear_chat()
|
|
422
|
+
|
|
423
|
+
elif command in ["quit", "exit", "q"]:
|
|
424
|
+
await self.action_quit()
|
|
425
|
+
|
|
426
|
+
else:
|
|
427
|
+
self.log_message(f"❌ Unknown command: /{command}. Use /help for available commands.", "error")
|
|
428
|
+
|
|
429
|
+
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
430
|
+
"""Handle input submission."""
|
|
431
|
+
if event.input.id == "user-input":
|
|
432
|
+
message = event.value
|
|
433
|
+
event.input.clear()
|
|
434
|
+
asyncio.create_task(self.process_user_input(message))
|
|
435
|
+
|
|
436
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
437
|
+
"""Handle button presses."""
|
|
438
|
+
if event.button.id == "send-button":
|
|
439
|
+
user_input = self.query_one("#user-input", Input)
|
|
440
|
+
message = user_input.value
|
|
441
|
+
user_input.clear()
|
|
442
|
+
asyncio.create_task(self.process_user_input(message))
|
|
443
|
+
|
|
444
|
+
async def action_show_tools(self) -> None:
|
|
445
|
+
"""Show available tools."""
|
|
446
|
+
if not self.agent:
|
|
447
|
+
self.log_message("❌ Agent not ready yet", "error")
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
await self.push_screen(ToolsModal(self.agent.tools))
|
|
451
|
+
|
|
452
|
+
async def action_show_history(self) -> None:
|
|
453
|
+
"""Show conversation history."""
|
|
454
|
+
if not self.agent:
|
|
455
|
+
self.log_message("❌ Agent not ready yet", "error")
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
history = self.agent.get_conversation_history()
|
|
459
|
+
await self.push_screen(HistoryModal(history))
|
|
460
|
+
|
|
461
|
+
async def action_clear_chat(self) -> None:
|
|
462
|
+
"""Clear the chat log."""
|
|
463
|
+
chat_log = self.query_one("#chat-log", Log)
|
|
464
|
+
chat_log.clear()
|
|
465
|
+
self.log_message("🧹 Chat cleared", "info")
|
|
466
|
+
|
|
467
|
+
async def action_interrupt(self) -> None:
|
|
468
|
+
"""Interrupt current task."""
|
|
469
|
+
if self.current_task and not self.current_task.done():
|
|
470
|
+
self.interrupt_requested = True
|
|
471
|
+
self.log_message("⚠️ Interruption requested...", "warning")
|
|
472
|
+
else:
|
|
473
|
+
self.log_message("ℹ️ No task to interrupt", "info")
|
|
474
|
+
|
|
475
|
+
async def action_quit(self) -> None:
|
|
476
|
+
"""Quit the application."""
|
|
477
|
+
if self.mcp_loader:
|
|
478
|
+
try:
|
|
479
|
+
await self.mcp_loader.close()
|
|
480
|
+
except Exception:
|
|
481
|
+
pass
|
|
482
|
+
|
|
483
|
+
self.exit()
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def main():
|
|
487
|
+
"""Main entry point."""
|
|
488
|
+
import argparse
|
|
489
|
+
|
|
490
|
+
parser = argparse.ArgumentParser(description="MinionCodeAgent Textual UI")
|
|
491
|
+
parser.add_argument("--config", "-c", type=str, help="Path to MCP configuration file")
|
|
492
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
|
|
493
|
+
|
|
494
|
+
args = parser.parse_args()
|
|
495
|
+
|
|
496
|
+
mcp_config = None
|
|
497
|
+
if args.config:
|
|
498
|
+
mcp_config = Path(args.config)
|
|
499
|
+
if not mcp_config.exists():
|
|
500
|
+
print(f"❌ MCP config file does not exist: {args.config}")
|
|
501
|
+
sys.exit(1)
|
|
502
|
+
|
|
503
|
+
app = MinionCodeTUI(mcp_config=mcp_config, verbose=args.verbose)
|
|
504
|
+
app.run()
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
if __name__ == "__main__":
|
|
508
|
+
main()
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Example of using Todo tools with MinionCodeAgent."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import asyncio
|
|
7
|
+
import uuid
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
# Add the parent directory to the path so we can import minion_code
|
|
11
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
12
|
+
|
|
13
|
+
from minion_code.agents.code_agent import MinionCodeAgent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def demonstrate_todo_workflow():
|
|
17
|
+
"""Demonstrate using Todo tools with MinionCodeAgent."""
|
|
18
|
+
print("=== MinionCodeAgent Todo Workflow Demo ===\n")
|
|
19
|
+
|
|
20
|
+
# Create agent
|
|
21
|
+
print("1. Creating MinionCodeAgent...")
|
|
22
|
+
agent = await MinionCodeAgent.create(
|
|
23
|
+
name="Todo Demo Agent",
|
|
24
|
+
llm="gpt-4o-mini"
|
|
25
|
+
)
|
|
26
|
+
print("✓ Agent created successfully\n")
|
|
27
|
+
|
|
28
|
+
# Show available tools
|
|
29
|
+
print("2. Available tools:")
|
|
30
|
+
tools_info = agent.get_tools_info()
|
|
31
|
+
todo_tools = [tool for tool in tools_info if 'todo' in tool['name'].lower()]
|
|
32
|
+
for tool in todo_tools:
|
|
33
|
+
readonly_icon = "🔒" if tool['readonly'] else "✏️"
|
|
34
|
+
print(f" {readonly_icon} {tool['name']}: {tool['description']}")
|
|
35
|
+
print()
|
|
36
|
+
|
|
37
|
+
# Define project tasks
|
|
38
|
+
tasks = [
|
|
39
|
+
"Analyze project requirements",
|
|
40
|
+
"Design system architecture",
|
|
41
|
+
"Implement core functionality",
|
|
42
|
+
"Write unit tests",
|
|
43
|
+
"Create documentation",
|
|
44
|
+
"Deploy to production"
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Create initial todos using agent
|
|
48
|
+
print("3. Creating initial todos...")
|
|
49
|
+
todos_data = []
|
|
50
|
+
for i, task in enumerate(tasks):
|
|
51
|
+
todos_data.append({
|
|
52
|
+
"id": str(uuid.uuid4()),
|
|
53
|
+
"content": task,
|
|
54
|
+
"status": "pending" if i > 0 else "in_progress", # First task in progress
|
|
55
|
+
"priority": "high" if i < 2 else "medium"
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
todos_json = json.dumps(todos_data)
|
|
59
|
+
|
|
60
|
+
# Use agent to create todos
|
|
61
|
+
create_message = f"""Please use the todo_write tool to create these todos:
|
|
62
|
+
{todos_json}"""
|
|
63
|
+
|
|
64
|
+
response = await agent.run_async(create_message)
|
|
65
|
+
print(f"✓ Agent response: {response.answer if hasattr(response, 'answer') else str(response)}\n")
|
|
66
|
+
|
|
67
|
+
# Read current todos
|
|
68
|
+
print("4. Reading current todos...")
|
|
69
|
+
read_message = """Please use the todo_read tool to show current todos."""
|
|
70
|
+
|
|
71
|
+
response = await agent.run_async(read_message)
|
|
72
|
+
print(f"✓ Current todos:\n{response.answer if hasattr(response, 'answer') else str(response)}\n")
|
|
73
|
+
|
|
74
|
+
# Simulate completing a few tasks
|
|
75
|
+
print("5. Completing tasks...")
|
|
76
|
+
|
|
77
|
+
# Complete first task and start next
|
|
78
|
+
for i in range(3): # Complete 3 tasks
|
|
79
|
+
print(f" Step {i+1}: Completing current task and starting next...")
|
|
80
|
+
|
|
81
|
+
# Get current todos from storage to update them
|
|
82
|
+
from minion_code.utils.todo_storage import get_todos, TodoStatus
|
|
83
|
+
# Use the same agent ID logic as the tool
|
|
84
|
+
import hashlib
|
|
85
|
+
agent_id = "demo_agent" # We'll use a consistent ID for this demo
|
|
86
|
+
current_todos = get_todos(agent_id)
|
|
87
|
+
|
|
88
|
+
if not current_todos:
|
|
89
|
+
print(" No todos found")
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
# Update todos: complete in_progress, start next pending
|
|
93
|
+
updated_todos = []
|
|
94
|
+
found_in_progress = False
|
|
95
|
+
started_next = False
|
|
96
|
+
|
|
97
|
+
for todo in current_todos:
|
|
98
|
+
if todo.status == TodoStatus.IN_PROGRESS:
|
|
99
|
+
# Mark as completed
|
|
100
|
+
todo_data = {
|
|
101
|
+
"id": todo.id,
|
|
102
|
+
"content": todo.content,
|
|
103
|
+
"status": "completed",
|
|
104
|
+
"priority": todo.priority.value
|
|
105
|
+
}
|
|
106
|
+
found_in_progress = True
|
|
107
|
+
elif todo.status == TodoStatus.PENDING and not started_next:
|
|
108
|
+
# Start next pending task
|
|
109
|
+
todo_data = {
|
|
110
|
+
"id": todo.id,
|
|
111
|
+
"content": todo.content,
|
|
112
|
+
"status": "in_progress",
|
|
113
|
+
"priority": todo.priority.value
|
|
114
|
+
}
|
|
115
|
+
started_next = True
|
|
116
|
+
else:
|
|
117
|
+
# Keep as is
|
|
118
|
+
todo_data = {
|
|
119
|
+
"id": todo.id,
|
|
120
|
+
"content": todo.content,
|
|
121
|
+
"status": todo.status.value,
|
|
122
|
+
"priority": todo.priority.value
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
updated_todos.append(todo_data)
|
|
126
|
+
|
|
127
|
+
if not found_in_progress:
|
|
128
|
+
print(" No in_progress task found")
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
# Update todos using agent
|
|
132
|
+
updated_json = json.dumps(updated_todos)
|
|
133
|
+
update_message = f"""Please use the todo_write tool to update todos:
|
|
134
|
+
{updated_json}"""
|
|
135
|
+
|
|
136
|
+
response = await agent.run_async(update_message)
|
|
137
|
+
print(f" ✓ Updated: {response.answer if hasattr(response, 'answer') else str(response)}")
|
|
138
|
+
|
|
139
|
+
print()
|
|
140
|
+
|
|
141
|
+
# Final status
|
|
142
|
+
print("6. Final todo status...")
|
|
143
|
+
final_message = """Please use the todo_read tool to show final todos."""
|
|
144
|
+
|
|
145
|
+
response = await agent.run_async(final_message)
|
|
146
|
+
print(f"✓ Final todos:\n{response.answer if hasattr(response, 'answer') else str(response)}\n")
|
|
147
|
+
|
|
148
|
+
# Show conversation history
|
|
149
|
+
print("7. Conversation summary:")
|
|
150
|
+
history = agent.get_conversation_history()
|
|
151
|
+
print(f" Total interactions: {len(history)}")
|
|
152
|
+
for i, interaction in enumerate(history, 1):
|
|
153
|
+
print(f" {i}. User: {interaction['user_message'][:50]}...")
|
|
154
|
+
print(f" Agent: {str(interaction['agent_response'])[:50]}...")
|
|
155
|
+
|
|
156
|
+
print("\n=== Demo Complete ===")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def main():
|
|
160
|
+
"""Run the async demo."""
|
|
161
|
+
asyncio.run(demonstrate_todo_workflow())
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
main()
|