AUDIT / src /lib /agentLoop /circuitBreaker.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
8.93 kB
// 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<string, string> = {
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<string, BreakerState>();
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 };
}