File size: 10,782 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
 * case3FinalBlock.ts — S506: extracted from agentLoop.ts
 *
 * Gestisce la fase finale di Case 3 dopo goalVerifyBlock:
 *   1. onSteps + emitFinalText (stream risposta)
 *   2. recordTaskOutcome success
 *   3. SmartSuggestions
 *   4. RouteSelector — record react success con qualityScore
 *   5. refactoringPlanner (fire-and-forget, > 5 write_file)
 *   6. Build validator hook (iter >= 2, _isCodeTask) → action "continue"
 *   7. Piano Tracker globale (subtasks progress)
 *   8. runProofOfWork
 *   9. persistWorldState
 *
 * Ritorna action "continue" se il build validator inietta hint (loop deve continuare),
 * oppure "done" se la risposta è stata emessa correttamente.
 *
 * Muta steps e loopMessages per reference.
 *
 * @module case3FinalBlock
 */

import type { AgentStep } from "../storage";
import type { ApiMsg } from "./networkTools";
import type { GoalContract } from "../_goalVerifier";
import { generateSuggestions, type Suggestion } from "../agent/SmartSuggestions";
import { addDecision } from "../projectMemory";
import { getLastArchGraph } from "@/lib/projectArchGraph";
import { runProofOfWork } from "./proofOfWorkBlock";
import { parseBuildResultFromSteps, formatBuildRepairHint } from "../buildRunner";
import { proofLedger } from "./proofLedger"; // GAP-TRUTH-3: DONE gate usa snapshot ledger invece di cast unsafe
import { persistWorldState } from "./worldStatePersister";
// GAP-TRUTH: gate gestito interamente da emitFinalText — nessun import diretto di finalOutputGate
import { recordOutcome as recordTaskOutcome } from "../agent/SuccessRateMonitor";
import type { TaskType } from "../taskClassifier";

// ─── Interfaces ────────────────────────────────────────────────────────────────

export interface Case3FinalCtx {
  finalText: string;
  rawText: string;
  iter: number;
  MAX_ITER: number;
  steps: AgentStep[];
  loopMessages: ApiMsg[];
  lastUserMsg: string;
  formalGoal: GoalContract | null;
  routeQualityScore: number | undefined;
  route: string;
  routeStart: number;
  taskPlan: unknown;
  isCodeTask: boolean;
  preCls: { type: string; isDirectAnswer?: boolean } | null;
  signal?: AbortSignal;
  // Callbacks
  onSteps: (steps: AgentStep[]) => void;
  onChunk: (chunk: string) => void;
  onStatus: (msg: string) => void;
  onSuggestions: ((s: Suggestion[]) => void) | undefined;
  // Closures
  emitFinalText: (text: string, signal?: AbortSignal) => void;
  writeSess: () => void;
  confSession?: { recordSignal: (s: string) => void };
  onRouteRecord: (success: boolean, qualityScore?: number) => void;
}

export type Case3FinalAction = "continue" | "done" | "build_ok" | "build_fail_serious";

export interface Case3FinalResult {
  action: Case3FinalAction;
}

// ─── Main export ───────────────────────────────────────────────────────────────

