""" backend/main.py — Entrypoint principale dell'Agente AI con migrazione automatica. """ import os import sys import logging import asyncio import argparse from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles # Configurazione Logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%H:%M:%S", ) _logger = logging.getLogger("agente_ai.main") app = FastAPI( title="Agente AI API", description="Backend per l'orchestrazione di agenti autonomi e tool-use.", version="1.5.3", ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ── P17-F1: RLS Fix & Auto-Migration ────────────────────────────────────────── async def _run_auto_migration(): """Esegue la migrazione SQL per RLS e indici al boot (Z-GAP-1/2/3/4).""" db_host = os.getenv("SUPABASE_DB_HOST") db_pass = os.getenv("SUPABASE_DB_PASSWORD") if not db_host or not db_pass: _logger.warning("BOOT: Migration skipped — SUPABASE_DB_HOST/PASSWORD non configurati.") return # Lista completa dal set SENSITIVE in state.py sensitive_keys = [ 'OPENROUTER_API_KEY', 'OPENAI_API_KEY', 'GEMINI_API_KEY', 'GROQ_API_KEY', 'HF_TOKEN', 'HUGGINGFACE_API_KEY', 'GH_TOKEN', 'GITHUB_TOKEN', 'QDRANT_API_KEY', 'DATABASE_URL', 'SESSION_SECRET', 'SECRET_KEY', 'RAILWAY_TOKEN', 'SUPABASE_KEY', 'SUPABASE_ANON_KEY', 'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'CF_API_TOKEN', 'CLOUDFLARE_API_TOKEN', 'CF_ACCOUNT_ID', 'CF_API_TOKEN_B', 'CF_ACCOUNT_ID_B', 'CEREBRAS_API_KEY', 'SAMBANOVA_API_KEY', 'VAULT_KEY', 'INTERNAL_TOKEN', 'DEPLOY_SECRET', 'WEBHOOK_TOKEN', 'TERMINAL_SECRET', 'EXEC_TOKEN', 'VITE_INTERNAL_TOKEN', 'VITE_TERMINAL_SECRET', 'VITE_OPENROUTER_API_KEY', 'VITE_HF_TOKEN', 'VITE_GROQ_API_KEY', 'GH_PAGES_TOKEN', 'VERCEL_TOKEN' ] # SAFETY: sensitive_keys è un literal Python hardcoded — nessun input utente, nessun rischio injection. keys_str = ", ".join(f"'{k}'" for k in sensitive_keys) # noqa: S608 sql = f""" -- 1. Indexing per performance CREATE INDEX IF NOT EXISTS idx_agent_memory_key ON public.agent_memory(key); CREATE INDEX IF NOT EXISTS idx_agent_memory_task_id ON public.agent_memory(task_id); -- 2. RLS Enforcement ALTER TABLE public.agent_memory ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ai_providers ENABLE ROW LEVEL SECURITY; -- 3. Policy: Deny Anonymous Access to sensitive keys (Full SENSITIVE set) DROP POLICY IF EXISTS "Frontend Anon Access" ON public.agent_memory; CREATE POLICY "Frontend Anon Access" ON public.agent_memory FOR SELECT USING ( auth.role() = 'anon' AND key NOT IN ({keys_str}) ); -- 4. Policy: Full access for service_role DROP POLICY IF EXISTS "Service Role Full Access" ON public.agent_memory; CREATE POLICY "Service Role Full Access" ON public.agent_memory FOR ALL TO service_role USING (true) WITH CHECK (true); -- 5. Healthcheck function CREATE OR REPLACE FUNCTION public.health_check() RETURNS jsonb AS $$ BEGIN RETURN jsonb_build_object('status', 'ok', 'timestamp', now()); END; $$ LANGUAGE plpgsql SECURITY DEFINER; """ try: import psycopg2 for port in [6543, 5432]: try: conn = psycopg2.connect(f"postgresql://postgres:{db_pass}@{db_host}:{port}/postgres?sslmode=require", connect_timeout=5) cur = conn.cursor() cur.execute(sql) conn.commit() cur.close() conn.close() _logger.info(f"✅ BOOT: Migrazione RLS completa applicata su porta {port}.") return except Exception as e: _logger.debug(f"BOOT: Fallito tentativo su porta {port}: {e}") except Exception as e: _logger.error(f"❌ BOOT: Errore migrazione: {e}") def _apply_rls_fix(): s_url = os.getenv("SUPABASE_URL") s_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY") or os.getenv("SUPABASE_KEY") if s_url and s_key: os.environ["SUPABASE_URL"] = s_url os.environ["SUPABASE_KEY"] = s_key _apply_rls_fix() # ── Importazione Route ──────────────────────────────────────────────────────── # S-GAP-FIX: Caricamento robusto dei router per evitare che un import fallito blocchi tutto. _ROUTER_MAP = { # ── Già montati ─────────────────────────────────────────────────────────── "state": "state", "research": "research", "agent_memory": "agent_memory", "agent": "agent", "exec": "exec", "vault": "vault", "browser": "browser", "deploy": "deploy", "scheduler": "scheduler", "blackboard": "blackboard", "conversations": "conversations", "benchmark": "benchmark", "files": "files", "telegram": "telegram_webhook", "marketplace": "marketplace", "plugins": "plugins", "skills": "skills", "auth": "auth_managed", # ── Aggiunti ROUTER-COMPLETE (29 moduli orfani rimontati) ───────────────── "agent_checkpoint": "agent_checkpoint", "agent_telemetry": "agent_telemetry", "coding": "coding", "daemon_status": "daemon_status", "database": "database", "decision_memory": "decision_memory", "email": "email", "event_bus": "event_bus", "event_store": "event_store", "gemini_vision": "gemini_vision", "incident_registry": "incident_registry", "integrity_manager": "integrity_manager", "job_queue": "job_queue", "kernel": "kernel", "llm_cache": "llm_cache", "mcp": "mcp", "memory_router": "memory_router", "notify_bot": "notify_bot", "policy": "policy", "providers": "providers", "search": "search", "semantic_cache": "semantic_cache", "session_manager": "session_manager", "structured_log": "structured_log", "telemetry": "telemetry", "terminal": "terminal", "vision": "vision", "web": "web", "webhook": "webhook", } for prefix, module_name in _ROUTER_MAP.items(): try: import importlib module = importlib.import_module(f"api.{module_name}") if hasattr(module, "router"): app.include_router(module.router) _logger.info(f"✅ Route montata: /api/{prefix} (da api.{module_name})") else: _logger.warning(f"⚠️ Modulo api.{module_name} non ha un attributo 'router'") except ImportError as e: _logger.error(f"❌ Errore import rotta {prefix} (api.{module_name}): {e}") except Exception as e: _logger.error(f"❌ Errore montaggio rotta {prefix}: {e}") # ── CLI Task Execution ──────────────────────────────────────────────────────── async def run_cli_task(task_description: str): _logger.info(f"CLI: Avvio task richiesto: {task_description[:50]}...") try: from agents.unified_loop import UnifiedAgentLoop from models.ai_client import AIClient llm = AIClient() agent = UnifiedAgentLoop(llm_client=llm) result = await agent.run(task_description) print("\nRESULT:\n", result) except Exception as e: _logger.error(f"CLI: Errore: {e}") import traceback traceback.print_exc() sys.exit(1) # ── Startup ─────────────────────────────────────────────────────────────────── @app.on_event("startup") async def startup_event(): _logger.info("Server starting up...") asyncio.create_task(_run_auto_migration()) if not any(arg in sys.argv for arg in ["--task", "-t"]): try: from api.job_queue import start_job_queue_consumer asyncio.create_task(start_job_queue_consumer()) except Exception: pass # ── SPA Hosting ─────────────────────────────────────────────────────────────── _STATIC_DIR = os.getenv('FRONTEND_DIST', '/app/backend/static') if os.path.isdir(_STATIC_DIR): app.mount('/', StaticFiles(directory=_STATIC_DIR, html=True), name='spa') if __name__ == "__main__": parser = argparse.ArgumentParser(description="Agente AI Backend & CLI") parser.add_argument("--task", "-t", type=str, help="Esegue un task e termina") parser.add_argument("--port", "-p", type=int, default=8000, help="Porta server") args = parser.parse_args() if args.task: asyncio.run(run_cli_task(args.task)) else: import uvicorn uvicorn.run(app, host="0.0.0.0", port=args.port)