"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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
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;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JobProcessor = void 0;
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const event_service_1 = require("../events/event.service");
const execution_lifecycle_hooks_1 = require("../execution-lifecycle/execution-lifecycle-hooks");
const execution_utils_1 = require("../executions/execution.utils");
const manual_execution_service_1 = require("../manual-execution.service");
const node_types_1 = require("../node-types");
const WorkflowExecuteAdditionalData = __importStar(require("../workflow-execute-additional-data"));
let JobProcessor = class JobProcessor {
    constructor(logger, executionRepository, workflowRepository, nodeTypes, instanceSettings, manualExecutionService, executionsConfig, eventService) {
        this.logger = logger;
        this.executionRepository = executionRepository;
        this.workflowRepository = workflowRepository;
        this.nodeTypes = nodeTypes;
        this.instanceSettings = instanceSettings;
        this.manualExecutionService = manualExecutionService;
        this.executionsConfig = executionsConfig;
        this.eventService = eventService;
        this.runningJobs = {};
        this.logger = this.logger.scoped('scaling');
    }
    async processJob(job) {
        const { executionId, loadStaticData } = job.data;
        const execution = await this.executionRepository.findSingleExecution(executionId, {
            includeData: true,
            unflattenData: true,
        });
        if (!execution) {
            throw new n8n_workflow_1.UnexpectedError(`Worker failed to find data for execution ${executionId} (job ${job.id})`);
        }
        if (execution.status === 'crashed')
            return { success: false };
        const workflowId = execution.workflowData.id;
        this.logger.info(`Worker started execution ${executionId} (job ${job.id})`, {
            executionId,
            workflowId,
            workflowName: execution.workflowData.name,
            jobId: job.id,
            ...(job.data.projectId !== undefined && { projectId: job.data.projectId }),
            ...(job.data.projectName !== undefined && { projectName: job.data.projectName }),
        });
        const startedAt = await this.executionRepository.setRunning(executionId);
        let { staticData } = execution.workflowData;
        if (loadStaticData) {
            const workflowData = await this.workflowRepository.findOne({
                select: ['id', 'staticData'],
                where: { id: workflowId },
            });
            if (workflowData === null) {
                throw new n8n_workflow_1.UnexpectedError(`Worker failed to find workflow ${workflowId} to run execution ${executionId} (job ${job.id})`);
            }
            staticData = workflowData.staticData;
        }
        const workflowSettings = execution.workflowData.settings ?? {};
        let workflowTimeout = workflowSettings.executionTimeout ?? this.executionsConfig.timeout;
        let executionTimeoutTimestamp;
        if (workflowTimeout > 0) {
            workflowTimeout = Math.min(workflowTimeout, this.executionsConfig.maxTimeout);
            executionTimeoutTimestamp = Date.now() + workflowTimeout * 1000;
        }
        const workflow = new n8n_workflow_1.Workflow({
            id: workflowId,
            name: execution.workflowData.name,
            nodes: execution.workflowData.nodes,
            connections: execution.workflowData.connections,
            active: (0, execution_utils_1.getWorkflowActiveStatusFromWorkflowData)(execution.workflowData),
            nodeTypes: this.nodeTypes,
            staticData,
            settings: execution.workflowData.settings,
        });
        const additionalData = await WorkflowExecuteAdditionalData.getBase({
            workflowId,
            executionTimeoutTimestamp,
            workflowSettings: execution.workflowData.settings,
        });
        additionalData.streamingEnabled = job.data.streamingEnabled;
        additionalData.restartExecutionId = job.data.restartExecutionId;
        const { pushRef } = job.data;
        const lifecycleHooks = (0, execution_lifecycle_hooks_1.getLifecycleHooksForScalingWorker)({
            executionMode: execution.mode,
            workflowData: execution.workflowData,
            retryOf: execution.retryOf,
            pushRef,
            userId: execution.data.manualData?.userId,
        }, executionId);
        additionalData.hooks = lifecycleHooks;
        if (pushRef) {
            additionalData.sendDataToUI = WorkflowExecuteAdditionalData.sendDataToUI.bind({ pushRef });
        }
        lifecycleHooks.addHandler('sendResponse', async (response) => {
            if (job.data.isMcpExecution && job.data.mcpSessionId) {
                const msg = {
                    kind: 'mcp-response',
                    executionId,
                    mcpType: job.data.mcpType ?? 'service',
                    sessionId: job.data.mcpSessionId,
                    messageId: job.data.mcpMessageId ?? '',
                    response,
                    workerId: this.instanceSettings.hostId,
                };
                await job.progress(msg);
                return;
            }
            const msg = {
                kind: 'respond-to-webhook',
                executionId,
                response: this.encodeWebhookResponse(response),
                workerId: this.instanceSettings.hostId,
            };
            await job.progress(msg);
        });
        lifecycleHooks.addHandler('sendChunk', async (chunk) => {
            const msg = {
                kind: 'send-chunk',
                executionId,
                chunkText: chunk,
                workerId: this.instanceSettings.hostId,
            };
            await job.progress(msg);
        });
        additionalData.executionId = executionId;
        additionalData.setExecutionStatus = (status) => {
            this.logger.debug(`Queued worker execution status for execution ${executionId} (job ${job.id}) is "${status}"`, {
                executionId,
                workflowId,
                workflowName: execution.workflowData.name,
                jobId: job.id,
                ...(job.data.projectId && { projectId: job.data.projectId }),
                ...(job.data.projectName && { projectName: job.data.projectName }),
            });
        };
        let workflowExecute;
        let workflowRun;
        const { startData, resultData, manualData } = execution.data;
        if (execution.data?.executionData) {
            workflowExecute = new n8n_core_1.WorkflowExecute(additionalData, execution.mode, execution.data);
            workflowRun = workflowExecute.processRunExecutionData(workflow);
        }
        else {
            const data = {
                executionMode: execution.mode,
                workflowData: execution.workflowData,
                destinationNode: startData?.destinationNode,
                startNodes: startData?.startNodes,
                runData: resultData.runData,
                pinData: resultData.pinData,
                dirtyNodeNames: manualData?.dirtyNodeNames,
                triggerToStartFrom: manualData?.triggerToStartFrom,
                userId: manualData?.userId,
            };
            try {
                workflowRun = this.manualExecutionService.runManually(data, workflow, additionalData, executionId, resultData.pinData);
            }
            catch (error) {
                if (error instanceof n8n_core_1.WorkflowHasIssuesError) {
                    const now = new Date();
                    const runData = {
                        mode: 'manual',
                        status: 'error',
                        finished: false,
                        startedAt: now,
                        stoppedAt: now,
                        data: (0, n8n_workflow_1.createRunExecutionData)({ resultData: { error, runData: {} } }),
                        storedAt: execution.storedAt,
                    };
                    await lifecycleHooks.runHook('workflowExecuteAfter', [runData]);
                    return { success: false };
                }
                throw error;
            }
        }
        const runningJob = {
            run: workflowRun,
            executionId,
            workflowId: execution.workflowId,
            workflowName: execution.workflowData.name,
            mode: execution.mode,
            startedAt,
            retryOf: execution.retryOf ?? undefined,
            status: execution.status,
        };
        this.runningJobs[job.id] = runningJob;
        const run = await workflowRun;
        delete this.runningJobs[job.id];
        if (run?.status === 'canceled') {
            throw new n8n_workflow_1.ManualExecutionCancelledError(executionId);
        }
        const props = process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING
            ? this.deriveJobFinishedProps(run, startedAt)
            : await this.fetchJobFinishedResult(executionId);
        this.logger.info(`Worker finished execution ${executionId} (job ${job.id})`, {
            executionId,
            workflowId,
            workflowName: execution.workflowData.name,
            jobId: job.id,
            success: props.success,
            ...(job.data.projectId && { projectId: job.data.projectId }),
            ...(job.data.projectName && { projectName: job.data.projectName }),
        });
        const msg = {
            kind: 'job-finished',
            version: 2,
            executionId,
            workerId: this.instanceSettings.hostId,
            ...props,
        };
        await job.progress(msg);
        if (job.data.isMcpExecution &&
            job.data.mcpType === 'trigger' &&
            job.data.mcpSessionId &&
            job.data.mcpToolCall?.sourceNodeName) {
            const { toolName, arguments: toolArgs, sourceNodeName } = job.data.mcpToolCall;
            let toolResult;
            try {
                toolResult = await this.invokeTool(workflow, sourceNodeName, toolArgs, additionalData);
            }
            catch (error) {
                this.logger.error('Tool node execution failed for MCP Trigger', {
                    executionId,
                    toolName,
                    sourceNodeName,
                    error: error instanceof Error ? error.message : String(error),
                });
                toolResult = {
                    error: error instanceof Error
                        ? { message: error.message, name: error.name }
                        : { message: String(error) },
                };
            }
            const mcpMsg = {
                kind: 'mcp-response',
                executionId,
                mcpType: 'trigger',
                sessionId: job.data.mcpSessionId,
                messageId: job.data.mcpMessageId ?? '',
                response: toolResult,
                workerId: this.instanceSettings.hostId,
            };
            await job.progress(mcpMsg);
        }
        else if (job.data.isMcpExecution && job.data.mcpSessionId) {
            const mcpMsg = {
                kind: 'mcp-response',
                executionId,
                mcpType: job.data.mcpType ?? 'service',
                sessionId: job.data.mcpSessionId,
                messageId: job.data.mcpMessageId ?? '',
                response: { success: props.success },
                workerId: this.instanceSettings.hostId,
            };
            await job.progress(mcpMsg);
        }
        return { success: true };
    }
    deriveJobFinishedProps(run, startedAt) {
        return {
            success: run.status !== 'error' && run.data.resultData.error === undefined,
            status: run.status,
            error: run.data.resultData.error,
            startedAt,
            stoppedAt: run.stoppedAt,
            lastNodeExecuted: run.data.resultData.lastNodeExecuted,
            usedDynamicCredentials: !!run.data.executionData?.runtimeData?.credentials,
            metadata: run.data.resultData.metadata,
        };
    }
    async fetchJobFinishedResult(executionId) {
        const execution = await this.executionRepository.findSingleExecution(executionId, {
            includeData: true,
            unflattenData: true,
        });
        if (!execution) {
            throw new n8n_workflow_1.UnexpectedError(`Worker failed to find execution ${executionId} immediately after workflow completed`);
        }
        return {
            success: execution.status !== 'error' && execution.data?.resultData?.error === undefined,
            status: execution.status,
            error: execution.data?.resultData?.error,
            startedAt: execution.startedAt,
            stoppedAt: execution.stoppedAt,
            lastNodeExecuted: execution.data?.resultData?.lastNodeExecuted,
            usedDynamicCredentials: !!execution.data?.executionData?.runtimeData?.credentials,
            metadata: execution.data?.resultData?.metadata,
        };
    }
    stopJob(jobId) {
        const runningJob = this.runningJobs[jobId];
        if (!runningJob)
            return;
        const { executionId, workflowId, workflowName } = runningJob;
        this.eventService.emit('execution-cancelled', {
            executionId,
            workflowId,
            workflowName,
            reason: 'manual',
        });
        runningJob.run.cancel();
        delete this.runningJobs[jobId];
    }
    getRunningJobIds() {
        return Object.keys(this.runningJobs);
    }
    getRunningJobsSummary() {
        return Object.values(this.runningJobs).map(({ run, ...summary }) => summary);
    }
    encodeWebhookResponse(response) {
        if (typeof response === 'object' && Buffer.isBuffer(response.body)) {
            response.body = {
                '__@N8nEncodedBuffer@__': response.body.toString(n8n_workflow_1.BINARY_ENCODING),
            };
        }
        return response;
    }
    async invokeTool(workflow, sourceNodeName, toolArgs, additionalData) {
        const toolNode = workflow.getNode(sourceNodeName);
        if (!toolNode) {
            throw new n8n_workflow_1.UnexpectedError(`Tool node "${sourceNodeName}" not found in workflow`);
        }
        const nodeType = this.nodeTypes.getByNameAndVersion(toolNode.type, toolNode.typeVersion);
        const validatedToolArgs = typeof toolArgs === 'object' && toolArgs !== null && !Array.isArray(toolArgs) ? toolArgs : {};
        const inputData = [
            [
                {
                    json: validatedToolArgs,
                },
            ],
        ];
        const runExecutionData = (0, n8n_workflow_1.createRunExecutionData)({});
        const executeData = {
            node: toolNode,
            data: {
                main: inputData,
            },
            source: null,
        };
        const closeFunctions = [];
        const context = new n8n_core_1.SupplyDataContext(workflow, toolNode, additionalData, 'webhook', runExecutionData, 0, inputData[0], { main: inputData }, n8n_workflow_1.NodeConnectionTypes.AiTool, executeData, closeFunctions);
        try {
            if (nodeType.supplyData) {
                const supplyDataResult = await nodeType.supplyData.call(context, 0);
                const tool = supplyDataResult.response;
                if (!tool || typeof tool.invoke !== 'function') {
                    throw new n8n_workflow_1.UnexpectedError(`Tool node "${sourceNodeName}" did not return a valid Tool`);
                }
                return await tool.invoke(validatedToolArgs);
            }
            if (nodeType.execute && nodeType.description.outputs.includes(n8n_workflow_1.NodeConnectionTypes.AiTool)) {
                context.addInputData(n8n_workflow_1.NodeConnectionTypes.AiTool, [
                    [{ json: validatedToolArgs }],
                ]);
                const result = await nodeType.execute.call(context);
                const response = result?.[0]?.flatMap((item) => item.json);
                context.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, 0, [
                    [{ json: { response } }],
                ]);
                return response;
            }
            throw new n8n_workflow_1.UnexpectedError(`Tool node "${sourceNodeName}" does not have supplyData or execute method`);
        }
        finally {
            for (const closeFunction of closeFunctions) {
                try {
                    await closeFunction();
                }
                catch (error) {
                    this.logger.warn(`Error closing tool resource: ${error}`);
                }
            }
        }
    }
};
exports.JobProcessor = JobProcessor;
exports.JobProcessor = JobProcessor = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [backend_common_1.Logger,
        db_1.ExecutionRepository,
        db_1.WorkflowRepository,
        node_types_1.NodeTypes,
        n8n_core_1.InstanceSettings,
        manual_execution_service_1.ManualExecutionService,
        config_1.ExecutionsConfig,
        event_service_1.EventService])
], JobProcessor);
//# sourceMappingURL=job-processor.js.map