"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mcpTriage = mcpTriage;
const hotmesh_1 = require("@hotmeshio/hotmesh");
const prompts_1 = require("./prompts");
const response_1 = require("./response");
const tool_result_guard_1 = require("../tool-result-guard");
const activities_proxy_1 = require("./activities-proxy");
// ── Workflow ─────────────────────────────────────────────────
/**
 * MCP Triage workflow (leaf).
 *
 * Activated when a human resolver flags `needsTriage` in their resolution
 * payload. Dynamically adapts to ANY workflow type using available
 * MCP tools scoped by tag affinity.
 *
 * Tool ecosystem grows over time:
 * - Built-in servers: translation, vision, mcp-workflows, human-queue,
 *   workflow-compiler, db, http-fetch, file-storage, oauth, claude-code
 * - User-registered external MCP servers
 * - Compiled YAML workflows from past triage executions
 *
 * Tools are scoped by the original workflow's `tool_tags` configuration
 * (plus base tags: workflows, compiled, database). Full tool definitions
 * are cached at the module level — only lightweight IDs flow through
 * the durable execution log.
 *
 * **First entry** (no `envelope.resolver`):
 *   1. Gather upstream tasks and escalation history
 *   2. Load scoped tools (tag-filtered, cached)
 *   3. LLM agentic loop with tool IDs
 *   4. Returns `{ correctedData }` or escalates to engineer
 *
 * **Re-entry** (has `envelope.resolver` — engineer responded):
 *   1. Engineer may have installed new tools or provided guidance
 *   2. LLM re-evaluates with fresh tool inventory
 *   3. Returns `{ correctedData }` or re-escalates
 */
async function mcpTriage(envelope) {
    const { originId, } = envelope.data;
    // ── Re-entry: engineer responded to our escalation ──
    if (envelope.resolver) {
        const resolver = envelope.resolver;
        return runTriageLLM(envelope, {
            additionalContext: prompts_1.TRIAGE_REENTRY_CONTEXT.replace('%RESOLVER_JSON%', JSON.stringify(resolver, null, 2)),
        });
    }
    // ── First entry: gather context and let LLM diagnose + fix ──
    const upstreamTasks = await (0, activities_proxy_1.getUpstreamTasks)(originId);
    const escalationHistory = await (0, activities_proxy_1.getEscalationHistory)(originId);
    const context = buildTriageContext(envelope.data, upstreamTasks, escalationHistory);
    return runTriageLLM(envelope, { additionalContext: context });
}
// ── LLM Agentic Loop ────────────────────────────────────────────
async function runTriageLLM(envelope, opts) {
    const { originalWorkflowType } = envelope.data;
    // 1. Infer tool tags from the original workflow type (from config cache, no DB hit)
    const workflowTags = await (0, activities_proxy_1.getToolTags)(originalWorkflowType);
    // 2. Load scoped tools with strategy advisor
    //    (Compiled workflow discovery happens in mcpTriageRouter, not here —
    //    this leaf only runs when no compiled match was found.)
    const raw = await (0, activities_proxy_1.loadTriageTools)(workflowTags.length > 0 ? workflowTags : undefined);
    // 3. Build system prompt with tool inventory
    const toolIds = raw.toolIds;
    const inventoryParts = [
        raw.strategy,
        `## Available MCP Servers\n${raw.inventory}`,
    ].filter(Boolean).join('\n\n');
    const systemPrompt = (0, prompts_1.TRIAGE_SYSTEM_PROMPT)(inventoryParts);
    const messages = [
        { role: 'system', content: systemPrompt },
        {
            role: 'user',
            content: `Handle this triage request:\n\n${opts.additionalContext}`,
        },
    ];
    let toolCallCount = 0;
    // Repeated-error circuit breaker
    const MAX_REPEATED_ERRORS = 2;
    let lastErrorKey = '';
    let repeatedErrorCount = 0;
    const BUDGET_WARNING_THRESHOLD = 3;
    for (let round = 0; round < activities_proxy_1.MAX_TOOL_ROUNDS; round++) {
        const remaining = activities_proxy_1.MAX_TOOL_ROUNDS - round;
        // Inject budget warning when approaching the limit
        if (remaining <= BUDGET_WARNING_THRESHOLD && remaining < activities_proxy_1.MAX_TOOL_ROUNDS) {
            messages.push({
                role: 'user',
                content: `[Rounds: ${remaining} remaining] Wrap up: consolidate your diagnosis and return your final JSON response.`,
            });
        }
        // Only lightweight toolIds (string[]) flow through the durable pipe
        const response = await (0, activities_proxy_1.callTriageLLM)(messages, toolIds);
        // No tool calls — LLM has produced its final answer
        if (!response.tool_calls?.length) {
            return (0, response_1.handleFinalResponse)(response.content || '', envelope, toolCallCount, activities_proxy_1.responseDeps);
        }
        // Execute each tool call
        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 = {};
            }
            // callTool returns errors as data so the LLM can adapt
            const result = await (0, activities_proxy_1.callTriageTool)(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 (0, activities_proxy_1.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 your diagnosis and what failed.`,
                    });
                    const bailResponse = await (0, activities_proxy_1.callTriageLLM)(messages, undefined);
                    return (0, response_1.handleFinalResponse)(bailResponse.content || '', envelope, toolCallCount, activities_proxy_1.responseDeps);
                }
            }
        }
        if (!roundHadError) {
            lastErrorKey = '';
            repeatedErrorCount = 0;
        }
    }
    // Exhausted rounds — ask for final synthesis without tools
    messages.push({
        role: 'user',
        content: prompts_1.TRIAGE_EXHAUSTED_ROUNDS,
    });
    const finalResponse = await (0, activities_proxy_1.callTriageLLM)(messages, undefined);
    return (0, response_1.handleFinalResponse)(finalResponse.content || '', envelope, toolCallCount, activities_proxy_1.responseDeps);
}
// ── Context builders ─────────────────────────────────────────────
/** Assemble the triage context string from workflow lineage and history. */
function buildTriageContext(data, upstreamTasks, escalationHistory) {
    const parts = [
        `**Original Workflow**: \`${data.originalWorkflowType}\` (queue: \`${data.originalTaskQueue}\`)`,
        `**Origin ID**: ${data.originId}`,
        `**Escalation Data** (what the workflow reported when it escalated):\n\`\`\`json\n${JSON.stringify(data.escalationPayload, null, 2)}\n\`\`\``,
        `**Resolver Payload** (what the human submitted):\n\`\`\`json\n${JSON.stringify(data.resolverPayload, null, 2)}\n\`\`\``,
    ];
    if (upstreamTasks.length > 0) {
        parts.push(`**Upstream Tasks** (${upstreamTasks.length}):\n\`\`\`json\n${JSON.stringify(upstreamTasks.map((t) => ({ id: t.id, type: t.workflow_type, status: t.status })), null, 2)}\n\`\`\``);
    }
    if (escalationHistory.length > 0) {
        parts.push(`**Escalation History** (${escalationHistory.length}):\n\`\`\`json\n${JSON.stringify(escalationHistory.map((e) => ({ id: e.id, type: e.type, role: e.role, status: e.status, description: e.description })), null, 2)}\n\`\`\``);
    }
    return parts.join('\n\n');
}
