// S450: extracted from agentLoop.ts — circuit breaker factory (per-call state) // States: CLOSED (normal) → OPEN (tripped, skip tool) → HALF-OPEN (probe) → CLOSED interface BreakerState { consecutiveFails: number; totalFails: number; openSince: number; lastError: string; } export const CB_ALT: Record = { web_search: "fetch_url o read_page", read_page: "fetch_url o web_search", fetch_url: "read_page o web_search", run_code: "execute_shell", execute_shell: "run_code", git_push: "git_push_multiple", git_push_multiple: "git_push", // S656: CB_ALT mancava di entries per tool aggiunti in S648-S655. // Senza entry, cbSkipMsg() non suggerisce alternative → LLM rimane bloccato // invece di provare un fallback. Aggiunta alternativa per ogni tool nuovo. call_api: "web_research o fetch_url", // se REST API fallisce, cerca via web send_email: "", // nessuna alternativa sicura (azione irreversibile) web_research: "web_search o fetch_url", // fallback a ricerca semplice generate_image: "", // nessuna alternativa — tool unico create_pdf: "", // nessuna alternativa — tool unico apply_patch: "write_file", // se patch fallisce, riscrivi il file // S671: CB_ALT mancava di entries per move_file/iterate_file/list_files/create_webpage/git_sync_vfs. // Senza entry, cbSkipMsg() non suggerisce alternative → LLM rimane bloccato invece di provare fallback. move_file: "write_file", // se move fallisce: scrivi nella destinazione + cancella sorgente manualmente iterate_file: "read_file", // se iterate fallisce: leggi il file e riscrivi con write_file list_files: "git_list_files", // fallback al listing da GitHub remoto git_sync_vfs: "git_list_files", // se sync fallisce: lista file remoti con git_list_files create_webpage: "write_file", // se create_webpage fallisce: scrivi HTML con write_file get_datetime: "", // nessuna alternativa — tool unico per data/ora corrente // S677: CB_ALT esteso con 21 alternative per tool ad alta frequenza d'uso. // Senza entry: quando il CB si apre, cbSkipMsg() non suggerisce fallback → LLM bloccato. // Convenzione: "" = nessuna alternativa sicura (irreversibile o tool unico). get_weather: "web_search o fetch_url (wttr.in)", // comune: fallisce per API key o CORS get_currency: "web_search 'tasso cambio XYZ EUR'", // comune: fallisce per rate limit get_news: "web_search 'site:ansa.it OR corriere.it'", // comune: fallisce per CORS get_stock: "web_search 'quotazione [TICKER] oggi'", // comune: fallisce per API search_github: "fetch_url con URL GitHub API diretta", // fallisce per rate limit search_wikipedia: "web_search 'site:it.wikipedia.org'", // fallisce per CORS search_npm: "fetch_url 'https://registry.npmjs.org/[pkg]/latest'", // fallisce per CORS run_python: "run_code", // Python → JS/WASM equivalente execute_sql: "run_python con sqlite3 in-memory", // DB → Python sqlite3 database_query: "execute_sql con SQLite in-memory", // DB remoto → locale translate: "web_search 'traduzione [testo] in [lingua]'", // common: API key recall: "remember (verifica chiave esatta)", // memoria: chiave sbagliata read_file: "git_read_file", // VFS → fallback a GitHub remoto write_file: "apply_patch", // riscrittura → patch parziale run_tests: "run_code con test inline", // test runner → codice diretto validate_json: "run_code con JSON.parse()", // validazione → codice math_eval: "run_code con espressione JS", // eval → codice analyze_image: "read_page sull'URL dell'immagine", // vision → fetch testo git_read_file: "read_file da cache VFS locale", // remoto → locale git_list_files: "list_files da VFS locale", // remoto → locale check_package: "search_npm o web_search 'site:npmjs.com'", // check → search // S684-B: CB_ALT esteso con 46 tool ADVANCED. Senza entry cbSkipMsg() non suggerisce // alternative → LLM bloccato quando questi tool aprono il circuit breaker. // Convenzione: "" = nessuna alternativa sicura (tool unico o azione irreversibile). profile_code: "run_code con console.time()/performance.now()", screenshot_url: "read_page o fetch_url", test_url: "fetch_url", check_performance: "screenshot_url o fetch_url", search_images: "web_search 'immagini [query]'", text_to_speech: "", read_pdf: "fetch_url con URL PDF diretto", create_chart: "run_code per generare SVG/Canvas", extract_table: "run_python con pandas", regex_test: "run_code con RegExp nativa", encode_decode: "run_code con Buffer/atob/btoa", format_code: "run_code con Prettier API", get_ip_info: "web_search 'IP info [ip]'", color_palette: "run_code con algoritmo HSL", write_files: "write_file (singolo file per volta)", delete_file: "move_file verso cartella .trash o rinomina con .bak", set_vercel_env: "", github_issue: "git_push_multiple con note nel AGENT_PLAN", minify_code: "run_code con terser/minify", generate_types: "run_code con TypeScript compiler API", bundle_size: "check_package o search_npm", test_links: "fetch_url per URL specifico", find_in_vfs: "read_file + run_code per grep", convert_data: "run_code con parser JSON/CSV", generate_mock_data:"run_code con algoritmo random", diff_commits: "git_read_file su entrambi i commit", accessibility_check:"read_page + analisi manuale", store_token: "", check_deploy: "fetch_url sull'URL del deploy", get_trending: "web_search 'trending tech oggi'", browser_navigate: "read_page o fetch_url", browser_open: "browser_navigate", browser_act: "browser_navigate", browser_close: "", browser_screenshot:"screenshot_url", set_project: "write_file con config progetto", record_bug: "write_file con bug report", run_background_task:"", restore_file: "git_read_file + write_file", ocr_image: "analyze_image", edit_image: "", generate_xlsx: "run_python con openpyxl", generate_docx: "run_python con python-docx", create_zip: "run_python con zipfile", remove_background: "", validate_project: "run_tests o lint_code", generate_readme: "write_file con README manuale", }; // ─── Factory (mancante dal S488 split — ripristinata da git history S488-fix) ─ const CB_TRIP = 4; // S656: innalzato da 2→4 (meno falsi positivi su rete lenta) const CB_COOL = 2; const CB_HARD = 5; export function createCircuitBreaker() { const _cb = new Map(); function _cbGet(name: string): BreakerState { if (!_cb.has(name)) _cb.set(name, { consecutiveFails: 0, totalFails: 0, openSince: -1, lastError: "" }); return _cb.get(name)!; } function _cbIsOpen(name: string, currentIter: number): boolean { const s = _cbGet(name); if (s.openSince < 0) return false; if (s.totalFails >= CB_HARD) return true; return currentIter - s.openSince < CB_COOL; } function _cbSkipMsg(name: string): string { const s = _cbGet(name); const alt = CB_ALT[name] ? ` — Usa invece: ${CB_ALT[name]}` : ""; const perm = s.totalFails >= CB_HARD ? " [PERMANENTE per questo task]" : ` [riprova tra ${CB_COOL} iter]`; return `❌ [CIRCUIT OPEN${perm}] "${name}" ha fallito ${s.consecutiveFails}× di fila${alt}. Ultimo errore: ${s.lastError.slice(0, 120)}`; } function _cbUpdate(name: string, result: string, currentIter: number): void { const s = _cbGet(name); if (result.startsWith("❌")) { s.consecutiveFails++; s.totalFails++; s.lastError = result.slice(0, 200); // S502-BUG4-FIX: rimosso `&& s.openSince < 0` — il cooldown deve essere resettato // anche quando una probe half-open fallisce. Senza il fix, openSince rimane al valore // originale → (currentIter - oldOpenSince) > CB_COOL immediatamente → la probe viene // ripetuta ogni iterazione invece di aspettare CB_COOL iterazioni dopo il fallimento. if (s.consecutiveFails >= CB_TRIP) s.openSince = currentIter; } else { s.consecutiveFails = 0; s.openSince = -1; } } function _cbStatus(): string { const open = [..._cb.entries()] .filter(([, s]) => s.openSince >= 0) .map(([n, s]) => `${n}(${s.totalFails}✗)`); return open.length ? ` · CB-OPEN: ${open.join(", ")}` : ""; } return { _cbIsOpen, _cbSkipMsg, _cbUpdate, _cbStatus }; }