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