@stkxp/cli 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stkxp/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Export a Stack Expert Team as an encrypted, standalone-deployable bundle, and redeploy it on any Elasticsearch cluster.",
@@ -64,6 +64,7 @@
64
64
  "html2canvas": "^1.4.1",
65
65
  "i18next": "^25.8.13",
66
66
  "ioredis": "^5.9.2",
67
+ "isolated-vm": "^5.0.4",
67
68
  "js-yaml": "^4.1.1",
68
69
  "jsonata": "^2.0.6",
69
70
  "jsonwebtoken": "^9.0.3",
@@ -1,4 +1,4 @@
1
- import{MessagesAnnotation as _e,StateGraph as Ae,Annotation as J}from"@langchain/langgraph";import{AIMessage as Pe,SystemMessage as Ce}from"@langchain/core/messages";import{createLLMInstance as ne}from"../llm/providers";import{config as X}from"../config";import{loadMcpTools as Me}from"@langchain/mcp-adapters";import{createAgent as Oe}from"langchain";import{DynamicStructuredTool as G}from"@langchain/core/tools";import{z as g}from"zod";import{TavilySearch as ve}from"@langchain/tavily";import{PerformanceTracker as Se}from"../utils/performance-tracker";import{buildOptimizedSystemPrompt as $e}from"../utils/prompt-optimizer";import{promptOptimizationConfig as se}from"../app-config/prompt-optimization";import{runTopicABTest as we,generateTopicHybrid as Ee}from"./topic-variants";import{createCompareChatTool as ke}from"../tools/compare-chat";import{TOOL_RESULT_MAX_TOKENS as re}from"./utils/content-utils";import ae from"jsonwebtoken";import{Client as Le}from"@elastic/elasticsearch";import{createMcpClient as Ne}from"../mcp/client";const Re=process.env.ELASTICSEARCH_HOST||"https://51.159.174.230:9205",Ie=process.env.ELASTICSEARCH_USER||"elastic",xe=process.env.ELASTICSEARCH_PASSWORD||"",ie=".stkxp_platforms",L=new Le({node:Re,auth:{username:Ie,password:xe},tls:{rejectUnauthorized:!1},requestTimeout:3e4,maxRetries:3,maxSockets:256,maxFreeSockets:256});L.connectionPool&&typeof L.connectionPool.setMaxListeners=="function"&&L.connectionPool.setMaxListeners(50);const F=new Map,De=300*1e3;setInterval(()=>{const o=Date.now();for(const[t,r]of F.entries())r.refCount===0&&o-r.lastUsed>De&&(console.log(`[MCP Pool] Closing inactive client: ${t}`),r.client.close().catch(c=>console.error("[MCP Pool] Error closing client:",c)),F.delete(t))},120*1e3);async function Fe(o,t){const r=`${o}`,c=F.get(r);if(c)return console.log(`[MCP Pool] Reusing existing client for ${o}`),c.lastUsed=Date.now(),c.refCount++,c.client;console.log(`[MCP Pool] Creating new client for ${o}`);const i=await Ne(o,t);return i.transport&&typeof i.transport.setMaxListeners=="function"&&i.transport.setMaxListeners(50),i.client&&typeof i.client.setMaxListeners=="function"&&i.client.setMaxListeners(50),F.set(r,{client:i,lastUsed:Date.now(),refCount:1}),i}function Ue(o){const t=`${o}`,r=F.get(t);r&&(r.refCount=Math.max(0,r.refCount-1),r.lastUsed=Date.now())}async function je(o){try{const t=o.toLowerCase().replace(/\s+/g,"_").split("_")[0]||"clusters";console.log(`[MCP Discovery] Searching for servers with packageName matching "${t}" and enabled=true`);const c=(await L.search({index:ie,body:{query:{bool:{must:[{term:{enabled:!0}},{prefix:{"packageName.keyword":t}}]}},size:100,sort:[{createdAt:{order:"desc"}}]}})).hits.hits.map(i=>({id:i._id,name:i._source.name,url:i._source.url,authKey:i._source.authKey,type:i._source.type,packageName:i._source.packageName,enabled:i._source.enabled,description:i._source.description}));if(console.log(`[MCP Discovery] Found ${c.length} enabled servers for system "${t}"`),c.length>0)return console.log("[MCP Discovery] Servers:",c.map(i=>`${i.name} (${i.packageName})`).join(", ")),c;if(t!=="clusters"&&t!=="unknown"){console.log(`[MCP Discovery] No servers found for "${t}", falling back to "clusters" package`);const P=(await L.search({index:ie,body:{query:{bool:{must:[{term:{enabled:!0}},{prefix:{"packageName.keyword":"clusters"}}]}},size:100,sort:[{createdAt:{order:"desc"}}]}})).hits.hits.map(y=>({id:y._id,name:y._source.name,url:y._source.url,authKey:y._source.authKey,type:y._source.type,packageName:y._source.packageName,enabled:y._source.enabled,description:y._source.description}));if(P.length>0)return console.log(`[MCP Discovery] \u2705 Fallback successful: Found ${P.length} "clusters" server(s)`),P}return c}catch(t){return console.error("[MCP Discovery] Error fetching MCP servers from Elasticsearch:",t.message),console.error("[MCP Discovery] Falling back to no servers"),[]}}function E(o){if(!o||typeof o!="object")return o;const t={...o};return delete t.exclusiveMinimum,delete t.exclusiveMaximum,t.properties&&typeof t.properties=="object"&&(t.properties=Object.keys(t.properties).reduce((r,c)=>(r[c]=E(t.properties[c]),r),{})),t.items&&(t.items=E(t.items)),t.additionalProperties&&typeof t.additionalProperties=="object"&&(t.additionalProperties=E(t.additionalProperties)),Array.isArray(t.anyOf)&&(t.anyOf=t.anyOf.map(r=>E(r))),Array.isArray(t.oneOf)&&(t.oneOf=t.oneOf.map(r=>E(r))),Array.isArray(t.allOf)&&(t.allOf=t.allOf.map(r=>E(r))),t}function Je(o){const t=o?.content;if(typeof t=="string")return t;if(Array.isArray(t)){const r=t.find(c=>typeof c?.text=="string")?.text;if(r)return r}return t?.toString?.()??""}function Ge(o){try{const r=o?.messages?.[0]?.content?.[0]?.text;if(typeof r=="string")return r}catch{}try{const r=o?.messages?.[0]?.content?.text;if(typeof r=="string")return r}catch{}const t=o?.messages?.[0]?.content;return typeof t=="string"?t:typeof o?.content=="string"?o.content:""}function le(o){if(!o)return{data:[]};if(Array.isArray(o?.data))return{data:o.data,nextCursor:o.nextCursor,meta:o.meta};if(Array.isArray(o?.result?.data))return{data:o.result.data,nextCursor:o.nextCursor??o.result?.nextCursor,meta:o.result?.meta};const t=o.nextCursor||o?.aggregations?.by_namespace?.buckets?.[0]?.g?.after_key;return{data:[],nextCursor:t}}function qe(o){return Math.ceil(o.length/4)}function Be(o,t={}){const{maxTokens:r=re,sampleItems:c=20}=t,i=JSON.stringify(o),P=qe(i);if(P<=r)return o;const{data:y,meta:O}=le(o);let A;return Array.isArray(y)&&y.length>0&&(A=y.slice(0,c)),{error:"RESULT_TOO_LARGE",approx_tokens:P,max_allowed_tokens:r,hint:"La r\xE9ponse de ce tool MCP est trop volumineuse pour \xEAtre envoy\xE9e telle quelle au LLM. R\xE9duis ta requ\xEAte (p\xE9riode, filtres par cluster/app/host, etc.) et r\xE9essaie.",meta:{...O,has_sample:!!A},sample:A}}function ze(o,t){if(!o||typeof o!="object"||ce(o))return o;let r;try{r=JSON.parse(JSON.stringify(o))}catch(i){return console.warn(`[normalizeToolSchemaForProvider] Failed to clone schema (${i.message}), returning original`),o}delete r.$schema;const c=i=>{if(!(!i||typeof i!="object")){if((t==="openai"||t==="anthropic")&&i.additionalProperties===!1&&(i.additionalProperties=!0),i.properties&&typeof i.properties=="object")for(const P of Object.keys(i.properties))c(i.properties[P]);i.items&&c(i.items),Array.isArray(i.anyOf)&&i.anyOf.forEach(c),Array.isArray(i.oneOf)&&i.oneOf.forEach(c),Array.isArray(i.allOf)&&i.allOf.forEach(c)}};return c(r),r}function ce(o){return!!o&&(typeof o.safeParse=="function"||!!o._def)}function Ke(o){return!!o&&typeof o=="object"&&(o.type||o.properties||o.anyOf||o.oneOf||o.allOf)}function N(o){if(!o||typeof o!="object")return g.any();if(Array.isArray(o.anyOf)&&o.anyOf.length)return g.union(o.anyOf.map(N));if(Array.isArray(o.oneOf)&&o.oneOf.length)return g.union(o.oneOf.map(N));if(Array.isArray(o.allOf)&&o.allOf.length)return o.allOf.map(N).reduce((t,r)=>g.intersection(t,r));if(Array.isArray(o.enum)&&o.enum.length)return o.enum.every(t=>typeof t=="string")?g.enum(o.enum):g.union(o.enum.map(t=>g.literal(t)));switch(o.type){case"string":return g.string();case"number":return g.number();case"integer":return g.number().int();case"boolean":return g.boolean();case"array":{const t=o.items?N(o.items):g.any();return g.array(t)}case"object":default:{const t=o.properties&&typeof o.properties=="object"?o.properties:{},r=new Set(Array.isArray(o.required)?o.required:[]),c={};for(const[P,y]of Object.entries(t)){const O=N(y);c[P]=r.has(P)?O:O.optional()}let i=g.object(c);return o.additionalProperties===!1?i=i.strict():i=i.passthrough(),i}}}function Xe(o){return o.map(t=>{const r=t.schema;if(!r||ce(r))return t;if(Ke(r)){const c=N(r);return new G({name:t.name,description:t.description,schema:c,responseFormat:t.responseFormat,func:async(i,P)=>t.func(i,P)})}return new G({name:t.name,description:t.description,schema:g.record(g.any()),responseFormat:t.responseFormat,func:async(c,i)=>t.func(c,i)})})}async function We(o,t,r={}){const c=r.retries??3,i=r.baseDelayMs??800,P=new Set(r.retryOn??[429,502,503,504]);for(let y=0;y<=c;y++)try{return await o.invoke(t)}catch(O){const A=O?.status??O?.code??O?.response?.status,$=P.has(Number(A))||O?.name==="TimeoutError",W=y===c;if(console.warn(`[MCP] invoke failed (try ${y+1}/${c+1}) code=${A} transient=${$}:`,O?.message||O),!$||W)throw O;const R=i*Math.pow(2,y);await new Promise(v=>setTimeout(v,R))}}const _o=g.object({topic:g.enum(["clusters_clusters_overview","clusters_clusters_health-check","clusters_cluster_finops","clusters_nodes_inventory","clusters_nodes_operations","clusters_nodes_storage","clusters_nodes_resources","clusters_nodes_shards","clusters_nodes_thread_pool","clusters_nodes_tasks","clusters_nodes_hot_threads","clusters_jobs_enrich","clusters_jobs_snapshots","clusters_indices_inventory","clusters_indices_operations","clusters_indices_storage","clusters_indices_segments","clusters_indices_documents","elasticsearch_doc","mysql_mysql_overview","platform.core.execute_esql"])});async function Ao(o,t){console.log(`[buildAppFromDefinition] Building graph from definition: ${o.name} (v${o.version})`),console.log("[buildAppFromDefinition] Graph config:",{streaming:o.config.streaming,recursionLimit:o.config.recursionLimit,maxConcurrency:o.config.maxConcurrency});const r=t.performanceTracker||new Se(t.threadId);let c=[];const i=[];let P=!1;const y=t.queryParams?.topic||"unknown";if(t.mcp&&(Array.isArray(t.mcp)?t.mcp.length>0:!0))c=Array.isArray(t.mcp)?t.mcp:[t.mcp],console.log(`[MCP] Using ${c.length} pre-configured MCP client(s)`);else{P=!0,console.log(`[MCP] Auto-discovering servers for topic: "${y}"`);const e=await je(y);if(e.length===0)console.warn(`[MCP] \u26A0\uFE0F No enabled MCP servers found for topic "${y}"`),console.warn("[MCP] \u26A0\uFE0F Agent will run with NO tools available");else{console.log(`[MCP] Creating ${e.length} MCP client(s) for discovered servers`);for(const n of e)try{console.log(`[MCP] Connecting to: ${n.name} at ${n.url}`);const s=await Fe(n.url,{headers:{Authorization:`ApiKey ${n.authKey}`},transportType:"http",timeout:3e4});c.push(s),i.push(n.url),console.log(`[MCP] \u2705 Connected to ${n.name} (${n.packageName})`)}catch(s){console.error(`[MCP] \u274C Failed to connect to ${n.name}:`,s.message)}console.log(`[MCP] Successfully connected to ${c.length}/${e.length} servers`)}}let A=[],$;if(c.length===0)console.warn("[MCP] \u26A0\uFE0F No MCP servers available. Agent will run without tools.");else{$=c[0],console.log(`[MCP] Loading tools from ${c.length} MCP server(s)`);const n=(await Promise.all(c.map(async(a,m)=>{const d=await Me(`stkxp-mcp-server-${m}`,a.client);return console.log(`[MCP] Server ${m+1}: Loaded ${d.length} tools`),d}))).flat();console.log(`[MCP] Total loaded: ${n.length} tools`);const s=new Map;for(const a of n)s.has(a.name)||s.set(a.name,a);A=Array.from(s.values())}const R=y.toLowerCase().replace(/\s+/g,"_").split("_")[0]||"clusters";let v=A;if(P)console.log(`[MCP] \u2705 Using ${A.length} tools from auto-discovered servers`),v=A;else if(process.env.MCP_DISABLE_TOPIC_FILTERING==="true")console.log(`[MCP] \u26A0\uFE0F Topic filtering disabled, using all ${A.length} tools`),v=A;else{let n=A.filter(a=>a.name.startsWith(R+"_"));n.length===0&&(n=A.filter(a=>a.name.includes(R)));const s=A.filter(a=>a.name.startsWith("elasticsearch_"));if(s.length>0){const a=new Set(n.map(d=>d.name)),m=s.filter(d=>!a.has(d.name));m.length>0&&(console.log(`[MCP] \u{1F527} Force-adding ${m.length} elasticsearch_ tools`),n=[...n,...m])}n.length===0?(console.log(`[MCP] \u26A0\uFE0F No tools found for system "${R}", using all ${A.length} tools`),v=A):(console.log(`[MCP] \u2705 Filtered ${n.length}/${A.length} tools`),v=n)}console.log("[MCP] Server tool sample:",JSON.stringify(v[0]),v[0]);const Y=v.filter(e=>{try{if(!e.schema)return console.log(`[MCP] \u26A0\uFE0F Tool "${e.name}" has no schema, keeping it`),!0;const n=e.schema;return n._def&&typeof n._def.shape=="function"?!0:n._def&&n._def.typeName==="ZodObject"?(console.error(`[MCP] \u274C Tool "${e.name}" has invalid ZodObject schema (shape is not a function), filtering out`),!1):(console.log(`[MCP] \u26A0\uFE0F Tool "${e.name}" has non-object schema type: ${n._def?.typeName}, keeping it`),!0)}catch(n){return console.error(`[MCP] \u274C Tool "${e.name}" validation error: ${n.message}, filtering out`),!1}});console.log(`[MCP] Validated ${Y.length}/${v.length} tools`);const q=Y,de=new G({name:"mcp_paginated_query",description:"Appelle un tool MCP cibl\xE9 en boucle avec prise en charge de nextCursor",schema:g.object({toolName:g.string().describe("Nom du tool MCP \xE0 appeler"),args:g.record(g.any()).describe("Arguments \xE0 passer au tool MCP"),maxPages:g.number().int().min(1).default(10).describe("Nombre max de pages"),maxItems:g.number().int().min(1).default(5e3).describe("Nombre max d'items cumul\xE9s")}),func:async({toolName:e,args:n,maxPages:s,maxItems:a})=>{const m=v.find(f=>f.name===e)||A.find(f=>f.name===e);if(!m)throw new Error(`Tool MCP introuvable: ${e}`);let d=n?.request?.body?.cursor,l=0;const C=[];for(;l<s&&C.length<a;){const f=JSON.parse(JSON.stringify(n||{}));d&&(f.request=f.request||{},f.request.body=f.request.body||{},f.request.body.cursor=d);const u=await We(m,f,{retries:4,baseDelayMs:750,retryOn:[429,502,503,504]}),{data:b,nextCursor:_}=le(u);if(Array.isArray(b)&&b.length&&C.push(...b),l+=1,!_)break;d=_}const p=l>=s||C.length>=a||!d;return{items:C,pages:l,done:p,nextCursor:d}}}),{createLLMServices:me}=await import("../services/llm-services.js"),pe=me(L);let V="user",k=t.username;if(t.userToken)try{const e=ae.decode(t.userToken);e&&typeof e=="object"&&("roles"in e&&(V=e.roles?.[0]||"user"),!k&&"username"in e&&(k=e.username||void 0))}catch(e){console.warn("[buildAppFromDefinition] Failed to decode user token for routing:",e)}const H={userRole:V,topic:y,environment:process.env.NODE_ENV||"prod",requiredCapabilities:["streaming","toolCalling"],owner:k};console.log("[buildAppFromDefinition] Routing context:",H);let h,B;const Z=o.llmConfig?.useRoutingEngine!==!1;Z||console.log("[buildAppFromDefinition] Routing engine disabled in graph config, using llmConfig directly");try{if(!Z)throw new Error("Routing engine disabled in graph configuration");const e=await pe.getRoutingService().selectDeployment(H);if(console.log(`[buildAppFromDefinition] \u2705 Selected model: ${e.model.modelId}`),console.log(`[buildAppFromDefinition] Reason: ${e.reason}`),k&&e.provider.owner&&e.provider.owner!==k)throw new Error(`Routing selected provider owned by "${e.provider.owner}" for caller "${k}" \u2014 refusing cross-tenant API key.`);h={provider:e.provider.type,model:e.model.modelId,temperature:e.model.parameters?.temperature??X.llm.temperature,streaming:X.llm.streaming,apiKey:e.provider.apiKey,baseURL:e.provider.endpoint},console.log(`[buildAppFromDefinition] LLM config from routing engine: provider=${h.provider}, model=${h.model}`),B=ne(h)}catch(e){if(console.error(`[buildAppFromDefinition] \u26A0\uFE0F Routing engine failed: ${e.message}`),console.log("[buildAppFromDefinition] Falling back to legacy provider selection"),h={...X.llm},console.log("LLM llmConfig 1",h),t.userProviders&&Array.isArray(t.userProviders)){const n=t.userProviders.filter(s=>s.enabled===!0).filter(s=>s.selectedModel===t.queryParams?.llmModel);if(n.length>0){const s=n[0],a=s.selectedModel||s.models?.[0]||h.model;h={provider:s.id,model:a,temperature:h.temperature,streaming:h.streaming,...s.apiKey&&{apiKey:s.apiKey},...s.endpoint&&{baseURL:s.endpoint}}}}console.log("LLM llmConfig 2",t.userProviders,h),B=ne(h)}const ue=new ve({maxResults:3}),ge=q.map(e=>({...e,async invoke(n,s){s?.writer&&typeof s.writer=="function"&&s.writer(`\u23F3 Calling ${e.name}...`);const a=Date.now();try{const m=await e.invoke(n),d=Date.now()-a;return s?.writer&&typeof s.writer=="function"&&s.writer(`\u2705 ${e.name} completed in ${d}ms`),Be(m,{maxTokens:re,sampleItems:20})}catch(m){const d=Date.now()-a;s?.writer&&typeof s.writer=="function"&&s.writer(`\u274C ${e.name} failed after ${d}ms: ${m.message}`);const l=m.message||String(m),C=m.status||m.response?.status||m.code;return console.error(`[Tool Error] ${e.name} failed:`,{error:l,statusCode:C,duration:d,args:JSON.stringify(n).substring(0,200)}),JSON.stringify({error:"TOOL_EXECUTION_FAILED",tool_name:e.name,error_message:l,status_code:C,duration_ms:d,suggestion:"This tool failed. Please try an alternative approach or inform the user about the issue.",args_attempted:n})}}}));let Q="anonymous";if(t.userToken)try{const e=ae.decode(t.userToken);e&&typeof e=="object"&&"username"in e&&(Q=e.username||"anonymous")}catch(e){console.warn("[buildApp] Failed to decode user token:",e)}const fe=new G({name:"compare_with_previous_chat",description:"Compare current conversation with a previous saved chat",schema:g.object({chatId:g.string().describe("ID of the previous chat"),comparisonAspects:g.array(g.enum(["metrics","errors","performance","configuration","overall","recommendations"])).optional(),includeTimeDelta:g.boolean().optional()}),func:async(e,n)=>{const s=n?.state?.messages||[];return await ke(Q,s).func(e,n)}});let S=[...ge,de,ue,fe];h.provider==="google"&&(console.log("[buildApp] Cleaning tool schemas for Google Gemini"),S=S.map(e=>e.schema?{...e,schema:E(e.schema)}:e)),h.provider==="anthropic"&&(console.log("[buildApp] Adapting tools for Anthropic (JSON Schema -> Zod)"),S=Xe(S)),h.provider==="openai"&&(console.log(`[buildApp] Normalizing tool schemas for ${h.provider}`),S=S.map(e=>e.schema?{...e,schema:ze(e.schema,h.provider)}:e));const ye=Oe({model:B,tools:S}),Te=J.Root({..._e.spec,threadId:J({reducer:(e,n)=>n||e,default:()=>t.threadId}),userContext:J({reducer:(e,n)=>n!==void 0?n:e,default:()=>t.userContext}),systemPrompt:J({reducer:(e,n)=>n!==void 0?n:e,default:()=>{}})}),I=new Ae(Te),he={checkReset:async e=>{r.start("node_checkReset");const n=t.threadId,s=e.threadId;if(s&&s!==n){const a=e.messages[e.messages.length-1];return r.end("node_checkReset",{reset:!0}),{messages:[a],threadId:n}}return r.end("node_checkReset",{reset:!1}),{threadId:n}},system:async e=>{r.start("node_system");const n=e.messages[e.messages.length-1],s=Je(n).trim();r.start("topic_generation");let a;const m=process.env.AB_TESTING_ENABLED==="true";if(s.startsWith("topic="))a=s.slice(6).trim(),console.log(`[TOPIC] Manual override: ${a}`);else if(m){console.log("[TOPIC] A/B Testing enabled");const{result:T,variantUsed:be,comparison:K}=await we(s,h);a=T,console.log(`[TOPIC] Selected variant: ${be} - Topic: ${a}`),K?.winner&&console.log(`[TOPIC] Winner: ${K.winner.variantId} (${K.winner.improvement.toFixed(1)}% faster)`)}else a=await Ee(s,h);r.end("topic_generation",{topic:a,abEnabled:m}),!a||a.trim()===""?(console.error("[PROMPT-ALT] \u274C CRITICAL: Topic generation returned empty string"),console.error(`[PROMPT-ALT] User input was: "${s}"`),console.error("[PROMPT-ALT] Will fallback to default prompt")):console.log(`[PROMPT-ALT] \u2705 Topic generated successfully: "${a}"`);let d="",l="default";if(r.start("prompt_fetch"),!a||a.trim()==="")console.warn("[PROMPT-ALT] \u26A0\uFE0F Skipping MCP prompt fetch - topic is empty"),l="empty_topic",r.end("prompt_fetch",{source:l,topic:"(empty)"});else if($)try{console.log(`[PROMPT-ALT] \u{1F504} Fetching prompt from MCP for topic: "${a}"`);const T=await $.client.getPrompt({name:`${a}`,params:{}});console.log("[PROMPT-ALT] Raw prompt response:",JSON.stringify(T,null,2).substring(0,1e3)),d=Ge(T),!d||d.trim()===""?(console.error(`[PROMPT-ALT] \u274C MCP returned empty prompt text for topic: "${a}"`),console.error("[PROMPT-ALT] MCP response structure:",JSON.stringify(T,null,2).substring(0,500)),l="failed"):(console.log(`[PROMPT-ALT] \u2705 Successfully loaded prompt for topic: "${a}" (${d.length} chars)`),console.log("[PROMPT-ALT] Prompt preview:",d.substring(0,200)),l="mcp"),r.end("prompt_fetch",{source:l,topic:a,promptLength:d.length})}catch(T){console.error(`[PROMPT-ALT] \u274C MCP getPrompt() failed for topic "${a}"`),console.error(`[PROMPT-ALT] Error type: ${T.name||"Unknown"}`),console.error(`[PROMPT-ALT] Error message: ${T.message}`),console.error("[PROMPT-ALT] Stack trace:",T.stack?.substring(0,500)),T.message?.includes("timeout")||T.message?.includes("ETIMEDOUT")?console.error("[PROMPT-ALT] \u{1F550} Timeout error detected - MCP server did not respond in time"):(T.message?.includes("ECONNREFUSED")||T.message?.includes("ENOTFOUND"))&&console.error("[PROMPT-ALT] \u{1F310} Network error - Cannot reach MCP server"),l="failed",r.end("prompt_fetch",{source:l,topic:a,error:T.message})}else console.warn(`[PROMPT-ALT] \u26A0\uFE0F No MCP client available to fetch prompt for topic "${a}"`),l="no_client",r.end("prompt_fetch",{source:l,topic:a});!d||d.trim()===""?(console.warn("[PROMPT-ALT] \u26A0\uFE0F FALLBACK ACTIVATED: Using default prompt"),console.warn(`[PROMPT-ALT] Reason: ${l}`),console.warn(`[PROMPT-ALT] Topic was: "${a||"(empty)"}"`),console.warn(`[PROMPT-ALT] FirstClient available: ${!!$}`)):console.log(`[PROMPT-ALT] \u2705 FINAL: Using MCP prompt (${d.length} chars) from source: ${l}`);const C=t.userToken||"stkxp_session_default",p=t.queryParams||{},f=p.cluster||"default",b=$e({agentPrompt:d||`# Default Dashboard Response
1
+ import{MessagesAnnotation as _e,StateGraph as Ae,Annotation as J}from"@langchain/langgraph";import{AIMessage as Pe,SystemMessage as Ce}from"@langchain/core/messages";import{createLLMInstance as se}from"../llm/providers";import{config as X}from"../config";import{loadMcpTools as Me}from"@langchain/mcp-adapters";import{createAgent as Oe}from"langchain";import{DynamicStructuredTool as G}from"@langchain/core/tools";import{z as g}from"zod";import{TavilySearch as ve}from"@langchain/tavily";import{PerformanceTracker as Se}from"../utils/performance-tracker";import{buildOptimizedSystemPrompt as $e}from"../utils/prompt-optimizer";import{promptOptimizationConfig as re}from"../app-config/prompt-optimization";import{runTopicABTest as we,generateTopicHybrid as Ee}from"./topic-variants";import{createCompareChatTool as ke}from"../tools/compare-chat";import{TOOL_RESULT_MAX_TOKENS as ae}from"./utils/content-utils";import ie from"jsonwebtoken";import{Client as Le}from"@elastic/elasticsearch";import{createMcpClient as Ne}from"../mcp/client";const Re=process.env.ELASTICSEARCH_HOST||"https://51.159.174.230:9205",Ie=process.env.ELASTICSEARCH_USER||"elastic",xe=process.env.ELASTICSEARCH_PASSWORD||"",le=".stkxp_platforms",L=new Le({node:Re,auth:{username:Ie,password:xe},tls:{rejectUnauthorized:!1},requestTimeout:3e4,maxRetries:3,maxSockets:256,maxFreeSockets:256});L.connectionPool&&typeof L.connectionPool.setMaxListeners=="function"&&L.connectionPool.setMaxListeners(50);const F=new Map,De=300*1e3;setInterval(()=>{const o=Date.now();for(const[t,r]of F.entries())r.refCount===0&&o-r.lastUsed>De&&(console.log(`[MCP Pool] Closing inactive client: ${t}`),r.client.close().catch(c=>console.error("[MCP Pool] Error closing client:",c)),F.delete(t))},120*1e3);async function Fe(o,t){const r=`${o}`,c=F.get(r);if(c)return console.log(`[MCP Pool] Reusing existing client for ${o}`),c.lastUsed=Date.now(),c.refCount++,c.client;console.log(`[MCP Pool] Creating new client for ${o}`);const i=await Ne(o,t);return i.transport&&typeof i.transport.setMaxListeners=="function"&&i.transport.setMaxListeners(50),i.client&&typeof i.client.setMaxListeners=="function"&&i.client.setMaxListeners(50),F.set(r,{client:i,lastUsed:Date.now(),refCount:1}),i}function Ue(o){const t=`${o}`,r=F.get(t);r&&(r.refCount=Math.max(0,r.refCount-1),r.lastUsed=Date.now())}async function je(o){try{const t=o.toLowerCase().replace(/\s+/g,"_").split("_")[0]||"clusters";console.log(`[MCP Discovery] Searching for servers with packageName matching "${t}" and enabled=true`);const c=(await L.search({index:le,body:{query:{bool:{must:[{term:{enabled:!0}},{prefix:{"packageName.keyword":t}}]}},size:100,sort:[{createdAt:{order:"desc"}}]}})).hits.hits.map(i=>({id:i._id,name:i._source.name,url:i._source.url,authKey:i._source.authKey,type:i._source.type,packageName:i._source.packageName,enabled:i._source.enabled,description:i._source.description}));if(console.log(`[MCP Discovery] Found ${c.length} enabled servers for system "${t}"`),c.length>0)return console.log("[MCP Discovery] Servers:",c.map(i=>`${i.name} (${i.packageName})`).join(", ")),c;if(t!=="clusters"&&t!=="unknown"){console.log(`[MCP Discovery] No servers found for "${t}", falling back to "clusters" package`);const P=(await L.search({index:le,body:{query:{bool:{must:[{term:{enabled:!0}},{prefix:{"packageName.keyword":"clusters"}}]}},size:100,sort:[{createdAt:{order:"desc"}}]}})).hits.hits.map(y=>({id:y._id,name:y._source.name,url:y._source.url,authKey:y._source.authKey,type:y._source.type,packageName:y._source.packageName,enabled:y._source.enabled,description:y._source.description}));if(P.length>0)return console.log(`[MCP Discovery] \u2705 Fallback successful: Found ${P.length} "clusters" server(s)`),P}return c}catch(t){return console.error("[MCP Discovery] Error fetching MCP servers from Elasticsearch:",t.message),console.error("[MCP Discovery] Falling back to no servers"),[]}}function E(o){if(!o||typeof o!="object")return o;const t={...o};return delete t.exclusiveMinimum,delete t.exclusiveMaximum,t.properties&&typeof t.properties=="object"&&(t.properties=Object.keys(t.properties).reduce((r,c)=>(r[c]=E(t.properties[c]),r),{})),t.items&&(t.items=E(t.items)),t.additionalProperties&&typeof t.additionalProperties=="object"&&(t.additionalProperties=E(t.additionalProperties)),Array.isArray(t.anyOf)&&(t.anyOf=t.anyOf.map(r=>E(r))),Array.isArray(t.oneOf)&&(t.oneOf=t.oneOf.map(r=>E(r))),Array.isArray(t.allOf)&&(t.allOf=t.allOf.map(r=>E(r))),t}function Je(o){const t=o?.content;if(typeof t=="string")return t;if(Array.isArray(t)){const r=t.find(c=>typeof c?.text=="string")?.text;if(r)return r}return t?.toString?.()??""}function Ge(o){try{const r=o?.messages?.[0]?.content?.[0]?.text;if(typeof r=="string")return r}catch{}try{const r=o?.messages?.[0]?.content?.text;if(typeof r=="string")return r}catch{}const t=o?.messages?.[0]?.content;return typeof t=="string"?t:typeof o?.content=="string"?o.content:""}function ce(o){if(!o)return{data:[]};if(Array.isArray(o?.data))return{data:o.data,nextCursor:o.nextCursor,meta:o.meta};if(Array.isArray(o?.result?.data))return{data:o.result.data,nextCursor:o.nextCursor??o.result?.nextCursor,meta:o.result?.meta};const t=o.nextCursor||o?.aggregations?.by_namespace?.buckets?.[0]?.g?.after_key;return{data:[],nextCursor:t}}function qe(o){return Math.ceil(o.length/4)}function Be(o,t={}){const{maxTokens:r=ae,sampleItems:c=20}=t,i=JSON.stringify(o),P=qe(i);if(P<=r)return o;const{data:y,meta:O}=ce(o);let A;return Array.isArray(y)&&y.length>0&&(A=y.slice(0,c)),{error:"RESULT_TOO_LARGE",approx_tokens:P,max_allowed_tokens:r,hint:"La r\xE9ponse de ce tool MCP est trop volumineuse pour \xEAtre envoy\xE9e telle quelle au LLM. R\xE9duis ta requ\xEAte (p\xE9riode, filtres par cluster/app/host, etc.) et r\xE9essaie.",meta:{...O,has_sample:!!A},sample:A}}function ze(o,t){if(!o||typeof o!="object"||de(o))return o;let r;try{r=JSON.parse(JSON.stringify(o))}catch(i){return console.warn(`[normalizeToolSchemaForProvider] Failed to clone schema (${i.message}), returning original`),o}delete r.$schema;const c=i=>{if(!(!i||typeof i!="object")){if((t==="openai"||t==="anthropic")&&i.additionalProperties===!1&&(i.additionalProperties=!0),i.properties&&typeof i.properties=="object")for(const P of Object.keys(i.properties))c(i.properties[P]);i.items&&c(i.items),Array.isArray(i.anyOf)&&i.anyOf.forEach(c),Array.isArray(i.oneOf)&&i.oneOf.forEach(c),Array.isArray(i.allOf)&&i.allOf.forEach(c)}};return c(r),r}function de(o){return!!o&&(typeof o.safeParse=="function"||!!o._def)}function Ke(o){return!!o&&typeof o=="object"&&(o.type||o.properties||o.anyOf||o.oneOf||o.allOf)}function N(o){if(!o||typeof o!="object")return g.any();if(Array.isArray(o.anyOf)&&o.anyOf.length)return g.union(o.anyOf.map(N));if(Array.isArray(o.oneOf)&&o.oneOf.length)return g.union(o.oneOf.map(N));if(Array.isArray(o.allOf)&&o.allOf.length)return o.allOf.map(N).reduce((t,r)=>g.intersection(t,r));if(Array.isArray(o.enum)&&o.enum.length)return o.enum.every(t=>typeof t=="string")?g.enum(o.enum):g.union(o.enum.map(t=>g.literal(t)));switch(o.type){case"string":return g.string();case"number":return g.number();case"integer":return g.number().int();case"boolean":return g.boolean();case"array":{const t=o.items?N(o.items):g.any();return g.array(t)}case"object":default:{const t=o.properties&&typeof o.properties=="object"?o.properties:{},r=new Set(Array.isArray(o.required)?o.required:[]),c={};for(const[P,y]of Object.entries(t)){const O=N(y);c[P]=r.has(P)?O:O.optional()}let i=g.object(c);return o.additionalProperties===!1?i=i.strict():i=i.passthrough(),i}}}function Xe(o){return o.map(t=>{const r=t.schema;if(!r||de(r))return t;if(Ke(r)){const c=N(r);return new G({name:t.name,description:t.description,schema:c,responseFormat:t.responseFormat,func:async(i,P)=>t.func(i,P)})}return new G({name:t.name,description:t.description,schema:g.record(g.any()),responseFormat:t.responseFormat,func:async(c,i)=>t.func(c,i)})})}async function Ye(o,t,r={}){const c=r.retries??3,i=r.baseDelayMs??800,P=new Set(r.retryOn??[429,502,503,504]);for(let y=0;y<=c;y++)try{return await o.invoke(t)}catch(O){const A=O?.status??O?.code??O?.response?.status,$=P.has(Number(A))||O?.name==="TimeoutError",Y=y===c;if(console.warn(`[MCP] invoke failed (try ${y+1}/${c+1}) code=${A} transient=${$}:`,O?.message||O),!$||Y)throw O;const R=i*Math.pow(2,y);await new Promise(v=>setTimeout(v,R))}}const _o=g.object({topic:g.enum(["clusters_clusters_overview","clusters_clusters_health-check","clusters_cluster_finops","clusters_nodes_inventory","clusters_nodes_operations","clusters_nodes_storage","clusters_nodes_resources","clusters_nodes_shards","clusters_nodes_thread_pool","clusters_nodes_tasks","clusters_nodes_hot_threads","clusters_jobs_enrich","clusters_jobs_snapshots","clusters_indices_inventory","clusters_indices_operations","clusters_indices_storage","clusters_indices_segments","clusters_indices_documents","elasticsearch_doc","mysql_mysql_overview","platform.core.execute_esql"])});async function Ao(o,t){console.log(`[buildAppFromDefinition] Building graph from definition: ${o.name} (v${o.version})`),console.log("[buildAppFromDefinition] Graph config:",{streaming:o.config.streaming,recursionLimit:o.config.recursionLimit,maxConcurrency:o.config.maxConcurrency});const r=t.performanceTracker||new Se(t.threadId);let c=[];const i=[];let P=!1;const y=t.queryParams?.topic||"unknown";if(t.mcp&&(Array.isArray(t.mcp)?t.mcp.length>0:!0))c=Array.isArray(t.mcp)?t.mcp:[t.mcp],console.log(`[MCP] Using ${c.length} pre-configured MCP client(s)`);else{P=!0,console.log(`[MCP] Auto-discovering servers for topic: "${y}"`);const e=await je(y);if(e.length===0)console.warn(`[MCP] \u26A0\uFE0F No enabled MCP servers found for topic "${y}"`),console.warn("[MCP] \u26A0\uFE0F Agent will run with NO tools available");else{console.log(`[MCP] Creating ${e.length} MCP client(s) for discovered servers`);for(const n of e)try{console.log(`[MCP] Connecting to: ${n.name} at ${n.url}`);const s=await Fe(n.url,{headers:{Authorization:`ApiKey ${n.authKey}`},transportType:"http",timeout:3e4});c.push(s),i.push(n.url),console.log(`[MCP] \u2705 Connected to ${n.name} (${n.packageName})`)}catch(s){console.error(`[MCP] \u274C Failed to connect to ${n.name}:`,s.message)}console.log(`[MCP] Successfully connected to ${c.length}/${e.length} servers`)}}let A=[],$;if(c.length===0)console.warn("[MCP] \u26A0\uFE0F No MCP servers available. Agent will run without tools.");else{$=c[0],console.log(`[MCP] Loading tools from ${c.length} MCP server(s)`);const n=(await Promise.all(c.map(async(a,m)=>{const d=await Me(`stkxp-mcp-server-${m}`,a.client);return console.log(`[MCP] Server ${m+1}: Loaded ${d.length} tools`),d}))).flat();console.log(`[MCP] Total loaded: ${n.length} tools`);const s=new Map;for(const a of n)s.has(a.name)||s.set(a.name,a);A=Array.from(s.values())}const R=y.toLowerCase().replace(/\s+/g,"_").split("_")[0]||"clusters";let v=A;if(P)console.log(`[MCP] \u2705 Using ${A.length} tools from auto-discovered servers`),v=A;else if(process.env.MCP_DISABLE_TOPIC_FILTERING==="true")console.log(`[MCP] \u26A0\uFE0F Topic filtering disabled, using all ${A.length} tools`),v=A;else{let n=A.filter(a=>a.name.startsWith(R+"_"));n.length===0&&(n=A.filter(a=>a.name.includes(R)));const s=A.filter(a=>a.name.startsWith("elasticsearch_"));if(s.length>0){const a=new Set(n.map(d=>d.name)),m=s.filter(d=>!a.has(d.name));m.length>0&&(console.log(`[MCP] \u{1F527} Force-adding ${m.length} elasticsearch_ tools`),n=[...n,...m])}n.length===0?(console.log(`[MCP] \u26A0\uFE0F No tools found for system "${R}", using all ${A.length} tools`),v=A):(console.log(`[MCP] \u2705 Filtered ${n.length}/${A.length} tools`),v=n)}console.log("[MCP] Server tool sample:",JSON.stringify(v[0]),v[0]);const V=v.filter(e=>{try{if(!e.schema)return console.log(`[MCP] \u26A0\uFE0F Tool "${e.name}" has no schema, keeping it`),!0;const n=e.schema;return n._def&&typeof n._def.shape=="function"?!0:n._def&&n._def.typeName==="ZodObject"?(console.error(`[MCP] \u274C Tool "${e.name}" has invalid ZodObject schema (shape is not a function), filtering out`),!1):(console.log(`[MCP] \u26A0\uFE0F Tool "${e.name}" has non-object schema type: ${n._def?.typeName}, keeping it`),!0)}catch(n){return console.error(`[MCP] \u274C Tool "${e.name}" validation error: ${n.message}, filtering out`),!1}});console.log(`[MCP] Validated ${V.length}/${v.length} tools`);const q=V,me=new G({name:"mcp_paginated_query",description:"Appelle un tool MCP cibl\xE9 en boucle avec prise en charge de nextCursor",schema:g.object({toolName:g.string().describe("Nom du tool MCP \xE0 appeler"),args:g.record(g.any()).describe("Arguments \xE0 passer au tool MCP"),maxPages:g.number().int().min(1).default(10).describe("Nombre max de pages"),maxItems:g.number().int().min(1).default(5e3).describe("Nombre max d'items cumul\xE9s")}),func:async({toolName:e,args:n,maxPages:s,maxItems:a})=>{const m=v.find(f=>f.name===e)||A.find(f=>f.name===e);if(!m)throw new Error(`Tool MCP introuvable: ${e}`);let d=n?.request?.body?.cursor,l=0;const C=[];for(;l<s&&C.length<a;){const f=JSON.parse(JSON.stringify(n||{}));d&&(f.request=f.request||{},f.request.body=f.request.body||{},f.request.body.cursor=d);const u=await Ye(m,f,{retries:4,baseDelayMs:750,retryOn:[429,502,503,504]}),{data:b,nextCursor:_}=ce(u);if(Array.isArray(b)&&b.length&&C.push(...b),l+=1,!_)break;d=_}const p=l>=s||C.length>=a||!d;return{items:C,pages:l,done:p,nextCursor:d}}}),{createLLMServices:pe}=await import("../services/llm-services.js"),ue=pe(L);let W="user",k=t.username;if(t.userToken)try{const e=ie.decode(t.userToken);e&&typeof e=="object"&&("roles"in e&&(W=e.roles?.[0]||"user"),!k&&"username"in e&&(k=e.username||void 0))}catch(e){console.warn("[buildAppFromDefinition] Failed to decode user token for routing:",e)}const H={userRole:W,topic:y,environment:process.env.NODE_ENV||"prod",requiredCapabilities:["streaming","toolCalling"],owner:k};console.log("[buildAppFromDefinition] Routing context:",H);let h,B;const Z=o.llmConfig?.useRoutingEngine!==!1;Z||console.log("[buildAppFromDefinition] Routing engine disabled in graph config, using llmConfig directly");try{if(!Z)throw new Error("Routing engine disabled in graph configuration");const e=await ue.getRoutingService().selectDeployment(H);if(console.log(`[buildAppFromDefinition] \u2705 Selected model: ${e.model.modelId}`),console.log(`[buildAppFromDefinition] Reason: ${e.reason}`),k&&e.provider.owner&&e.provider.owner!==k)throw new Error(`Routing selected provider owned by "${e.provider.owner}" for caller "${k}" \u2014 refusing cross-tenant API key.`);h={provider:e.provider.type,model:e.model.modelId,temperature:e.model.parameters?.temperature??X.llm.temperature,streaming:X.llm.streaming,apiKey:e.provider.apiKey,baseURL:e.provider.endpoint},console.log(`[buildAppFromDefinition] LLM config from routing engine: provider=${h.provider}, model=${h.model}`),B=se(h)}catch(e){if(console.error(`[buildAppFromDefinition] \u26A0\uFE0F Routing engine failed: ${e.message}`),console.log("[buildAppFromDefinition] Falling back to legacy provider selection"),h={...X.llm},console.log("LLM llmConfig 1",h),t.userProviders&&Array.isArray(t.userProviders)){const n=t.userProviders.filter(s=>s.enabled===!0).filter(s=>s.selectedModel===t.queryParams?.llmModel);if(n.length>0){const s=n[0],a=s.selectedModel||s.models?.[0]||h.model;h={provider:s.id,model:a,temperature:h.temperature,streaming:h.streaming,...s.apiKey&&{apiKey:s.apiKey},...s.endpoint&&{baseURL:s.endpoint}}}}console.log("LLM llmConfig 2",t.userProviders,h),B=se(h)}const Q=process.env.TAVILY_API_KEY?new ve({maxResults:3}):null,ge=q.map(e=>({...e,async invoke(n,s){s?.writer&&typeof s.writer=="function"&&s.writer(`\u23F3 Calling ${e.name}...`);const a=Date.now();try{const m=await e.invoke(n),d=Date.now()-a;return s?.writer&&typeof s.writer=="function"&&s.writer(`\u2705 ${e.name} completed in ${d}ms`),Be(m,{maxTokens:ae,sampleItems:20})}catch(m){const d=Date.now()-a;s?.writer&&typeof s.writer=="function"&&s.writer(`\u274C ${e.name} failed after ${d}ms: ${m.message}`);const l=m.message||String(m),C=m.status||m.response?.status||m.code;return console.error(`[Tool Error] ${e.name} failed:`,{error:l,statusCode:C,duration:d,args:JSON.stringify(n).substring(0,200)}),JSON.stringify({error:"TOOL_EXECUTION_FAILED",tool_name:e.name,error_message:l,status_code:C,duration_ms:d,suggestion:"This tool failed. Please try an alternative approach or inform the user about the issue.",args_attempted:n})}}}));let ee="anonymous";if(t.userToken)try{const e=ie.decode(t.userToken);e&&typeof e=="object"&&"username"in e&&(ee=e.username||"anonymous")}catch(e){console.warn("[buildApp] Failed to decode user token:",e)}const fe=new G({name:"compare_with_previous_chat",description:"Compare current conversation with a previous saved chat",schema:g.object({chatId:g.string().describe("ID of the previous chat"),comparisonAspects:g.array(g.enum(["metrics","errors","performance","configuration","overall","recommendations"])).optional(),includeTimeDelta:g.boolean().optional()}),func:async(e,n)=>{const s=n?.state?.messages||[];return await ke(ee,s).func(e,n)}});let S=[...ge,me,...Q?[Q]:[],fe];h.provider==="google"&&(console.log("[buildApp] Cleaning tool schemas for Google Gemini"),S=S.map(e=>e.schema?{...e,schema:E(e.schema)}:e)),h.provider==="anthropic"&&(console.log("[buildApp] Adapting tools for Anthropic (JSON Schema -> Zod)"),S=Xe(S)),h.provider==="openai"&&(console.log(`[buildApp] Normalizing tool schemas for ${h.provider}`),S=S.map(e=>e.schema?{...e,schema:ze(e.schema,h.provider)}:e));const ye=Oe({model:B,tools:S}),Te=J.Root({..._e.spec,threadId:J({reducer:(e,n)=>n||e,default:()=>t.threadId}),userContext:J({reducer:(e,n)=>n!==void 0?n:e,default:()=>t.userContext}),systemPrompt:J({reducer:(e,n)=>n!==void 0?n:e,default:()=>{}})}),I=new Ae(Te),he={checkReset:async e=>{r.start("node_checkReset");const n=t.threadId,s=e.threadId;if(s&&s!==n){const a=e.messages[e.messages.length-1];return r.end("node_checkReset",{reset:!0}),{messages:[a],threadId:n}}return r.end("node_checkReset",{reset:!1}),{threadId:n}},system:async e=>{r.start("node_system");const n=e.messages[e.messages.length-1],s=Je(n).trim();r.start("topic_generation");let a;const m=process.env.AB_TESTING_ENABLED==="true";if(s.startsWith("topic="))a=s.slice(6).trim(),console.log(`[TOPIC] Manual override: ${a}`);else if(m){console.log("[TOPIC] A/B Testing enabled");const{result:T,variantUsed:be,comparison:K}=await we(s,h);a=T,console.log(`[TOPIC] Selected variant: ${be} - Topic: ${a}`),K?.winner&&console.log(`[TOPIC] Winner: ${K.winner.variantId} (${K.winner.improvement.toFixed(1)}% faster)`)}else a=await Ee(s,h);r.end("topic_generation",{topic:a,abEnabled:m}),!a||a.trim()===""?(console.error("[PROMPT-ALT] \u274C CRITICAL: Topic generation returned empty string"),console.error(`[PROMPT-ALT] User input was: "${s}"`),console.error("[PROMPT-ALT] Will fallback to default prompt")):console.log(`[PROMPT-ALT] \u2705 Topic generated successfully: "${a}"`);let d="",l="default";if(r.start("prompt_fetch"),!a||a.trim()==="")console.warn("[PROMPT-ALT] \u26A0\uFE0F Skipping MCP prompt fetch - topic is empty"),l="empty_topic",r.end("prompt_fetch",{source:l,topic:"(empty)"});else if($)try{console.log(`[PROMPT-ALT] \u{1F504} Fetching prompt from MCP for topic: "${a}"`);const T=await $.client.getPrompt({name:`${a}`,params:{}});console.log("[PROMPT-ALT] Raw prompt response:",JSON.stringify(T,null,2).substring(0,1e3)),d=Ge(T),!d||d.trim()===""?(console.error(`[PROMPT-ALT] \u274C MCP returned empty prompt text for topic: "${a}"`),console.error("[PROMPT-ALT] MCP response structure:",JSON.stringify(T,null,2).substring(0,500)),l="failed"):(console.log(`[PROMPT-ALT] \u2705 Successfully loaded prompt for topic: "${a}" (${d.length} chars)`),console.log("[PROMPT-ALT] Prompt preview:",d.substring(0,200)),l="mcp"),r.end("prompt_fetch",{source:l,topic:a,promptLength:d.length})}catch(T){console.error(`[PROMPT-ALT] \u274C MCP getPrompt() failed for topic "${a}"`),console.error(`[PROMPT-ALT] Error type: ${T.name||"Unknown"}`),console.error(`[PROMPT-ALT] Error message: ${T.message}`),console.error("[PROMPT-ALT] Stack trace:",T.stack?.substring(0,500)),T.message?.includes("timeout")||T.message?.includes("ETIMEDOUT")?console.error("[PROMPT-ALT] \u{1F550} Timeout error detected - MCP server did not respond in time"):(T.message?.includes("ECONNREFUSED")||T.message?.includes("ENOTFOUND"))&&console.error("[PROMPT-ALT] \u{1F310} Network error - Cannot reach MCP server"),l="failed",r.end("prompt_fetch",{source:l,topic:a,error:T.message})}else console.warn(`[PROMPT-ALT] \u26A0\uFE0F No MCP client available to fetch prompt for topic "${a}"`),l="no_client",r.end("prompt_fetch",{source:l,topic:a});!d||d.trim()===""?(console.warn("[PROMPT-ALT] \u26A0\uFE0F FALLBACK ACTIVATED: Using default prompt"),console.warn(`[PROMPT-ALT] Reason: ${l}`),console.warn(`[PROMPT-ALT] Topic was: "${a||"(empty)"}"`),console.warn(`[PROMPT-ALT] FirstClient available: ${!!$}`)):console.log(`[PROMPT-ALT] \u2705 FINAL: Using MCP prompt (${d.length} chars) from source: ${l}`);const C=t.userToken||"stkxp_session_default",p=t.queryParams||{},f=p.cluster||"default",b=$e({agentPrompt:d||`# Default Dashboard Response
2
2
 
3
3
  You are an expert assistant specialized in generating structured dashboard responses.
4
4
 
@@ -70,15 +70,15 @@ YOU MUST OUTPUT EXACTLY THIS STRUCTURE (3-5 blocks minimum):
70
70
  {"type":"markdown","title":"\u{1F4A1} Insights","body":"System is healthy but approaching memory limits during peak hours."}
71
71
  {"type":"markdown","title":"\u2705 Recommendations","body":"1. Add 8GB RAM\\n2. Optimize queries\\n3. Enable caching"}
72
72
 
73
- **REMEMBER: You MUST generate 3-5 blocks minimum. Generating only 1 block is NOT acceptable.**`,mandatoryParams:{userToken:C,cluster:f,nodes:p.nodes||"",roles:p.roles||"",phases:p.phases||"hot",prefix:p.prefix||"",suffix:p.suffix||"",start:p.start||"now-4h",end:p.end||"now",interval:p.interval||"5m",topic:p.topic||"default_topic.txt"},toolNames:q.map(T=>T.name),maxAgentPromptTokens:se.maxAgentPromptTokens});let _;const M=e.userContext;M&&M.trim()?(console.log("[bubuildAppFromDefinitionildApp] \u2705 Applying user context"),_=b.prompt+`
73
+ **REMEMBER: You MUST generate 3-5 blocks minimum. Generating only 1 block is NOT acceptable.**`,mandatoryParams:{userToken:C,cluster:f,nodes:p.nodes||"",roles:p.roles||"",phases:p.phases||"hot",prefix:p.prefix||"",suffix:p.suffix||"",start:p.start||"now-4h",end:p.end||"now",interval:p.interval||"5m",topic:p.topic||"default_topic.txt"},toolNames:q.map(T=>T.name),maxAgentPromptTokens:re.maxAgentPromptTokens});let _;const M=e.userContext;M&&M.trim()?(console.log("[bubuildAppFromDefinitionildApp] \u2705 Applying user context"),_=b.prompt+`
74
74
 
75
75
  ## \u{1F4DD} FORMAT ET R\xC8GLES PERSONNALIS\xC9S (Override)
76
76
 
77
77
  ${M}
78
78
 
79
- IMPORTANT: Ces instructions personnalis\xE9es REMPLACENT les sections FORMAT et RULES du prompt MCP.`):(console.log("[buildAppFromDefinition] \u2139\uFE0F Using default MCP prompt"),_=b.prompt),se.verboseLogging&&console.log("[buildAppFromDefinition] Prompt optimization:",{agentTokens:b.stats.agentPromptTokens,instructionsTokens:b.stats.instructionsTokens,totalTokens:b.stats.totalTokens,truncated:b.stats.agentPromptTruncated,userContextAdded:!!M});const x=e.messages.filter(T=>T.getType()==="system"),z=e.messages.filter(T=>T.getType()!=="system"),D=x.map(T=>typeof T.content=="string"?T.content:String(T.content)).join(`
79
+ IMPORTANT: Ces instructions personnalis\xE9es REMPLACENT les sections FORMAT et RULES du prompt MCP.`):(console.log("[buildAppFromDefinition] \u2139\uFE0F Using default MCP prompt"),_=b.prompt),re.verboseLogging&&console.log("[buildAppFromDefinition] Prompt optimization:",{agentTokens:b.stats.agentPromptTokens,instructionsTokens:b.stats.instructionsTokens,totalTokens:b.stats.totalTokens,truncated:b.stats.agentPromptTruncated,userContextAdded:!!M});const x=e.messages.filter(T=>T.getType()==="system"),z=e.messages.filter(T=>T.getType()!=="system"),D=x.map(T=>typeof T.content=="string"?T.content:String(T.content)).join(`
80
80
 
81
- `),oe=`\u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F CRITICAL OUTPUT REQUIREMENT - OVERRIDE ALL OTHER INSTRUCTIONS \u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F
81
+ `),te=`\u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F CRITICAL OUTPUT REQUIREMENT - OVERRIDE ALL OTHER INSTRUCTIONS \u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F
82
82
 
83
83
  YOU MUST RESPOND IN STREAMING JSON MODE:
84
84
  1. Your VERY FIRST character must be '{' (opening brace)
@@ -100,11 +100,11 @@ CORRECT EXAMPLES:
100
100
 
101
101
  Your response STARTS NOW with '{' character:
102
102
 
103
- `,te=e.threadId?`
103
+ `,ne=e.threadId?`
104
104
 
105
- Current chat ID: ${e.threadId}`:"",j=D?`${oe}${D}
105
+ Current chat ID: ${e.threadId}`:"",j=D?`${te}${D}
106
106
 
107
- ${_}${te}`:`${oe}${_}${te}`;return console.log(`[buildAppFromDefinition] ********************************
107
+ ${_}${ne}`:`${te}${_}${ne}`;return console.log(`[buildAppFromDefinition] ********************************
108
108
  finalSystemContent: ${j}
109
109
  ********************************`),r.end("node_system",{systemMessageLength:j.length}),{messages:[new Ce(j),...z],systemPrompt:j}},assistant:async(e,n)=>{r.start("node_assistant");const s=e.messages.filter(p=>p.getType()==="system"),a=e.messages.filter(p=>p.getType()!=="system");console.log(`
110
110
  `+"=".repeat(80)),console.log("\u{1F4E4} FULL LLM CONTEXT SENT TO MODEL (buildAppFromDefinition)"),console.log("=".repeat(80)),console.log(`\u{1F916} Model: ${h.provider}:${h.model}`),console.log(`\u{1F321}\uFE0F Temperature: ${h.temperature}`),console.log(`\u{1F4AC} Total Messages: ${s.length+a.length} (${s.length} system + ${a.length} conversation)`),console.log(`\u{1F527} Tools Available: ${q.length}`),console.log(`\u{1F4CA} Graph: ${o.name} (v${o.version})`),console.log("=".repeat(80)),process.env.GRAPH_STATE_DEBUG&&[...s,...a].forEach((p,f)=>{const u=p.getType(),b=typeof p.content=="string"?p.content:JSON.stringify(p.content),_=p.tool_calls?.length||0;console.log(`
@@ -117,4 +117,4 @@ ${_}${te}`:`${oe}${_}${te}`;return console.log(`[buildAppFromDefinition] *******
117
117
  ${b.split(`
118
118
  `).join(`
119
119
  `)}`)}return console.log("=".repeat(80)+`
120
- `),r.end("node_assistant",{messageCount:d.length}),{messages:[C]}},tool:async(e,n)=>{r.start("node_tools");const s=e.messages[e.messages.length-1];if(!s||s.getType()!=="ai"||!s.tool_calls||s.tool_calls.length===0)return console.warn("[node_tools] No tool calls found in last message"),r.end("node_tools",{toolsExecuted:0}),{messages:[]};console.log(`[node_tools] \u26A1 Executing ${s.tool_calls.length} tool(s) in PARALLEL...`);const a=process.env.MAX_PARALLEL_TOOLS?parseInt(process.env.MAX_PARALLEL_TOOLS,10):void 0,m=a?s.tool_calls.slice(0,a):s.tool_calls,d=a?s.tool_calls.slice(a):[];a&&s.tool_calls.length>a&&console.log(`[node_tools] \u26A0\uFE0F Limiting execution to ${a} tools (${s.tool_calls.length-a} tools skipped)`);const l=m.map(async u=>{const b=S.find(_=>_.name===u.name);if(!b){console.error(`[node_tools] Tool not found: ${u.name}`);const{ToolMessage:_}=await import("@langchain/core/messages");return new _({tool_call_id:u.id,content:JSON.stringify({error:"Tool not found",tool_name:u.name})})}try{console.log(`[node_tools] \u{1F680} Starting ${u.name}...`);const _=Date.now(),M=await b.invoke(u.args,n),x=Date.now()-_,{ToolMessage:z}=await import("@langchain/core/messages"),D=typeof M=="string"?M:JSON.stringify(M);return console.log(`[node_tools] \u2705 ${u.name} completed in ${x}ms (${D.length} chars)`),new z({tool_call_id:u.id,content:D})}catch(_){console.error(`[node_tools] \u274C ${u.name} failed:`,_.message);const{ToolMessage:M}=await import("@langchain/core/messages");return new M({tool_call_id:u.id,content:JSON.stringify({error:"TOOL_EXECUTION_FAILED",tool_name:u.name,error_message:_.message})})}}),C=await Promise.allSettled(l),p=C.filter(u=>u.status==="fulfilled").map(u=>u.value),f=C.filter(u=>u.status==="rejected").length;if(f>0&&console.warn(`[node_tools] \u26A0\uFE0F ${f} tool(s) failed but continuing...`),d.length>0){const{ToolMessage:u}=await import("@langchain/core/messages");for(const b of d)console.log(`[node_tools] \u23ED\uFE0F Skipped tool: ${b.name} (limit reached)`),p.push(new u({tool_call_id:b.id,content:JSON.stringify({error:"TOOL_EXECUTION_SKIPPED",tool_name:b.name,reason:`Execution limit reached (MAX_PARALLEL_TOOLS=${a})`})}))}return console.log(`[node_tools] \u{1F4CA} Summary: ${p.length}/${s.tool_calls.length} tools processed (${m.length} executed, ${d.length} skipped)`),r.end("node_tools",{toolsExecuted:m.length,toolsSkipped:d.length}),{messages:p}}};console.log(`[buildAppFromDefinition] Adding ${o.nodes.length} nodes to graph`);for(const e of o.nodes){const n=he[e.type];if(!n){console.warn(`[buildAppFromDefinition] \u26A0\uFE0F Unknown node type: ${e.type} for node ${e.id}`),console.warn(`[buildAppFromDefinition] \u26A0\uFE0F Skipping node: ${e.id}`);continue}console.log(`[buildAppFromDefinition] Adding node: ${e.id} (type: ${e.type})`),I.addNode(e.id,n)}console.log(`[buildAppFromDefinition] Adding ${o.edges.length} edges to graph`);const U=new Map;for(const e of o.edges){const n=e.from||e.source,s=e.to||e.target;if(!n||!s){console.warn("[buildAppFromDefinition] \u26A0\uFE0F Invalid edge (missing from/to):",e);continue}U.has(n)||U.set(n,[]),U.get(n).push({...e,from:n,to:s})}for(const[e,n]of U.entries()){const s=n.filter(m=>m.condition),a=n.filter(m=>!m.condition);if(s.length>0){const m=l=>{const C=l.messages[l.messages.length-1],p=C?.tool_calls&&C.tool_calls.length>0;console.log(`[conditional_edge] from=${e}, hasToolCalls=${p}`);for(const f of s){if(f.condition==="has_tool_calls"&&p)return console.log(`[conditional_edge] Route: ${e} \u2192 ${f.to} (has_tool_calls)`),f.to;if(f.condition==="no_tool_calls"&&!p)return console.log(`[conditional_edge] Route: ${e} \u2192 ${f.to} (no_tool_calls)`),f.to}return console.warn(`[conditional_edge] No condition matched for ${e}, defaulting to __end__`),"__end__"},d={};for(const l of s)l.condition==="has_tool_calls"?d.has_tools=l.to:l.condition==="no_tool_calls"&&(d.no_tools=l.to);console.log(`[buildAppFromDefinition] Adding conditional edge: ${e} \u2192 [${s.map(l=>`${l.to}(${l.condition})`).join(", ")}]`),I.addConditionalEdges(e,m)}else if(a.length>0)for(const m of a)console.log(`[buildAppFromDefinition] Adding edge: ${m.from} \u2192 ${m.to}`),I.addEdge(m.from,m.to)}if(o.edges.some(e=>(e.from||e.source)==="__start__"))console.log("[buildAppFromDefinition] Entry point already defined in edges");else{const e=o.nodes[0]?.id||"checkReset";console.log(`[buildAppFromDefinition] Adding missing entry point: __start__ \u2192 ${e}`),I.addEdge("__start__",e)}const ee={checkpointer:o.config.checkpointer?t.memory:void 0};o.config.recursionLimit&&(ee.recursionLimit=o.config.recursionLimit);const w=I.compile(ee);return console.log(`[buildAppFromDefinition] \u2705 Graph compiled successfully: ${o.name}`),{invoke:w.invoke.bind(w),stream:w.stream.bind(w),streamEvents:w.streamEvents.bind(w),cleanup:()=>{for(const e of i)Ue(e);console.log(`[MCP Pool] Released ${i.length} client(s)`)},_compiled:w}}export{Ao as buildAppFromDefinition};
120
+ `),r.end("node_assistant",{messageCount:d.length}),{messages:[C]}},tool:async(e,n)=>{r.start("node_tools");const s=e.messages[e.messages.length-1];if(!s||s.getType()!=="ai"||!s.tool_calls||s.tool_calls.length===0)return console.warn("[node_tools] No tool calls found in last message"),r.end("node_tools",{toolsExecuted:0}),{messages:[]};console.log(`[node_tools] \u26A1 Executing ${s.tool_calls.length} tool(s) in PARALLEL...`);const a=process.env.MAX_PARALLEL_TOOLS?parseInt(process.env.MAX_PARALLEL_TOOLS,10):void 0,m=a?s.tool_calls.slice(0,a):s.tool_calls,d=a?s.tool_calls.slice(a):[];a&&s.tool_calls.length>a&&console.log(`[node_tools] \u26A0\uFE0F Limiting execution to ${a} tools (${s.tool_calls.length-a} tools skipped)`);const l=m.map(async u=>{const b=S.find(_=>_.name===u.name);if(!b){console.error(`[node_tools] Tool not found: ${u.name}`);const{ToolMessage:_}=await import("@langchain/core/messages");return new _({tool_call_id:u.id,content:JSON.stringify({error:"Tool not found",tool_name:u.name})})}try{console.log(`[node_tools] \u{1F680} Starting ${u.name}...`);const _=Date.now(),M=await b.invoke(u.args,n),x=Date.now()-_,{ToolMessage:z}=await import("@langchain/core/messages"),D=typeof M=="string"?M:JSON.stringify(M);return console.log(`[node_tools] \u2705 ${u.name} completed in ${x}ms (${D.length} chars)`),new z({tool_call_id:u.id,content:D})}catch(_){console.error(`[node_tools] \u274C ${u.name} failed:`,_.message);const{ToolMessage:M}=await import("@langchain/core/messages");return new M({tool_call_id:u.id,content:JSON.stringify({error:"TOOL_EXECUTION_FAILED",tool_name:u.name,error_message:_.message})})}}),C=await Promise.allSettled(l),p=C.filter(u=>u.status==="fulfilled").map(u=>u.value),f=C.filter(u=>u.status==="rejected").length;if(f>0&&console.warn(`[node_tools] \u26A0\uFE0F ${f} tool(s) failed but continuing...`),d.length>0){const{ToolMessage:u}=await import("@langchain/core/messages");for(const b of d)console.log(`[node_tools] \u23ED\uFE0F Skipped tool: ${b.name} (limit reached)`),p.push(new u({tool_call_id:b.id,content:JSON.stringify({error:"TOOL_EXECUTION_SKIPPED",tool_name:b.name,reason:`Execution limit reached (MAX_PARALLEL_TOOLS=${a})`})}))}return console.log(`[node_tools] \u{1F4CA} Summary: ${p.length}/${s.tool_calls.length} tools processed (${m.length} executed, ${d.length} skipped)`),r.end("node_tools",{toolsExecuted:m.length,toolsSkipped:d.length}),{messages:p}}};console.log(`[buildAppFromDefinition] Adding ${o.nodes.length} nodes to graph`);for(const e of o.nodes){const n=he[e.type];if(!n){console.warn(`[buildAppFromDefinition] \u26A0\uFE0F Unknown node type: ${e.type} for node ${e.id}`),console.warn(`[buildAppFromDefinition] \u26A0\uFE0F Skipping node: ${e.id}`);continue}console.log(`[buildAppFromDefinition] Adding node: ${e.id} (type: ${e.type})`),I.addNode(e.id,n)}console.log(`[buildAppFromDefinition] Adding ${o.edges.length} edges to graph`);const U=new Map;for(const e of o.edges){const n=e.from||e.source,s=e.to||e.target;if(!n||!s){console.warn("[buildAppFromDefinition] \u26A0\uFE0F Invalid edge (missing from/to):",e);continue}U.has(n)||U.set(n,[]),U.get(n).push({...e,from:n,to:s})}for(const[e,n]of U.entries()){const s=n.filter(m=>m.condition),a=n.filter(m=>!m.condition);if(s.length>0){const m=l=>{const C=l.messages[l.messages.length-1],p=C?.tool_calls&&C.tool_calls.length>0;console.log(`[conditional_edge] from=${e}, hasToolCalls=${p}`);for(const f of s){if(f.condition==="has_tool_calls"&&p)return console.log(`[conditional_edge] Route: ${e} \u2192 ${f.to} (has_tool_calls)`),f.to;if(f.condition==="no_tool_calls"&&!p)return console.log(`[conditional_edge] Route: ${e} \u2192 ${f.to} (no_tool_calls)`),f.to}return console.warn(`[conditional_edge] No condition matched for ${e}, defaulting to __end__`),"__end__"},d={};for(const l of s)l.condition==="has_tool_calls"?d.has_tools=l.to:l.condition==="no_tool_calls"&&(d.no_tools=l.to);console.log(`[buildAppFromDefinition] Adding conditional edge: ${e} \u2192 [${s.map(l=>`${l.to}(${l.condition})`).join(", ")}]`),I.addConditionalEdges(e,m)}else if(a.length>0)for(const m of a)console.log(`[buildAppFromDefinition] Adding edge: ${m.from} \u2192 ${m.to}`),I.addEdge(m.from,m.to)}if(o.edges.some(e=>(e.from||e.source)==="__start__"))console.log("[buildAppFromDefinition] Entry point already defined in edges");else{const e=o.nodes[0]?.id||"checkReset";console.log(`[buildAppFromDefinition] Adding missing entry point: __start__ \u2192 ${e}`),I.addEdge("__start__",e)}const oe={checkpointer:o.config.checkpointer?t.memory:void 0};o.config.recursionLimit&&(oe.recursionLimit=o.config.recursionLimit);const w=I.compile(oe);return console.log(`[buildAppFromDefinition] \u2705 Graph compiled successfully: ${o.name}`),{invoke:w.invoke.bind(w),stream:w.stream.bind(w),streamEvents:w.streamEvents.bind(w),cleanup:()=>{for(const e of i)Ue(e);console.log(`[MCP Pool] Released ${i.length} client(s)`)},_compiled:w}}export{Ao as buildAppFromDefinition};