"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.mcpQuery = mcpQuery;
const hotmesh_1 = require("@hotmeshio/hotmesh");
const defaults_1 = require("../../../modules/defaults");
const activities = __importStar(require("./activities"));
const interceptorActivities = __importStar(require("../../../services/interceptor/activities"));
const prompts_1 = require("./prompts");
const tool_result_guard_1 = require("../tool-result-guard");
const { loadTools, callMcpTool, callQueryLLM, } = hotmesh_1.Durable.workflow.proxyActivities({
    activities,
    retry: {
        maximumAttempts: 3,
        backoffCoefficient: 2,
        maximumInterval: '10 seconds',
    },
});
const { ltEnrichEscalationRouting, } = hotmesh_1.Durable.workflow.proxyActivities({
    activities: interceptorActivities,
    taskQueue: 'lt-interceptor',
    retry: { maximumAttempts: 3 },
});
const MAX_TOOL_ROUNDS = defaults_1.TOOL_ROUNDS_MCP_QUERY;
/**
 * MCP Query workflow (leaf).
 *
 * Dynamic MCP tool orchestration via LLM agentic loop.
 * Discovers available MCP tools by tag and uses an LLM to
 * fulfill arbitrary requests. This workflow ONLY handles
 * dynamic execution — compiled workflow matching is done
 * by the mcpQueryRouter parent.
 *
 * Clean execution traces from this workflow are what get
 * compiled into deterministic YAML workflows.
 */
