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,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration module for the agent status display system.
|
|
3
|
+
|
|
4
|
+
This module provides configuration management utilities for controlling
|
|
5
|
+
the behavior of the status tracking and display system.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from .status_core import (
|
|
14
|
+
EventType,
|
|
15
|
+
StatusDisplayConfig,
|
|
16
|
+
StatusDisplayLevel,
|
|
17
|
+
StatusUpdateFrequency,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StatusConfigManager:
|
|
22
|
+
"""Manager for status system configuration."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config_file: Optional[Path] = None):
|
|
25
|
+
"""
|
|
26
|
+
Initialize configuration manager.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
config_file: Path to configuration file (optional)
|
|
30
|
+
"""
|
|
31
|
+
self.config_file = config_file or self._get_default_config_path()
|
|
32
|
+
self.config = StatusDisplayConfig()
|
|
33
|
+
|
|
34
|
+
# Try to load existing configuration
|
|
35
|
+
self._load_config()
|
|
36
|
+
|
|
37
|
+
def _get_default_config_path(self) -> Path:
|
|
38
|
+
"""Get default configuration file path."""
|
|
39
|
+
config_dir = Path.home() / ".omnimancer" / "config"
|
|
40
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
return config_dir / "agent_status_config.json"
|
|
42
|
+
|
|
43
|
+
def _load_config(self) -> None:
|
|
44
|
+
"""Load configuration from file."""
|
|
45
|
+
try:
|
|
46
|
+
if self.config_file.exists():
|
|
47
|
+
with open(self.config_file, "r") as f:
|
|
48
|
+
config_data = json.load(f)
|
|
49
|
+
|
|
50
|
+
# Update configuration from loaded data
|
|
51
|
+
self._update_config(config_data)
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
# If loading fails, use defaults
|
|
55
|
+
print(f"Warning: Failed to load status config: {e}")
|
|
56
|
+
|
|
57
|
+
def _update_config(self, data: Dict[str, Any]) -> None:
|
|
58
|
+
"""Update configuration from dictionary."""
|
|
59
|
+
for key, value in data.items():
|
|
60
|
+
if hasattr(self.config, key):
|
|
61
|
+
# Handle enum fields specially
|
|
62
|
+
if key == "display_level" and isinstance(value, str):
|
|
63
|
+
try:
|
|
64
|
+
value = StatusDisplayLevel(value)
|
|
65
|
+
except ValueError:
|
|
66
|
+
continue
|
|
67
|
+
elif key == "update_frequency" and isinstance(value, (str, float)):
|
|
68
|
+
try:
|
|
69
|
+
if isinstance(value, str):
|
|
70
|
+
value = StatusUpdateFrequency(value)
|
|
71
|
+
else:
|
|
72
|
+
# Find closest enum value
|
|
73
|
+
for freq in StatusUpdateFrequency:
|
|
74
|
+
if abs(freq.value - value) < 0.01:
|
|
75
|
+
value = freq
|
|
76
|
+
break
|
|
77
|
+
except ValueError:
|
|
78
|
+
continue
|
|
79
|
+
elif key in [
|
|
80
|
+
"filtered_event_types",
|
|
81
|
+
"important_event_types",
|
|
82
|
+
] and isinstance(value, list):
|
|
83
|
+
try:
|
|
84
|
+
value = {EventType(event_type) for event_type in value}
|
|
85
|
+
except ValueError:
|
|
86
|
+
continue
|
|
87
|
+
elif key == "colors" and isinstance(value, dict):
|
|
88
|
+
# Merge colors instead of replacing
|
|
89
|
+
self.config.colors.update(value)
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
setattr(self.config, key, value)
|
|
93
|
+
|
|
94
|
+
def save_config(self) -> None:
|
|
95
|
+
"""Save configuration to file."""
|
|
96
|
+
try:
|
|
97
|
+
config_data = self._config_to_dict()
|
|
98
|
+
|
|
99
|
+
# Ensure directory exists
|
|
100
|
+
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
|
|
102
|
+
with open(self.config_file, "w") as f:
|
|
103
|
+
json.dump(config_data, f, indent=2, default=str)
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print(f"Warning: Failed to save status config: {e}")
|
|
107
|
+
|
|
108
|
+
def _config_to_dict(self) -> Dict[str, Any]:
|
|
109
|
+
"""Convert config to dictionary."""
|
|
110
|
+
from enum import Enum
|
|
111
|
+
|
|
112
|
+
config = {}
|
|
113
|
+
for key, value in self.config.__dict__.items():
|
|
114
|
+
if isinstance(value, Enum):
|
|
115
|
+
config[key] = value.value
|
|
116
|
+
elif isinstance(value, set):
|
|
117
|
+
config[key] = [
|
|
118
|
+
item.value if isinstance(item, Enum) else item for item in value
|
|
119
|
+
]
|
|
120
|
+
elif isinstance(value, Path):
|
|
121
|
+
config[key] = str(value)
|
|
122
|
+
else:
|
|
123
|
+
config[key] = value
|
|
124
|
+
return config
|
|
125
|
+
|
|
126
|
+
def update_display_level(self, level: StatusDisplayLevel) -> None:
|
|
127
|
+
"""Update display level."""
|
|
128
|
+
self.config.display_level = level
|
|
129
|
+
self.save_config()
|
|
130
|
+
|
|
131
|
+
def update_update_frequency(self, frequency: StatusUpdateFrequency) -> None:
|
|
132
|
+
"""Update update frequency."""
|
|
133
|
+
self.config.update_frequency = frequency
|
|
134
|
+
self.save_config()
|
|
135
|
+
|
|
136
|
+
def toggle_status_display(self) -> bool:
|
|
137
|
+
"""Toggle status display on/off."""
|
|
138
|
+
self.config.enabled = not self.config.enabled
|
|
139
|
+
self.save_config()
|
|
140
|
+
return self.config.enabled
|
|
141
|
+
|
|
142
|
+
def set_compact_mode(self, compact: bool) -> None:
|
|
143
|
+
"""Set compact display mode."""
|
|
144
|
+
self.config.compact_mode = compact
|
|
145
|
+
self.save_config()
|
|
146
|
+
|
|
147
|
+
def add_filtered_event_type(self, event_type: EventType) -> None:
|
|
148
|
+
"""Add event type to filter (hide from display)."""
|
|
149
|
+
self.config.filtered_event_types.add(event_type)
|
|
150
|
+
self.save_config()
|
|
151
|
+
|
|
152
|
+
def remove_filtered_event_type(self, event_type: EventType) -> None:
|
|
153
|
+
"""Remove event type from filter."""
|
|
154
|
+
self.config.filtered_event_types.discard(event_type)
|
|
155
|
+
self.save_config()
|
|
156
|
+
|
|
157
|
+
def reset_to_defaults(self) -> None:
|
|
158
|
+
"""Reset configuration to defaults."""
|
|
159
|
+
self.config = StatusDisplayConfig()
|
|
160
|
+
self.save_config()
|
|
161
|
+
|
|
162
|
+
def get_effective_config(self) -> Dict[str, Any]:
|
|
163
|
+
"""Get the effective configuration as a dictionary."""
|
|
164
|
+
return self._config_to_dict()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Global configuration instance
|
|
168
|
+
_global_config_manager: Optional[StatusConfigManager] = None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_status_config_manager(
|
|
172
|
+
config_file: Optional[Path] = None,
|
|
173
|
+
) -> StatusConfigManager:
|
|
174
|
+
"""
|
|
175
|
+
Get the global status configuration manager.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
config_file: Optional custom config file path
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Status configuration manager instance
|
|
182
|
+
"""
|
|
183
|
+
global _global_config_manager
|
|
184
|
+
if _global_config_manager is None:
|
|
185
|
+
_global_config_manager = StatusConfigManager(config_file)
|
|
186
|
+
return _global_config_manager
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def load_config_from_env() -> StatusDisplayConfig:
|
|
190
|
+
"""
|
|
191
|
+
Load status configuration from environment variables.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Configuration with values from environment variables
|
|
195
|
+
"""
|
|
196
|
+
config = StatusDisplayConfig()
|
|
197
|
+
|
|
198
|
+
# Load from environment variables with omnimancer_STATUS_ prefix
|
|
199
|
+
if os.getenv("omnimancer_STATUS_ENABLED"):
|
|
200
|
+
config.enabled = (
|
|
201
|
+
os.getenv("omnimancer_STATUS_ENABLED", "true").lower() == "true"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if os.getenv("omnimancer_STATUS_LEVEL"):
|
|
205
|
+
try:
|
|
206
|
+
config.display_level = StatusDisplayLevel(
|
|
207
|
+
os.getenv("omnimancer_STATUS_LEVEL")
|
|
208
|
+
)
|
|
209
|
+
except ValueError:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
if os.getenv("omnimancer_STATUS_UPDATE_FREQ"):
|
|
213
|
+
try:
|
|
214
|
+
freq_value = float(os.getenv("omnimancer_STATUS_UPDATE_FREQ"))
|
|
215
|
+
for freq in StatusUpdateFrequency:
|
|
216
|
+
if abs(freq.value - freq_value) < 0.01:
|
|
217
|
+
config.update_frequency = freq
|
|
218
|
+
break
|
|
219
|
+
except ValueError:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
if os.getenv("omnimancer_STATUS_AUTO_START"):
|
|
223
|
+
config.auto_start_display = (
|
|
224
|
+
os.getenv("omnimancer_STATUS_AUTO_START", "false").lower() == "true"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if os.getenv("omnimancer_STATUS_MAX_OPS"):
|
|
228
|
+
try:
|
|
229
|
+
config.max_visible_operations = int(os.getenv("omnimancer_STATUS_MAX_OPS"))
|
|
230
|
+
except ValueError:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
if os.getenv("omnimancer_STATUS_COMPACT"):
|
|
234
|
+
config.compact_mode = (
|
|
235
|
+
os.getenv("omnimancer_STATUS_COMPACT", "false").lower() == "true"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return config
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core Status Data Models and Types for Omnimancer Agents.
|
|
3
|
+
|
|
4
|
+
This module provides the consolidated core data structures, enums, and base classes
|
|
5
|
+
for the agent status tracking system. All other status modules should import
|
|
6
|
+
from this module to avoid circular dependencies.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import uuid
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, Optional, Set
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentStatus(Enum):
|
|
18
|
+
"""Agent status enumeration."""
|
|
19
|
+
|
|
20
|
+
ENABLED = "enabled"
|
|
21
|
+
DISABLED = "disabled"
|
|
22
|
+
RUNNING = "running"
|
|
23
|
+
PAUSED = "paused"
|
|
24
|
+
ERROR = "error"
|
|
25
|
+
INITIALIZING = "initializing"
|
|
26
|
+
SHUTTING_DOWN = "shutting_down"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OperationType(Enum):
|
|
30
|
+
"""Types of operations that can be tracked."""
|
|
31
|
+
|
|
32
|
+
FILE_READ = "file_read"
|
|
33
|
+
FILE_WRITE = "file_write"
|
|
34
|
+
FILE_DELETE = "file_delete"
|
|
35
|
+
DIRECTORY_CREATE = "directory_create"
|
|
36
|
+
DIRECTORY_DELETE = "directory_delete"
|
|
37
|
+
COMMAND_EXECUTION = "command_execution"
|
|
38
|
+
WEB_REQUEST = "web_request"
|
|
39
|
+
API_CALL = "api_call"
|
|
40
|
+
APPROVAL_REQUEST = "approval_request"
|
|
41
|
+
CONFIGURATION_CHANGE = "configuration_change"
|
|
42
|
+
AGENT_SWITCH = "agent_switch"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OperationStatus(Enum):
|
|
46
|
+
"""Status of individual operations."""
|
|
47
|
+
|
|
48
|
+
PENDING = "pending"
|
|
49
|
+
RUNNING = "running"
|
|
50
|
+
COMPLETED = "completed"
|
|
51
|
+
FAILED = "failed"
|
|
52
|
+
CANCELLED = "cancelled"
|
|
53
|
+
WAITING_APPROVAL = "waiting_approval"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class EventType(Enum):
|
|
57
|
+
"""Types of agent events."""
|
|
58
|
+
|
|
59
|
+
AGENT_STATE_CHANGED = "agent_state_changed"
|
|
60
|
+
OPERATION_STARTED = "operation_started"
|
|
61
|
+
OPERATION_PROGRESS = "operation_progress"
|
|
62
|
+
OPERATION_COMPLETED = "operation_completed"
|
|
63
|
+
OPERATION_FAILED = "operation_failed"
|
|
64
|
+
OPERATION_CANCELLED = "operation_cancelled"
|
|
65
|
+
ERROR_OCCURRED = "error_occurred"
|
|
66
|
+
APPROVAL_REQUESTED = "approval_requested"
|
|
67
|
+
APPROVAL_GRANTED = "approval_granted"
|
|
68
|
+
APPROVAL_DENIED = "approval_denied"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class StreamPriority(Enum):
|
|
72
|
+
"""Priority levels for status stream events."""
|
|
73
|
+
|
|
74
|
+
LOW = "low"
|
|
75
|
+
NORMAL = "normal"
|
|
76
|
+
HIGH = "high"
|
|
77
|
+
CRITICAL = "critical"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class StatusDisplayLevel(Enum):
|
|
81
|
+
"""Display detail levels for status information."""
|
|
82
|
+
|
|
83
|
+
MINIMAL = "minimal" # Only basic status
|
|
84
|
+
STANDARD = "standard" # Standard status with operations
|
|
85
|
+
DETAILED = "detailed" # Full detailed status
|
|
86
|
+
DEBUG = "debug" # Debug level with all events
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class StatusUpdateFrequency(Enum):
|
|
90
|
+
"""Update frequency for status display."""
|
|
91
|
+
|
|
92
|
+
REAL_TIME = 0.1 # 10 times per second
|
|
93
|
+
FAST = 0.5 # 2 times per second
|
|
94
|
+
NORMAL = 1.0 # 1 time per second
|
|
95
|
+
SLOW = 2.0 # Every 2 seconds
|
|
96
|
+
MINIMAL = 5.0 # Every 5 seconds
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class AgentOperation:
|
|
101
|
+
"""Data class representing a single agent operation."""
|
|
102
|
+
|
|
103
|
+
operation_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
104
|
+
operation_type: OperationType = OperationType.API_CALL
|
|
105
|
+
description: str = ""
|
|
106
|
+
status: OperationStatus = OperationStatus.PENDING
|
|
107
|
+
start_time: datetime = field(default_factory=datetime.now)
|
|
108
|
+
end_time: Optional[datetime] = None
|
|
109
|
+
progress_percentage: float = 0.0
|
|
110
|
+
error_details: Optional[str] = None
|
|
111
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
112
|
+
agent_id: Optional[str] = None
|
|
113
|
+
parent_operation_id: Optional[str] = None
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def duration(self) -> Optional[timedelta]:
|
|
117
|
+
"""Get operation duration."""
|
|
118
|
+
if self.end_time:
|
|
119
|
+
return self.end_time - self.start_time
|
|
120
|
+
elif self.status in [OperationStatus.RUNNING, OperationStatus.PENDING]:
|
|
121
|
+
return datetime.now() - self.start_time
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def is_active(self) -> bool:
|
|
126
|
+
"""Check if operation is currently active."""
|
|
127
|
+
return self.status in [
|
|
128
|
+
OperationStatus.PENDING,
|
|
129
|
+
OperationStatus.RUNNING,
|
|
130
|
+
OperationStatus.WAITING_APPROVAL,
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def is_completed(self) -> bool:
|
|
135
|
+
"""Check if operation is completed (successfully or not)."""
|
|
136
|
+
return self.status in [
|
|
137
|
+
OperationStatus.COMPLETED,
|
|
138
|
+
OperationStatus.FAILED,
|
|
139
|
+
OperationStatus.CANCELLED,
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
def update_progress(
|
|
143
|
+
self, percentage: float, description: Optional[str] = None
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Update operation progress."""
|
|
146
|
+
self.progress_percentage = max(0.0, min(100.0, percentage))
|
|
147
|
+
if description:
|
|
148
|
+
self.description = description
|
|
149
|
+
if percentage >= 100.0 and self.status == OperationStatus.RUNNING:
|
|
150
|
+
self.complete()
|
|
151
|
+
|
|
152
|
+
def complete(self, result_metadata: Optional[Dict[str, Any]] = None) -> None:
|
|
153
|
+
"""Mark operation as completed."""
|
|
154
|
+
self.status = OperationStatus.COMPLETED
|
|
155
|
+
self.end_time = datetime.now()
|
|
156
|
+
self.progress_percentage = 100.0
|
|
157
|
+
if result_metadata:
|
|
158
|
+
self.metadata.update(result_metadata)
|
|
159
|
+
|
|
160
|
+
def fail(
|
|
161
|
+
self,
|
|
162
|
+
error_details: str,
|
|
163
|
+
error_metadata: Optional[Dict[str, Any]] = None,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Mark operation as failed."""
|
|
166
|
+
self.status = OperationStatus.FAILED
|
|
167
|
+
self.end_time = datetime.now()
|
|
168
|
+
self.error_details = error_details
|
|
169
|
+
if error_metadata:
|
|
170
|
+
self.metadata.update(error_metadata)
|
|
171
|
+
|
|
172
|
+
def cancel(self, reason: Optional[str] = None) -> None:
|
|
173
|
+
"""Cancel the operation."""
|
|
174
|
+
self.status = OperationStatus.CANCELLED
|
|
175
|
+
self.end_time = datetime.now()
|
|
176
|
+
if reason:
|
|
177
|
+
self.error_details = f"Cancelled: {reason}"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass
|
|
181
|
+
class AgentEvent:
|
|
182
|
+
"""Data class representing an agent event."""
|
|
183
|
+
|
|
184
|
+
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
185
|
+
event_type: EventType = EventType.AGENT_STATE_CHANGED
|
|
186
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
187
|
+
agent_id: Optional[str] = None
|
|
188
|
+
operation_id: Optional[str] = None
|
|
189
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
190
|
+
source: str = "unknown"
|
|
191
|
+
|
|
192
|
+
def __post_init__(self):
|
|
193
|
+
"""Ensure timestamp is always set."""
|
|
194
|
+
if not self.timestamp:
|
|
195
|
+
self.timestamp = datetime.now()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@dataclass
|
|
199
|
+
class StatusStreamEvent:
|
|
200
|
+
"""Event wrapper for status streaming with priority and metadata."""
|
|
201
|
+
|
|
202
|
+
event: AgentEvent
|
|
203
|
+
priority: StreamPriority = StreamPriority.NORMAL
|
|
204
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
205
|
+
retry_count: int = 0
|
|
206
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
207
|
+
|
|
208
|
+
def __post_init__(self):
|
|
209
|
+
"""Ensure timestamp is set."""
|
|
210
|
+
if not self.timestamp:
|
|
211
|
+
self.timestamp = datetime.now()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass
|
|
215
|
+
class StreamMetrics:
|
|
216
|
+
"""Metrics for status stream performance."""
|
|
217
|
+
|
|
218
|
+
events_processed: int = 0
|
|
219
|
+
events_dropped: int = 0
|
|
220
|
+
events_retried: int = 0
|
|
221
|
+
listeners_count: int = 0
|
|
222
|
+
queue_size: int = 0
|
|
223
|
+
avg_processing_time: float = 0.0
|
|
224
|
+
last_update: datetime = field(default_factory=datetime.now)
|
|
225
|
+
|
|
226
|
+
def update_processing_time(self, processing_time: float) -> None:
|
|
227
|
+
"""Update average processing time with new sample."""
|
|
228
|
+
if self.events_processed == 0:
|
|
229
|
+
self.avg_processing_time = processing_time
|
|
230
|
+
else:
|
|
231
|
+
# Simple moving average
|
|
232
|
+
self.avg_processing_time = (
|
|
233
|
+
self.avg_processing_time * min(self.events_processed, 100)
|
|
234
|
+
+ processing_time
|
|
235
|
+
) / (min(self.events_processed, 100) + 1)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class StatusDisplayConfig:
|
|
240
|
+
"""Consolidated configuration for status display system."""
|
|
241
|
+
|
|
242
|
+
# Display settings
|
|
243
|
+
enabled: bool = True
|
|
244
|
+
display_level: StatusDisplayLevel = StatusDisplayLevel.STANDARD
|
|
245
|
+
update_frequency: StatusUpdateFrequency = StatusUpdateFrequency.NORMAL
|
|
246
|
+
auto_start_display: bool = False
|
|
247
|
+
max_visible_operations: int = 5
|
|
248
|
+
max_visible_events: int = 10
|
|
249
|
+
show_timestamps: bool = True
|
|
250
|
+
show_progress_bars: bool = True
|
|
251
|
+
|
|
252
|
+
# Event filtering
|
|
253
|
+
filtered_event_types: Set[EventType] = field(default_factory=set)
|
|
254
|
+
important_event_types: Set[EventType] = field(
|
|
255
|
+
default_factory=lambda: {
|
|
256
|
+
EventType.AGENT_STATE_CHANGED,
|
|
257
|
+
EventType.ERROR_OCCURRED,
|
|
258
|
+
EventType.APPROVAL_REQUESTED,
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Terminal settings
|
|
263
|
+
terminal_width: Optional[int] = None # Auto-detect if None
|
|
264
|
+
terminal_height: Optional[int] = None # Auto-detect if None
|
|
265
|
+
use_color: bool = True
|
|
266
|
+
use_emoji: bool = True
|
|
267
|
+
compact_mode: bool = False
|
|
268
|
+
|
|
269
|
+
# Performance settings
|
|
270
|
+
max_queue_size: int = 1000
|
|
271
|
+
throttle_rate: int = 10 # Events per second
|
|
272
|
+
max_listeners: int = 50
|
|
273
|
+
batch_size: int = 10
|
|
274
|
+
flush_interval: float = 0.1
|
|
275
|
+
|
|
276
|
+
# Persistence settings
|
|
277
|
+
save_event_history: bool = False
|
|
278
|
+
event_history_file: Optional[Path] = None
|
|
279
|
+
max_history_size: int = 10000
|
|
280
|
+
max_operation_history: int = 1000
|
|
281
|
+
max_event_history: int = 5000
|
|
282
|
+
|
|
283
|
+
# Integration settings
|
|
284
|
+
enable_cli_integration: bool = True
|
|
285
|
+
enable_web_interface: bool = False
|
|
286
|
+
web_interface_port: int = 8080
|
|
287
|
+
|
|
288
|
+
# UI theme settings
|
|
289
|
+
theme: str = "default"
|
|
290
|
+
colors: Dict[str, str] = field(
|
|
291
|
+
default_factory=lambda: {
|
|
292
|
+
"primary": "blue",
|
|
293
|
+
"secondary": "cyan",
|
|
294
|
+
"success": "green",
|
|
295
|
+
"warning": "yellow",
|
|
296
|
+
"error": "red",
|
|
297
|
+
"info": "white",
|
|
298
|
+
"dim": "dim",
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Component visibility
|
|
303
|
+
show_agent_table: bool = True
|
|
304
|
+
show_operation_progress: bool = True
|
|
305
|
+
show_event_log: bool = True
|
|
306
|
+
show_system_stats: bool = True
|
|
307
|
+
show_operation_tree: bool = False
|
|
308
|
+
|
|
309
|
+
# Layout settings
|
|
310
|
+
layout_style: str = "comprehensive" # "simple", "comprehensive", "minimal"
|
|
311
|
+
panel_borders: bool = True
|
|
312
|
+
panel_titles: bool = True
|
|
313
|
+
|
|
314
|
+
def __post_init__(self):
|
|
315
|
+
"""Validate and adjust configuration after initialization."""
|
|
316
|
+
# Ensure update frequency is valid
|
|
317
|
+
if isinstance(self.update_frequency, float):
|
|
318
|
+
# Convert float to enum
|
|
319
|
+
for freq in StatusUpdateFrequency:
|
|
320
|
+
if abs(freq.value - self.update_frequency) < 0.01:
|
|
321
|
+
self.update_frequency = freq
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
# Ensure reasonable limits
|
|
325
|
+
self.max_visible_operations = max(1, min(50, self.max_visible_operations))
|
|
326
|
+
self.max_visible_events = max(1, min(100, self.max_visible_events))
|
|
327
|
+
self.throttle_rate = max(1, min(100, self.throttle_rate))
|
|
328
|
+
|
|
329
|
+
# Set up event history file if saving is enabled
|
|
330
|
+
if self.save_event_history and not self.event_history_file:
|
|
331
|
+
self.event_history_file = (
|
|
332
|
+
Path.home() / ".omnimancer" / "agent_status_history.json"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def get_color(self, color_name: str, default: str = "white") -> str:
|
|
336
|
+
"""Get color by name with fallback."""
|
|
337
|
+
return self.colors.get(color_name, default)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# Base classes for event handling
|
|
341
|
+
class EventListener:
|
|
342
|
+
"""Base class for event listeners."""
|
|
343
|
+
|
|
344
|
+
def __init__(self, event_types: Optional[Set[EventType]] = None):
|
|
345
|
+
"""
|
|
346
|
+
Initialize event listener.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
event_types: Set of event types to listen for. If None, listens to all events.
|
|
350
|
+
"""
|
|
351
|
+
self.event_types = event_types or set(EventType)
|
|
352
|
+
self.active = True
|
|
353
|
+
|
|
354
|
+
async def handle_event(self, event: AgentEvent) -> None:
|
|
355
|
+
"""
|
|
356
|
+
Handle an agent event.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
event: The agent event to handle
|
|
360
|
+
"""
|
|
361
|
+
if self.active and event.event_type in self.event_types:
|
|
362
|
+
await self._process_event(event)
|
|
363
|
+
|
|
364
|
+
async def _process_event(self, event: AgentEvent) -> None:
|
|
365
|
+
"""Process the event. Override in subclasses."""
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
def stop(self) -> None:
|
|
369
|
+
"""Stop listening to events."""
|
|
370
|
+
self.active = False
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class StatusStreamListener:
|
|
374
|
+
"""Base class for status stream listeners."""
|
|
375
|
+
|
|
376
|
+
def __init__(self, listener_id: str, event_types: Optional[Set[EventType]] = None):
|
|
377
|
+
"""
|
|
378
|
+
Initialize status stream listener.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
listener_id: Unique identifier for this listener
|
|
382
|
+
event_types: Set of event types to listen for (None = all events)
|
|
383
|
+
"""
|
|
384
|
+
self.listener_id = listener_id
|
|
385
|
+
self.event_types = event_types or set(EventType)
|
|
386
|
+
self.active = True
|
|
387
|
+
self.last_event_time: Optional[datetime] = None
|
|
388
|
+
self.event_count = 0
|
|
389
|
+
|
|
390
|
+
async def handle_event(self, stream_event: StatusStreamEvent) -> None:
|
|
391
|
+
"""
|
|
392
|
+
Handle a status stream event.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
stream_event: The status stream event to handle
|
|
396
|
+
"""
|
|
397
|
+
if not self.active or stream_event.event.event_type not in self.event_types:
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
await self._process_event(stream_event)
|
|
402
|
+
self.last_event_time = datetime.now()
|
|
403
|
+
self.event_count += 1
|
|
404
|
+
except Exception as e:
|
|
405
|
+
# Import here to avoid circular imports
|
|
406
|
+
import logging
|
|
407
|
+
|
|
408
|
+
logger = logging.getLogger(__name__)
|
|
409
|
+
logger.error(f"Error processing event in listener {self.listener_id}: {e}")
|
|
410
|
+
|
|
411
|
+
async def _process_event(self, stream_event: StatusStreamEvent) -> None:
|
|
412
|
+
"""Process the event. Override in subclasses."""
|
|
413
|
+
pass
|
|
414
|
+
|
|
415
|
+
def stop(self) -> None:
|
|
416
|
+
"""Stop listening to events."""
|
|
417
|
+
self.active = False
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# Utility functions for common configuration operations
|
|
421
|
+
def create_minimal_config() -> StatusDisplayConfig:
|
|
422
|
+
"""Create a minimal configuration for low-resource environments."""
|
|
423
|
+
return StatusDisplayConfig(
|
|
424
|
+
display_level=StatusDisplayLevel.MINIMAL,
|
|
425
|
+
update_frequency=StatusUpdateFrequency.SLOW,
|
|
426
|
+
max_visible_operations=3,
|
|
427
|
+
max_visible_events=5,
|
|
428
|
+
compact_mode=True,
|
|
429
|
+
show_progress_bars=False,
|
|
430
|
+
use_emoji=False,
|
|
431
|
+
max_queue_size=100,
|
|
432
|
+
throttle_rate=5,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def create_debug_config() -> StatusDisplayConfig:
|
|
437
|
+
"""Create a debug configuration with full visibility."""
|
|
438
|
+
return StatusDisplayConfig(
|
|
439
|
+
display_level=StatusDisplayLevel.DEBUG,
|
|
440
|
+
update_frequency=StatusUpdateFrequency.REAL_TIME,
|
|
441
|
+
max_visible_operations=20,
|
|
442
|
+
max_visible_events=50,
|
|
443
|
+
save_event_history=True,
|
|
444
|
+
max_queue_size=5000,
|
|
445
|
+
throttle_rate=100,
|
|
446
|
+
)
|