Spaces:
Sleeping
Sleeping
File size: 4,867 Bytes
95eb75a | 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 | /**
* dynamicReplanBlock.ts — Dynamic replan + iterGate expansion (estratto da agentLoop.ts — P3-6)
*
* Gestisce due logiche mid-loop strettamente correlate:
* 1. Budget expansion (checkIterBudgetExpansion): espande _effectiveMaxIter se il loop è bloccato
* 2. Dynamic replan: se ≥2 errori recenti → pianifica nuovo approccio via planTaskJit
*
* Invarianti (da ARCHITECTURE_INVARIANTS.md):
* - L3: decideRetry non può triggerare su ultima iterazione — questo blocco non chiama decideRetry
* - L4: stallo (stesso tool ≥3 volte) → il _shouldReplan lo interrompe cambiando approccio
*/
import { checkIterBudgetExpansion } from "./iterGateBlock";
import { planTaskJit } from "./jitPlanner";
import type { ApiMsg } from "./networkTools";
import type { AgentStep } from "../storage";
import type { ExecutionPlan } from "../executionPlanner"; // BUG-D1-FIX: proper type (was any)
export interface DynamicReplanParams {
iter: number;
effectiveMaxIter: number;
replanCount: number;
maxReplans: number;
recentToolCalls: Array<{ name: string; argsHash: string }>;
recentTexts: string[];
steps: AgentStep[];
loopMessages: ApiMsg[];
lastUserMsg: string;
hoistedEpPlan: ExecutionPlan | null; // BUG-D1-FIX: proper type
onStatus: (msg: string) => void;
reasoning: { setMaxLoops: (n: number) => void };
}
export interface DynamicReplanResult {
loopMessages: ApiMsg[];
replanCount: number;
effectiveMaxIter: number;
hoistedEpPlan: ExecutionPlan | null;
}
export async function runDynamicReplanBlock(p: DynamicReplanParams): Promise<DynamicReplanResult> {
let loopMessages = p.loopMessages;
let replanCount = p.replanCount;
let effectiveMaxIter = p.effectiveMaxIter;
let hoistedEpPlan: ExecutionPlan | null = p.hoistedEpPlan;
const _recentErrors = p.steps.slice(-4).filter(s => s.status === "error").length;
const _ibe = checkIterBudgetExpansion({
iter: p.iter,
effectiveMaxIter,
recentToolCalls: p.recentToolCalls,
recentTexts: p.recentTexts,
recentErrorCount: _recentErrors,
alreadyReplanned: replanCount > 0,
onStatus: p.onStatus,
});
// GAP-B v2: early trigger se ≥2 errori recenti, indipendente dal budget residuo
const _earlyReplanNeeded = _recentErrors >= 2 && p.iter > 1;
const _shouldReplan = (_ibe.triggerReplan || _earlyReplanNeeded) && replanCount < p.maxReplans;
if (_shouldReplan) {
replanCount++;
try {
const _failedTools = p.steps.slice(-4)
.filter(s => s.status === "error")
.map(s => s.tool)
.join(", ");
// Usa planTaskJit (JIT planner, 800ms timeout) — più rapido, funziona senza _hoistedEpPlan
const _replanGoal = p.lastUserMsg + ` [EVITA: ${_failedTools} — usa approccio alternativo]`;
// BUG-D3-FIX: outer timeout guard (planTaskJit internal 800ms + network slack)
const _newLightPlan = await Promise.race([
planTaskJit(_replanGoal, loopMessages.slice(-4) as Array<{role:string;content:string}>),
new Promise<null>(res => { setTimeout(() => res(null), 1500); }),
]);
if (!_newLightPlan) throw new Error("planTaskJit returned null or timed out");
const _newStepsText = _newLightPlan.subtasks.map((s: { description: string }) => s.description).join(" → ");
loopMessages = [
...loopMessages,
{
role: "user" as const,
content: `[REPLAN DINAMICO #${replanCount} — ${_recentErrors} errori su: ${_failedTools}]\nNuova strategia: ${_newStepsText}.\nCambia approccio — non ripetere i tool falliti.`,
},
] as ApiMsg[];
p.onStatus(`🔄 Replan #${replanCount} — cambio strategia…`);
// Se esiste anche il piano architettura, aggiorna anche quello
if (hoistedEpPlan) {
const _hep = hoistedEpPlan;
const _done = ((_hep.steps ?? []) as { completed?: boolean }[]).filter(s => !!s.completed);
const _newSteps = _newLightPlan.subtasks.map((s: { description: string; tool?: string }) => ({
description: s.description, filesToCreate: [] as string[], tool: s.tool ?? "", completed: false,
}));
_hep.steps = [..._done, ..._newSteps] as any;
}
} catch { /* replan non critico — il loop continua invariato */ }
}
if (_ibe.expanded) {
effectiveMaxIter = _ibe.newMaxIter;
p.reasoning.setMaxLoops(_ibe.newMaxIter); // S759: sync ReasoningCore — senza questo isExhausted() esce al vecchio limite
if (_ibe.strategyHint) {
loopMessages = [
...loopMessages,
{ role: "system" as const, content: `[STRATEGIA AGGIORNATA — iter ${p.iter + 1}] ${_ibe.strategyHint}` },
] as ApiMsg[];
}
}
return { loopMessages, replanCount, effectiveMaxIter, hoistedEpPlan };
}
|