Spaces:
Sleeping
Sleeping
| /** | |
| * actionSimulator.ts — S-PRED: Predizione esito tool prima dell'esecuzione | |
| * | |
| * Consulta lo storico errori (errorLog.recent — già in-memory, zero I/O) | |
| * e produce un "outcome prediction" PRIMA della tool call. | |
| * | |
| * Zero LLM calls, Safari-safe, additive, non-blocking. | |
| * Integrato in case2Block.ts prima di executeToolGated. | |
| * | |
| * @module actionSimulator | |
| */ | |
| import type { ErrorEntry } from "../agentBrain"; | |
| export interface ActionPrediction { | |
| toolName: string; | |
| predictedOutcome: "likely_success" | "likely_fail" | "uncertain"; | |
| reason: string; | |
| similarPastFailures: number; | |
| } | |
| // ── Tool rischiosi — modificano stato filesystem/git/rete ───────────────────── | |
| const RISKY_TOOLS = new Set([ | |
| "apply_patch", "write_file", "iterate_file", "move_file", "delete_file", | |
| "git_push", "git_push_multiple", | |
| "run_code", "run_terminal_cmd", "run_python", | |
| ]); | |
| function _kwFromText(text: string): string[] { | |
| return text | |
| .toLowerCase() | |
| .replace(/[^a-z0-9_./-]+/g, " ") | |
| .split(" ") | |
| .filter(w => w.length > 3) | |
| .slice(0, 15); | |
| } | |
| function _kwSim(a: string[], b: string[]): number { | |
| if (a.length === 0 || b.length === 0) return 0; | |
| const sa = new Set(a); | |
| return b.filter(w => sa.has(w)).length / Math.max(a.length, b.length); | |
| } | |
| /** | |
| * Predice l'esito di un tool call usando errori recenti in-memory. | |
| * | |
| * @param toolName Nome del tool da eseguire | |
| * @param args Argomenti del tool | |
| * @param pastErrors Errori recenti (da errorLog.recent(50)) | |
| */ | |
| export function predictOutcome( | |
| toolName: string, | |
| args: Record<string, unknown>, | |
| pastErrors: ErrorEntry[], | |
| ): ActionPrediction { | |
| // Tool non rischiosi → sempre likely_success senza analisi | |
| if (!RISKY_TOOLS.has(toolName)) { | |
| return { toolName, predictedOutcome: "likely_success", reason: "tool non rischioso", similarPastFailures: 0 }; | |
| } | |
| const argSig = JSON.stringify(args).slice(0, 150); | |
| const argKw = _kwFromText(argSig); | |
| // Filtra errori non risolti per questo tool | |
| const toolErrors = pastErrors.filter(e => !e.resolved && e.context.includes(toolName)); | |
| // U6: path identity → similarity = 1.0 (exact path match = stesso file → forte segnale) | |
| // U6: threshold Jaccard 0.35 → 0.25 (più sensibile su argomenti parzialmente overlapping) | |
| const _argPath = String(args.path ?? args.file_path ?? ""); | |
| const similar = toolErrors.filter(e => { | |
| if (_argPath && e.context.includes(_argPath)) return true; // path identity | |
| return _kwSim(argKw, _kwFromText(e.message + " " + e.context)) > 0.25; | |
| }); | |
| if (similar.length >= 2) { | |
| return { | |
| toolName, | |
| predictedOutcome: "likely_fail", | |
| reason: `${similar.length} fallimenti simili non risolti su "${toolName}"`, | |
| similarPastFailures: similar.length, | |
| }; | |
| } | |
| if (toolErrors.length >= 4 && similar.length >= 1) { | |
| return { | |
| toolName, | |
| predictedOutcome: "likely_fail", | |
| reason: `${toolErrors.length} errori non risolti su "${toolName}" (1 con args simili)`, | |
| similarPastFailures: similar.length, | |
| }; | |
| } | |
| return { | |
| toolName, | |
| predictedOutcome: "uncertain", | |
| reason: toolErrors.length > 0 | |
| ? `${toolErrors.length} errori passati su "${toolName}", nessuno simile agli args correnti` | |
| : "nessun pattern noto", | |
| similarPastFailures: similar.length, | |
| }; | |
| } | |
| /** | |
| * Bypass immediato di REVISION_THRESHOLD: se la predizione era "likely_fail" | |
| * e l'esito reale è un errore → attiva belief revision al primo fallimento confermato. | |
| */ | |
| export function shouldReviseImmediately( | |
| predicted: ActionPrediction, | |
| actualFailed: boolean, | |
| ): boolean { | |
| return predicted.predictedOutcome === "likely_fail" && actualFailed; | |
| } | |