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,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
+ )