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