Spaces:
Sleeping
Sleeping
| /** | |
| * errorRecovery.ts — Autonomous Error Recovery Engine (S31) | |
| * | |
| * Chiude il loop di autonomia: quando un tool fallisce, il sistema non si limita | |
| * a loggare l'errore — controlla agentBrain.errorLog per fix noti e li inietta | |
| * come contesto LLM. Quando il tool poi riesce, segna l'errore come risolto. | |
| * | |
| * PIPELINE: | |
| * tool fail ❌ | |
| * → recordAndSuggestFix(ctx) | |
| * → errorLog.findSimilar(msg) | |
| * → se trovato con fix noto → ritorna {hadFix:true, fix} | |
| * → agentLoop inietta fix come messaggio LLM | |
| * → LLM applica il fix nel prossimo tool call | |
| * → tool riesce ✅ | |
| * → markToolFixed(hash, appliedFix) se pendingFix noto | |
| * → errorLog.markFixed → persiste su Dexie | |
| * | |
| * INVARIANTI: | |
| * - Zero dipendenze da React, EventRuntime o store Zustand | |
| * - Tutte le operazioni sync (Dexie write è fire-and-forget nell'errorLog) | |
| * - Non lancia mai — catch interno, fallback graceful | |
| * - _setTestLog(): DI per unit test (zero vi.mock) | |
| * - Fix suggeriti: max 400 chars (safe per context window mobile) | |
| * - Auto-track pending fix: Map<toolName, {hash, fix}> per auto-markFixed | |
| * | |
| * WIRING: | |
| * agentLoop.ts linea 595 (OpenAI parallel) → recordAndSuggestFix() | |
| * agentLoop.ts linea 649 (ReAct path) → recordAndSuggestFix() | |
| * agentLoop.ts success path → notifyToolSuccess() | |
| * | |
| * SESSIONE: S31 — Tool Error Recovery Loop | |
| */ | |
| import { errorLog as _defaultErrorLog, type ErrorEntry } from "@/lib/agentBrain"; | |
| // ─── DI interface per test ──────────────────────────────────────────────────── | |
| interface ErrorLogFacade { | |
| record(message: string, context: string, lang?: string, code?: string): ErrorEntry; | |
| markFixed(hash: string, fix: string): boolean; | |
| findSimilar(message: string): ErrorEntry[]; | |
| hashOf(message: string): string; | |
| } | |
| let _log: ErrorLogFacade = _defaultErrorLog; | |
| /** | |
| * Dependency injection per i test. | |
| * Permette di passare un mock errorLog senza vi.mock('@/lib/agentBrain'). | |
| * @internal — solo per unit test | |
| */ | |
| export function _setTestLog(mock: ErrorLogFacade): void { | |
| _log = mock; | |
| } | |
| export function _resetLog(): void { | |
| _log = _defaultErrorLog; | |
| } | |
| // ─── Tipi pubblici ──────────────────────────────────────────────────────────── | |
| export interface RecoveryContext { | |
| /** Nome del tool che ha fallito (es. "web_search", "write_file") */ | |
| toolName: string; | |
| /** Risultato raw del tool (inizia con ❌) */ | |
| errorMsg: string; | |
| /** Linguaggio rilevante se l'errore è di codice */ | |
| lang?: string; | |
| /** Args passati al tool (per contesto nel fix) */ | |
| args?: Record<string, unknown>; | |
| } | |
| export interface RecoveryResult { | |
| /** Hash stabile dell'errore normalizzato */ | |
| hash: string; | |
| /** true = fix noto trovato in errorLog */ | |
| hadFix: boolean; | |
| /** Testo del fix da iniettare nel contesto LLM (≤400 chars) */ | |
| fix?: string; | |
| /** Quante volte questo errore è stato visto in sessione */ | |
| count: number; | |
| /** Suggerimento di strategia alternativa se nessun fix noto */ | |
| hint?: string; | |
| } | |
| // ─── Pending fix tracker ────────────────────────────────────────────────────── | |
| // Traccia il fix suggerito per ogni tool, così quando il tool riesce | |
| // possiamo chiamare markFixed automaticamente. | |
| interface PendingFix { | |
| hash: string; | |
| fix: string; | |
| } | |
| const _pendingFixes = new Map<string, PendingFix>(); | |
| // ─── Strategie alternative per tool comuni ──────────────────────────────────── | |
| const FALLBACK_STRATEGIES: Record<string, string> = { | |
| web_search: "Se web_search fallisce: usa fetch_url con URL diretto, oppure read_page con URL di Wikipedia o sito autorevole.", | |
| fetch_url: "Se fetch_url fallisce: il sito potrebbe bloccare i bot. Prova con web_search per trovare un URL alternativo o un mirror.", | |
| write_file: "Se write_file fallisce: verifica che il percorso sia assoluto e che la directory esista. Usa list_files per controllare.", | |
| read_file: "Se read_file fallisce: il file potrebbe non esistere. Usa list_files per verificare il path esatto.", | |
| run_code: "Se run_code fallisce con errore di sintassi: correggi il codice e riprova. Se errore di runtime: semplifica il codice.", | |
| list_files: "Se list_files fallisce: usa '.' come directory o verifica che il path non contenga caratteri speciali.", | |
| github_push: "Se github_push fallisce: verifica il token GitHub e il nome del repo. Usa i log CI per diagnosticare.", | |
| }; | |
| // ─── Normalizzazione errore ─────────────────────────────────────────────────── | |
| function stripErrorPrefix(msg: string): string { | |
| return msg.replace(/^❌\s*/, "").trim(); | |
| } | |
| // ─── Core: recordAndSuggestFix ──────────────────────────────────────────────── | |
| /** | |
| * Registra un tool failure su agentBrain.errorLog e, se esiste un fix noto | |
| * per un errore simile, lo ritorna per essere iniettato nel contesto LLM. | |
| * | |
| * È SINCRONO e non lancia mai — safe per uso fire-and-forget in agentLoop. | |
| */ | |
| export function recordAndSuggestFix(ctx: RecoveryContext): RecoveryResult { | |
| const rawMsg = stripErrorPrefix(ctx.errorMsg); | |
| try { | |
| // 1. Cerca fix noti per errori simili | |
| const similar = _log.findSimilar(rawMsg); | |
| const known = similar.find(e => e.resolved && e.fix); | |
| // 2. Registra l'errore (incrementa contatore, preserva fix esistente) | |
| const _code = (ctx.args?.code ?? ctx.args?.command ?? ctx.args?.script) as string | undefined; | |
| const entry = _log.record(rawMsg, `tool:${ctx.toolName}`, ctx.lang, _code); | |
| // 3. Se fix noto → track pending + ritorna | |
| if (known?.fix) { | |
| const fix = known.fix.slice(0, 400); | |
| _pendingFixes.set(ctx.toolName, { hash: entry.hash, fix }); | |
| return { | |
| hash: entry.hash, | |
| hadFix: true, | |
| fix, | |
| count: entry.count, | |
| }; | |
| } | |
| // 4. Nessun fix noto → suggerisci strategia alternativa | |
| const hint = FALLBACK_STRATEGIES[ctx.toolName]; | |
| return { | |
| hash: entry.hash, | |
| hadFix: false, | |
| count: entry.count, | |
| ...(hint ? { hint } : {}), | |
| }; | |
| } catch { | |
| // Fallback totale — non crasha agentLoop | |
| return { hash: "", hadFix: false, count: 0 }; | |
| } | |
| } | |
| // ─── Notifica successo → auto-markFixed ─────────────────────────────────────── | |
| /** | |
| * Chiamato quando un tool riesce dopo aver avuto un fix suggerito. | |
| * Segna l'errore come risolto su agentBrain.errorLog (persiste su Dexie). | |
| * | |
| * @param toolName Nome del tool (deve corrispondere alla chiamata precedente) | |
| * @param appliedFix Testo del fix applicato (se diverso da quello suggerito) | |
| * @returns true se l'errore è stato marcato come risolto | |
| */ | |
| export function notifyToolSuccess(toolName: string, appliedFix?: string): boolean { | |
| const pending = _pendingFixes.get(toolName); | |
| if (!pending) return false; | |
| try { | |
| const fix = (appliedFix ?? pending.fix).slice(0, 1000); | |
| const ok = _log.markFixed(pending.hash, fix); | |
| _pendingFixes.delete(toolName); | |
| return ok; | |
| } catch { | |
| _pendingFixes.delete(toolName); | |
| return false; | |
| } | |
| } | |
| // ─── Utility: build hint message per agentLoop ─────────────────────────────── | |
| /** | |
| * Costruisce il messaggio da iniettare in loopMessages quando c'è un fix noto. | |
| * Sostituisce il messaggio generico "Alcuni tool hanno fallito". | |
| */ | |
| export function buildRecoveryMessage( | |
| failures: Array<{ toolName: string; result: RecoveryResult }>, | |
| ): string { | |
| if (failures.length === 0) return ""; | |
| const parts: string[] = []; | |
| for (const { toolName, result } of failures) { | |
| if (result.hadFix && result.fix) { | |
| parts.push(`💡 Fix noto per "${toolName}" (visto ${result.count}x): ${result.fix}`); | |
| } else if (result.hint) { | |
| parts.push(`⚡ "${toolName}" ha fallito. ${result.hint}`); | |
| } else { | |
| parts.push(`⚡ "${toolName}" ha fallito (${result.count}x). Cambia strategia.`); | |
| } | |
| } | |
| return parts.join("\n") + "\n\nContinua il task applicando questi suggerimenti."; | |
| } | |
| // ─── Utility: pending fix info ──────────────────────────────────────────────── | |
| /** Ritorna info sul pending fix per un tool (utile per test) */ | |
| export function getPendingFix(toolName: string): PendingFix | undefined { | |
| return _pendingFixes.get(toolName); | |
| } | |
| /** Svuota tutti i pending fixes (utile per test e cleanup sessione) */ | |
| export function clearPendingFixes(): void { | |
| _pendingFixes.clear(); | |
| } | |