claude-mpm 3.9.9__py3-none-any.whl → 3.9.11__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/templates/memory_manager.json +155 -0
- claude_mpm/cli/__init__.py +15 -2
- claude_mpm/cli/commands/__init__.py +3 -0
- claude_mpm/cli/commands/mcp.py +280 -134
- claude_mpm/cli/commands/run_guarded.py +511 -0
- claude_mpm/cli/parser.py +8 -2
- claude_mpm/config/experimental_features.py +219 -0
- claude_mpm/config/memory_guardian_yaml.py +335 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/memory_aware_runner.py +353 -0
- claude_mpm/services/infrastructure/context_preservation.py +537 -0
- claude_mpm/services/infrastructure/graceful_degradation.py +616 -0
- claude_mpm/services/infrastructure/health_monitor.py +775 -0
- claude_mpm/services/infrastructure/memory_dashboard.py +479 -0
- claude_mpm/services/infrastructure/memory_guardian.py +189 -15
- claude_mpm/services/infrastructure/restart_protection.py +642 -0
- claude_mpm/services/infrastructure/state_manager.py +774 -0
- claude_mpm/services/mcp_gateway/__init__.py +11 -11
- claude_mpm/services/mcp_gateway/core/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/core/interfaces.py +10 -9
- claude_mpm/services/mcp_gateway/main.py +35 -5
- claude_mpm/services/mcp_gateway/manager.py +334 -0
- claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -8
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +60 -59
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +1 -2
- claude_mpm/services/ticket_manager.py +8 -8
- claude_mpm/services/ticket_manager_di.py +5 -5
- claude_mpm/storage/__init__.py +9 -0
- claude_mpm/storage/state_storage.py +556 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-3.9.11.dist-info}/METADATA +25 -2
- {claude_mpm-3.9.9.dist-info → claude_mpm-3.9.11.dist-info}/RECORD +37 -24
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
- {claude_mpm-3.9.9.dist-info → claude_mpm-3.9.11.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-3.9.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-3.9.11.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-3.9.11.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,616 @@ | |
| 1 | 
            +
            """Graceful degradation service for Memory Guardian system.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Implements fallback strategies when monitoring components fail, ensuring
         | 
| 4 | 
            +
            the system continues to function even with reduced capabilities.
         | 
| 5 | 
            +
            """
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            import asyncio
         | 
| 8 | 
            +
            import logging
         | 
| 9 | 
            +
            import os
         | 
| 10 | 
            +
            import subprocess
         | 
| 11 | 
            +
            import time
         | 
| 12 | 
            +
            from dataclasses import dataclass
         | 
| 13 | 
            +
            from datetime import datetime
         | 
| 14 | 
            +
            from enum import Enum
         | 
| 15 | 
            +
            from pathlib import Path
         | 
| 16 | 
            +
            from typing import Dict, List, Optional, Any, Callable, Set
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            from claude_mpm.services.core.base import BaseService
         | 
| 19 | 
            +
             | 
| 20 | 
            +
             | 
| 21 | 
            +
            class DegradationLevel(Enum):
         | 
| 22 | 
            +
                """System degradation levels."""
         | 
| 23 | 
            +
                NORMAL = "normal"           # Full functionality
         | 
| 24 | 
            +
                MINOR = "minor"             # Some features disabled
         | 
| 25 | 
            +
                MODERATE = "moderate"       # Running with reduced capabilities
         | 
| 26 | 
            +
                SEVERE = "severe"          # Minimal functionality
         | 
| 27 | 
            +
                EMERGENCY = "emergency"     # Emergency mode only
         | 
| 28 | 
            +
             | 
| 29 | 
            +
             | 
| 30 | 
            +
            class FeatureState(Enum):
         | 
| 31 | 
            +
                """Feature availability state."""
         | 
| 32 | 
            +
                AVAILABLE = "available"
         | 
| 33 | 
            +
                DEGRADED = "degraded"
         | 
| 34 | 
            +
                UNAVAILABLE = "unavailable"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
             | 
| 37 | 
            +
            @dataclass
         | 
| 38 | 
            +
            class Feature:
         | 
| 39 | 
            +
                """System feature definition."""
         | 
| 40 | 
            +
                name: str
         | 
| 41 | 
            +
                state: FeatureState
         | 
| 42 | 
            +
                fallback_mode: Optional[str]
         | 
| 43 | 
            +
                reason: Optional[str]
         | 
| 44 | 
            +
                degraded_at: Optional[float]
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 47 | 
            +
                    """Convert to dictionary."""
         | 
| 48 | 
            +
                    return {
         | 
| 49 | 
            +
                        'name': self.name,
         | 
| 50 | 
            +
                        'state': self.state.value,
         | 
| 51 | 
            +
                        'fallback_mode': self.fallback_mode,
         | 
| 52 | 
            +
                        'reason': self.reason,
         | 
| 53 | 
            +
                        'degraded_at': self.degraded_at,
         | 
| 54 | 
            +
                        'degraded_at_iso': datetime.fromtimestamp(self.degraded_at).isoformat() if self.degraded_at else None
         | 
| 55 | 
            +
                    }
         | 
| 56 | 
            +
             | 
| 57 | 
            +
             | 
| 58 | 
            +
            @dataclass
         | 
| 59 | 
            +
            class DegradationStatus:
         | 
| 60 | 
            +
                """System degradation status."""
         | 
| 61 | 
            +
                level: DegradationLevel
         | 
| 62 | 
            +
                features: List[Feature]
         | 
| 63 | 
            +
                active_fallbacks: List[str]
         | 
| 64 | 
            +
                notifications: List[str]
         | 
| 65 | 
            +
                timestamp: float
         | 
| 66 | 
            +
                
         | 
| 67 | 
            +
                @property
         | 
| 68 | 
            +
                def available_features(self) -> int:
         | 
| 69 | 
            +
                    """Count of available features."""
         | 
| 70 | 
            +
                    return sum(1 for f in self.features if f.state == FeatureState.AVAILABLE)
         | 
| 71 | 
            +
                
         | 
| 72 | 
            +
                @property
         | 
| 73 | 
            +
                def degraded_features(self) -> int:
         | 
| 74 | 
            +
                    """Count of degraded features."""
         | 
| 75 | 
            +
                    return sum(1 for f in self.features if f.state == FeatureState.DEGRADED)
         | 
| 76 | 
            +
                
         | 
| 77 | 
            +
                @property
         | 
| 78 | 
            +
                def unavailable_features(self) -> int:
         | 
| 79 | 
            +
                    """Count of unavailable features."""
         | 
| 80 | 
            +
                    return sum(1 for f in self.features if f.state == FeatureState.UNAVAILABLE)
         | 
| 81 | 
            +
                
         | 
| 82 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 83 | 
            +
                    """Convert to dictionary."""
         | 
| 84 | 
            +
                    return {
         | 
| 85 | 
            +
                        'level': self.level.value,
         | 
| 86 | 
            +
                        'available_features': self.available_features,
         | 
| 87 | 
            +
                        'degraded_features': self.degraded_features,
         | 
| 88 | 
            +
                        'unavailable_features': self.unavailable_features,
         | 
| 89 | 
            +
                        'features': [f.to_dict() for f in self.features],
         | 
| 90 | 
            +
                        'active_fallbacks': self.active_fallbacks,
         | 
| 91 | 
            +
                        'notifications': self.notifications,
         | 
| 92 | 
            +
                        'timestamp': self.timestamp,
         | 
| 93 | 
            +
                        'timestamp_iso': datetime.fromtimestamp(self.timestamp).isoformat()
         | 
| 94 | 
            +
                    }
         | 
| 95 | 
            +
             | 
| 96 | 
            +
             | 
| 97 | 
            +
            class GracefulDegradation(BaseService):
         | 
| 98 | 
            +
                """Service for managing graceful degradation of system features."""
         | 
| 99 | 
            +
                
         | 
| 100 | 
            +
                def __init__(
         | 
| 101 | 
            +
                    self,
         | 
| 102 | 
            +
                    enable_notifications: bool = True,
         | 
| 103 | 
            +
                    log_degradation_events: bool = True,
         | 
| 104 | 
            +
                    state_file: Optional[Path] = None
         | 
| 105 | 
            +
                ):
         | 
| 106 | 
            +
                    """Initialize graceful degradation service.
         | 
| 107 | 
            +
                    
         | 
| 108 | 
            +
                    Args:
         | 
| 109 | 
            +
                        enable_notifications: Whether to show user notifications
         | 
| 110 | 
            +
                        log_degradation_events: Whether to log degradation events
         | 
| 111 | 
            +
                        state_file: Optional file for persisting degradation state
         | 
| 112 | 
            +
                    """
         | 
| 113 | 
            +
                    super().__init__("GracefulDegradation")
         | 
| 114 | 
            +
                    
         | 
| 115 | 
            +
                    # Configuration
         | 
| 116 | 
            +
                    self.enable_notifications = enable_notifications
         | 
| 117 | 
            +
                    self.log_degradation_events = log_degradation_events
         | 
| 118 | 
            +
                    self.state_file = state_file
         | 
| 119 | 
            +
                    
         | 
| 120 | 
            +
                    # Feature registry
         | 
| 121 | 
            +
                    self.features: Dict[str, Feature] = {}
         | 
| 122 | 
            +
                    self.fallback_handlers: Dict[str, Callable] = {}
         | 
| 123 | 
            +
                    self.recovery_handlers: Dict[str, Callable] = {}
         | 
| 124 | 
            +
                    
         | 
| 125 | 
            +
                    # State tracking
         | 
| 126 | 
            +
                    self.degradation_level = DegradationLevel.NORMAL
         | 
| 127 | 
            +
                    self.active_fallbacks: Set[str] = set()
         | 
| 128 | 
            +
                    self.pending_notifications: List[str] = []
         | 
| 129 | 
            +
                    self.degradation_history: List[DegradationStatus] = []
         | 
| 130 | 
            +
                    
         | 
| 131 | 
            +
                    # Initialize core features
         | 
| 132 | 
            +
                    self._initialize_core_features()
         | 
| 133 | 
            +
                    
         | 
| 134 | 
            +
                    self.log_info("Graceful degradation service initialized")
         | 
| 135 | 
            +
                
         | 
| 136 | 
            +
                async def initialize(self) -> bool:
         | 
| 137 | 
            +
                    """Initialize the graceful degradation service.
         | 
| 138 | 
            +
                    
         | 
| 139 | 
            +
                    Returns:
         | 
| 140 | 
            +
                        True if initialization successful
         | 
| 141 | 
            +
                    """
         | 
| 142 | 
            +
                    try:
         | 
| 143 | 
            +
                        self.log_info("Initializing graceful degradation service")
         | 
| 144 | 
            +
                        
         | 
| 145 | 
            +
                        # Check system capabilities
         | 
| 146 | 
            +
                        await self._check_system_capabilities()
         | 
| 147 | 
            +
                        
         | 
| 148 | 
            +
                        # Start notification handler
         | 
| 149 | 
            +
                        if self.enable_notifications:
         | 
| 150 | 
            +
                            asyncio.create_task(self._notification_handler())
         | 
| 151 | 
            +
                        
         | 
| 152 | 
            +
                        self._initialized = True
         | 
| 153 | 
            +
                        self.log_info("Graceful degradation service initialized successfully")
         | 
| 154 | 
            +
                        return True
         | 
| 155 | 
            +
                        
         | 
| 156 | 
            +
                    except Exception as e:
         | 
| 157 | 
            +
                        self.log_error(f"Failed to initialize graceful degradation: {e}")
         | 
| 158 | 
            +
                        return False
         | 
| 159 | 
            +
                
         | 
| 160 | 
            +
                async def shutdown(self) -> None:
         | 
| 161 | 
            +
                    """Shutdown the graceful degradation service."""
         | 
| 162 | 
            +
                    try:
         | 
| 163 | 
            +
                        self.log_info("Shutting down graceful degradation service")
         | 
| 164 | 
            +
                        
         | 
| 165 | 
            +
                        # Save state if configured
         | 
| 166 | 
            +
                        if self.state_file:
         | 
| 167 | 
            +
                            self._save_state()
         | 
| 168 | 
            +
                        
         | 
| 169 | 
            +
                        self._shutdown = True
         | 
| 170 | 
            +
                        self.log_info("Graceful degradation service shutdown complete")
         | 
| 171 | 
            +
                        
         | 
| 172 | 
            +
                    except Exception as e:
         | 
| 173 | 
            +
                        self.log_error(f"Error during graceful degradation shutdown: {e}")
         | 
| 174 | 
            +
                
         | 
| 175 | 
            +
                def register_feature(
         | 
| 176 | 
            +
                    self,
         | 
| 177 | 
            +
                    name: str,
         | 
| 178 | 
            +
                    fallback_handler: Optional[Callable] = None,
         | 
| 179 | 
            +
                    recovery_handler: Optional[Callable] = None
         | 
| 180 | 
            +
                ) -> None:
         | 
| 181 | 
            +
                    """Register a system feature with optional fallback/recovery handlers.
         | 
| 182 | 
            +
                    
         | 
| 183 | 
            +
                    Args:
         | 
| 184 | 
            +
                        name: Feature name
         | 
| 185 | 
            +
                        fallback_handler: Handler to call when feature degrades
         | 
| 186 | 
            +
                        recovery_handler: Handler to call when feature recovers
         | 
| 187 | 
            +
                    """
         | 
| 188 | 
            +
                    self.features[name] = Feature(
         | 
| 189 | 
            +
                        name=name,
         | 
| 190 | 
            +
                        state=FeatureState.AVAILABLE,
         | 
| 191 | 
            +
                        fallback_mode=None,
         | 
| 192 | 
            +
                        reason=None,
         | 
| 193 | 
            +
                        degraded_at=None
         | 
| 194 | 
            +
                    )
         | 
| 195 | 
            +
                    
         | 
| 196 | 
            +
                    if fallback_handler:
         | 
| 197 | 
            +
                        self.fallback_handlers[name] = fallback_handler
         | 
| 198 | 
            +
                    
         | 
| 199 | 
            +
                    if recovery_handler:
         | 
| 200 | 
            +
                        self.recovery_handlers[name] = recovery_handler
         | 
| 201 | 
            +
                    
         | 
| 202 | 
            +
                    self.log_debug(f"Registered feature: {name}")
         | 
| 203 | 
            +
                
         | 
| 204 | 
            +
                async def degrade_feature(
         | 
| 205 | 
            +
                    self,
         | 
| 206 | 
            +
                    feature_name: str,
         | 
| 207 | 
            +
                    reason: str,
         | 
| 208 | 
            +
                    fallback_mode: Optional[str] = None
         | 
| 209 | 
            +
                ) -> bool:
         | 
| 210 | 
            +
                    """Degrade a system feature to fallback mode.
         | 
| 211 | 
            +
                    
         | 
| 212 | 
            +
                    Args:
         | 
| 213 | 
            +
                        feature_name: Name of feature to degrade
         | 
| 214 | 
            +
                        reason: Reason for degradation
         | 
| 215 | 
            +
                        fallback_mode: Optional description of fallback mode
         | 
| 216 | 
            +
                        
         | 
| 217 | 
            +
                    Returns:
         | 
| 218 | 
            +
                        True if degradation successful
         | 
| 219 | 
            +
                    """
         | 
| 220 | 
            +
                    if feature_name not in self.features:
         | 
| 221 | 
            +
                        self.log_warning(f"Unknown feature: {feature_name}")
         | 
| 222 | 
            +
                        return False
         | 
| 223 | 
            +
                    
         | 
| 224 | 
            +
                    feature = self.features[feature_name]
         | 
| 225 | 
            +
                    
         | 
| 226 | 
            +
                    if feature.state == FeatureState.UNAVAILABLE:
         | 
| 227 | 
            +
                        self.log_debug(f"Feature {feature_name} already unavailable")
         | 
| 228 | 
            +
                        return True
         | 
| 229 | 
            +
                    
         | 
| 230 | 
            +
                    # Update feature state
         | 
| 231 | 
            +
                    old_state = feature.state
         | 
| 232 | 
            +
                    feature.state = FeatureState.DEGRADED
         | 
| 233 | 
            +
                    feature.fallback_mode = fallback_mode or "basic mode"
         | 
| 234 | 
            +
                    feature.reason = reason
         | 
| 235 | 
            +
                    feature.degraded_at = time.time()
         | 
| 236 | 
            +
                    
         | 
| 237 | 
            +
                    # Log degradation
         | 
| 238 | 
            +
                    if self.log_degradation_events:
         | 
| 239 | 
            +
                        self.log_warning(
         | 
| 240 | 
            +
                            f"Feature degraded: {feature_name} - {reason} "
         | 
| 241 | 
            +
                            f"(fallback: {feature.fallback_mode})"
         | 
| 242 | 
            +
                        )
         | 
| 243 | 
            +
                    
         | 
| 244 | 
            +
                    # Add notification
         | 
| 245 | 
            +
                    self._add_notification(
         | 
| 246 | 
            +
                        f"System feature '{feature_name}' running in {feature.fallback_mode} due to: {reason}"
         | 
| 247 | 
            +
                    )
         | 
| 248 | 
            +
                    
         | 
| 249 | 
            +
                    # Execute fallback handler if available
         | 
| 250 | 
            +
                    if feature_name in self.fallback_handlers:
         | 
| 251 | 
            +
                        try:
         | 
| 252 | 
            +
                            await self.fallback_handlers[feature_name](reason)
         | 
| 253 | 
            +
                        except Exception as e:
         | 
| 254 | 
            +
                            self.log_error(f"Fallback handler failed for {feature_name}: {e}")
         | 
| 255 | 
            +
                    
         | 
| 256 | 
            +
                    # Add to active fallbacks
         | 
| 257 | 
            +
                    self.active_fallbacks.add(feature_name)
         | 
| 258 | 
            +
                    
         | 
| 259 | 
            +
                    # Update degradation level
         | 
| 260 | 
            +
                    self._update_degradation_level()
         | 
| 261 | 
            +
                    
         | 
| 262 | 
            +
                    # Record status
         | 
| 263 | 
            +
                    self._record_status()
         | 
| 264 | 
            +
                    
         | 
| 265 | 
            +
                    return True
         | 
| 266 | 
            +
                
         | 
| 267 | 
            +
                async def disable_feature(
         | 
| 268 | 
            +
                    self,
         | 
| 269 | 
            +
                    feature_name: str,
         | 
| 270 | 
            +
                    reason: str
         | 
| 271 | 
            +
                ) -> bool:
         | 
| 272 | 
            +
                    """Completely disable a system feature.
         | 
| 273 | 
            +
                    
         | 
| 274 | 
            +
                    Args:
         | 
| 275 | 
            +
                        feature_name: Name of feature to disable
         | 
| 276 | 
            +
                        reason: Reason for disabling
         | 
| 277 | 
            +
                        
         | 
| 278 | 
            +
                    Returns:
         | 
| 279 | 
            +
                        True if disable successful
         | 
| 280 | 
            +
                    """
         | 
| 281 | 
            +
                    if feature_name not in self.features:
         | 
| 282 | 
            +
                        self.log_warning(f"Unknown feature: {feature_name}")
         | 
| 283 | 
            +
                        return False
         | 
| 284 | 
            +
                    
         | 
| 285 | 
            +
                    feature = self.features[feature_name]
         | 
| 286 | 
            +
                    
         | 
| 287 | 
            +
                    if feature.state == FeatureState.UNAVAILABLE:
         | 
| 288 | 
            +
                        self.log_debug(f"Feature {feature_name} already disabled")
         | 
| 289 | 
            +
                        return True
         | 
| 290 | 
            +
                    
         | 
| 291 | 
            +
                    # Update feature state
         | 
| 292 | 
            +
                    feature.state = FeatureState.UNAVAILABLE
         | 
| 293 | 
            +
                    feature.fallback_mode = None
         | 
| 294 | 
            +
                    feature.reason = reason
         | 
| 295 | 
            +
                    feature.degraded_at = time.time()
         | 
| 296 | 
            +
                    
         | 
| 297 | 
            +
                    # Log disable
         | 
| 298 | 
            +
                    if self.log_degradation_events:
         | 
| 299 | 
            +
                        self.log_error(f"Feature disabled: {feature_name} - {reason}")
         | 
| 300 | 
            +
                    
         | 
| 301 | 
            +
                    # Add notification
         | 
| 302 | 
            +
                    self._add_notification(
         | 
| 303 | 
            +
                        f"System feature '{feature_name}' has been disabled: {reason}"
         | 
| 304 | 
            +
                    )
         | 
| 305 | 
            +
                    
         | 
| 306 | 
            +
                    # Remove from active fallbacks if present
         | 
| 307 | 
            +
                    self.active_fallbacks.discard(feature_name)
         | 
| 308 | 
            +
                    
         | 
| 309 | 
            +
                    # Update degradation level
         | 
| 310 | 
            +
                    self._update_degradation_level()
         | 
| 311 | 
            +
                    
         | 
| 312 | 
            +
                    # Record status
         | 
| 313 | 
            +
                    self._record_status()
         | 
| 314 | 
            +
                    
         | 
| 315 | 
            +
                    return True
         | 
| 316 | 
            +
                
         | 
| 317 | 
            +
                async def recover_feature(self, feature_name: str) -> bool:
         | 
| 318 | 
            +
                    """Recover a degraded feature to normal operation.
         | 
| 319 | 
            +
                    
         | 
| 320 | 
            +
                    Args:
         | 
| 321 | 
            +
                        feature_name: Name of feature to recover
         | 
| 322 | 
            +
                        
         | 
| 323 | 
            +
                    Returns:
         | 
| 324 | 
            +
                        True if recovery successful
         | 
| 325 | 
            +
                    """
         | 
| 326 | 
            +
                    if feature_name not in self.features:
         | 
| 327 | 
            +
                        self.log_warning(f"Unknown feature: {feature_name}")
         | 
| 328 | 
            +
                        return False
         | 
| 329 | 
            +
                    
         | 
| 330 | 
            +
                    feature = self.features[feature_name]
         | 
| 331 | 
            +
                    
         | 
| 332 | 
            +
                    if feature.state == FeatureState.AVAILABLE:
         | 
| 333 | 
            +
                        self.log_debug(f"Feature {feature_name} already available")
         | 
| 334 | 
            +
                        return True
         | 
| 335 | 
            +
                    
         | 
| 336 | 
            +
                    # Update feature state
         | 
| 337 | 
            +
                    old_state = feature.state
         | 
| 338 | 
            +
                    feature.state = FeatureState.AVAILABLE
         | 
| 339 | 
            +
                    feature.fallback_mode = None
         | 
| 340 | 
            +
                    feature.reason = None
         | 
| 341 | 
            +
                    feature.degraded_at = None
         | 
| 342 | 
            +
                    
         | 
| 343 | 
            +
                    # Log recovery
         | 
| 344 | 
            +
                    self.log_info(f"Feature recovered: {feature_name}")
         | 
| 345 | 
            +
                    
         | 
| 346 | 
            +
                    # Add notification
         | 
| 347 | 
            +
                    self._add_notification(
         | 
| 348 | 
            +
                        f"System feature '{feature_name}' has been restored to normal operation"
         | 
| 349 | 
            +
                    )
         | 
| 350 | 
            +
                    
         | 
| 351 | 
            +
                    # Execute recovery handler if available
         | 
| 352 | 
            +
                    if feature_name in self.recovery_handlers:
         | 
| 353 | 
            +
                        try:
         | 
| 354 | 
            +
                            await self.recovery_handlers[feature_name]()
         | 
| 355 | 
            +
                        except Exception as e:
         | 
| 356 | 
            +
                            self.log_error(f"Recovery handler failed for {feature_name}: {e}")
         | 
| 357 | 
            +
                    
         | 
| 358 | 
            +
                    # Remove from active fallbacks
         | 
| 359 | 
            +
                    self.active_fallbacks.discard(feature_name)
         | 
| 360 | 
            +
                    
         | 
| 361 | 
            +
                    # Update degradation level
         | 
| 362 | 
            +
                    self._update_degradation_level()
         | 
| 363 | 
            +
                    
         | 
| 364 | 
            +
                    # Record status
         | 
| 365 | 
            +
                    self._record_status()
         | 
| 366 | 
            +
                    
         | 
| 367 | 
            +
                    return True
         | 
| 368 | 
            +
                
         | 
| 369 | 
            +
                def get_status(self) -> DegradationStatus:
         | 
| 370 | 
            +
                    """Get current degradation status.
         | 
| 371 | 
            +
                    
         | 
| 372 | 
            +
                    Returns:
         | 
| 373 | 
            +
                        DegradationStatus object
         | 
| 374 | 
            +
                    """
         | 
| 375 | 
            +
                    return DegradationStatus(
         | 
| 376 | 
            +
                        level=self.degradation_level,
         | 
| 377 | 
            +
                        features=list(self.features.values()),
         | 
| 378 | 
            +
                        active_fallbacks=list(self.active_fallbacks),
         | 
| 379 | 
            +
                        notifications=list(self.pending_notifications),
         | 
| 380 | 
            +
                        timestamp=time.time()
         | 
| 381 | 
            +
                    )
         | 
| 382 | 
            +
                
         | 
| 383 | 
            +
                def get_feature_state(self, feature_name: str) -> Optional[FeatureState]:
         | 
| 384 | 
            +
                    """Get the state of a specific feature.
         | 
| 385 | 
            +
                    
         | 
| 386 | 
            +
                    Args:
         | 
| 387 | 
            +
                        feature_name: Name of feature
         | 
| 388 | 
            +
                        
         | 
| 389 | 
            +
                    Returns:
         | 
| 390 | 
            +
                        FeatureState or None if feature not found
         | 
| 391 | 
            +
                    """
         | 
| 392 | 
            +
                    feature = self.features.get(feature_name)
         | 
| 393 | 
            +
                    return feature.state if feature else None
         | 
| 394 | 
            +
                
         | 
| 395 | 
            +
                def is_degraded(self) -> bool:
         | 
| 396 | 
            +
                    """Check if system is running in degraded mode.
         | 
| 397 | 
            +
                    
         | 
| 398 | 
            +
                    Returns:
         | 
| 399 | 
            +
                        True if any features are degraded
         | 
| 400 | 
            +
                    """
         | 
| 401 | 
            +
                    return self.degradation_level != DegradationLevel.NORMAL
         | 
| 402 | 
            +
                
         | 
| 403 | 
            +
                async def fallback_to_basic_monitoring(self) -> bool:
         | 
| 404 | 
            +
                    """Fallback to basic memory monitoring without psutil.
         | 
| 405 | 
            +
                    
         | 
| 406 | 
            +
                    Returns:
         | 
| 407 | 
            +
                        True if fallback successful
         | 
| 408 | 
            +
                    """
         | 
| 409 | 
            +
                    self.log_info("Falling back to basic memory monitoring")
         | 
| 410 | 
            +
                    
         | 
| 411 | 
            +
                    # Degrade memory monitoring feature
         | 
| 412 | 
            +
                    await self.degrade_feature(
         | 
| 413 | 
            +
                        "memory_monitoring",
         | 
| 414 | 
            +
                        "psutil unavailable",
         | 
| 415 | 
            +
                        "basic OS commands"
         | 
| 416 | 
            +
                    )
         | 
| 417 | 
            +
                    
         | 
| 418 | 
            +
                    # Use platform-specific commands as fallback
         | 
| 419 | 
            +
                    if os.name == 'posix':
         | 
| 420 | 
            +
                        # Unix-like systems
         | 
| 421 | 
            +
                        self._add_notification(
         | 
| 422 | 
            +
                            "Using basic memory monitoring via 'ps' command. "
         | 
| 423 | 
            +
                            "Install psutil for full functionality."
         | 
| 424 | 
            +
                        )
         | 
| 425 | 
            +
                    elif os.name == 'nt':
         | 
| 426 | 
            +
                        # Windows
         | 
| 427 | 
            +
                        self._add_notification(
         | 
| 428 | 
            +
                            "Using basic memory monitoring via 'wmic' command. "
         | 
| 429 | 
            +
                            "Install psutil for full functionality."
         | 
| 430 | 
            +
                        )
         | 
| 431 | 
            +
                    
         | 
| 432 | 
            +
                    return True
         | 
| 433 | 
            +
                
         | 
| 434 | 
            +
                async def fallback_to_manual_checks(self) -> bool:
         | 
| 435 | 
            +
                    """Fallback to manual memory checks when automated monitoring fails.
         | 
| 436 | 
            +
                    
         | 
| 437 | 
            +
                    Returns:
         | 
| 438 | 
            +
                        True if fallback successful
         | 
| 439 | 
            +
                    """
         | 
| 440 | 
            +
                    self.log_info("Falling back to manual memory checks")
         | 
| 441 | 
            +
                    
         | 
| 442 | 
            +
                    # Degrade automated monitoring
         | 
| 443 | 
            +
                    await self.degrade_feature(
         | 
| 444 | 
            +
                        "automated_monitoring",
         | 
| 445 | 
            +
                        "monitoring service failure",
         | 
| 446 | 
            +
                        "manual periodic checks"
         | 
| 447 | 
            +
                    )
         | 
| 448 | 
            +
                    
         | 
| 449 | 
            +
                    self._add_notification(
         | 
| 450 | 
            +
                        "Automated memory monitoring disabled. "
         | 
| 451 | 
            +
                        "Manual checks will be performed periodically."
         | 
| 452 | 
            +
                    )
         | 
| 453 | 
            +
                    
         | 
| 454 | 
            +
                    return True
         | 
| 455 | 
            +
                
         | 
| 456 | 
            +
                async def continue_without_state(self) -> bool:
         | 
| 457 | 
            +
                    """Continue operation without state preservation.
         | 
| 458 | 
            +
                    
         | 
| 459 | 
            +
                    Returns:
         | 
| 460 | 
            +
                        True if fallback successful
         | 
| 461 | 
            +
                    """
         | 
| 462 | 
            +
                    self.log_info("Continuing without state preservation")
         | 
| 463 | 
            +
                    
         | 
| 464 | 
            +
                    # Disable state preservation
         | 
| 465 | 
            +
                    await self.disable_feature(
         | 
| 466 | 
            +
                        "state_preservation",
         | 
| 467 | 
            +
                        "storage failure"
         | 
| 468 | 
            +
                    )
         | 
| 469 | 
            +
                    
         | 
| 470 | 
            +
                    self._add_notification(
         | 
| 471 | 
            +
                        "State preservation disabled due to storage issues. "
         | 
| 472 | 
            +
                        "System state will not persist across restarts."
         | 
| 473 | 
            +
                    )
         | 
| 474 | 
            +
                    
         | 
| 475 | 
            +
                    return True
         | 
| 476 | 
            +
                
         | 
| 477 | 
            +
                def _initialize_core_features(self) -> None:
         | 
| 478 | 
            +
                    """Initialize core system features."""
         | 
| 479 | 
            +
                    core_features = [
         | 
| 480 | 
            +
                        "memory_monitoring",
         | 
| 481 | 
            +
                        "automated_monitoring",
         | 
| 482 | 
            +
                        "state_preservation",
         | 
| 483 | 
            +
                        "restart_protection",
         | 
| 484 | 
            +
                        "health_monitoring",
         | 
| 485 | 
            +
                        "process_management",
         | 
| 486 | 
            +
                        "notification_system",
         | 
| 487 | 
            +
                        "logging_system"
         | 
| 488 | 
            +
                    ]
         | 
| 489 | 
            +
                    
         | 
| 490 | 
            +
                    for feature in core_features:
         | 
| 491 | 
            +
                        self.register_feature(feature)
         | 
| 492 | 
            +
                
         | 
| 493 | 
            +
                async def _check_system_capabilities(self) -> None:
         | 
| 494 | 
            +
                    """Check and degrade features based on system capabilities."""
         | 
| 495 | 
            +
                    # Check for psutil
         | 
| 496 | 
            +
                    try:
         | 
| 497 | 
            +
                        import psutil
         | 
| 498 | 
            +
                    except ImportError:
         | 
| 499 | 
            +
                        await self.fallback_to_basic_monitoring()
         | 
| 500 | 
            +
                    
         | 
| 501 | 
            +
                    # Check for network connectivity
         | 
| 502 | 
            +
                    try:
         | 
| 503 | 
            +
                        import socket
         | 
| 504 | 
            +
                        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         | 
| 505 | 
            +
                        sock.settimeout(2)
         | 
| 506 | 
            +
                        result = sock.connect_ex(('8.8.8.8', 53))
         | 
| 507 | 
            +
                        sock.close()
         | 
| 508 | 
            +
                        if result != 0:
         | 
| 509 | 
            +
                            await self.degrade_feature(
         | 
| 510 | 
            +
                                "health_monitoring",
         | 
| 511 | 
            +
                                "no network connectivity",
         | 
| 512 | 
            +
                                "local checks only"
         | 
| 513 | 
            +
                            )
         | 
| 514 | 
            +
                    except:
         | 
| 515 | 
            +
                        await self.degrade_feature(
         | 
| 516 | 
            +
                            "health_monitoring",
         | 
| 517 | 
            +
                            "network check failed",
         | 
| 518 | 
            +
                            "local checks only"
         | 
| 519 | 
            +
                        )
         | 
| 520 | 
            +
                    
         | 
| 521 | 
            +
                    # Check for storage access
         | 
| 522 | 
            +
                    if self.state_file:
         | 
| 523 | 
            +
                        try:
         | 
| 524 | 
            +
                            self.state_file.parent.mkdir(parents=True, exist_ok=True)
         | 
| 525 | 
            +
                            test_file = self.state_file.parent / '.test'
         | 
| 526 | 
            +
                            test_file.write_text('test')
         | 
| 527 | 
            +
                            test_file.unlink()
         | 
| 528 | 
            +
                        except:
         | 
| 529 | 
            +
                            await self.continue_without_state()
         | 
| 530 | 
            +
                
         | 
| 531 | 
            +
                def _update_degradation_level(self) -> None:
         | 
| 532 | 
            +
                    """Update overall degradation level based on feature states."""
         | 
| 533 | 
            +
                    total_features = len(self.features)
         | 
| 534 | 
            +
                    if total_features == 0:
         | 
| 535 | 
            +
                        self.degradation_level = DegradationLevel.NORMAL
         | 
| 536 | 
            +
                        return
         | 
| 537 | 
            +
                    
         | 
| 538 | 
            +
                    unavailable_count = sum(1 for f in self.features.values() if f.state == FeatureState.UNAVAILABLE)
         | 
| 539 | 
            +
                    degraded_count = sum(1 for f in self.features.values() if f.state == FeatureState.DEGRADED)
         | 
| 540 | 
            +
                    
         | 
| 541 | 
            +
                    unavailable_ratio = unavailable_count / total_features
         | 
| 542 | 
            +
                    degraded_ratio = degraded_count / total_features
         | 
| 543 | 
            +
                    
         | 
| 544 | 
            +
                    if unavailable_ratio >= 0.5:
         | 
| 545 | 
            +
                        self.degradation_level = DegradationLevel.EMERGENCY
         | 
| 546 | 
            +
                    elif unavailable_ratio >= 0.25:
         | 
| 547 | 
            +
                        self.degradation_level = DegradationLevel.SEVERE
         | 
| 548 | 
            +
                    elif degraded_ratio >= 0.5:
         | 
| 549 | 
            +
                        self.degradation_level = DegradationLevel.MODERATE
         | 
| 550 | 
            +
                    elif degraded_ratio >= 0.25:
         | 
| 551 | 
            +
                        self.degradation_level = DegradationLevel.MINOR
         | 
| 552 | 
            +
                    else:
         | 
| 553 | 
            +
                        self.degradation_level = DegradationLevel.NORMAL
         | 
| 554 | 
            +
                
         | 
| 555 | 
            +
                def _add_notification(self, message: str) -> None:
         | 
| 556 | 
            +
                    """Add a notification for the user.
         | 
| 557 | 
            +
                    
         | 
| 558 | 
            +
                    Args:
         | 
| 559 | 
            +
                        message: Notification message
         | 
| 560 | 
            +
                    """
         | 
| 561 | 
            +
                    if self.enable_notifications:
         | 
| 562 | 
            +
                        self.pending_notifications.append(message)
         | 
| 563 | 
            +
                        self.log_info(f"Notification: {message}")
         | 
| 564 | 
            +
                
         | 
| 565 | 
            +
                def _record_status(self) -> None:
         | 
| 566 | 
            +
                    """Record current degradation status."""
         | 
| 567 | 
            +
                    status = self.get_status()
         | 
| 568 | 
            +
                    self.degradation_history.append(status)
         | 
| 569 | 
            +
                    
         | 
| 570 | 
            +
                    # Trim history
         | 
| 571 | 
            +
                    if len(self.degradation_history) > 100:
         | 
| 572 | 
            +
                        self.degradation_history = self.degradation_history[-100:]
         | 
| 573 | 
            +
                
         | 
| 574 | 
            +
                async def _notification_handler(self) -> None:
         | 
| 575 | 
            +
                    """Background task to handle user notifications."""
         | 
| 576 | 
            +
                    while not self._shutdown:
         | 
| 577 | 
            +
                        try:
         | 
| 578 | 
            +
                            if self.pending_notifications:
         | 
| 579 | 
            +
                                # Process pending notifications
         | 
| 580 | 
            +
                                notifications = list(self.pending_notifications)
         | 
| 581 | 
            +
                                self.pending_notifications.clear()
         | 
| 582 | 
            +
                                
         | 
| 583 | 
            +
                                for notification in notifications:
         | 
| 584 | 
            +
                                    # In a real implementation, this would show system notifications
         | 
| 585 | 
            +
                                    # For now, just log them
         | 
| 586 | 
            +
                                    self.log_info(f"USER NOTIFICATION: {notification}")
         | 
| 587 | 
            +
                            
         | 
| 588 | 
            +
                            await asyncio.sleep(5)  # Check every 5 seconds
         | 
| 589 | 
            +
                            
         | 
| 590 | 
            +
                        except Exception as e:
         | 
| 591 | 
            +
                            self.log_error(f"Error in notification handler: {e}")
         | 
| 592 | 
            +
                            await asyncio.sleep(5)
         | 
| 593 | 
            +
                
         | 
| 594 | 
            +
                def _save_state(self) -> None:
         | 
| 595 | 
            +
                    """Save degradation state to file."""
         | 
| 596 | 
            +
                    if not self.state_file:
         | 
| 597 | 
            +
                        return
         | 
| 598 | 
            +
                    
         | 
| 599 | 
            +
                    try:
         | 
| 600 | 
            +
                        import json
         | 
| 601 | 
            +
                        
         | 
| 602 | 
            +
                        state = {
         | 
| 603 | 
            +
                            'degradation_level': self.degradation_level.value,
         | 
| 604 | 
            +
                            'features': [f.to_dict() for f in self.features.values()],
         | 
| 605 | 
            +
                            'active_fallbacks': list(self.active_fallbacks),
         | 
| 606 | 
            +
                            'history': [s.to_dict() for s in self.degradation_history[-10:]]
         | 
| 607 | 
            +
                        }
         | 
| 608 | 
            +
                        
         | 
| 609 | 
            +
                        self.state_file.parent.mkdir(parents=True, exist_ok=True)
         | 
| 610 | 
            +
                        with open(self.state_file, 'w') as f:
         | 
| 611 | 
            +
                            json.dump(state, f, indent=2)
         | 
| 612 | 
            +
                        
         | 
| 613 | 
            +
                        self.log_debug(f"Saved degradation state to {self.state_file}")
         | 
| 614 | 
            +
                        
         | 
| 615 | 
            +
                    except Exception as e:
         | 
| 616 | 
            +
                        self.log_error(f"Failed to save degradation state: {e}")
         |