/** Processa la fase finale di Case 3: stream risposta + side effects post-emission. */
export function processCase3FinalBlock(ctx: Case3FinalCtx): Case3FinalResult {
  const {
    finalText, iter, MAX_ITER, steps, loopMessages, lastUserMsg, formalGoal,
    routeQualityScore, route, taskPlan, isCodeTask, preCls, signal,
    onSteps, onChunk, onStatus, onSuggestions, emitFinalText, writeSess, confSession, onRouteRecord,
  } = ctx;

  // ── 1. Emit final answer ──────────────────────────────────────────────────
  // S710-3: Reality proof — blocca DONE se step critici non hanno verified proof in proofLedger.
  // GAP-TRUTH-3: _CRIT_TOOLS locale rimossa — usa PROOF_VERIFIED_TOOLS da proofToolRegistry.
  // Subset intenzionale: solo azioni ad alto impatto irreversibile triggherano una retry pre-DONE.
  // Il check usa proofLedger.snapshot() — unica fonte di verità (non il campo verified su AgentStep
  // che non esiste nell'interfaccia e causava un cast unsafe con esito sempre-false).
  const _DONE_GATE_TOOLS = new Set(["git_push", "deploy_trigger", "write_file"]);
  const _snap = proofLedger.snapshot();
  const _unverCrit = steps.filter(s => {
    if (!_DONE_GATE_TOOLS.has(s.tool)) return false;
    const entries = _snap.filter(p => p.action === s.tool);
    return entries.length > 0 && !entries.some(e => e.verified);
  });
  if (_unverCrit.length > 0 && iter < MAX_ITER - 1) {
    onStatus("⚠️ Verifico azioni critiche prima di concludere...");
    loopMessages.push({
      role: "user",
      content: `⚠️ STOP: step critici non verificati: ${_unverCrit.map(s => s.tool).join(", ")}. Verifica proof di ciascuno prima di dichiarare DONE.`,
    } as ApiMsg);
    return { action: "continue" };
  }

  // ── GAP-TRUTH: emitFinalText è l'unico arbitro — gestisce gate + proof warnings ─────
  // Non chiamare runFinalOutputGate direttamente qui: emitFinalText (agentLoop closure)
  // è già configurata con steps + lastUserMsg → fail-closed + warning banner integrati.
  onSteps([...steps]); // steps prima del testo — chip hanno dati subito
  emitFinalText(finalText, signal);
  // S-GAP7: recordTaskOutcome("success") SPOSTATA → dopo build validator (evita double record su build-fail→retry)

  // ── 2. SmartSuggestions ───────────────────────────────────────────────────
  try { onSuggestions?.(generateSuggestions(lastUserMsg, steps)); } catch { /* non-blocking */ }

  // ── 3. RouteSelector — record react success ───────────────────────────────
  if (taskPlan && route === "react") {
    const success = routeQualityScore !== undefined
      ? routeQualityScore >= 0.65
      : (finalText.length > 80 && !finalText.startsWith("❌"));
    onRouteRecord(success, routeQualityScore);
  }

  // ── 4. refactoringPlanner (fire-and-forget, > 5 write_file) ────────��─────
  const _refPlanPaths = steps.filter(s => s.tool === "write_file").map(s => String((s.args as Record<string, unknown>)?.path ?? "")).filter(Boolean);
  if (_refPlanPaths.length > 5) {
    try { const _archGr = getLastArchGraph(); if (_archGr) import("../refactoringPlanner").then(({ planRefactoring }) => planRefactoring(_refPlanPaths, _archGr)).catch(() => {}); } catch { /* non-blocking */ }
  }

  // ── 5. Build validator hook (iter >= 2, _isCodeTask) ─────────────────────
  if (iter >= 2 && isCodeTask) {
    const _bpResult = parseBuildResultFromSteps(steps);
    if (_bpResult.checked) {
      if (_bpResult.errors.length === 0) {
        confSession?.recordSignal("build_ok");
        // S-GAP7: build validation passata → unico record per questo exit path
        try { recordTaskOutcome((preCls?.type ?? "unknown") as TaskType, "success"); } catch { /* non-blocking */ }
        return { action: "build_ok" };
      }
      const _bvHint = formatBuildRepairHint(_bpResult.errors, _bpResult.cmd ?? "");
      if (_bvHint) {
        const _isSeriousFail = _bpResult.errors.length > 3;
        steps.push({ tool: "_build_validator", args: { hint: _bvHint.slice(0, 200) }, result: _bvHint.slice(0, 200), status: "error" });
        loopMessages.push({ role: "user", content: _bvHint } as ApiMsg);
        onSteps([...steps]);
        confSession?.recordSignal(_isSeriousFail ? "build_fail_serious" : "build_fail");
        return { action: _isSeriousFail ? "build_fail_serious" : "continue" };
      }
    }
  }

  // ── 6. Piano Tracker globale ──────────────────────────────────────────────
  if (taskPlan && (taskPlan as { subtasks?: unknown[] }).subtasks && (taskPlan as { subtasks: unknown[] }).subtasks.length > 1) {
    const _tp = taskPlan as { subtasks: Array<{ outputs?: string[] }> };
    const _allPlanFiles = _tp.subtasks.flatMap(t => t.outputs ?? []);
    const _totalPlan = _allPlanFiles.length || _tp.subtasks.length;
    const _written = new Set(steps.filter(s => s.tool === "write_file").map(s => String((s.args as Record<string, unknown>)?.path ?? "")).filter(Boolean));
    const _writtenCount = _written.size;
    const _allMissing = _allPlanFiles.filter((o: string) => !_written.has(o));
    const _percent = _totalPlan > 0 ? Math.round((_writtenCount / _totalPlan) * 100) : 100;
    // Rimuovi il tracker precedente prima di inserirne uno nuovo
    const _prevTrackerIdx = steps.findLastIndex((s: AgentStep) => s.tool === "__plan_tracker__");
    if (_prevTrackerIdx >= 0) steps.splice(_prevTrackerIdx, 1);
    steps.push({ tool: "__plan_tracker__", args: { percent: _percent, missing: _allMissing.length, total: _totalPlan }, result: `${_percent}% completato — mancano ancora: ${_allMissing.slice(0, 3).join(", ")}`, status: "done" });
    onSteps([...steps]);
  }

  // ── 7. Proof-of-work ─────────────────────────────────────────────────────
  runProofOfWork({ _isCodeTask: isCodeTask, finalText, signal, onChunk, onStatus });

  // ── 8. World state persister ─────────────────────────────────────────────
  if (!(preCls?.isDirectAnswer ?? false)) {
    persistWorldState({ steps, lastUserMsg, onSteps });
  }

  // ── 9. addDecision su PASS path (S454) ───────────────────────────────────
  if (formalGoal) {
    try {
      const _wfPaths = steps.filter(s => s.tool === "write_file").map(s => String((s.args as Record<string, unknown>)?.path ?? "")).filter(Boolean);
      if (isCodeTask) {
        addDecision({ type: "ARCHITECTURAL_DECISION", feature: `${formalGoal.goal}`, decision: "code verified", reason: "write_file + verifyGoal passed", affects: _wfPaths.length ? _wfPaths : ["response_quality"] });
      } else {
        addDecision({ type: "BUG_RESOLVED", feature: `${formalGoal.goal}`, decision: "goal satisfied", reason: "verifyGoal check passed", affects: _wfPaths.length ? _wfPaths : ["response_quality"] });
      }
    } catch { /* non-blocking */ }
  }

  // S-GAP7: percorso normale → 1 solo record su completion effettiva
  try { recordTaskOutcome((preCls?.type ?? "unknown") as TaskType, "success"); } catch { /* non-blocking */ }
  writeSess();
  return { action: "done" };
}