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.
Files changed (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,865 @@
1
+ """
2
+ Agent CLI Handler for Omnimancer.
3
+
4
+ This module provides the AgentCLIHandler class that processes /agents commands
5
+ and coordinates with the agent engine to expose agent functionality to users.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ from typing import List, Optional
11
+
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.prompt import Confirm
15
+ from rich.status import Status
16
+ from rich.table import Table
17
+
18
+ from ...core.agent.approval_manager import EnhancedApprovalManager
19
+ from ...core.agent.persona import (
20
+ PersonaManager,
21
+ PersonaStatus,
22
+ get_persona_manager,
23
+ )
24
+ from ...core.agent_engine import AgentEngine
25
+ from ...utils.errors import AgentError
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class AgentError(Exception):
31
+ """Agent-specific error."""
32
+
33
+ pass
34
+
35
+
36
+ class AgentNotFoundError(AgentError):
37
+ """Agent not found error."""
38
+
39
+ pass
40
+
41
+
42
+ class PermissionDeniedError(AgentError):
43
+ """Permission denied error."""
44
+
45
+ pass
46
+
47
+
48
+ class AgentBusyError(AgentError):
49
+ """Agent is busy error."""
50
+
51
+ pass
52
+
53
+
54
+ class ConfigurationError(AgentError):
55
+ """Configuration error."""
56
+
57
+ pass
58
+
59
+
60
+ class AgentCLIHandler:
61
+ """
62
+ Handler class that processes /agents commands and coordinates with the agent engine.
63
+
64
+ This class serves as the bridge between CLI commands and the agent engine,
65
+ providing user-friendly interfaces for agent management.
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ agent_engine: Optional[AgentEngine] = None,
71
+ approval_manager: Optional[EnhancedApprovalManager] = None,
72
+ console: Optional[Console] = None,
73
+ persona_manager: Optional[PersonaManager] = None,
74
+ verbose: bool = False,
75
+ ):
76
+ """
77
+ Initialize the agent CLI handler.
78
+
79
+ Args:
80
+ agent_engine: Agent engine instance for coordination
81
+ approval_manager: Enhanced approval manager for permissions
82
+ console: Rich console for output formatting
83
+ persona_manager: Persona manager for agent personality management
84
+ verbose: Enable verbose logging and debugging output
85
+ """
86
+ try:
87
+ self.agent_engine = agent_engine
88
+ self.approval_manager = approval_manager
89
+ self.console = console or Console()
90
+ self.verbose = verbose
91
+
92
+ # Initialize persona manager with error handling
93
+ try:
94
+ self.persona_manager = persona_manager or get_persona_manager()
95
+ except Exception as e:
96
+ logger.error(f"Failed to initialize PersonaManager: {e}")
97
+ if self.verbose:
98
+ self.console.print(
99
+ f"[yellow]Warning: PersonaManager initialization failed: {e}[/yellow]"
100
+ )
101
+ raise ConfigurationError(f"Failed to initialize persona system: {e}")
102
+
103
+ # Current agent tracking
104
+ self.current_agent = None
105
+
106
+ if self.verbose:
107
+ self.console.print(
108
+ "[dim]Agent CLI Handler initialized successfully[/dim]"
109
+ )
110
+
111
+ except Exception as e:
112
+ logger.error(f"Failed to initialize AgentCLIHandler: {e}")
113
+ if console:
114
+ console.print(
115
+ f"[red]Error: Failed to initialize agent system: {e}[/red]"
116
+ )
117
+ raise ConfigurationError(f"Agent handler initialization failed: {e}")
118
+
119
+ async def handle_command(self, args: List[str]) -> None:
120
+ """
121
+ Handle /agents command with subcommands.
122
+
123
+ Args:
124
+ args: Command arguments from CLI parser
125
+ """
126
+ if not args:
127
+ # Default to list if no subcommand provided
128
+ args = ["list"]
129
+
130
+ subcommand = args[0].lower()
131
+
132
+ try:
133
+ if self.verbose:
134
+ self.console.print(
135
+ f"[dim]Executing agents subcommand: {subcommand}[/dim]"
136
+ )
137
+
138
+ if subcommand == "list":
139
+ await self.handle_list(args[1:] if len(args) > 1 else [])
140
+ elif subcommand == "enable":
141
+ await self.handle_enable(args[1:] if len(args) > 1 else [])
142
+ elif subcommand == "disable":
143
+ await self.handle_disable(args[1:] if len(args) > 1 else [])
144
+ elif subcommand == "status":
145
+ await self.handle_status(args[1:] if len(args) > 1 else [])
146
+ elif subcommand == "config":
147
+ await self.handle_config(args[1:] if len(args) > 1 else [])
148
+ elif subcommand == "switch":
149
+ await self.handle_switch(args[1:] if len(args) > 1 else [])
150
+ elif subcommand == "current":
151
+ await self.handle_current()
152
+ elif subcommand == "info":
153
+ await self.handle_info(args[1:] if len(args) > 1 else [])
154
+ else:
155
+ self._show_error_with_suggestions(
156
+ f"Unknown agents subcommand: '{subcommand}'",
157
+ f"Did you mean one of: list, enable, disable, status, config, switch, current, info?",
158
+ )
159
+ self._show_help()
160
+
161
+ except AgentNotFoundError as e:
162
+ logger.error(f"Agent not found: {e}")
163
+ self._show_error_with_suggestions(
164
+ str(e), "Use '/agents list' to see available agents."
165
+ )
166
+ except PermissionDeniedError as e:
167
+ logger.error(f"Permission denied: {e}")
168
+ self._show_error_with_suggestions(
169
+ f"Permission denied: {e}",
170
+ "Check your user permissions or contact an administrator.",
171
+ )
172
+ except AgentBusyError as e:
173
+ logger.error(f"Agent busy: {e}")
174
+ self._show_error_with_suggestions(
175
+ f"Agent is busy: {e}",
176
+ "Wait for the current operation to complete or use '/agents status' to check progress.",
177
+ )
178
+ except ConfigurationError as e:
179
+ logger.error(f"Configuration error: {e}")
180
+ self._show_error_with_suggestions(
181
+ f"Configuration error: {e}",
182
+ "Check your agent configuration and try again.",
183
+ )
184
+ except Exception as e:
185
+ logger.error(f"Agent command failed unexpectedly: {e}", exc_info=True)
186
+ self._show_error_with_suggestions(
187
+ f"Unexpected error occurred: {str(e)[:100]}{'...' if len(str(e)) > 100 else ''}",
188
+ "Try the command again or use '/agents status' to check system health.",
189
+ )
190
+
191
+ if self.verbose:
192
+ self.console.print(f"[dim]Full error details: {e}[/dim]")
193
+
194
+ async def handle_list(self, args: List[str]) -> None:
195
+ """
196
+ Handle 'list' subcommand to show available agents.
197
+
198
+ Args:
199
+ args: Additional arguments
200
+ """
201
+ try:
202
+ table = Table(title="Available Agent Personas")
203
+ table.add_column("Agent ID", style="cyan", width=12)
204
+ table.add_column("Name", style="bold", width=18)
205
+ table.add_column("Description", style="white", width=40)
206
+ table.add_column("Provider", style="green", width=12)
207
+ table.add_column("Model", style="blue", width=25)
208
+ table.add_column("Status", style="yellow", width=10)
209
+
210
+ # Get personas from PersonaManager
211
+ personas = self.persona_manager.get_all_personas()
212
+
213
+ for agent_id, persona in personas.items():
214
+ # Map persona status to display
215
+ if persona.status == PersonaStatus.ACTIVE:
216
+ status = "🟢 Active"
217
+ elif persona.status == PersonaStatus.AVAILABLE:
218
+ status = "⚪ Available"
219
+ elif persona.status == PersonaStatus.DISABLED:
220
+ status = "🔴 Disabled"
221
+ elif persona.status == PersonaStatus.ERROR:
222
+ status = "❌ Error"
223
+ elif persona.status == PersonaStatus.LOADING:
224
+ status = "🔄 Loading"
225
+ else:
226
+ status = "❓ Unknown"
227
+
228
+ # Get provider info from configuration
229
+ config = persona.configuration
230
+ provider_name = (
231
+ config.provider_type.value if config.provider_type else "Unknown"
232
+ )
233
+
234
+ table.add_row(
235
+ agent_id,
236
+ persona.name,
237
+ (
238
+ persona.description[:37] + "..."
239
+ if len(persona.description) > 40
240
+ else persona.description
241
+ ),
242
+ provider_name,
243
+ config.model_id or "Unknown",
244
+ status,
245
+ )
246
+
247
+ self.console.print(table)
248
+
249
+ # Show current agent if any
250
+ active_persona = self.persona_manager.active_persona
251
+ if active_persona:
252
+ self.console.print(
253
+ f"\n[bold green]Current active agent:[/bold green] {active_persona.name} ({active_persona.id})"
254
+ )
255
+ else:
256
+ self.console.print(
257
+ f"\n[dim]No agent currently active. Use '/agents enable <agent_id>' to activate.[/dim]"
258
+ )
259
+
260
+ except Exception as e:
261
+ self._show_error(f"Failed to list agents: {e}")
262
+
263
+ async def handle_enable(self, args: List[str]) -> None:
264
+ """
265
+ Handle 'enable' subcommand to activate an agent.
266
+
267
+ Args:
268
+ args: Agent ID to enable
269
+ """
270
+ if not args:
271
+ self._show_error_with_suggestions(
272
+ "Agent enable requires an agent ID.",
273
+ "Use '/agents list' to see available agents, then try '/agents enable <agent_id>'",
274
+ )
275
+ return
276
+
277
+ agent_id = args[0].lower()
278
+
279
+ try:
280
+ if self.verbose:
281
+ self.console.print(
282
+ f"[dim]Checking if agent '{agent_id}' exists...[/dim]"
283
+ )
284
+
285
+ # Check if persona exists
286
+ persona = self.persona_manager.get_persona(agent_id)
287
+ if not persona:
288
+ # Suggest similar agents
289
+ available_agents = list(self.persona_manager.get_all_personas().keys())
290
+ suggestions = [
291
+ agent
292
+ for agent in available_agents
293
+ if agent_id in agent or agent in agent_id
294
+ ]
295
+
296
+ if suggestions:
297
+ suggestion_text = f"Did you mean: {', '.join(suggestions)[:50]}?"
298
+ else:
299
+ suggestion_text = (
300
+ f"Available agents: {', '.join(available_agents)[:50]}..."
301
+ )
302
+
303
+ raise AgentNotFoundError(
304
+ f"Agent '{agent_id}' not found. {suggestion_text}"
305
+ )
306
+
307
+ # Check if agent is already active
308
+ if persona.is_active:
309
+ self._show_info_with_action(
310
+ f"Agent '{agent_id}' is already active.",
311
+ f"Use '/agents status {agent_id}' to see details or '/agents disable {agent_id}' to deactivate.",
312
+ )
313
+ return
314
+
315
+ # Check permissions if approval manager is available
316
+ if self.approval_manager:
317
+ try:
318
+ # This would check if user has permission to enable agents
319
+ if not await self._check_permission_for_action(
320
+ "agent_enable", agent_id
321
+ ):
322
+ raise PermissionDeniedError(
323
+ f"You don't have permission to enable agent '{agent_id}'"
324
+ )
325
+ except Exception as perm_e:
326
+ if self.verbose:
327
+ self.console.print(
328
+ f"[dim]Permission check failed: {perm_e}[/dim]"
329
+ )
330
+ # Continue if permission system is not available
331
+
332
+ # Show progress indicator for activation
333
+ with Status(
334
+ f"[bold blue]Activating {persona.name}...",
335
+ console=self.console,
336
+ ):
337
+ if self.verbose:
338
+ self.console.print(
339
+ f"[dim]Calling PersonaManager.activate_persona('{agent_id}')...[/dim]"
340
+ )
341
+
342
+ # Small delay to show the spinner (activation is usually instant)
343
+ time.sleep(0.5)
344
+
345
+ # Activate the persona using PersonaManager
346
+ success = self.persona_manager.activate_persona(agent_id)
347
+
348
+ if not success:
349
+ raise ConfigurationError(
350
+ f"Failed to activate persona '{agent_id}'. Check system configuration."
351
+ )
352
+
353
+ # Update current agent tracking
354
+ self.current_agent = agent_id
355
+
356
+ # Show success message
357
+ config = persona.configuration
358
+ capabilities_list = [
359
+ cap.value.replace("_", " ").title() for cap in persona.capabilities
360
+ ]
361
+
362
+ self.console.print(
363
+ Panel(
364
+ f"[bold green]Agent Enabled Successfully[/bold green]\n\n"
365
+ f"[bold]Agent:[/bold] {persona.name}\n"
366
+ f"[bold]Provider:[/bold] {config.provider_type.value if config.provider_type else 'Unknown'}\n"
367
+ f"[bold]Model:[/bold] {config.model_id or 'Unknown'}\n"
368
+ f"[bold]Capabilities:[/bold] {', '.join(capabilities_list)}\n\n"
369
+ f"The {persona.name} is now active and ready for use.\n"
370
+ f"Use '/agents status' to monitor or '/agents disable {agent_id}' to deactivate.",
371
+ title=f"✅ {persona.name} Active",
372
+ border_style="green",
373
+ )
374
+ )
375
+
376
+ except (AgentNotFoundError, PermissionDeniedError, ConfigurationError):
377
+ # These are handled by the main command handler
378
+ raise
379
+ except Exception as e:
380
+ logger.error(
381
+ f"Unexpected error enabling agent '{agent_id}': {e}",
382
+ exc_info=True,
383
+ )
384
+ raise ConfigurationError(f"Failed to enable agent '{agent_id}': {e}")
385
+
386
+ async def handle_disable(self, args: List[str]) -> None:
387
+ """
388
+ Handle 'disable' subcommand to deactivate an agent.
389
+
390
+ Args:
391
+ args: Agent ID to disable (optional if only one is active)
392
+ """
393
+ try:
394
+ agent_id = None
395
+ persona = None
396
+
397
+ if args:
398
+ agent_id = args[0].lower()
399
+ if self.verbose:
400
+ self.console.print(f"[dim]Looking up agent '{agent_id}'...[/dim]")
401
+
402
+ persona = self.persona_manager.get_persona(agent_id)
403
+ if not persona:
404
+ available_agents = list(
405
+ self.persona_manager.get_all_personas().keys()
406
+ )
407
+ suggestions = [
408
+ agent
409
+ for agent in available_agents
410
+ if agent_id in agent or agent in agent_id
411
+ ]
412
+
413
+ if suggestions:
414
+ suggestion_text = (
415
+ f"Did you mean: {', '.join(suggestions)[:50]}?"
416
+ )
417
+ else:
418
+ suggestion_text = f"Use '/agents list' to see available agents."
419
+
420
+ raise AgentNotFoundError(
421
+ f"Agent '{agent_id}' not found. {suggestion_text}"
422
+ )
423
+ else:
424
+ # Use currently active persona
425
+ if self.verbose:
426
+ self.console.print(
427
+ "[dim]Looking for currently active persona...[/dim]"
428
+ )
429
+
430
+ active_persona = self.persona_manager.active_persona
431
+ if active_persona:
432
+ agent_id = active_persona.id
433
+ persona = active_persona
434
+ else:
435
+ self._show_info_with_action(
436
+ "No agent is currently active.",
437
+ "Use '/agents list' to see available agents or '/agents enable <agent_id>' to activate one.",
438
+ )
439
+ return
440
+
441
+ if not persona.is_active:
442
+ self._show_info_with_action(
443
+ f"Agent '{agent_id}' is already disabled.",
444
+ f"Use '/agents enable {agent_id}' to activate it or '/agents list' to see active agents.",
445
+ )
446
+ return
447
+
448
+ # Check permissions if approval manager is available
449
+ if self.approval_manager:
450
+ try:
451
+ if not await self._check_permission_for_action(
452
+ "agent_disable", agent_id
453
+ ):
454
+ raise PermissionDeniedError(
455
+ f"You don't have permission to disable agent '{agent_id}'"
456
+ )
457
+ except Exception as perm_e:
458
+ if self.verbose:
459
+ self.console.print(
460
+ f"[dim]Permission check failed: {perm_e}[/dim]"
461
+ )
462
+ # Continue if permission system is not available
463
+
464
+ # Check for active operations (placeholder - would integrate with agent engine)
465
+ active_operations = 0 # This would come from agent engine status
466
+
467
+ # Show confirmation prompt for destructive action
468
+ if active_operations > 0:
469
+ warning_msg = f"⚠️ Agent '{agent_id}' has {active_operations} active operations.\nDisabling will terminate these operations."
470
+ if not Confirm.ask(f"{warning_msg}\n\nDisable anyway?", default=False):
471
+ self._show_info("Agent remains enabled.")
472
+ return
473
+ elif not args: # Only confirm if no specific agent was provided
474
+ if not Confirm.ask(
475
+ f"Disable the active agent '{persona.name}'?", default=True
476
+ ):
477
+ self._show_info("Agent remains enabled.")
478
+ return
479
+
480
+ # Show progress indicator for deactivation
481
+ with Status(
482
+ f"[bold red]Deactivating {persona.name}...",
483
+ console=self.console,
484
+ ):
485
+ if self.verbose:
486
+ self.console.print(
487
+ f"[dim]Calling PersonaManager.deactivate_persona()...[/dim]"
488
+ )
489
+
490
+ # Small delay to show the spinner
491
+ time.sleep(0.3)
492
+
493
+ # Deactivate the persona using PersonaManager
494
+ success = self.persona_manager.deactivate_persona()
495
+
496
+ if not success:
497
+ raise ConfigurationError(
498
+ f"Failed to deactivate persona '{agent_id}'. Check system state."
499
+ )
500
+
501
+ # Update current agent tracking
502
+ if self.current_agent == agent_id:
503
+ self.current_agent = None
504
+
505
+ self.console.print(
506
+ Panel(
507
+ f"[bold red]Agent Disabled Successfully[/bold red]\n\n"
508
+ f"[bold]Agent:[/bold] {persona.name}\n"
509
+ f"All operations have been stopped.\n\n"
510
+ f"Use '/agents enable {agent_id}' to reactivate or '/agents list' to see other options.",
511
+ title=f"🔴 {persona.name} Disabled",
512
+ border_style="red",
513
+ )
514
+ )
515
+
516
+ except (AgentNotFoundError, PermissionDeniedError, ConfigurationError):
517
+ # These are handled by the main command handler
518
+ raise
519
+ except Exception as e:
520
+ logger.error(f"Unexpected error disabling agent: {e}", exc_info=True)
521
+ raise ConfigurationError(f"Failed to disable agent: {e}")
522
+
523
+ async def handle_status(self, args: List[str]) -> None:
524
+ """
525
+ Handle 'status' subcommand to show agent status.
526
+
527
+ Args:
528
+ args: Optional agent ID for specific status
529
+ """
530
+ try:
531
+ if args:
532
+ # Show specific agent status
533
+ agent_id = args[0].lower()
534
+ persona = self.persona_manager.get_persona(agent_id)
535
+ if not persona:
536
+ raise AgentNotFoundError(f"Agent '{agent_id}' not found")
537
+
538
+ # Map persona status to colors and text
539
+ status_mapping = {
540
+ PersonaStatus.ACTIVE: ("green", "Active"),
541
+ PersonaStatus.AVAILABLE: ("yellow", "Available"),
542
+ PersonaStatus.DISABLED: ("red", "Disabled"),
543
+ PersonaStatus.ERROR: ("red", "Error"),
544
+ PersonaStatus.LOADING: ("blue", "Loading"),
545
+ }
546
+
547
+ status_color, status_text = status_mapping.get(
548
+ persona.status, ("gray", "Unknown")
549
+ )
550
+ config = persona.configuration
551
+ capabilities_list = [
552
+ cap.value.replace("_", " ").title() for cap in persona.capabilities
553
+ ]
554
+
555
+ self.console.print(
556
+ Panel(
557
+ f"[bold]Name:[/bold] {persona.name}\n"
558
+ f"[bold]Status:[/bold] [{status_color}]{status_text}[/{status_color}]\n"
559
+ f"[bold]Provider:[/bold] {config.provider_type.value if config.provider_type else 'Unknown'}\n"
560
+ f"[bold]Model:[/bold] {config.model_id or 'Unknown'}\n"
561
+ f"[bold]Description:[/bold] {persona.description}\n"
562
+ f"[bold]Capabilities:[/bold] {', '.join(capabilities_list)}\n",
563
+ title=f"Agent Status: {persona.name}",
564
+ border_style=status_color,
565
+ )
566
+ )
567
+ else:
568
+ # Show general agent status
569
+ personas = self.persona_manager.get_all_personas()
570
+ active_count = sum(1 for p in personas.values() if p.is_active)
571
+ total_count = len(personas)
572
+
573
+ status_text = f"[bold]Agent System Status[/bold]\n\n"
574
+ status_text += f"Available agents: {total_count}\n"
575
+ status_text += f"Active agents: {active_count}\n"
576
+
577
+ active_persona = self.persona_manager.active_persona
578
+ if active_persona:
579
+ status_text += f"Current active agent: {active_persona.name} ({active_persona.id})\n\n"
580
+ config = active_persona.configuration
581
+ status_text += f"[bold green]Active Agent Details:[/bold green]\n"
582
+ status_text += f"• Name: {active_persona.name}\n"
583
+ status_text += f"• Provider: {config.provider_type.value if config.provider_type else 'Unknown'}\n"
584
+ status_text += f"• Model: {config.model_id or 'Unknown'}\n"
585
+ else:
586
+ status_text += f"Current active agent: None\n\n"
587
+ status_text += f"[dim]Use '/agents enable <agent_id>' to activate an agent.[/dim]"
588
+
589
+ self.console.print(
590
+ Panel(
591
+ status_text,
592
+ title="🤖 Agent System Status",
593
+ border_style="blue",
594
+ )
595
+ )
596
+
597
+ except AgentNotFoundError as e:
598
+ self._show_error(str(e))
599
+ except Exception as e:
600
+ self._show_error(f"Failed to get agent status: {e}")
601
+
602
+ async def handle_config(self, args: List[str]) -> None:
603
+ """
604
+ Handle 'config' subcommand to view/modify agent settings.
605
+
606
+ Args:
607
+ args: Agent ID and optional config parameters
608
+ """
609
+ if not args:
610
+ self._show_error(
611
+ "Config command requires an agent ID. Use '/agents list' to see available agents."
612
+ )
613
+ return
614
+
615
+ agent_id = args[0].lower()
616
+
617
+ try:
618
+ persona = self.persona_manager.get_persona(agent_id)
619
+ if not persona:
620
+ raise AgentNotFoundError(f"Agent '{agent_id}' not found")
621
+
622
+ config = persona.configuration
623
+ capabilities_list = [
624
+ cap.value.replace("_", " ").title() for cap in persona.capabilities
625
+ ]
626
+ status_text = (
627
+ "Active"
628
+ if persona.is_active
629
+ else (
630
+ "Available"
631
+ if persona.status == PersonaStatus.AVAILABLE
632
+ else persona.status.value.title()
633
+ )
634
+ )
635
+
636
+ # For now, just show current configuration
637
+ # In the future, this would allow modifying settings
638
+ self.console.print(
639
+ Panel(
640
+ f"[bold]Configuration for {persona.name}[/bold]\n\n"
641
+ f"[bold]Provider:[/bold] {config.provider_type.value if config.provider_type else 'Unknown'}\n"
642
+ f"[bold]Model:[/bold] {config.model_id or 'Unknown'}\n"
643
+ f"[bold]Temperature:[/bold] {config.temperature if config.temperature is not None else 'Default'}\n"
644
+ f"[bold]Max Tokens:[/bold] {config.max_tokens or 'Default'}\n"
645
+ f"[bold]Tools Enabled:[/bold] {'Yes' if config.tools_enabled else 'No'}\n"
646
+ f"[bold]Web Search:[/bold] {'Yes' if config.web_search_enabled else 'No'}\n"
647
+ f"[bold]File Operations:[/bold] {'Yes' if config.file_operations_enabled else 'No'}\n"
648
+ f"[bold]Approval Required:[/bold] {'Yes' if config.approval_required else 'No'}\n"
649
+ f"[bold]Capabilities:[/bold] {', '.join(capabilities_list)}\n"
650
+ f"[bold]Status:[/bold] {status_text}\n\n"
651
+ f"[dim]Configuration editing will be available in future versions.[/dim]",
652
+ title=f"⚙️ {persona.name} Configuration",
653
+ border_style="cyan",
654
+ )
655
+ )
656
+
657
+ except AgentNotFoundError as e:
658
+ self._show_error(str(e))
659
+ except Exception as e:
660
+ self._show_error(f"Failed to show agent config: {e}")
661
+
662
+ async def handle_switch(self, args: List[str]) -> None:
663
+ """
664
+ Handle 'switch' subcommand to switch between agents.
665
+
666
+ Args:
667
+ args: Agent ID to switch to
668
+ """
669
+ if not args:
670
+ self._show_error(
671
+ "Switch command requires an agent ID. Use '/agents list' to see available agents."
672
+ )
673
+ return
674
+
675
+ # Switch is essentially disable current + enable new
676
+ await self.handle_enable(args)
677
+
678
+ async def handle_current(self) -> None:
679
+ """Handle 'current' subcommand to show currently active agent."""
680
+ active_persona = self.persona_manager.active_persona
681
+ if active_persona:
682
+ config = active_persona.configuration
683
+ self.console.print(
684
+ f"[bold green]Current active agent:[/bold green] {active_persona.name} ({active_persona.id})"
685
+ )
686
+ self.console.print(
687
+ f"[bold]Provider:[/bold] {config.provider_type.value if config.provider_type else 'Unknown'}"
688
+ )
689
+ self.console.print(f"[bold]Model:[/bold] {config.model_id or 'Unknown'}")
690
+ else:
691
+ self.console.print("[dim]No agent is currently active.[/dim]")
692
+
693
+ async def handle_info(self, args: List[str]) -> None:
694
+ """
695
+ Handle 'info' subcommand to show detailed agent information.
696
+
697
+ Args:
698
+ args: Agent ID to get info for
699
+ """
700
+ if not args:
701
+ self._show_error(
702
+ "Info command requires an agent ID. Use '/agents list' to see available agents."
703
+ )
704
+ return
705
+
706
+ agent_id = args[0].lower()
707
+
708
+ try:
709
+ persona = self.persona_manager.get_persona(agent_id)
710
+ if not persona:
711
+ raise AgentNotFoundError(f"Agent '{agent_id}' not found")
712
+
713
+ config = persona.configuration
714
+ capabilities_list = [
715
+ cap.value.replace("_", " ").title() for cap in persona.capabilities
716
+ ]
717
+
718
+ # Get status with appropriate emoji
719
+ if persona.is_active:
720
+ status_display = "🟢 Active"
721
+ elif persona.status == PersonaStatus.AVAILABLE:
722
+ status_display = "⚪ Available"
723
+ elif persona.status == PersonaStatus.DISABLED:
724
+ status_display = "🔴 Disabled"
725
+ elif persona.status == PersonaStatus.ERROR:
726
+ status_display = "❌ Error"
727
+ elif persona.status == PersonaStatus.LOADING:
728
+ status_display = "🔄 Loading"
729
+ else:
730
+ status_display = f"❓ {persona.status.value.title()}"
731
+
732
+ info_text = f"[bold]{persona.name}[/bold]\n\n"
733
+ info_text += f"[bold]Description:[/bold] {persona.description}\n\n"
734
+ info_text += f"[bold]Technical Details:[/bold]\n"
735
+ info_text += f"• Provider: {config.provider_type.value if config.provider_type else 'Unknown'}\n"
736
+ info_text += f"• Model: {config.model_id or 'Unknown'}\n"
737
+ info_text += f"• Temperature: {config.temperature if config.temperature is not None else 'Default'}\n"
738
+ info_text += f"• Max Tokens: {config.max_tokens or 'Default'}\n"
739
+ info_text += f"• Status: {status_display}\n\n"
740
+ info_text += f"[bold]Capabilities:[/bold]\n"
741
+ for capability in capabilities_list:
742
+ info_text += f"• {capability}\n"
743
+
744
+ info_text += f"\n[bold]Settings:[/bold]\n"
745
+ info_text += f"• Tools Enabled: {'Yes' if config.tools_enabled else 'No'}\n"
746
+ info_text += (
747
+ f"• Web Search: {'Yes' if config.web_search_enabled else 'No'}\n"
748
+ )
749
+ info_text += f"• File Operations: {'Yes' if config.file_operations_enabled else 'No'}\n"
750
+ info_text += (
751
+ f"• Approval Required: {'Yes' if config.approval_required else 'No'}\n"
752
+ )
753
+
754
+ info_text += f"\n[bold]Usage:[/bold]\n"
755
+ info_text += f"• Enable: [cyan]/agents enable {agent_id}[/cyan]\n"
756
+ info_text += f"• Disable: [cyan]/agents disable {agent_id}[/cyan]\n"
757
+ info_text += f"• Status: [cyan]/agents status {agent_id}[/cyan]\n"
758
+
759
+ self.console.print(
760
+ Panel(
761
+ info_text,
762
+ title=f"ℹ️ {persona.name} Information",
763
+ border_style="blue",
764
+ )
765
+ )
766
+
767
+ except AgentNotFoundError as e:
768
+ self._show_error(str(e))
769
+ except Exception as e:
770
+ self._show_error(f"Failed to show agent info: {e}")
771
+
772
+ def _show_help(self) -> None:
773
+ """Show help for agents command."""
774
+ help_text = """[bold]Agent Commands:[/bold]
775
+
776
+ [cyan]/agents list[/cyan] - List all available agent personas
777
+ [cyan]/agents enable <agent_id>[/cyan] - Enable/activate an agent persona
778
+ [cyan]/agents disable [agent_id][/cyan] - Disable active agent
779
+ [cyan]/agents switch <agent_id>[/cyan] - Switch to different agent persona
780
+ [cyan]/agents status [agent_id][/cyan] - Show agent status (general or specific)
781
+ [cyan]/agents current[/cyan] - Show currently active agent
782
+ [cyan]/agents config <agent_id>[/cyan] - View agent configuration
783
+ [cyan]/agents info <agent_id>[/cyan] - Show detailed agent information
784
+
785
+ [bold]Available Agent Types:[/bold]
786
+ • [green]coding[/green] - Development-focused with Claude Sonnet + dev tools
787
+ • [blue]research[/blue] - Research-oriented with Perplexity + web search
788
+ • [magenta]creative[/magenta] - Creative writing with high-temperature models
789
+ • [yellow]performance[/yellow] - Fast and cost-efficient with optimized models
790
+ • [cyan]general[/cyan] - Balanced general-purpose configuration
791
+
792
+ [bold]Examples:[/bold]
793
+ [cyan]/agents enable coding[/cyan] - Activate the coding agent
794
+ [cyan]/agents status[/cyan] - Show overall agent system status
795
+ [cyan]/agents info research[/cyan] - Get details about the research agent"""
796
+
797
+ self.console.print(
798
+ Panel(
799
+ help_text,
800
+ title="🤖 Agent Management Help",
801
+ border_style="blue",
802
+ )
803
+ )
804
+
805
+ def _show_error(self, message: str) -> None:
806
+ """Show error message."""
807
+ self.console.print(f"[bold red]Error:[/bold red] {message}")
808
+
809
+ def _show_error_with_suggestions(self, error_message: str, suggestion: str) -> None:
810
+ """Show error message with actionable suggestions."""
811
+ self.console.print(
812
+ Panel(
813
+ f"[bold red]Error:[/bold red] {error_message}\n\n"
814
+ f"[bold]Suggestion:[/bold] {suggestion}",
815
+ title="❌ Error",
816
+ border_style="red",
817
+ )
818
+ )
819
+
820
+ def _show_info(self, message: str) -> None:
821
+ """Show info message."""
822
+ self.console.print(f"[blue]Info:[/blue] {message}")
823
+
824
+ def _show_info_with_action(self, info_message: str, action: str) -> None:
825
+ """Show info message with suggested action."""
826
+ self.console.print(
827
+ Panel(
828
+ f"[blue]Info:[/blue] {info_message}\n\n"
829
+ f"[bold]Next Steps:[/bold] {action}",
830
+ title="ℹ️ Information",
831
+ border_style="blue",
832
+ )
833
+ )
834
+
835
+ def _show_success(self, message: str) -> None:
836
+ """Show success message."""
837
+ self.console.print(f"[bold green]Success:[/bold green] {message}")
838
+
839
+ async def _check_permission_for_action(self, action: str, target: str) -> bool:
840
+ """
841
+ Check if user has permission for a specific action.
842
+
843
+ Args:
844
+ action: The action being attempted (e.g., 'agent_enable', 'agent_disable')
845
+ target: The target of the action (e.g., agent_id)
846
+
847
+ Returns:
848
+ True if permission is granted, False otherwise
849
+ """
850
+ try:
851
+ if not self.approval_manager:
852
+ return True # No approval manager means no restrictions
853
+
854
+ # This would integrate with the EnhancedApprovalManager
855
+ # For now, we'll assume all actions are permitted
856
+ if self.verbose:
857
+ self.console.print(
858
+ f"[dim]Checking permission for action '{action}' on target '{target}'[/dim]"
859
+ )
860
+
861
+ return True # Placeholder - would implement real permission checking
862
+
863
+ except Exception as e:
864
+ logger.error(f"Permission check failed for action '{action}': {e}")
865
+ return True # Fail open for now