Spaces:
Sleeping
Sleeping
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" };
}
|