# core/kernel.py import sys import yaml import importlib import os class JarvisKernel: """ Il cuore di Jarvis. Gestisce la configurazione, il caricamento e il ciclo di vita dei servizi modulari di backend. """ def __init__(self, config_path, profile_name=None): self.services = [] self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) self.config = self._load_config(config_path) self.python_executable = sys.executable self._event_subscribers = {} if profile_name: self.active_profile = profile_name else: self.active_profile = self.config.get('profile', 'default') # --- NUOVO METODO API PER LA GESTIONE DEI PERCORSI --- def get_data_path(self, relative_path: str): """ Costruisce un percorso assoluto all'interno della cartella /data del progetto e assicura che la directory genitore esista. È il metodo standard per tutti i servizi che devono leggere/scrivere dati. Args: relative_path: Il percorso relativo a partire dalla cartella /data (es. "logs/jarvis.log" o "memory/chat.sqlite"). Returns: Il percorso assoluto e validato. """ # Unisce il percorso base del progetto, la cartella 'data', e il percorso relativo fornito. full_path = os.path.join(self.base_dir, 'data', relative_path) # Estrae la directory dal percorso completo. parent_directory = os.path.dirname(full_path) # Crea la directory genitore se non esiste. `exist_ok=True` previene errori. os.makedirs(parent_directory, exist_ok=True) return full_path # --- FINE NUOVO METODO --- def start(self): """ Avvia il Kernel e tutti i servizi configurati. """ self.log("[KERNEL] Avvio del Kernel...") self._load_services() for service in self.services: try: service.start() except Exception as e: self.log(f"ERRORE CRITICO avvio {service.__class__.__name__}: {e}", 'error') self.log("[KERNEL] Kernel e tutti i servizi sono attivi.") self.publish_event("kernel:started") def stop(self): """Ferma tutti i servizi attivi in ordine inverso.""" self.log("[KERNEL] Arresto del Kernel di Jarvis...") self.publish_event("kernel:stopping") for service in reversed(self.services): try: service.stop() except Exception as e: self.log(f"Errore arresto {service.__class__.__name__}: {e}", 'warning') self.log("[KERNEL] Kernel fermato.") def _load_config(self, path): """Carica il file di configurazione principale YAML.""" try: with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) except Exception as e: print(f"[KERNEL] ERRORE CRITICO: Impossibile caricare config {path}: {e}") sys.exit(1) def _load_services(self): """Carica le istanze di tutti i servizi, ma non li avvia.""" self.log(f"[KERNEL] Profilo attivo: '{self.active_profile}'") services_to_load = self.config.get('profiles', {}).get(self.active_profile, []) for service_config in services_to_load: service_name = service_config.get('service_name') try: # MODIFICA: Usa la chiave 'module' esplicita dalla config per l'import. module_path = service_config.get('module') module = importlib.import_module(module_path) service_class = getattr(module, service_name) config_data = service_config.get('config', {}) service_instance = service_class(self, config_data) self.services.append(service_instance) self.log(f"[KERNEL] Servizio '{service_name}' caricato.") except Exception as e: self.log(f"[KERNEL] ERRORE caricamento servizio '{service_name}': {e}", 'error') # --- API del Kernel per i Servizi --- def log(self, message, level='info'): """ API pubblica per loggare. Pubblica un evento 'log:new_message' sull'Event Bus. """ self.publish_event("log:new_message", {"message": message, "level": level}) def get_python_executable(self): """Restituisce il percorso dell'interprete Python in uso.""" return self.python_executable def get_scripts_base_path(self): """Restituisce il percorso di default per gli script, basato sul profilo.""" default_path = os.path.expanduser('~') path = self.config.get('base_paths', {}).get(self.active_profile, default_path) return os.path.expanduser(path) def get_service(self, service_name): """Restituisce un'istanza di un servizio attivo, dato il suo nome.""" for service in self.services: if service.__class__.__name__ == service_name: return service return None # --- Event Bus API --- def subscribe_to_event(self, event_name, callback): if event_name not in self._event_subscribers: self._event_subscribers[event_name] = [] self._event_subscribers[event_name].append(callback) def publish_event(self, event_name, data=None): if event_name in self._event_subscribers: for callback in self._event_subscribers[event_name]: try: callback(data) except Exception as e: print(f"[EVENTBUS] Errore in callback per evento '{event_name}': {e}")