File size: 3,818 Bytes
cc11e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
 * 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;
}