Spaces:
Running
Running
File size: 17,027 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 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | /**
* contextAssembler.ts — S-EXTRACT-CTX: context loading + system prompt assembly
* Estratto da agentLoop.ts (righe 564–732) per ridurre la dimensione del file principale.
*
* Responsabilità:
* 1. Carica i 5 contesti in parallelo (S733: Promise.all)
* 2. Calcola budget/provider flags (S337/S524)
* 3. Assembla _staticPromptPart (REMOVE-2 pattern)
* 4. Espone _rebuildSys() come closure reattiva via oggetto state condiviso
* 5. Mappa loopMessages con il system prompt assemblato
*
* Pattern "state object": le variabili mutabili mid-loop (_semanticCtx, _worldStateCtx,
* _parallelSubCtx) sono proprietà di `state` — _rebuildSys() le legge da lì, così il
* chiamante può aggiornarle e _rebuildSys resta reattiva senza side-effect nascosti.
*/
import { assembleSystemPrompt, section, SELF_HEALING_PROMPT } from "../systemPromptBuilder";
import { detectRoutingHint } from "../providers/adapters";
import { semanticSearch } from "../agent/SemanticSearch";
import { EPISTEMIC_INSTRUCTIONS } from "../epistemicTagger";
import { getUserModelContext, buildMemoryContextAsync } from "../sessionMemory";
import { suggestPattern } from "../skillPersistence";
import { projectMemory, getDecisionsContext } from "../projectMemory";
import { getKnowledgeRules, enrichSystemPrompt } from "../contextBridge";
import { getRepoMapForPrompt } from "../context/repoMap";
import { getGroqToken, getGeminiToken, getOpenRouterToken, getHFToken } from "../providerChain";
import { renderStateForPrompt } from "@/system/project-state";
import { buildContext as buildStateGraphContext } from "@/lib/projectStateGraph";
import { getLastArchGraph, buildFeatureGraph, renderFeatureGraph } from "@/lib/projectArchGraph";
import { buildMetaHint } from "../sessionAnalyzer";
import { FORMAT_DIRECTIVES } from "../formatClassifier";
import type { ApiMsg } from "./networkTools";
import { computeSystemPromptBudget } from "./systemPromptBudget";
import { loadWorldStateCtx } from "./worldStateLoader";
import { loadContextHints } from "./contextHintsLoader";
import { loadRelevantFileCtx } from "./relevantFilesLoader";
import { loadRepairAuditHint } from "./repairAuditHintLoader";
import { PLANNER_DEPS_HINT as _PLANNER_DEPS_HINT } from "./plannerDepsHint";
import { GOAL_FORMAL_INSTRUCTIONS } from "./goalFormalInstructions";
import { loadRagCtx } from "./ragCtxLoader";
import { loadSessionCtx } from "./sessionMemCtxLoader";
import { REACT_SYSTEM_SUFFIX, buildRouteAddons } from "./reactSuffix";
import { buildArchCtxCached as _buildArchCtxCached } from "./archCtxCache";
import { runWorkflowPrep } from "./workflowPrepRunner";
import { getConstraintsForPrompt } from "../agentConstraints"; // P35: negative constraints
import { buildPersonaHint } from "./personaInjector"; // P44: Expertise Personas
import { getTopKForWorkflow } from "./workflowRegistry";
import type { ResolvedWorkflow } from "./workflowTypes";
import { getConnectors } from "../connectors"; // GAP-1: connettori API visibili all'agente
// ─── Tipi ────────────────────────────────────────────────────────────────────
export interface ContextAssemblerDeps {
lastUserMsg: string;
initialMessages: Array<{ role: string; content: string }>;
_earlyProjectMap: string;
_earlyFileCtx: string;
_workflow: ResolvedWorkflow;
_isCodeTask: boolean;
_isHighComplexityTask: boolean;
_fmt: import("../formatClassifier").OutputFormat;
orchestrationHint: string;
_extThinking: { hint: string; isExtended: boolean; statusLabel: string };
_knowledgeGap: { hint: string };
onStatus: (msg: string) => void;
getRelevantFiles: Parameters<typeof loadRelevantFileCtx>[1];
// lazy-import values (passati dopo Promise.all in agentLoop)
selfLearning: import("../selfLearning").SelfLearningEngine;
adaptiveSolverHint: string; // ADAPTIVE_SOLVER_SYSTEM_HINT
contextBuilder: { buildContextString: (q: string) => string };
slGetCtxCached: (msg: string) => string;
// agentMemory — lazy import, opzionale (GAP ARCH-3: Dexie cache)
agentMem: { list: () => unknown[] } | null;
// formalGoal — letto da _rebuildSys, può essere null inizialmente
getFormalGoal: () => { goal: string; doneWhen: string; outOfScope: string } | null;
// executeTool — per runWorkflowPrep executeShell
executeTool: (name: string, args: unknown, signal: AbortSignal) => Promise<string>;
}
/** Stato mutabile mid-loop — passato per riferimento così _rebuildSys rimane reattiva */
export interface ContextState {
_semanticCtx: string;
_semanticCtxTs: number;
_worldStateCtx: string;
_parallelSubCtx: string;
/** V7-3 GAP-3: Working Memory Snapshot — updated after runPlanTracker(), immune to applyContextWindow */
_workingMemCtx: string;
}
export interface ContextAssemblerResult {
loopMessages: ApiMsg[];
/** Ricostruisce il system prompt dinamico con i valori correnti di `state` */
_rebuildSys: () => string;
state: ContextState;
_sessStats: { autoFixed: number; propagated: number; verifyFails: number; totalIter: number };
_repairAuditHint: string;
/** C4-FIX: true se i connettori sono cambiati dall'ultima chiamata a _rebuildSys().
* Il chiamante (agentLoop/midLoopContextRefresh) può controllare questo flag per forzare
* un rebuild immediato del system prompt invece di aspettare il prossimo mid-loop refresh. */
connectorsChangedSignal: { changed: boolean };
}
// ─── Implementazione ─────────────────────────────────────────────────────────
export async function buildLoopContext(deps: ContextAssemblerDeps): Promise<ContextAssemblerResult> {
const {
lastUserMsg, initialMessages, _earlyProjectMap, _earlyFileCtx,
_workflow, _isCodeTask, _isHighComplexityTask, _fmt, orchestrationHint,
_extThinking, _knowledgeGap, onStatus, getRelevantFiles,
selfLearning, adaptiveSolverHint, contextBuilder, slGetCtxCached,
agentMem, getFormalGoal, executeTool,
} = deps;
// 1. Calcoli sincroni — nessun await, nessuna dipendenza cross
const brainCtx = (() => {
// S-FIX4-9: direct import instead of fragile globalThis
try { return enrichSystemPrompt(lastUserMsg); }
catch { return ""; }
})();
// GAP ARCH-3: legge la cache Dexie (già idratata in-memory al boot via initMemory())
const _agentMemCtx = (() => {
try {
if (!agentMem) return "";
type _ME = { category: string; key: string; value: string; updatedAt: number };
const _entries = (agentMem.list() as _ME[])
.sort((a, b) => b.updatedAt - a.updatedAt)
.slice(0, 15);
if (!_entries.length) return "";
const _lines = _entries
.map(e => `- [${e.category}] ${e.key}: ${String(e.value).slice(0, 80)}`)
.join('\n');
return `### Memoria agente (${_entries.length} voci — usa recall() per ricerca semantica):\n${_lines}`;
} catch { return ""; }
})();
const agentCtx = (() => { try { return contextBuilder.buildContextString(lastUserMsg); } catch { return ""; } })();
const _nowIso = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
const ROUTE_ADDONS = buildRouteAddons(_nowIso);
const detectedRoute = (() => { try { return detectRoutingHint(lastUserMsg); } catch { return "reasoning"; } })();
const routeAddon = ROUTE_ADDONS[detectedRoute] ?? "";
const _baseContent = initialMessages.find(m => m.role === "system")?.content ?? "";
const _isFirstMsg = initialMessages.filter(m => m.role !== "system").length <= 1; // S99
const _personaHint = buildPersonaHint(lastUserMsg, _isCodeTask); // P44: auto-selects Coder/Architect/Researcher/Debugger
// 2. Promise.all — 5 fetch asincroni in parallelo (S733)
const [
projectMap,
fileCtx,
_semanticCtxInit,
_ragCtx,
_sessionCtxR,
] = await Promise.all([
// S206/Gap1-FIX: riusa cache se già fetchato (zero doppio fetch)
_earlyProjectMap
? Promise.resolve(_earlyProjectMap)
: (async () => { try { return await getRepoMapForPrompt(); } catch { return ""; } })(),
// S212/S527: riusa cache se già fetchato (zero doppio fetch quando _needsPlan)
_earlyFileCtx
? Promise.resolve(_earlyFileCtx)
: loadRelevantFileCtx(lastUserMsg, getRelevantFiles, _workflow.topK ?? getTopKForWorkflow(_workflow.id)).catch(() => ""),
// S167-Fix8: semantic search context
semanticSearch.contextForAsync(lastUserMsg).catch((): string => ""),
// S290-RAG/S532: RAG context da file allegati
loadRagCtx(lastUserMsg),
// S99/S380/S534: session memory + world state
loadSessionCtx(lastUserMsg, _isFirstMsg, { buildMemoryContextAsync, loadWorldStateCtx }),
]);
// 3. Stato mutabile mid-loop (oggetto condiviso per riferimento)
const state: ContextState = {
_semanticCtx: _semanticCtxInit,
_semanticCtxTs: semanticSearch.cacheTs,
_worldStateCtx: _sessionCtxR.worldStateCtx,
_parallelSubCtx: "",
_workingMemCtx: "", // V7-3 GAP-3: populated after first runPlanTracker() in agentLoop
};
const _sessionMemCtx = _sessionCtxR.sessionMemCtx;
// P35: vincoli negativi — async read da VFS /.agent/constraints.json
const _negativeConstraintsCtx = await getConstraintsForPrompt().catch(() => "");
// 4. Context hints — Step4/7/8/S127/S526
const { strategyHint: _strategyHint, knowledgeRules: _knowledgeRules, userModelCtx: _userModelCtx, skillHint: _skillHint, recipeHint: _recipeHint } =
loadContextHints(lastUserMsg, { selfLearning, getKnowledgeRules, getUserModelContext, suggestPattern });
// 5. Repair Auditor (S415b/S529)
const _repairAuditHint = loadRepairAuditHint(_isCodeTask, lastUserMsg);
// 6. Workflow prep — sincrono/leggero (S753)
// Chiamato PRIMA di _rebuildSys → _workflowPrepCtx è già pronto alla prima invocazione
const _workflowPrepCtx = await runWorkflowPrep(_workflow, {
userMsg: lastUserMsg,
onStatus,
getRelevantFiles,
topK: _workflow.topK ?? getTopKForWorkflow(_workflow.id),
executeShell: async (cmd: string) => {
try {
const r = await Promise.race([
executeTool("execute_shell", { command: cmd }, {} as AbortSignal),
new Promise<string>(res => { setTimeout(() => res(""), 5_000); }),
]);
return typeof r === "string" ? r : String(r ?? "");
} catch { return ""; }
},
}).catch(() => "");
// 7. Budget per-provider (S337/S350/S524)
const _hasGemini = !!getGeminiToken();
const _hasOpenRouter = !!getOpenRouterToken();
const _hasGroqOnly = !_hasGemini && !_hasOpenRouter && !!getGroqToken();
const _isSmallCtx = !getGeminiToken() && !getOpenRouterToken() && !!getGroqToken();
void _isSmallCtx; // S337-F1: alias esplicito per regression test
const _hasHFOnly = !_hasGemini && !_hasOpenRouter && !getGroqToken() && !!getHFToken();
const { sysCharBudget: _sysCharBudget, baseMaxChars: _baseMaxChars, reactMaxChars: _reactMaxChars } =
computeSystemPromptBudget({ hasGemini: _hasGemini, hasOpenRouter: _hasOpenRouter, hasGroqOnly: _hasGroqOnly, hasHFOnly: _hasHFOnly }, _isHighComplexityTask, _baseContent.length);
// 8. Static prompt part — REMOVE-2: pre-computato una volta, ~22K chars
const _staticPromptPart = assembleSystemPrompt([
{ ...section("react_suffix", adaptiveSolverHint + routeAddon + REACT_SYSTEM_SUFFIX + orchestrationHint), maxChars: _reactMaxChars },
section("epistemic", EPISTEMIC_INSTRUCTIONS),
section("self_healing", SELF_HEALING_PROMPT),
section("goal_formal", GOAL_FORMAL_INSTRUCTIONS),
section("planner_deps", _PLANNER_DEPS_HINT),
], 32_000);
// 9. _rebuildSys — closure reattiva su `state` (S382/S384 Fix 3+6)
const _rebuildSys = (): string => {
const _formalGoal = getFormalGoal();
const _dynPart = assembleSystemPrompt([
{ ...section("base", _baseContent), maxChars: _baseMaxChars },
section("project_map", projectMap), // S767: repo map early for LLM context
section("brain", brainCtx + (_knowledgeRules ? "\n\n" + _knowledgeRules : "") + (state._parallelSubCtx ? "\n\n" + state._parallelSubCtx : "")), // Step7 + S731
section("procedural", _strategyHint), // Step4
section("user_model", _userModelCtx), // Step8
section("skill_hint", _skillHint), // S127 Gap 5
section("api_connectors", (() => { // GAP-1: connettori configurati visibili all'agente
try {
const _cs = getConnectors();
if (!_cs.length) return "";
return "### Connettori API configurati (usa connector_id= per riferirli nei tool call):\n" +
_cs.map(c =>
`- ${c.name} (${c.baseUrl}): connector_id="${c.id}"${c.description ? " — " + c.description.slice(0, 120) : ""}`
).join("\n");
} catch { return ""; }
})()),
section("recipe_hint", _recipeHint), // S-RECIPE
section("repair_audit", _repairAuditHint), // S415b
section("agent_ctx", agentCtx),
section("semantic", state._semanticCtx), // Fix 3: sostituzione completa mid-loop
section("working_memory", state._workingMemCtx), // V7-3 GAP-3: immune a applyContextWindow
section("rag_context", _ragCtx), // S290-RAG
section("session_mem", _sessionMemCtx),
section("agent_memory", _agentMemCtx), // GAP ARCH-3: Dexie memories
section("negative_constraints", _negativeConstraintsCtx), // P35: vincoli negativi appresi
section("world_state", state._worldStateCtx), // Fix 6: refresh mid-loop
section("reality_state", (() => { try { return renderStateForPrompt(); } catch { return ""; } })()), // G1-S202
section("state_graph", (() => { try { return buildStateGraphContext(); } catch { return ""; } })()), // S405
section("arch_graph", (() => { try { return _buildArchCtxCached(); } catch { return ""; } })()), // S407+S440
section("feature_graph", (() => { try { const _ag = getLastArchGraph(); if (!_ag) return ""; return renderFeatureGraph(buildFeatureGraph(_ag.nodes)); } catch { return ""; } })()), // S453
section("file_ctx", fileCtx),
section("project_memory", projectMemory.getContext()), // S194 Gap 5
section("sl_ctx", (() => { try { return slGetCtxCached(lastUserMsg); } catch { return ""; } })()), // S765-M
section("arch_decisions", (() => { try { return getDecisionsContext(_formalGoal?.goal ?? ""); } catch { return ""; } })()), // S453
section("ext_thinking", _extThinking.hint), // S689
section("knowledge_gap", _knowledgeGap.hint), // S691
section("workflow_ctx", _workflowPrepCtx), // S753
{ name: "meta_hint", priority: 6, maxChars: 200, required: false, content: (() => { try { return buildMetaHint(); } catch { return ""; } })() }, // S-GAP3
{ name: "format_directive", priority: 9, maxChars: 300, required: false, content: FORMAT_DIRECTIVES[_fmt] }, // S316
{ name: "persona", priority: 8, maxChars: 600, required: false, content: _personaHint }, // P44: Expertise Personas
], _sysCharBudget - _staticPromptPart.length);
return _staticPromptPart + "\n\n" + _dynPart;
};
// 10. Assembla loopMessages iniziali
const _assembledSys = _rebuildSys();
const loopMessages: ApiMsg[] = initialMessages.map(m =>
m.role === "system" ? { ...m, content: _assembledSys } as ApiMsg : m as ApiMsg
);
const _sessStats = { autoFixed: 0, propagated: 0, verifyFails: 0, totalIter: 0 };
// C4-FIX: segnale reattivo per i connettori — l'evento agent:connectors-changed
// (emesso da saveConnectors()) setta changed=true, il chiamante (agentLoop) forza un rebuild.
// _rebuildSys() chiama già getConnectors() lazily, quindi basta richiamarla al prossimo iter.
const connectorsChangedSignal = { changed: false };
if (typeof window !== "undefined") {
const _c4Handler = () => { connectorsChangedSignal.changed = true; };
window.addEventListener("agent:connectors-changed", _c4Handler);
// Cleanup automatico: listener rimosso se il segnale viene usato > 1 volta e poi pulito dal chiamante
// (il chiamante deve chiamare window.removeEventListener("agent:connectors-changed", _c4Handler)
// se vuole pulizia esplicita — non è richiesto perché l'agentLoop dura la durata della sessione)
}
return { loopMessages, _rebuildSys, state, _sessStats, _repairAuditHint, connectorsChangedSignal };
}
|