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,535 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rich-based Terminal UI Components for Agent Status Display.
|
|
3
|
+
|
|
4
|
+
This module provides reusable Rich UI components for displaying agent status
|
|
5
|
+
information in a comprehensive and user-friendly terminal interface.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any, Dict, List, Union
|
|
10
|
+
|
|
11
|
+
from rich.console import Group
|
|
12
|
+
from rich.layout import Layout
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.progress import (
|
|
15
|
+
BarColumn,
|
|
16
|
+
Progress,
|
|
17
|
+
SpinnerColumn,
|
|
18
|
+
TextColumn,
|
|
19
|
+
TimeElapsedColumn,
|
|
20
|
+
)
|
|
21
|
+
from rich.rule import Rule
|
|
22
|
+
from rich.table import Table
|
|
23
|
+
from rich.text import Text
|
|
24
|
+
from rich.tree import Tree
|
|
25
|
+
|
|
26
|
+
from .status_core import (
|
|
27
|
+
AgentEvent,
|
|
28
|
+
AgentOperation,
|
|
29
|
+
AgentStatus,
|
|
30
|
+
EventType,
|
|
31
|
+
OperationStatus,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AgentStatusTable:
|
|
36
|
+
"""Rich table component for displaying agent status information."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, title: str = "Agent Status"):
|
|
39
|
+
self.title = title
|
|
40
|
+
|
|
41
|
+
def create_table(
|
|
42
|
+
self,
|
|
43
|
+
agent_statuses: Dict[str, AgentStatus],
|
|
44
|
+
agent_metadata: Dict[str, Dict[str, Any]] = None,
|
|
45
|
+
) -> Table:
|
|
46
|
+
"""
|
|
47
|
+
Create a Rich table displaying agent statuses.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
agent_statuses: Dictionary of agent IDs to their current status
|
|
51
|
+
agent_metadata: Optional metadata for each agent
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Rich Table object
|
|
55
|
+
"""
|
|
56
|
+
table = Table(title=self.title, show_header=True, header_style="bold magenta")
|
|
57
|
+
|
|
58
|
+
table.add_column("Agent ID", style="cyan", width=12)
|
|
59
|
+
table.add_column("Status", style="bold", width=12)
|
|
60
|
+
table.add_column("State", width=15)
|
|
61
|
+
table.add_column("Last Update", style="dim", width=12)
|
|
62
|
+
table.add_column("Operations", justify="right", width=10)
|
|
63
|
+
|
|
64
|
+
agent_metadata = agent_metadata or {}
|
|
65
|
+
|
|
66
|
+
for agent_id, status in agent_statuses.items():
|
|
67
|
+
metadata = agent_metadata.get(agent_id, {})
|
|
68
|
+
|
|
69
|
+
# Status with emoji and color
|
|
70
|
+
status_display = self._format_agent_status(status)
|
|
71
|
+
|
|
72
|
+
# State information
|
|
73
|
+
state = metadata.get("state", "Unknown")
|
|
74
|
+
|
|
75
|
+
# Last update time
|
|
76
|
+
last_update = metadata.get("last_update", datetime.now())
|
|
77
|
+
if isinstance(last_update, datetime):
|
|
78
|
+
time_str = last_update.strftime("%H:%M:%S")
|
|
79
|
+
else:
|
|
80
|
+
time_str = str(last_update)
|
|
81
|
+
|
|
82
|
+
# Active operations count
|
|
83
|
+
operations_count = metadata.get("active_operations", 0)
|
|
84
|
+
|
|
85
|
+
table.add_row(
|
|
86
|
+
agent_id,
|
|
87
|
+
status_display,
|
|
88
|
+
state,
|
|
89
|
+
time_str,
|
|
90
|
+
str(operations_count),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return table
|
|
94
|
+
|
|
95
|
+
def _format_agent_status(self, status: AgentStatus) -> Text:
|
|
96
|
+
"""Format agent status with appropriate color and emoji."""
|
|
97
|
+
status_mapping = {
|
|
98
|
+
AgentStatus.ENABLED: ("🟢", "green", "Enabled"),
|
|
99
|
+
AgentStatus.DISABLED: ("🔴", "red", "Disabled"),
|
|
100
|
+
AgentStatus.RUNNING: ("🔵", "blue", "Running"),
|
|
101
|
+
AgentStatus.PAUSED: ("🟡", "yellow", "Paused"),
|
|
102
|
+
AgentStatus.ERROR: ("❌", "red", "Error"),
|
|
103
|
+
AgentStatus.INITIALIZING: ("🔄", "blue", "Starting"),
|
|
104
|
+
AgentStatus.SHUTTING_DOWN: ("⏹️", "orange", "Stopping"),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
emoji, color, text = status_mapping.get(status, ("❓", "white", status.value))
|
|
108
|
+
|
|
109
|
+
return Text(f"{emoji} {text}", style=color)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class OperationProgress:
|
|
113
|
+
"""Rich progress component for displaying operation progress."""
|
|
114
|
+
|
|
115
|
+
def __init__(self):
|
|
116
|
+
self.progress = Progress(
|
|
117
|
+
SpinnerColumn(),
|
|
118
|
+
TextColumn("[progress.description]{task.description}"),
|
|
119
|
+
BarColumn(bar_width=None),
|
|
120
|
+
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
121
|
+
TimeElapsedColumn(),
|
|
122
|
+
console=None,
|
|
123
|
+
auto_refresh=True,
|
|
124
|
+
refresh_per_second=2,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def create_progress_display(
|
|
128
|
+
self, operations: List[AgentOperation]
|
|
129
|
+
) -> Union[Progress, Text]:
|
|
130
|
+
"""
|
|
131
|
+
Create a progress display for active operations.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
operations: List of operations to display
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Progress widget or Text if no operations
|
|
138
|
+
"""
|
|
139
|
+
if not operations:
|
|
140
|
+
return Text("No active operations", style="dim")
|
|
141
|
+
|
|
142
|
+
# Clear existing tasks
|
|
143
|
+
for task_id in list(self.progress.task_ids):
|
|
144
|
+
self.progress.remove_task(task_id)
|
|
145
|
+
|
|
146
|
+
# Add current operations
|
|
147
|
+
for operation in operations:
|
|
148
|
+
if operation.is_active:
|
|
149
|
+
description = (
|
|
150
|
+
f"[{operation.agent_id or 'system'}] {operation.description[:40]}"
|
|
151
|
+
)
|
|
152
|
+
if len(operation.description) > 40:
|
|
153
|
+
description += "..."
|
|
154
|
+
|
|
155
|
+
self.progress.add_task(
|
|
156
|
+
description=description,
|
|
157
|
+
total=100,
|
|
158
|
+
completed=operation.progress_percentage,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
self.progress
|
|
163
|
+
if self.progress.task_ids
|
|
164
|
+
else Text("No active operations", style="dim")
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class EventLogPanel:
|
|
169
|
+
"""Rich panel component for displaying recent events."""
|
|
170
|
+
|
|
171
|
+
def __init__(self, max_events: int = 10):
|
|
172
|
+
self.max_events = max_events
|
|
173
|
+
|
|
174
|
+
def create_event_log(
|
|
175
|
+
self, events: List[AgentEvent], title: str = "Recent Events"
|
|
176
|
+
) -> Panel:
|
|
177
|
+
"""
|
|
178
|
+
Create a panel displaying recent events.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
events: List of recent events
|
|
182
|
+
title: Panel title
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Rich Panel object
|
|
186
|
+
"""
|
|
187
|
+
if not events:
|
|
188
|
+
content = Text("No recent events", style="dim")
|
|
189
|
+
else:
|
|
190
|
+
# Take only the most recent events
|
|
191
|
+
recent_events = (
|
|
192
|
+
events[-self.max_events :] if len(events) > self.max_events else events
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
lines = []
|
|
196
|
+
for event in recent_events:
|
|
197
|
+
# Format timestamp
|
|
198
|
+
timestamp = event.timestamp.strftime("%H:%M:%S.%f")[
|
|
199
|
+
:-3
|
|
200
|
+
] # Include milliseconds
|
|
201
|
+
|
|
202
|
+
# Format event type with color
|
|
203
|
+
event_display = self._format_event_type(event.event_type)
|
|
204
|
+
|
|
205
|
+
# Agent ID
|
|
206
|
+
agent_id = event.agent_id or "system"
|
|
207
|
+
|
|
208
|
+
# Event description based on type and data
|
|
209
|
+
description = self._get_event_description(event)
|
|
210
|
+
|
|
211
|
+
# Combine into a line
|
|
212
|
+
line = (
|
|
213
|
+
f"[dim]{timestamp}[/dim] [{agent_id}] {event_display} {description}"
|
|
214
|
+
)
|
|
215
|
+
lines.append(line)
|
|
216
|
+
|
|
217
|
+
content = "\n".join(lines)
|
|
218
|
+
|
|
219
|
+
return Panel(content, title=title, border_style="yellow", padding=(0, 1))
|
|
220
|
+
|
|
221
|
+
def _format_event_type(self, event_type: EventType) -> Text:
|
|
222
|
+
"""Format event type with appropriate color."""
|
|
223
|
+
type_colors = {
|
|
224
|
+
EventType.AGENT_STATE_CHANGED: ("blue", "📝"),
|
|
225
|
+
EventType.OPERATION_STARTED: ("green", "▶️"),
|
|
226
|
+
EventType.OPERATION_PROGRESS: ("cyan", "📊"),
|
|
227
|
+
EventType.OPERATION_COMPLETED: ("green", "✅"),
|
|
228
|
+
EventType.OPERATION_FAILED: ("red", "❌"),
|
|
229
|
+
EventType.OPERATION_CANCELLED: ("yellow", "⏹️"),
|
|
230
|
+
EventType.ERROR_OCCURRED: ("red", "🚨"),
|
|
231
|
+
EventType.APPROVAL_REQUESTED: ("magenta", "🔐"),
|
|
232
|
+
EventType.APPROVAL_GRANTED: ("green", "🔓"),
|
|
233
|
+
EventType.APPROVAL_DENIED: ("red", "🔒"),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
color, emoji = type_colors.get(event_type, ("white", "📌"))
|
|
237
|
+
return Text(f"{emoji}", style=color)
|
|
238
|
+
|
|
239
|
+
def _get_event_description(self, event: AgentEvent) -> str:
|
|
240
|
+
"""Generate event description based on event type and data."""
|
|
241
|
+
if event.event_type == EventType.AGENT_STATE_CHANGED:
|
|
242
|
+
old_status = event.data.get("old_status", "unknown")
|
|
243
|
+
new_status = event.data.get("new_status", "unknown")
|
|
244
|
+
return f"Status: {old_status} → {new_status}"
|
|
245
|
+
|
|
246
|
+
elif event.event_type == EventType.OPERATION_STARTED:
|
|
247
|
+
op_type = event.data.get("operation_type", "unknown")
|
|
248
|
+
description = event.data.get("description", "No description")
|
|
249
|
+
return (
|
|
250
|
+
f"{op_type}: {description[:30]}{'...' if len(description) > 30 else ''}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
elif event.event_type == EventType.OPERATION_PROGRESS:
|
|
254
|
+
progress = event.data.get("progress", 0)
|
|
255
|
+
return f"Progress: {progress:.1f}%"
|
|
256
|
+
|
|
257
|
+
elif event.event_type == EventType.OPERATION_COMPLETED:
|
|
258
|
+
duration = event.data.get("duration", 0)
|
|
259
|
+
return f"Completed in {duration:.2f}s"
|
|
260
|
+
|
|
261
|
+
elif event.event_type == EventType.OPERATION_FAILED:
|
|
262
|
+
error = event.data.get("error", "Unknown error")
|
|
263
|
+
return f"Failed: {error[:30]}{'...' if len(error) > 30 else ''}"
|
|
264
|
+
|
|
265
|
+
elif event.event_type == EventType.ERROR_OCCURRED:
|
|
266
|
+
error = event.data.get("error", "Unknown error")
|
|
267
|
+
return f"Error: {error[:30]}{'...' if len(error) > 30 else ''}"
|
|
268
|
+
|
|
269
|
+
else:
|
|
270
|
+
return event.event_type.value.replace("_", " ").title()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class SystemStatsPanel:
|
|
274
|
+
"""Rich panel component for displaying system statistics."""
|
|
275
|
+
|
|
276
|
+
def create_stats_panel(
|
|
277
|
+
self, stats: Dict[str, Any], title: str = "System Statistics"
|
|
278
|
+
) -> Panel:
|
|
279
|
+
"""
|
|
280
|
+
Create a panel displaying system statistics.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
stats: Dictionary of statistics
|
|
284
|
+
title: Panel title
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Rich Panel object
|
|
288
|
+
"""
|
|
289
|
+
# Create a table for stats
|
|
290
|
+
table = Table(show_header=False, show_edge=False, padding=(0, 2))
|
|
291
|
+
table.add_column("Metric", style="bold", width=20)
|
|
292
|
+
table.add_column("Value", justify="right", width=10)
|
|
293
|
+
|
|
294
|
+
# Add key statistics
|
|
295
|
+
key_stats = [
|
|
296
|
+
("Active Agents", stats.get("enabled_agents", 0)),
|
|
297
|
+
("Total Agents", stats.get("total_agents", 0)),
|
|
298
|
+
("Operations", f"{stats.get('operations_started', 0)}"),
|
|
299
|
+
("Completed", f"{stats.get('operations_completed', 0)}"),
|
|
300
|
+
("Failed", f"{stats.get('operations_failed', 0)}"),
|
|
301
|
+
("Events", f"{stats.get('events_emitted', 0)}"),
|
|
302
|
+
("Listeners", f"{stats.get('event_listeners', 0)}"),
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
for metric, value in key_stats:
|
|
306
|
+
# Color coding for certain metrics
|
|
307
|
+
if metric == "Failed" and int(str(value)) > 0:
|
|
308
|
+
value_style = "red"
|
|
309
|
+
elif metric in ["Completed", "Active Agents"] and int(str(value)) > 0:
|
|
310
|
+
value_style = "green"
|
|
311
|
+
else:
|
|
312
|
+
value_style = "white"
|
|
313
|
+
|
|
314
|
+
table.add_row(metric, Text(str(value), style=value_style))
|
|
315
|
+
|
|
316
|
+
return Panel(table, title=title, border_style="blue", padding=(0, 1))
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class OperationDetailsTree:
|
|
320
|
+
"""Rich tree component for displaying operation hierarchy."""
|
|
321
|
+
|
|
322
|
+
def create_operation_tree(
|
|
323
|
+
self, operations: List[AgentOperation], title: str = "Operations"
|
|
324
|
+
) -> Panel:
|
|
325
|
+
"""
|
|
326
|
+
Create a tree view of operations grouped by agent.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
operations: List of operations to display
|
|
330
|
+
title: Tree title
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Rich Panel containing the tree
|
|
334
|
+
"""
|
|
335
|
+
if not operations:
|
|
336
|
+
return Panel(
|
|
337
|
+
Text("No operations to display", style="dim"),
|
|
338
|
+
title=title,
|
|
339
|
+
border_style="green",
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Group operations by agent
|
|
343
|
+
by_agent: Dict[str, List[AgentOperation]] = {}
|
|
344
|
+
for operation in operations:
|
|
345
|
+
agent_id = operation.agent_id or "system"
|
|
346
|
+
if agent_id not in by_agent:
|
|
347
|
+
by_agent[agent_id] = []
|
|
348
|
+
by_agent[agent_id].append(operation)
|
|
349
|
+
|
|
350
|
+
# Create tree
|
|
351
|
+
tree = Tree(f"[bold]{title}[/bold]")
|
|
352
|
+
|
|
353
|
+
for agent_id, agent_operations in by_agent.items():
|
|
354
|
+
# Add agent branch
|
|
355
|
+
agent_branch = tree.add(
|
|
356
|
+
f"[cyan]{agent_id}[/cyan] ({len(agent_operations)} operations)"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
for operation in agent_operations[:5]: # Limit to 5 operations per agent
|
|
360
|
+
# Format operation
|
|
361
|
+
status_emoji = {
|
|
362
|
+
OperationStatus.PENDING: "⏳",
|
|
363
|
+
OperationStatus.RUNNING: "▶️",
|
|
364
|
+
OperationStatus.COMPLETED: "✅",
|
|
365
|
+
OperationStatus.FAILED: "❌",
|
|
366
|
+
OperationStatus.CANCELLED: "⏹️",
|
|
367
|
+
OperationStatus.WAITING_APPROVAL: "🔐",
|
|
368
|
+
}.get(operation.status, "❓")
|
|
369
|
+
|
|
370
|
+
duration_str = ""
|
|
371
|
+
if operation.duration:
|
|
372
|
+
duration_str = f" ({operation.duration.total_seconds():.1f}s)"
|
|
373
|
+
|
|
374
|
+
operation_text = (
|
|
375
|
+
f"{status_emoji} {operation.operation_type.value} "
|
|
376
|
+
f"[{operation.progress_percentage:.0f}%]{duration_str}"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if operation.description:
|
|
380
|
+
operation_text += f"\n {operation.description[:50]}"
|
|
381
|
+
if len(operation.description) > 50:
|
|
382
|
+
operation_text += "..."
|
|
383
|
+
|
|
384
|
+
agent_branch.add(operation_text)
|
|
385
|
+
|
|
386
|
+
if len(agent_operations) > 5:
|
|
387
|
+
agent_branch.add(
|
|
388
|
+
f"[dim]... and {len(agent_operations) - 5} more operations[/dim]"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return Panel(tree, title=title, border_style="green")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class ComprehensiveStatusDisplay:
|
|
395
|
+
"""
|
|
396
|
+
Comprehensive status display combining all UI components.
|
|
397
|
+
|
|
398
|
+
Creates a rich, multi-panel display showing all aspects of agent status.
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
def __init__(self):
|
|
402
|
+
self.agent_table = AgentStatusTable()
|
|
403
|
+
self.operation_progress = OperationProgress()
|
|
404
|
+
self.event_log = EventLogPanel()
|
|
405
|
+
self.stats_panel = SystemStatsPanel()
|
|
406
|
+
self.operation_tree = OperationDetailsTree()
|
|
407
|
+
|
|
408
|
+
def create_comprehensive_display(
|
|
409
|
+
self,
|
|
410
|
+
agent_statuses: Dict[str, AgentStatus],
|
|
411
|
+
agent_metadata: Dict[str, Dict[str, Any]],
|
|
412
|
+
active_operations: List[AgentOperation],
|
|
413
|
+
recent_events: List[AgentEvent],
|
|
414
|
+
system_stats: Dict[str, Any],
|
|
415
|
+
) -> Layout:
|
|
416
|
+
"""
|
|
417
|
+
Create a comprehensive status display with all components.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
agent_statuses: Current agent statuses
|
|
421
|
+
agent_metadata: Agent metadata
|
|
422
|
+
active_operations: Active operations
|
|
423
|
+
recent_events: Recent events
|
|
424
|
+
system_stats: System statistics
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Rich Layout object
|
|
428
|
+
"""
|
|
429
|
+
# Create layout
|
|
430
|
+
layout = Layout()
|
|
431
|
+
|
|
432
|
+
# Split into main sections
|
|
433
|
+
layout.split_row(Layout(name="left"), Layout(name="right"))
|
|
434
|
+
|
|
435
|
+
# Left side: Agent info and operations
|
|
436
|
+
layout["left"].split_column(
|
|
437
|
+
Layout(
|
|
438
|
+
self.agent_table.create_table(agent_statuses, agent_metadata),
|
|
439
|
+
name="agents",
|
|
440
|
+
),
|
|
441
|
+
Layout(
|
|
442
|
+
self.operation_progress.create_progress_display(active_operations),
|
|
443
|
+
name="progress",
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
# Right side: Events, stats, and operation tree
|
|
448
|
+
layout["right"].split_column(
|
|
449
|
+
Layout(self.event_log.create_event_log(recent_events), name="events"),
|
|
450
|
+
Layout(name="bottom_right"),
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Split bottom right into stats and tree
|
|
454
|
+
layout["bottom_right"].split_row(
|
|
455
|
+
Layout(self.stats_panel.create_stats_panel(system_stats), name="stats"),
|
|
456
|
+
Layout(
|
|
457
|
+
self.operation_tree.create_operation_tree(active_operations),
|
|
458
|
+
name="tree",
|
|
459
|
+
),
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
return layout
|
|
463
|
+
|
|
464
|
+
def create_simple_display(
|
|
465
|
+
self,
|
|
466
|
+
agent_statuses: Dict[str, AgentStatus],
|
|
467
|
+
recent_events: List[AgentEvent],
|
|
468
|
+
system_stats: Dict[str, Any],
|
|
469
|
+
) -> Group:
|
|
470
|
+
"""
|
|
471
|
+
Create a simpler status display for constrained terminals.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
agent_statuses: Current agent statuses
|
|
475
|
+
recent_events: Recent events
|
|
476
|
+
system_stats: System statistics
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
Rich Group object
|
|
480
|
+
"""
|
|
481
|
+
components = [
|
|
482
|
+
self.stats_panel.create_stats_panel(system_stats, "📊 System Status"),
|
|
483
|
+
Rule(),
|
|
484
|
+
self.agent_table.create_table(agent_statuses),
|
|
485
|
+
Rule(),
|
|
486
|
+
self.event_log.create_event_log(recent_events, "📝 Recent Activity"),
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
return Group(*components)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# Utility functions for creating common display components
|
|
493
|
+
def create_agent_status_summary(
|
|
494
|
+
agent_statuses: Dict[str, AgentStatus],
|
|
495
|
+
) -> Text:
|
|
496
|
+
"""Create a brief text summary of agent statuses."""
|
|
497
|
+
if not agent_statuses:
|
|
498
|
+
return Text("No agents available", style="dim")
|
|
499
|
+
|
|
500
|
+
enabled = sum(
|
|
501
|
+
1 for status in agent_statuses.values() if status == AgentStatus.ENABLED
|
|
502
|
+
)
|
|
503
|
+
total = len(agent_statuses)
|
|
504
|
+
|
|
505
|
+
summary = f"Agents: {enabled}/{total} enabled"
|
|
506
|
+
|
|
507
|
+
if enabled > 0:
|
|
508
|
+
style = "green"
|
|
509
|
+
elif total > 0:
|
|
510
|
+
style = "yellow"
|
|
511
|
+
else:
|
|
512
|
+
style = "red"
|
|
513
|
+
|
|
514
|
+
return Text(summary, style=style)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def create_operation_summary(operations: List[AgentOperation]) -> Text:
|
|
518
|
+
"""Create a brief text summary of operations."""
|
|
519
|
+
if not operations:
|
|
520
|
+
return Text("No active operations", style="dim")
|
|
521
|
+
|
|
522
|
+
active = sum(1 for op in operations if op.is_active)
|
|
523
|
+
completed = sum(1 for op in operations if op.status == OperationStatus.COMPLETED)
|
|
524
|
+
failed = sum(1 for op in operations if op.status == OperationStatus.FAILED)
|
|
525
|
+
|
|
526
|
+
summary = f"Ops: {active} active, {completed} done, {failed} failed"
|
|
527
|
+
|
|
528
|
+
if failed > 0:
|
|
529
|
+
style = "red"
|
|
530
|
+
elif active > 0:
|
|
531
|
+
style = "blue"
|
|
532
|
+
else:
|
|
533
|
+
style = "green"
|
|
534
|
+
|
|
535
|
+
return Text(summary, style=style)
|