/** * useTerminalAutoRepair.ts — S761: estratto da MainWorkspace (era ~60L inline). * * Encapsula: * - terminalBadge state ("error" | "repairing" | null) * - tutti i refs interni di polling/guard * - handleTerminalError (fetch buffer backend → sendMessage auto-repair) * - startPolling (da passare come onSessionId a TerminalPanel) * - useEffect polling reset dopo isStreaming → false (3s debounce) * - useEffect cleanup interval al dismount * * Props minimale: { sendMessage } — unica dipendenza esterna da MainWorkspace. * terminalBadge viene RITORNATO al chiamante (usato da WorkspaceTopBar + WorkspaceTabBar). */ import { useRef, useState, useEffect, useCallback } from "react"; import { useChatStore } from "@/store/chatStore"; interface UseTerminalAutoRepairOptions { sendMessage: (text: string) => Promise; } export function useTerminalAutoRepair({ sendMessage }: UseTerminalAutoRepairOptions) { // S-FIX2-BADGE: stato visuale auto-repair terminale const [terminalBadge, setTerminalBadge] = useState<"error" | "repairing" | null>(null); // S-FIX2: guard + session ref + poll cleanup const _terminalAutoRepairFired = useRef(false); const _terminalSidRef = useRef(""); // S-FIX2-POLL: ref stabile per polling — evita stale closure nel setInterval const _handleTerminalErrorRef = useRef<((sid: string) => Promise) | null>(null); const _terminalPollCleanup = useRef | null>(null); const isStreaming = useChatStore(s => s.isStreaming); // S-FIX2-POLL: reset auto-repair flag dopo ogni risposta agente (3s debounce) // → se il terminale ha ancora errori, il prossimo tick del polling li rileva senza aspettare l'utente useEffect(() => { if (isStreaming) return; const t = setTimeout(() => { _terminalAutoRepairFired.current = false; setTerminalBadge(null); // S-FIX2-BADGE: repair completato }, 3_000); return () => clearTimeout(t); }, [isStreaming]); // S-FIX2-POLL: cleanup interval al dismount del componente host useEffect(() => () => { if (_terminalPollCleanup.current) clearInterval(_terminalPollCleanup.current); }, []); // S-FIX2 — Terminal Error Feedback Loop: agente auto-ripara errori terminale const handleTerminalError = useCallback(async (sessionId: string) => { if (_terminalAutoRepairFired.current) return; const _cs = useChatStore.getState(); if (_cs.isStreaming) return; const backendUrl = (import.meta.env.VITE_EXEC_BACKEND_URL ?? "").replace(/\/$/, ""); if (!backendUrl) return; try { const res = await fetch(`${backendUrl}/api/terminal/buffer/${encodeURIComponent(sessionId)}`); if (!res.ok) return; const data = await res.json() as { buffer: string; active: boolean }; if (!data.active || !data.buffer) return; const errorPatterns = [ /Error:/i, /Traceback/i, /FAILED/i, /npm ERR!/i, /SyntaxError/i, /ModuleNotFoundError/i, /Cannot find module/i, /command not found/i, ]; if (!errorPatterns.some(p => p.test(data.buffer))) return; _terminalAutoRepairFired.current = true; setTerminalBadge("repairing"); // S-FIX2-BADGE: segnala repair in corso const snippet = data.buffer.slice(-1500); void sendMessage( `🔴 **Errore rilevato nel terminale** (auto-repair S-FIX2)\n\n` + `\`\`\`\n${snippet}\n\`\`\`\n\n` + `Analizza l'errore, identifica il file responsabile nel VFS, correggilo e riesegui il comando.` ); } catch { /* non-blocking */ } }, [sendMessage]); // S-FIX2-POLL: tieni il ref sempre aggiornato all'ultima versione di handleTerminalError useEffect(() => { _handleTerminalErrorRef.current = handleTerminalError; }, [handleTerminalError]); // startPolling: da passare come onSessionId={startPolling} a TerminalPanel. // Avvia polling automatico ogni 20s — rileva errori terminale senza input utente. const startPolling = useCallback((sid: string) => { _terminalSidRef.current = sid; // S-FIX2-POLL: resetta interval precedente (es: reconnect terminale) if (_terminalPollCleanup.current) clearInterval(_terminalPollCleanup.current); _terminalPollCleanup.current = setInterval(() => { const _s = _terminalSidRef.current; if (_s) void _handleTerminalErrorRef.current?.(_s); }, 20_000); }, []); return { terminalBadge, handleTerminalError, startPolling }; }