async function mcpQuery(envelope) {
    const prompt = (envelope.data?.prompt || envelope.data?.question);
    const tags = envelope.data?.tags;
    if (!prompt) {
        return {
            type: 'return',
            data: {
                title: 'No prompt provided',
                summary: 'Please provide a prompt describing what you want to accomplish.',
                result: null,
                tool_calls_made: 0,
            },
        };
    }
    // Load raw MCP tools (no compiled workflow discovery — router handles that)
    const raw = await loadTools(tags);
    const toolIds = raw.toolIds;
    // Build system prompt: strategy first, then tool inventory
    let serverSection = '';
    if (raw.strategy) {
        serverSection += `${raw.strategy}\n\n`;
    }
    serverSection += `## Available MCP Servers\n\n${raw.inventory}`;
    const messages = [
        {
            role: 'system',
            content: prompts_1.MCP_QUERY_SYSTEM_PROMPT + `\n\n${serverSection}`,
        },
    ];
    // If this is a re-run after triage, inject triage learnings as context
    const triageContext = envelope.resolver?._triageContext;
    if (triageContext) {
        const triageHints = [
            `This is a re-run after a previous attempt failed and was triaged.`,
            triageContext.diagnosis ? `Previous diagnosis: ${triageContext.diagnosis}` : '',
            triageContext.actions_taken?.length ? `Actions already taken: ${triageContext.actions_taken.join('; ')}` : '',
            triageContext.recommendation ? `Recommendation: ${triageContext.recommendation}` : '',
        ].filter(Boolean).join('\n');
        messages.push({ role: 'user', content: triageHints });
        messages.push({ role: 'assistant', content: 'Understood. I will use these learnings to take a more targeted approach this time.' });
    }
    // If invoked from the help assistant, inject dashboard context
    const dashContext = envelope.data?.context;
    if (dashContext) {
        const lines = [
            `[Dashboard Context]`,
            `Page: ${dashContext.page || 'unknown'}`,
            dashContext.entities?.workflowId ? `Workflow: ${dashContext.entities.workflowId}` : '',
            dashContext.entities?.workflowStatus ? `Status: ${dashContext.entities.workflowStatus}` : '',
            dashContext.entities?.yamlContent ? `YAML:\n${dashContext.entities.yamlContent}` : '',
            dashContext.entities?.prompt ? `Original query: ${dashContext.entities.prompt}` : '',
            ``,
            `IMPORTANT: This is a help assistant conversation. Always include the actual data, content, and results from tool calls in your response. Do not just confirm an action was taken — show the user what was retrieved, stored, or produced.`,
            `[End Context]`,
        ].filter(Boolean).join('\n');
        messages.push({ role: 'user', content: lines });
        messages.push({ role: 'assistant', content: 'I have the dashboard context. I will always show actual data and results, not just confirmations.' });
    }
    messages.push({ role: 'user', content: prompt });
    let toolCallCount = 0;
    // Repeated-error circuit breaker: if the same tool returns the same
    // error twice in a row, the LLM is stuck — break out immediately.
    const MAX_REPEATED_ERRORS = 2;
    let lastErrorKey = '';
    let repeatedErrorCount = 0;
    // Agentic loop: LLM decides → execute tools → feed back → repeat
    const BUDGET_WARNING_THRESHOLD = 3;
    for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
        const remaining = MAX_TOOL_ROUNDS - round;
        // Inject budget warning when approaching the limit
        if (remaining <= BUDGET_WARNING_THRESHOLD && remaining < MAX_TOOL_ROUNDS) {
            messages.push({
                role: 'user',
                content: `[Rounds: ${remaining} remaining] Wrap up: consolidate results and return your final JSON response. Do not start new multi-step work.`,
            });
        }
        const response = await callQueryLLM(messages, toolIds);
        if (!response.tool_calls?.length) {
            return buildQueryReturn(response.content || '', toolCallCount);
        }
        const fnCalls = response.tool_calls.filter((tc) => tc.type === 'function');
        messages.push({
            role: 'assistant',
            content: response.content,
            tool_calls: fnCalls,
        });
        let roundHadError = false;
        for (const toolCall of fnCalls) {
            toolCallCount++;
            let args = {};
            try {
                args = JSON.parse(toolCall.function.arguments || '{}');
            }
            catch {
                args = {};
            }
            const result = await callMcpTool(toolCall.function.name, args);
            // Durable waitFor: if the tool returns a signal, pause until human responds
            if (result?.type === 'waitFor' && result?.signalId) {
                const ctx = hotmesh_1.Durable.workflow.workflowInfo();
                const workflowType = ctx.workflowTopic.replace(`${ctx.taskQueue}-`, '');
                await ltEnrichEscalationRouting({
                    escalationId: result.escalationId,
                    signalRouting: {
                        taskQueue: ctx.taskQueue,
                        workflowType,
                        workflowId: ctx.workflowId,
                        signalId: result.signalId,
                    },
                    claimForUserId: envelope.lt?.userId,
                });
                const signalData = await hotmesh_1.Durable.workflow.condition(result.signalId);
                messages.push({
                    role: 'tool',
                    tool_call_id: toolCall.id,
                    content: JSON.stringify(signalData),
                });
                continue;
            }
            const resultStr = (0, tool_result_guard_1.sanitizeToolResult)(result);
            messages.push({
                role: 'tool',
                tool_call_id: toolCall.id,
                content: resultStr,
            });
            // Track repeated errors: same tool + same error = stuck
            const isError = resultStr.includes('MCP error') || resultStr.includes('"error"');
            if (isError) {
                roundHadError = true;
                const errorKey = `${toolCall.function.name}::${resultStr.slice(0, 200)}`;
                if (errorKey === lastErrorKey) {
                    repeatedErrorCount++;
                }
                else {
                    lastErrorKey = errorKey;
                    repeatedErrorCount = 1;
                }
                if (repeatedErrorCount >= MAX_REPEATED_ERRORS) {
                    messages.push({
                        role: 'user',
                        content: `[CIRCUIT BREAKER] The tool "${toolCall.function.name}" has returned the same error ${repeatedErrorCount} times. Stop retrying this tool. Summarize what you accomplished and what failed.`,
                    });
                    const bailResponse = await callQueryLLM(messages, undefined);
                    return buildQueryReturn(bailResponse.content || 'Workflow stopped: repeated tool errors.', toolCallCount, [{ name: 'circuit_breaker', value: toolCall.function.name }]);
                }
            }
        }
        if (!roundHadError) {
            lastErrorKey = '';
            repeatedErrorCount = 0;
        }
    }
    // Exhausted rounds — ask LLM for diagnostic summary
    messages.push({
        role: 'user',
        content: prompts_1.ROUNDS_EXHAUSTED_DIAGNOSTIC_PROMPT,
    });
    const finalResponse = await callQueryLLM(messages, undefined);
    const parsed = parseJsonResponse(finalResponse.content || '');
    return {
        type: 'return',
        data: {
            ...parsed,
            tool_calls_made: toolCallCount,
            rounds_exhausted: true,
        },
        milestones: [
            { name: 'mcp_query', value: 'completed' },
            { name: 'tool_calls', value: String(toolCallCount) },
            { name: 'rounds_exhausted', value: 'true' },
        ],
    };
}
/** Build the LTReturn for a completed query, with standard + enriched milestones. */
function buildQueryReturn(content, toolCallCount, extraMilestones = []) {
    const parsed = parseJsonResponse(content);
    const milestones = [
        { name: 'mcp_query', value: 'completed' },
        { name: 'tool_calls', value: String(toolCallCount) },
    ];
    if (parsed.knowledge_updated?.length) {
        milestones.push({ name: 'knowledge_updated', value: String(parsed.knowledge_updated.length) });
    }
    if (parsed.compilation_candidate) {
        milestones.push({ name: 'compilation_candidate', value: 'true' });
    }
    milestones.push(...extraMilestones);
    return {
        type: 'return',
        data: { ...parsed, tool_calls_made: toolCallCount },
        milestones,
    };
}
function parseJsonResponse(content) {
    const cleaned = content
        .replace(/^```(?:json)?\s*/m, '')
        .replace(/\s*```$/m, '')
        .trim();
    try {
        return JSON.parse(cleaned);
    }
    catch {
        return {
            title: 'Query Complete',
            summary: cleaned || 'No response generated.',
            result: null,
        };
    }
}
