omnimancer-cli 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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Discovery and Management Interface for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides CLI commands for managing custom agents including
|
|
5
|
+
listing, searching, loading, activation, and detailed inspection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from rich.box import ROUNDED, SIMPLE
|
|
14
|
+
from rich.columns import Columns
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
18
|
+
from rich.prompt import Confirm
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
from rich.text import Text
|
|
21
|
+
|
|
22
|
+
from ..core.agent.agent_config import (
|
|
23
|
+
AgentRepository,
|
|
24
|
+
CustomAgentConfig,
|
|
25
|
+
CustomAgentStatus,
|
|
26
|
+
)
|
|
27
|
+
from ..core.agent.persona import PersonaCategory
|
|
28
|
+
from .agent_wizard import AgentCreationWizard
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AgentManager:
|
|
34
|
+
"""Manager for custom agent discovery and operations."""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
console: Optional[Console] = None,
|
|
39
|
+
repository: Optional[AgentRepository] = None,
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
Initialize agent manager.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
console: Rich console for output
|
|
46
|
+
repository: Agent repository for storage operations
|
|
47
|
+
"""
|
|
48
|
+
self.console = console or Console()
|
|
49
|
+
self.repository = repository or AgentRepository()
|
|
50
|
+
|
|
51
|
+
# Cache for active agent
|
|
52
|
+
self._active_agent: Optional[CustomAgentConfig] = None
|
|
53
|
+
self._session_stats: Dict[str, Any] = {
|
|
54
|
+
"agents_loaded": 0,
|
|
55
|
+
"agents_activated": 0,
|
|
56
|
+
"session_start": datetime.now(),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async def list_agents(
|
|
60
|
+
self,
|
|
61
|
+
category: Optional[str] = None,
|
|
62
|
+
status: Optional[str] = None,
|
|
63
|
+
show_details: bool = False,
|
|
64
|
+
sort_by: str = "name",
|
|
65
|
+
) -> List[CustomAgentConfig]:
|
|
66
|
+
"""
|
|
67
|
+
List all custom agents with optional filtering.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
category: Filter by category (optional)
|
|
71
|
+
status: Filter by status (optional)
|
|
72
|
+
show_details: Show detailed information
|
|
73
|
+
sort_by: Sort field (name, created_at, last_used, usage_count)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List of matching agent configurations
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
with Progress(
|
|
80
|
+
SpinnerColumn(),
|
|
81
|
+
TextColumn("[progress.description]{task.description}"),
|
|
82
|
+
console=self.console,
|
|
83
|
+
transient=True,
|
|
84
|
+
) as progress:
|
|
85
|
+
task = progress.add_task("Loading agents...", total=None)
|
|
86
|
+
|
|
87
|
+
# Get all agents
|
|
88
|
+
all_agents = self.repository.list_all()
|
|
89
|
+
|
|
90
|
+
# Apply filters
|
|
91
|
+
filtered_agents = all_agents
|
|
92
|
+
|
|
93
|
+
if category:
|
|
94
|
+
try:
|
|
95
|
+
cat_filter = PersonaCategory(category.lower())
|
|
96
|
+
filtered_agents = [
|
|
97
|
+
a for a in filtered_agents if a.category == cat_filter
|
|
98
|
+
]
|
|
99
|
+
except ValueError:
|
|
100
|
+
self.console.print(f"[red]Invalid category: {category}[/red]")
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
if status:
|
|
104
|
+
try:
|
|
105
|
+
status_filter = CustomAgentStatus(status.lower())
|
|
106
|
+
filtered_agents = [
|
|
107
|
+
a for a in filtered_agents if a.status == status_filter
|
|
108
|
+
]
|
|
109
|
+
except ValueError:
|
|
110
|
+
self.console.print(f"[red]Invalid status: {status}[/red]")
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
# Sort agents
|
|
114
|
+
filtered_agents = self._sort_agents(filtered_agents, sort_by)
|
|
115
|
+
|
|
116
|
+
progress.update(task, completed=100)
|
|
117
|
+
|
|
118
|
+
# Display results
|
|
119
|
+
if not filtered_agents:
|
|
120
|
+
self.console.print(
|
|
121
|
+
"[yellow]No agents found matching the criteria.[/yellow]"
|
|
122
|
+
)
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
if show_details:
|
|
126
|
+
self._show_detailed_agent_list(filtered_agents)
|
|
127
|
+
else:
|
|
128
|
+
self._show_summary_agent_list(filtered_agents)
|
|
129
|
+
|
|
130
|
+
return filtered_agents
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
self.console.print(f"[red]Error listing agents: {e}[/red]")
|
|
134
|
+
logger.error(f"Agent listing error: {e}", exc_info=True)
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
async def show_agent(self, identifier: str) -> Optional[CustomAgentConfig]:
|
|
138
|
+
"""
|
|
139
|
+
Show detailed information about a specific agent.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
identifier: Agent ID or name
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Agent configuration if found
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
# Try to find by ID first, then by name
|
|
149
|
+
agent = self.repository.get(identifier)
|
|
150
|
+
if not agent:
|
|
151
|
+
agent = self.repository.get_by_name(identifier)
|
|
152
|
+
|
|
153
|
+
if not agent:
|
|
154
|
+
self.console.print(f"[red]Agent '{identifier}' not found.[/red]")
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
self._show_agent_details(agent)
|
|
158
|
+
return agent
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
self.console.print(f"[red]Error showing agent: {e}[/red]")
|
|
162
|
+
logger.error(f"Agent show error: {e}", exc_info=True)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
async def search_agents(
|
|
166
|
+
self, query: str, fields: Optional[List[str]] = None
|
|
167
|
+
) -> List[CustomAgentConfig]:
|
|
168
|
+
"""
|
|
169
|
+
Search agents by query string.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
query: Search query
|
|
173
|
+
fields: Fields to search in (name, description, capabilities)
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of matching agents
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
if not query.strip():
|
|
180
|
+
self.console.print("[red]Search query cannot be empty.[/red]")
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
with Progress(
|
|
184
|
+
SpinnerColumn(),
|
|
185
|
+
TextColumn("[progress.description]{task.description}"),
|
|
186
|
+
console=self.console,
|
|
187
|
+
transient=True,
|
|
188
|
+
) as progress:
|
|
189
|
+
task = progress.add_task(f"Searching for '{query}'...", total=None)
|
|
190
|
+
|
|
191
|
+
results = self.repository.search(query, fields)
|
|
192
|
+
|
|
193
|
+
progress.update(task, completed=100)
|
|
194
|
+
|
|
195
|
+
if not results:
|
|
196
|
+
self.console.print(
|
|
197
|
+
f"[yellow]No agents found matching '{query}'.[/yellow]"
|
|
198
|
+
)
|
|
199
|
+
return []
|
|
200
|
+
|
|
201
|
+
self.console.print(
|
|
202
|
+
f"\\n[green]Found {len(results)} agent(s) matching '{query}':[/green]"
|
|
203
|
+
)
|
|
204
|
+
self._show_summary_agent_list(results)
|
|
205
|
+
|
|
206
|
+
return results
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
self.console.print(f"[red]Error searching agents: {e}[/red]")
|
|
210
|
+
logger.error(f"Agent search error: {e}", exc_info=True)
|
|
211
|
+
return []
|
|
212
|
+
|
|
213
|
+
async def load_agent(
|
|
214
|
+
self, identifier: str, activate: bool = True
|
|
215
|
+
) -> Optional[CustomAgentConfig]:
|
|
216
|
+
"""
|
|
217
|
+
Load an agent configuration.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
identifier: Agent ID or name
|
|
221
|
+
activate: Whether to activate the agent for current session
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Loaded agent configuration
|
|
225
|
+
"""
|
|
226
|
+
try:
|
|
227
|
+
# Find the agent
|
|
228
|
+
agent = self.repository.get(identifier)
|
|
229
|
+
if not agent:
|
|
230
|
+
agent = self.repository.get_by_name(identifier)
|
|
231
|
+
|
|
232
|
+
if not agent:
|
|
233
|
+
self.console.print(f"[red]Agent '{identifier}' not found.[/red]")
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
# Check if agent is in usable state
|
|
237
|
+
if agent.status not in [
|
|
238
|
+
CustomAgentStatus.ACTIVE,
|
|
239
|
+
CustomAgentStatus.DRAFT,
|
|
240
|
+
]:
|
|
241
|
+
self.console.print(
|
|
242
|
+
f"[yellow]Warning: Agent status is '{agent.status.value}'.[/yellow]"
|
|
243
|
+
)
|
|
244
|
+
if not Confirm.ask("Load anyway?", default=False):
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
# Load the agent
|
|
248
|
+
with Progress(
|
|
249
|
+
SpinnerColumn(),
|
|
250
|
+
TextColumn("[progress.description]{task.description}"),
|
|
251
|
+
console=self.console,
|
|
252
|
+
transient=True,
|
|
253
|
+
) as progress:
|
|
254
|
+
task = progress.add_task(f"Loading agent '{agent.name}'...", total=None)
|
|
255
|
+
|
|
256
|
+
# Update usage statistics
|
|
257
|
+
agent.increment_usage()
|
|
258
|
+
self.repository.update(agent)
|
|
259
|
+
|
|
260
|
+
if activate:
|
|
261
|
+
self._active_agent = agent
|
|
262
|
+
self._session_stats["agents_activated"] += 1
|
|
263
|
+
|
|
264
|
+
self._session_stats["agents_loaded"] += 1
|
|
265
|
+
|
|
266
|
+
progress.update(task, completed=100)
|
|
267
|
+
|
|
268
|
+
self.console.print(
|
|
269
|
+
f"[green]✅ Agent '{agent.name}' loaded successfully![/green]"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if activate:
|
|
273
|
+
self.console.print(
|
|
274
|
+
f"[cyan]🚀 Agent '{agent.name}' is now active for this session.[/cyan]"
|
|
275
|
+
)
|
|
276
|
+
self._show_agent_summary(agent)
|
|
277
|
+
|
|
278
|
+
return agent
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
self.console.print(f"[red]Error loading agent: {e}[/red]")
|
|
282
|
+
logger.error(f"Agent loading error: {e}", exc_info=True)
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
async def delete_agent(self, identifier: str, confirm: bool = True) -> bool:
|
|
286
|
+
"""
|
|
287
|
+
Delete an agent configuration.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
identifier: Agent ID or name
|
|
291
|
+
confirm: Whether to ask for confirmation
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
True if deleted successfully
|
|
295
|
+
"""
|
|
296
|
+
try:
|
|
297
|
+
# Find the agent
|
|
298
|
+
agent = self.repository.get(identifier)
|
|
299
|
+
if not agent:
|
|
300
|
+
agent = self.repository.get_by_name(identifier)
|
|
301
|
+
|
|
302
|
+
if not agent:
|
|
303
|
+
self.console.print(f"[red]Agent '{identifier}' not found.[/red]")
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
# Show agent info before deletion
|
|
307
|
+
self.console.print(f"\\n[yellow]Agent to delete:[/yellow]")
|
|
308
|
+
self._show_agent_summary(agent)
|
|
309
|
+
|
|
310
|
+
# Confirm deletion
|
|
311
|
+
if confirm:
|
|
312
|
+
if not Confirm.ask(
|
|
313
|
+
f"\\n[red]Are you sure you want to delete '{agent.name}'?[/red]",
|
|
314
|
+
default=False,
|
|
315
|
+
):
|
|
316
|
+
self.console.print("[yellow]Deletion cancelled.[/yellow]")
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
# Check if agent is currently active
|
|
320
|
+
if self._active_agent and self._active_agent.id == agent.id:
|
|
321
|
+
self.console.print(
|
|
322
|
+
"[yellow]Warning: Deleting currently active agent.[/yellow]"
|
|
323
|
+
)
|
|
324
|
+
self._active_agent = None
|
|
325
|
+
|
|
326
|
+
# Delete the agent
|
|
327
|
+
success = self.repository.delete(agent.id)
|
|
328
|
+
|
|
329
|
+
if success:
|
|
330
|
+
self.console.print(
|
|
331
|
+
f"[green]✅ Agent '{agent.name}' deleted successfully.[/green]"
|
|
332
|
+
)
|
|
333
|
+
else:
|
|
334
|
+
self.console.print(
|
|
335
|
+
f"[red]❌ Failed to delete agent '{agent.name}'.[/red]"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
return success
|
|
339
|
+
|
|
340
|
+
except Exception as e:
|
|
341
|
+
self.console.print(f"[red]Error deleting agent: {e}[/red]")
|
|
342
|
+
logger.error(f"Agent deletion error: {e}", exc_info=True)
|
|
343
|
+
return False
|
|
344
|
+
|
|
345
|
+
async def create_agent(
|
|
346
|
+
self, interactive: bool = True
|
|
347
|
+
) -> Optional[CustomAgentConfig]:
|
|
348
|
+
"""
|
|
349
|
+
Create a new agent using the wizard.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
interactive: Whether to use interactive wizard
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Created agent configuration
|
|
356
|
+
"""
|
|
357
|
+
try:
|
|
358
|
+
if interactive:
|
|
359
|
+
wizard = AgentCreationWizard(
|
|
360
|
+
console=self.console, repository=self.repository
|
|
361
|
+
)
|
|
362
|
+
return await wizard.run_wizard()
|
|
363
|
+
else:
|
|
364
|
+
# Quick creation - would need additional parameters
|
|
365
|
+
self.console.print(
|
|
366
|
+
"[yellow]Non-interactive creation not yet implemented.[/yellow]"
|
|
367
|
+
)
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
except Exception as e:
|
|
371
|
+
self.console.print(f"[red]Error creating agent: {e}[/red]")
|
|
372
|
+
logger.error(f"Agent creation error: {e}", exc_info=True)
|
|
373
|
+
return None
|
|
374
|
+
|
|
375
|
+
async def clone_agent(
|
|
376
|
+
self, identifier: str, new_name: str
|
|
377
|
+
) -> Optional[CustomAgentConfig]:
|
|
378
|
+
"""
|
|
379
|
+
Clone an existing agent with a new name.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
identifier: Source agent ID or name
|
|
383
|
+
new_name: Name for the cloned agent
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Cloned agent configuration
|
|
387
|
+
"""
|
|
388
|
+
try:
|
|
389
|
+
# Find source agent
|
|
390
|
+
source_agent = self.repository.get(identifier)
|
|
391
|
+
if not source_agent:
|
|
392
|
+
source_agent = self.repository.get_by_name(identifier)
|
|
393
|
+
|
|
394
|
+
if not source_agent:
|
|
395
|
+
self.console.print(f"[red]Source agent '{identifier}' not found.[/red]")
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
# Check name availability
|
|
399
|
+
if self.repository.get_by_name(new_name):
|
|
400
|
+
self.console.print(
|
|
401
|
+
f"[red]Agent named '{new_name}' already exists.[/red]"
|
|
402
|
+
)
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
# Create clone
|
|
406
|
+
import uuid
|
|
407
|
+
|
|
408
|
+
cloned_config = CustomAgentConfig(
|
|
409
|
+
id=str(uuid.uuid4()),
|
|
410
|
+
name=new_name,
|
|
411
|
+
description=f"Clone of {source_agent.name}\\n{source_agent.description}",
|
|
412
|
+
category=source_agent.category,
|
|
413
|
+
base_template_id=source_agent.base_template_id,
|
|
414
|
+
model_settings=source_agent.model_settings,
|
|
415
|
+
context_parameters=source_agent.context_parameters,
|
|
416
|
+
behavior_rules=source_agent.behavior_rules,
|
|
417
|
+
capabilities=source_agent.capabilities.copy(),
|
|
418
|
+
enabled_tools=source_agent.enabled_tools.copy(),
|
|
419
|
+
security_overrides=source_agent.security_overrides,
|
|
420
|
+
agent_overrides=source_agent.agent_overrides,
|
|
421
|
+
status=CustomAgentStatus.DRAFT, # Start as draft
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Save cloned agent
|
|
425
|
+
saved_clone = self.repository.create(cloned_config)
|
|
426
|
+
|
|
427
|
+
self.console.print(
|
|
428
|
+
f"[green]✅ Agent '{new_name}' cloned from '{source_agent.name}'![/green]"
|
|
429
|
+
)
|
|
430
|
+
self._show_agent_summary(saved_clone)
|
|
431
|
+
|
|
432
|
+
return saved_clone
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
self.console.print(f"[red]Error cloning agent: {e}[/red]")
|
|
436
|
+
logger.error(f"Agent cloning error: {e}", exc_info=True)
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
async def export_agent(
|
|
440
|
+
self, identifier: str, export_path: Optional[Path] = None
|
|
441
|
+
) -> bool:
|
|
442
|
+
"""
|
|
443
|
+
Export an agent configuration to file.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
identifier: Agent ID or name
|
|
447
|
+
export_path: Export file path (optional)
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
True if exported successfully
|
|
451
|
+
"""
|
|
452
|
+
try:
|
|
453
|
+
# Find the agent
|
|
454
|
+
agent = self.repository.get(identifier)
|
|
455
|
+
if not agent:
|
|
456
|
+
agent = self.repository.get_by_name(identifier)
|
|
457
|
+
|
|
458
|
+
if not agent:
|
|
459
|
+
self.console.print(f"[red]Agent '{identifier}' not found.[/red]")
|
|
460
|
+
return False
|
|
461
|
+
|
|
462
|
+
# Determine export path
|
|
463
|
+
if not export_path:
|
|
464
|
+
safe_name = "".join(
|
|
465
|
+
c if c.isalnum() or c in "-_" else "_" for c in agent.name
|
|
466
|
+
)
|
|
467
|
+
export_path = Path.cwd() / f"{safe_name}_agent.json"
|
|
468
|
+
|
|
469
|
+
# Export the agent
|
|
470
|
+
success = self.repository.export_config(agent.id, export_path)
|
|
471
|
+
|
|
472
|
+
if success:
|
|
473
|
+
self.console.print(
|
|
474
|
+
f"[green]✅ Agent '{agent.name}' exported to {export_path}[/green]"
|
|
475
|
+
)
|
|
476
|
+
else:
|
|
477
|
+
self.console.print(
|
|
478
|
+
f"[red]❌ Failed to export agent '{agent.name}'.[/red]"
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
return success
|
|
482
|
+
|
|
483
|
+
except Exception as e:
|
|
484
|
+
self.console.print(f"[red]Error exporting agent: {e}[/red]")
|
|
485
|
+
logger.error(f"Agent export error: {e}", exc_info=True)
|
|
486
|
+
return False
|
|
487
|
+
|
|
488
|
+
async def import_agent(
|
|
489
|
+
self, import_path: Path, new_name: Optional[str] = None
|
|
490
|
+
) -> Optional[CustomAgentConfig]:
|
|
491
|
+
"""
|
|
492
|
+
Import an agent configuration from file.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
import_path: Import file path
|
|
496
|
+
new_name: Optional new name to avoid conflicts
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
Imported agent configuration
|
|
500
|
+
"""
|
|
501
|
+
try:
|
|
502
|
+
if not import_path.exists():
|
|
503
|
+
self.console.print(f"[red]Import file not found: {import_path}[/red]")
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
# Import the agent
|
|
507
|
+
imported_agent = self.repository.import_config(import_path, new_id=True)
|
|
508
|
+
|
|
509
|
+
if not imported_agent:
|
|
510
|
+
self.console.print(
|
|
511
|
+
f"[red]Failed to import agent from {import_path}[/red]"
|
|
512
|
+
)
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
# Rename if requested
|
|
516
|
+
if new_name:
|
|
517
|
+
imported_agent.name = new_name
|
|
518
|
+
imported_agent = self.repository.update(imported_agent)
|
|
519
|
+
|
|
520
|
+
self.console.print(
|
|
521
|
+
f"[green]✅ Agent '{imported_agent.name}' imported successfully![/green]"
|
|
522
|
+
)
|
|
523
|
+
self._show_agent_summary(imported_agent)
|
|
524
|
+
|
|
525
|
+
return imported_agent
|
|
526
|
+
|
|
527
|
+
except Exception as e:
|
|
528
|
+
self.console.print(f"[red]Error importing agent: {e}[/red]")
|
|
529
|
+
logger.error(f"Agent import error: {e}", exc_info=True)
|
|
530
|
+
return None
|
|
531
|
+
|
|
532
|
+
def get_active_agent(self) -> Optional[CustomAgentConfig]:
|
|
533
|
+
"""Get the currently active agent."""
|
|
534
|
+
return self._active_agent
|
|
535
|
+
|
|
536
|
+
def show_statistics(self):
|
|
537
|
+
"""Show agent repository and session statistics."""
|
|
538
|
+
try:
|
|
539
|
+
# Repository statistics
|
|
540
|
+
repo_stats = self.repository.get_statistics()
|
|
541
|
+
|
|
542
|
+
# Create statistics display
|
|
543
|
+
stats_table = Table(show_header=False, box=SIMPLE)
|
|
544
|
+
stats_table.add_column("Metric", style="cyan")
|
|
545
|
+
stats_table.add_column("Value", style="white")
|
|
546
|
+
|
|
547
|
+
# Repository stats
|
|
548
|
+
stats_table.add_row("Total Agents", str(repo_stats["total_agents"]))
|
|
549
|
+
|
|
550
|
+
if repo_stats["status_distribution"]:
|
|
551
|
+
for status, count in repo_stats["status_distribution"].items():
|
|
552
|
+
stats_table.add_row(f" {status.title()}", str(count))
|
|
553
|
+
|
|
554
|
+
stats_table.add_row(
|
|
555
|
+
"Storage Location", str(repo_stats["storage_directory"])
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# Session stats
|
|
559
|
+
stats_table.add_row("", "") # Separator
|
|
560
|
+
stats_table.add_row(
|
|
561
|
+
"Session Started",
|
|
562
|
+
self._session_stats["session_start"].strftime("%Y-%m-%d %H:%M"),
|
|
563
|
+
)
|
|
564
|
+
stats_table.add_row(
|
|
565
|
+
"Agents Loaded", str(self._session_stats["agents_loaded"])
|
|
566
|
+
)
|
|
567
|
+
stats_table.add_row(
|
|
568
|
+
"Agents Activated",
|
|
569
|
+
str(self._session_stats["agents_activated"]),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
if self._active_agent:
|
|
573
|
+
stats_table.add_row("Active Agent", self._active_agent.name)
|
|
574
|
+
|
|
575
|
+
self.console.print(
|
|
576
|
+
Panel(stats_table, title="📊 Agent Statistics", box=ROUNDED)
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
except Exception as e:
|
|
580
|
+
self.console.print(f"[red]Error showing statistics: {e}[/red]")
|
|
581
|
+
logger.error(f"Statistics display error: {e}", exc_info=True)
|
|
582
|
+
|
|
583
|
+
def _show_summary_agent_list(self, agents: List[CustomAgentConfig]):
|
|
584
|
+
"""Display agents in summary table format."""
|
|
585
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
586
|
+
table.add_column("Name", style="cyan")
|
|
587
|
+
table.add_column("Category", style="yellow")
|
|
588
|
+
table.add_column("Model", style="green")
|
|
589
|
+
table.add_column("Status", style="white")
|
|
590
|
+
table.add_column("Tools", style="blue")
|
|
591
|
+
table.add_column("Last Used", style="dim")
|
|
592
|
+
|
|
593
|
+
for agent in agents:
|
|
594
|
+
# Format model info
|
|
595
|
+
model_info = f"{agent.model_settings.provider_type.value}:{agent.model_settings.model_name}"
|
|
596
|
+
if len(model_info) > 20:
|
|
597
|
+
model_info = model_info[:17] + "..."
|
|
598
|
+
|
|
599
|
+
# Format status with emoji
|
|
600
|
+
status_emoji = {
|
|
601
|
+
CustomAgentStatus.ACTIVE: "✅",
|
|
602
|
+
CustomAgentStatus.DRAFT: "📝",
|
|
603
|
+
CustomAgentStatus.DISABLED: "❌",
|
|
604
|
+
CustomAgentStatus.ARCHIVED: "📦",
|
|
605
|
+
CustomAgentStatus.ERROR: "⚠️",
|
|
606
|
+
}
|
|
607
|
+
status_display = (
|
|
608
|
+
f"{status_emoji.get(agent.status, '')} {agent.status.value}"
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
# Format tools count
|
|
612
|
+
tools_count = f"{len(agent.enabled_tools)} tools"
|
|
613
|
+
|
|
614
|
+
# Format last used
|
|
615
|
+
if agent.metadata.last_used:
|
|
616
|
+
last_used = agent.metadata.last_used.strftime("%m-%d %H:%M")
|
|
617
|
+
else:
|
|
618
|
+
last_used = "Never"
|
|
619
|
+
|
|
620
|
+
table.add_row(
|
|
621
|
+
agent.name[:25] + ("..." if len(agent.name) > 25 else ""),
|
|
622
|
+
agent.category.value.title(),
|
|
623
|
+
model_info,
|
|
624
|
+
status_display,
|
|
625
|
+
tools_count,
|
|
626
|
+
last_used,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
self.console.print(table)
|
|
630
|
+
self.console.print(f"\\n[dim]Showing {len(agents)} agent(s)[/dim]")
|
|
631
|
+
|
|
632
|
+
def _show_detailed_agent_list(self, agents: List[CustomAgentConfig]):
|
|
633
|
+
"""Display agents with detailed information."""
|
|
634
|
+
for i, agent in enumerate(agents):
|
|
635
|
+
if i > 0:
|
|
636
|
+
self.console.print() # Spacing between agents
|
|
637
|
+
|
|
638
|
+
self._show_agent_summary(agent)
|
|
639
|
+
|
|
640
|
+
def _show_agent_details(self, agent: CustomAgentConfig):
|
|
641
|
+
"""Show comprehensive details for a single agent."""
|
|
642
|
+
# Header
|
|
643
|
+
header = f"🤖 {agent.name}"
|
|
644
|
+
if agent.description:
|
|
645
|
+
header += f" - {agent.description}"
|
|
646
|
+
|
|
647
|
+
self.console.print(Panel(header, style="bold cyan", box=ROUNDED))
|
|
648
|
+
|
|
649
|
+
# Basic information table
|
|
650
|
+
info_table = Table(show_header=False, box=None)
|
|
651
|
+
info_table.add_column("Field", style="cyan", width=20)
|
|
652
|
+
info_table.add_column("Value", style="white")
|
|
653
|
+
|
|
654
|
+
info_table.add_row("ID", agent.id)
|
|
655
|
+
info_table.add_row("Category", agent.category.value.title())
|
|
656
|
+
info_table.add_row("Status", f"{agent.status.value.title()}")
|
|
657
|
+
info_table.add_row("Created", agent.created_at.strftime("%Y-%m-%d %H:%M:%S"))
|
|
658
|
+
info_table.add_row(
|
|
659
|
+
"Last Modified", agent.updated_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
660
|
+
)
|
|
661
|
+
info_table.add_row("Usage Count", str(agent.metadata.usage_count))
|
|
662
|
+
|
|
663
|
+
if agent.metadata.last_used:
|
|
664
|
+
info_table.add_row(
|
|
665
|
+
"Last Used",
|
|
666
|
+
agent.metadata.last_used.strftime("%Y-%m-%d %H:%M:%S"),
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
if agent.base_template_id:
|
|
670
|
+
info_table.add_row("Base Template", agent.base_template_id)
|
|
671
|
+
|
|
672
|
+
self.console.print(Panel(info_table, title="📋 Basic Information", box=ROUNDED))
|
|
673
|
+
|
|
674
|
+
# Model configuration
|
|
675
|
+
model_table = Table(show_header=False, box=None)
|
|
676
|
+
model_table.add_column("Field", style="cyan", width=20)
|
|
677
|
+
model_table.add_column("Value", style="white")
|
|
678
|
+
|
|
679
|
+
model_table.add_row("Provider", agent.model_settings.provider_type.value)
|
|
680
|
+
model_table.add_row("Model", agent.model_settings.model_name)
|
|
681
|
+
model_table.add_row("Temperature", str(agent.model_settings.temperature))
|
|
682
|
+
|
|
683
|
+
if agent.model_settings.max_tokens:
|
|
684
|
+
model_table.add_row("Max Tokens", str(agent.model_settings.max_tokens))
|
|
685
|
+
|
|
686
|
+
self.console.print(
|
|
687
|
+
Panel(model_table, title="🧠 Model Configuration", box=ROUNDED)
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
# Tools and capabilities
|
|
691
|
+
tools_text = "\\n".join(
|
|
692
|
+
[
|
|
693
|
+
f"• {tool.value.replace('_', ' ').title()}"
|
|
694
|
+
for tool in agent.enabled_tools
|
|
695
|
+
]
|
|
696
|
+
)
|
|
697
|
+
caps_text = "\\n".join(
|
|
698
|
+
[f"• {cap.value.replace('_', ' ').title()}" for cap in agent.capabilities]
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
tools_panel = Panel(
|
|
702
|
+
tools_text or "[dim]None[/dim]",
|
|
703
|
+
title="🔧 Enabled Tools",
|
|
704
|
+
box=ROUNDED,
|
|
705
|
+
)
|
|
706
|
+
caps_panel = Panel(
|
|
707
|
+
caps_text or "[dim]None[/dim]",
|
|
708
|
+
title="⭐ Capabilities",
|
|
709
|
+
box=ROUNDED,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
self.console.print(Columns([tools_panel, caps_panel], equal=True))
|
|
713
|
+
|
|
714
|
+
# Context and behavior
|
|
715
|
+
context_table = Table(show_header=False, box=None)
|
|
716
|
+
context_table.add_column("Field", style="cyan", width=20)
|
|
717
|
+
context_table.add_column("Value", style="white")
|
|
718
|
+
|
|
719
|
+
context_table.add_row(
|
|
720
|
+
"Context Window",
|
|
721
|
+
f"{agent.context_parameters.context_window_size:,} tokens",
|
|
722
|
+
)
|
|
723
|
+
context_table.add_row(
|
|
724
|
+
"Memory Enabled",
|
|
725
|
+
"✅" if agent.context_parameters.conversation_memory else "❌",
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
if agent.context_parameters.conversation_memory:
|
|
729
|
+
context_table.add_row(
|
|
730
|
+
"Memory Limit",
|
|
731
|
+
f"{agent.context_parameters.memory_limit} messages",
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
context_table.add_row(
|
|
735
|
+
"Response Format", agent.context_parameters.response_format
|
|
736
|
+
)
|
|
737
|
+
context_table.add_row("Safety Level", agent.behavior_rules.safety_level.title())
|
|
738
|
+
context_table.add_row(
|
|
739
|
+
"Reasoning Style",
|
|
740
|
+
agent.behavior_rules.reasoning_style.replace("_", " ").title(),
|
|
741
|
+
)
|
|
742
|
+
context_table.add_row(
|
|
743
|
+
"Creativity Level", agent.behavior_rules.creativity_level.title()
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
self.console.print(
|
|
747
|
+
Panel(context_table, title="💭 Context & Behavior", box=ROUNDED)
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
# System prompt (if present)
|
|
751
|
+
if agent.context_parameters.system_prompt:
|
|
752
|
+
prompt_preview = agent.context_parameters.system_prompt[:300]
|
|
753
|
+
if len(agent.context_parameters.system_prompt) > 300:
|
|
754
|
+
prompt_preview += "\\n\\n[dim]... (truncated)[/dim]"
|
|
755
|
+
|
|
756
|
+
self.console.print(
|
|
757
|
+
Panel(
|
|
758
|
+
prompt_preview,
|
|
759
|
+
title="📝 System Prompt (Preview)",
|
|
760
|
+
box=ROUNDED,
|
|
761
|
+
)
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
def _show_agent_summary(self, agent: CustomAgentConfig):
|
|
765
|
+
"""Show a concise summary of an agent."""
|
|
766
|
+
summary_text = Text.assemble(
|
|
767
|
+
f"🤖 ",
|
|
768
|
+
("", "bold cyan"),
|
|
769
|
+
f"{agent.name}",
|
|
770
|
+
("", "bold cyan"),
|
|
771
|
+
f" ({agent.category.value}) - ",
|
|
772
|
+
f"{agent.model_settings.provider_type.value}:{agent.model_settings.model_name}",
|
|
773
|
+
f"\\n {len(agent.enabled_tools)} tools, {len(agent.capabilities)} capabilities",
|
|
774
|
+
f" • Status: {agent.status.value}",
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
self.console.print(Panel(summary_text, box=SIMPLE))
|
|
778
|
+
|
|
779
|
+
def _sort_agents(
|
|
780
|
+
self, agents: List[CustomAgentConfig], sort_by: str
|
|
781
|
+
) -> List[CustomAgentConfig]:
|
|
782
|
+
"""Sort agents by specified field."""
|
|
783
|
+
sort_functions = {
|
|
784
|
+
"name": lambda a: a.name.lower(),
|
|
785
|
+
"created_at": lambda a: a.created_at,
|
|
786
|
+
"updated_at": lambda a: a.updated_at,
|
|
787
|
+
"last_used": lambda a: a.metadata.last_used or datetime.min,
|
|
788
|
+
"usage_count": lambda a: a.metadata.usage_count,
|
|
789
|
+
"category": lambda a: a.category.value,
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if sort_by not in sort_functions:
|
|
793
|
+
logger.warning(f"Unknown sort field: {sort_by}, using name")
|
|
794
|
+
sort_by = "name"
|
|
795
|
+
|
|
796
|
+
return sorted(agents, key=sort_functions[sort_by])
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
# Utility functions for easy integration
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def create_agent_manager(console: Optional[Console] = None) -> AgentManager:
|
|
803
|
+
"""Create and initialize an agent manager."""
|
|
804
|
+
return AgentManager(console=console)
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
async def list_available_agents(
|
|
808
|
+
console: Optional[Console] = None,
|
|
809
|
+
) -> List[CustomAgentConfig]:
|
|
810
|
+
"""Quick function to list all available agents."""
|
|
811
|
+
manager = create_agent_manager(console)
|
|
812
|
+
return await manager.list_agents()
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
async def load_agent_by_name(
|
|
816
|
+
name: str, console: Optional[Console] = None
|
|
817
|
+
) -> Optional[CustomAgentConfig]:
|
|
818
|
+
"""Quick function to load an agent by name."""
|
|
819
|
+
manager = create_agent_manager(console)
|
|
820
|
+
return await manager.load_agent(name)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def get_agent_statistics(console: Optional[Console] = None) -> Dict[str, Any]:
|
|
824
|
+
"""Get agent repository statistics."""
|
|
825
|
+
repository = AgentRepository()
|
|
826
|
+
return repository.get_statistics()
|