""" DSS Context Module ================== Singleton context manager for the DSS Plugin. Handles configuration loading, mode detection, and strategy instantiation. """ import asyncio import logging from typing import Optional, Dict, Any from .config import DSSConfig, DSSMode # Logger setup logger = logging.getLogger(__name__) # Protocol/Type placeholder for Strategies (to be replaced by base class in next steps) Strategy = Any class DSSContext: """ Singleton context manager for the DSS Plugin. Handles configuration loading, mode detection (Local/Remote), and strategy instantiation. """ _instance: Optional['DSSContext'] = None _lock: asyncio.Lock = asyncio.Lock() def __init__(self) -> None: """ Private initializer. Use get_instance() instead. """ if DSSContext._instance is not None: raise RuntimeError("DSSContext is a singleton. Use get_instance() to access it.") self.config: Optional[DSSConfig] = None self.active_mode: DSSMode = DSSMode.REMOTE # Default safe fallback self._capabilities: Dict[str, bool] = {} self._strategy_cache: Dict[str, Strategy] = {} self.session_id: Optional[str] = None @classmethod async def get_instance(cls) -> 'DSSContext': """ Async factory method to get the singleton instance. Ensures config is loaded and mode is detected before returning. """ if not cls._instance: async with cls._lock: # Double-check locking pattern if not cls._instance: instance = cls() await instance._initialize() cls._instance = instance return cls._instance @classmethod def reset(cls) -> None: """ Resets the singleton instance. Useful for testing. """ cls._instance = None async def _initialize(self) -> None: """ Internal initialization logic: 1. Load Config 2. Detect Mode 3. Cache Capabilities """ try: # 1. Load Configuration self.config = DSSConfig.load() self.session_id = self.config.session_id # 2. Detect Mode (Async check) self.active_mode = await self.config.get_active_mode() logger.info(f"DSSContext initialized. Mode: {self.active_mode.value}, Session: {self.session_id}") # 3. Cache Capabilities self._cache_capabilities() except Exception as e: logger.error(f"Failed to initialize DSSContext: {e}") # Fallback to defaults if initialization fails self.active_mode = DSSMode.REMOTE self._capabilities = {"limited": True} def _cache_capabilities(self) -> None: """ Determines what the plugin can do based on the active mode. """ # Base capabilities caps = { "can_read_files": False, "can_execute_browser": False, "can_screenshot": False, "can_connect_remote": True } if self.active_mode == DSSMode.LOCAL: # Local mode allows direct filesystem access and local browser control caps["can_read_files"] = True caps["can_execute_browser"] = True caps["can_screenshot"] = True elif self.active_mode == DSSMode.REMOTE: # Remote mode relies on API capabilities # Depending on remote configuration, these might differ caps["can_execute_browser"] = False # Typically restricted in pure remote unless via API caps["can_read_files"] = False # Security restriction self._capabilities = caps def get_capability(self, key: str) -> bool: """Check if a specific capability is active.""" return self._capabilities.get(key, False) def get_api_url(self) -> str: """Get the correct API URL for the current mode.""" if self.config is None: return "https://dss.overbits.luz.uy" # Default fallback return self.config.get_api_url(self.active_mode) def get_strategy(self, strategy_type: str) -> Any: """ Factory method to retrieve operational strategies. Args: strategy_type: One of 'browser', 'filesystem', 'screenshot' Returns: An instance of the requested strategy. """ # Return cached strategy if available if strategy_type in self._strategy_cache: return self._strategy_cache[strategy_type] strategy_instance = None # NOTE: Strategy classes will be implemented in the next step. # We use local imports here to avoid circular dependency issues # if strategies define their own types using DSSContext. try: if strategy_type == "browser": # Will be implemented in Phase 2 & 3 if self.active_mode == DSSMode.LOCAL: from ..strategies.local.browser import LocalBrowserStrategy strategy_instance = LocalBrowserStrategy(self) else: from ..strategies.remote.browser import RemoteBrowserStrategy strategy_instance = RemoteBrowserStrategy(self) elif strategy_type == "filesystem": # Will be implemented in Phase 2 if self.active_mode == DSSMode.LOCAL: from ..strategies.local.filesystem import LocalFilesystemStrategy strategy_instance = LocalFilesystemStrategy(self) else: from ..strategies.remote.filesystem import RemoteFilesystemStrategy strategy_instance = RemoteFilesystemStrategy(self) elif strategy_type == "screenshot": # Screenshot is part of browser strategy return self.get_strategy("browser") else: raise ValueError(f"Unknown strategy type: {strategy_type}") except ImportError as e: logger.error(f"Failed to import strategy {strategy_type}: {e}") raise NotImplementedError(f"Strategy {strategy_type} not yet implemented") from e # Cache and return self._strategy_cache[strategy_type] = strategy_instance return strategy_instance