UNPKG

@n8n-plus/n8n-plus

Version:

n8n Workflow Automation Tool (plus edition)

3,671 lines 161 kB
"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.InstanceAiService = void 0;
const api_types_1 = require("@n8n/api-types");
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const decorators_1 = require("@n8n/decorators");
const n8n_core_1 = require("n8n-core");
const ssrf_protection_service_1 = require("../../services/ssrf/ssrf-protection.service");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const url_service_1 = require("../../services/url.service");
const instance_ai_1 = require("@n8n/instance-ai");
const workflow_sdk_1 = require("@n8n/workflow-sdk");
const nanoid_1 = require("nanoid");
const n8n_workflow_1 = require("n8n-workflow");
const uuid_1 = require("uuid");
const constants_1 = require("../../constants");
const event_service_1 = require("../../events/event.service");
const source_control_preferences_service_ee_1 = require("../../modules/source-control.ee/source-control-preferences.service.ee");
const ai_service_1 = require("../../services/ai.service");
const push_1 = require("../../push");
const telemetry_1 = require("../../telemetry");
const in_process_event_bus_1 = require("./event-bus/in-process-event-bus");
const filesystem_1 = require("./filesystem");
const instance_ai_settings_service_1 = require("./instance-ai-settings.service");
const instance_ai_adapter_service_1 = require("./instance-ai.adapter.service");
const internal_messages_1 = require("./internal-messages");
const db_snapshot_storage_1 = require("./storage/db-snapshot-storage");
const db_iteration_log_storage_1 = require("./storage/db-iteration-log-storage");
const typeorm_agent_checkpoint_store_1 = require("./storage/typeorm-agent-checkpoint-store");
const typeorm_agent_memory_1 = require("./storage/typeorm-agent-memory");
const proxy_token_manager_1 = require("../../services/proxy-token-manager");
const instance_ai_thread_repository_1 = require("./repositories/instance-ai-thread.repository");
const trace_replay_state_1 = require("./trace-replay-state");
const liveness_1 = require("./liveness");
const run_trace_metadata_1 = require("./run-trace-metadata");
function getErrorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function isTelemetryConfigurableAgent(agent) {
    return (typeof agent === 'object' &&
        agent !== null &&
        typeof Reflect.get(agent, 'telemetry') === 'function');
}
const INSTANCE_AI_CHECKPOINT_PRUNE_RETRY_MS = 30 * 1000;
function isTextMessagePart(part) {
    return (typeof part === 'object' &&
        part !== null &&
        'type' in part &&
        part.type === 'text' &&
        'text' in part &&
        typeof part.text === 'string');
}
const ORCHESTRATOR_AGENT_ID = 'agent-001';
const SANDBOX_NAME_MAX_LEN = 63;
const SANDBOX_LABEL_MAX_LEN = 63;
const NAME_PREFIX_SLUG_MAX_LEN = 24;
const SHORT_RUN_ID_LEN = 8;
const DEFAULT_SANDBOX_TTL_MS = 15 * 60 * 1000;
function slugifySandboxName(value, maxLen) {
    const slug = value
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '');
    return slug.slice(0, maxLen).replace(/-+$/, '');
}
function slugifySandboxLabel(value, maxLen) {
    return value
        .replace(/[^A-Za-z0-9_.-]+/g, '-')
        .replace(/^[-.]+|[-.]+$/g, '')
        .slice(0, maxLen)
        .replace(/[-.]+$/, '');
}
function getThreadScopedSandboxName(threadId) {
    return `instance-ai-thread-${threadId}`;
}
function buildThreadScopedSandboxName(threadId, namePrefix, runId) {
    const parts = [];
    if (namePrefix) {
        const prefixSlug = slugifySandboxName(namePrefix, NAME_PREFIX_SLUG_MAX_LEN);
        if (prefixSlug)
            parts.push(prefixSlug);
    }
    if (runId) {
        const runSlug = slugifySandboxName(runId, SHORT_RUN_ID_LEN);
        if (runSlug)
            parts.push(runSlug);
    }
    const threadSlug = slugifySandboxName(getThreadScopedSandboxName(threadId), SANDBOX_NAME_MAX_LEN);
    if (threadSlug)
        parts.push(threadSlug);
    const name = slugifySandboxName(parts.join('-'), SANDBOX_NAME_MAX_LEN);
    if (!name)
        throw new n8n_workflow_1.UnexpectedError('Failed to build thread-scoped sandbox name');
    return name;
}
function buildThreadScopedSandboxLabels(threadId, namePrefix, runId) {
    const baseName = getThreadScopedSandboxName(threadId);
    const labels = {
        'n8n-builder': slugifySandboxLabel(baseName, SANDBOX_LABEL_MAX_LEN),
        thread_id: slugifySandboxLabel(threadId, SANDBOX_LABEL_MAX_LEN),
    };
    if (namePrefix)
        labels.name_prefix = slugifySandboxLabel(namePrefix, SANDBOX_LABEL_MAX_LEN);
    if (runId)
        labels.run_id = slugifySandboxLabel(runId, SANDBOX_LABEL_MAX_LEN);
    return labels;
}
function withThreadScopedSandboxIdentity(config, threadId, runId) {
    if (!config.enabled || config.provider !== 'daytona')
        return config;
    const name = buildThreadScopedSandboxName(threadId, config.namePrefix, runId);
    return {
        ...config,
        id: name,
        name,
        labels: {
            ...buildThreadScopedSandboxLabels(threadId, config.namePrefix, runId),
            ...config.labels,
        },
    };
}
function getUserFacingErrorMessage(error) {
    if (error instanceof n8n_workflow_1.UserError) {
        return error.message;
    }
    if (error instanceof n8n_workflow_1.OperationalError) {
        return 'I hit an operational error before I could finish that response. Please try again.';
    }
    if (error instanceof n8n_workflow_1.UnexpectedError) {
        return 'Something went wrong before I could finish that response. Please try again.';
    }
    return 'Something went wrong before I could finish that response. Please try again.';
}
function getBackgroundOutcomeResponseId(outcome) {
    return `background-outcome:${outcome.id}`;
}
function createTerminalOutcomeAgentTree(outcome, responseId) {
    return {
        agentId: ORCHESTRATOR_AGENT_ID,
        role: 'orchestrator',
        status: outcome.status === 'cancelled'
            ? 'cancelled'
            : outcome.status === 'failed'
                ? 'error'
                : 'completed',
        textContent: outcome.userFacingMessage,
        reasoning: '',
        toolCalls: [],
        children: [],
        timeline: [{ type: 'text', content: outcome.userFacingMessage, responseId }],
    };
}
function appendTerminalOutcomeToAgentTree(tree, outcome, responseId) {
    const text = outcome.userFacingMessage.trim();
    if (!text)
        return { tree, appended: false };
    const alreadyInTimeline = tree.timeline.some((entry) => entry.type === 'text' && entry.responseId === responseId);
    if (alreadyInTimeline) {
        return { tree, appended: false };
    }
    return {
        appended: true,
        tree: {
            ...tree,
            textContent: tree.textContent ? `${tree.textContent}\n\n${outcome.userFacingMessage}` : text,
            timeline: [
                ...tree.timeline,
                { type: 'text', content: outcome.userFacingMessage, responseId },
            ],
        },
    };
}
function createInertAbortSignal() {
    return new AbortController().signal;
}
function getAbortReason(signal) {
    const reason = signal.reason;
    if (typeof reason === 'object' &&
        reason !== null &&
        'name' in reason &&
        reason.name === 'AbortError') {
        return 'user_cancelled';
    }
    if (reason instanceof Error)
        return reason.message;
    return typeof reason === 'string' ? reason : 'user_cancelled';
}
const INSTANCE_AI_FEEDBACK_NAMESPACE = 'c5be4c87-5b6e-49ed-afe1-9c5c1f99a5c0';
const MAX_CONCURRENT_BACKGROUND_TASKS_PER_THREAD = 5;
function stringifyForContextValue(value) {
    if (typeof value === 'string')
        return value;
    try {
        return JSON.stringify(value);
    }
    catch {
        return String(value);
    }
}
const PLANNED_TASK_CONTEXT_VALUE_LIMIT = 1_500;
function truncateContextValue(value) {
    if (value.length <= PLANNED_TASK_CONTEXT_VALUE_LIMIT)
        return value;
    return `${value.slice(0, PLANNED_TASK_CONTEXT_VALUE_LIMIT)}...`;
}
function buildPlannedTaskConversationContext(task, graph) {
    if (!graph)
        return undefined;
    const parts = [
        `Approved plan task: ${task.title}`,
        `Task id: ${task.id}`,
        `Task kind: ${task.kind}`,
        `Plan run id: ${graph.planRunId}`,
    ];
    if (task.workflowId) {
        parts.push(`Target workflow id: ${task.workflowId}`);
    }
    const dependencies = graph.tasks.filter((candidate) => task.deps.includes(candidate.id));
    if (dependencies.length > 0) {
        parts.push('Completed dependency context:');
        for (const dependency of dependencies) {
            const dependencyParts = [
                `- ${dependency.id} (${dependency.kind}, ${dependency.status}): ${dependency.title}`,
            ];
            if (dependency.result) {
                dependencyParts.push(`result=${truncateContextValue(dependency.result)}`);
            }
            if (dependency.error) {
                dependencyParts.push(`error=${truncateContextValue(dependency.error)}`);
            }
            if (dependency.outcome) {
                dependencyParts.push(`outcome=${truncateContextValue(stringifyForContextValue(dependency.outcome))}`);
            }
            parts.push(dependencyParts.join(' '));
        }
    }
    return parts.join('\n');
}
function getProxyFetch() {
    const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
    if (!proxyUrl)
        return undefined;
    const { ProxyAgent } = require('undici');
    const dispatcher = new ProxyAgent(proxyUrl);
    return (async (url, init) => await globalThis.fetch(url, {
        ...init,
        dispatcher,
    }));
}
function toConfirmationData(request) {
    switch (request.kind) {
        case 'approval':
            return { approved: request.approved, userInput: request.userInput };
        case 'domainAccessApprove':
            return { approved: true, domainAccessAction: request.domainAccessAction };
        case 'domainAccessDeny':
            return { approved: false };
        case 'planDeny':
            return { approved: false, denied: true };
        case 'questions':
            return { approved: true, answers: request.answers };
        case 'credentialSelection':
            return { approved: true, credentials: request.credentials };
        case 'resourceDecision':
            return { approved: true, resourceDecision: request.resourceDecision };
        case 'setupWorkflowApply':
            return {
                approved: true,
                action: 'apply',
                nodeCredentials: request.nodeCredentials,
                nodeParameters: request.nodeParameters,
            };
        case 'setupWorkflowTestTrigger':
            return {
                approved: true,
                action: 'test-trigger',
                testTriggerNode: request.testTriggerNode,
                nodeCredentials: request.nodeCredentials,
                nodeParameters: request.nodeParameters,
            };
    }
}
let InstanceAiService = class InstanceAiService {
    get mcpClientManager() {
        if (!this._mcpClientManager) {
            this._mcpClientManager = new instance_ai_1.McpClientManager(this._ssrfProtectionConfig.enabled ? this._ssrfProtectionService : undefined);
        }
        return this._mcpClientManager;
    }
    constructor(logger, globalConfig, instanceSettings, adapterService, eventBus, settingsService, agentMemory, checkpointStore, aiService, push, threadRepo, urlService, dbSnapshotStorage, dbIterationLogStorage, sourceControlPreferencesService, telemetry, userRepository, aiBuilderTemporaryWorkflowRepository, errorReporter, ssrfProtectionConfig, ssrfProtectionService, eventService) {
        this.instanceSettings = instanceSettings;
        this.adapterService = adapterService;
        this.eventBus = eventBus;
        this.settingsService = settingsService;
        this.agentMemory = agentMemory;
        this.checkpointStore = checkpointStore;
        this.aiService = aiService;
        this.push = push;
        this.threadRepo = threadRepo;
        this.urlService = urlService;
        this.dbSnapshotStorage = dbSnapshotStorage;
        this.dbIterationLogStorage = dbIterationLogStorage;
        this.sourceControlPreferencesService = sourceControlPreferencesService;
        this.telemetry = telemetry;
        this.userRepository = userRepository;
        this.aiBuilderTemporaryWorkflowRepository = aiBuilderTemporaryWorkflowRepository;
        this.errorReporter = errorReporter;
        this.eventService = eventService;
        this.runState = new instance_ai_1.RunStateRegistry();
        this.backgroundTasks = new instance_ai_1.BackgroundTaskManager(MAX_CONCURRENT_BACKGROUND_TASKS_PER_THREAD);
        this.traceContextsByRunId = new Map();
        this.sandboxes = new Map();
        this.sandboxCreations = new Map();
        this.gatewayRegistry = new filesystem_1.LocalGatewayRegistry();
        this.domainAccessTrackersByThread = new Map();
        this.threadPushRef = new Map();
        this.schedulerLocks = new Map();
        this.pendingCheckpointReentries = new Map();
        this.pendingTerminalOutcomes = new Map();
        this.creditedThreads = new Set();
        this.traceReplay = new trace_replay_state_1.TraceReplayState();
        this.checkpointPruningStopped = true;
        this.logger = logger.scoped('instance-ai');
        this.instanceAiConfig = globalConfig.instanceAi;
        const livenessPolicyConfig = (0, instance_ai_1.createInstanceAiLivenessPolicyConfig)({
            confirmationTimeoutMs: this.instanceAiConfig.confirmationTimeout,
        });
        this.liveness = new liveness_1.InstanceAiLivenessService({
            policy: new instance_ai_1.InstanceAiLivenessPolicy(livenessPolicyConfig),
            backgroundTaskIdleTimeoutMs: livenessPolicyConfig.backgroundTaskIdleTimeoutMs,
            runState: this.runState,
            backgroundTasks: this.backgroundTasks,
            eventBus: this.eventBus,
            logger: this.logger,
            finalizeCancelledSuspendedRun: (suspended, reason) => {
                void this.finalizeCancelledSuspendedRun(suspended, reason);
            },
        });
        this.defaultTimeZone = globalConfig.generic.timezone;
        const restEndpoint = globalConfig.endpoints.rest;
        this.oauth2CallbackUrl = `${this.urlService.getInstanceBaseUrl()}/${restEndpoint}/oauth2-credential/callback`;
        this.webhookBaseUrl = `${this.urlService.getWebhookBaseUrl()}${globalConfig.endpoints.webhook}`;
        this.formBaseUrl = `${this.urlService.getWebhookBaseUrl()}${globalConfig.endpoints.form}`;
        this._ssrfProtectionConfig = ssrfProtectionConfig;
        this._ssrfProtectionService = ssrfProtectionService;
        this.eventService.on('instance-ai-settings-updated', ({ mcpSettingsChanged }) => {
            if (!mcpSettingsChanged)
                return;
            if (!this._mcpClientManager)
                return;
            this._mcpClientManager.disconnect().catch((error) => {
                this.logger.warn('Failed to disconnect MCP clients after settings change', {
                    error: getErrorMessage(error),
                });
            });
        });
        this.liveness.start();
        if (this.instanceSettings.isLeader)
            this.startCheckpointPruning();
    }
    getSandboxConfigFromEnv() {
        const { sandboxEnabled, sandboxProvider, daytonaApiUrl, daytonaApiKey, n8nSandboxServiceUrl, n8nSandboxServiceApiKey, sandboxImage, sandboxTimeout, sandboxNamePrefix, daytonaTokenRefreshSkewMs, } = this.instanceAiConfig;
        if (!sandboxEnabled) {
            return {
                enabled: false,
                provider: sandboxProvider === 'n8n-sandbox'
                    ? 'n8n-sandbox'
                    : sandboxProvider === 'daytona'
                        ? 'daytona'
                        : 'local',
                timeout: sandboxTimeout,
            };
        }
        if (sandboxProvider === 'daytona') {
            return {
                enabled: true,
                provider: 'daytona',
                daytonaApiUrl: daytonaApiUrl || undefined,
                daytonaApiKey: daytonaApiKey || undefined,
                image: sandboxImage || undefined,
                n8nVersion: constants_1.N8N_VERSION || undefined,
                timeout: sandboxTimeout,
                namePrefix: sandboxNamePrefix || undefined,
                refreshSkewMs: daytonaTokenRefreshSkewMs,
            };
        }
        if (sandboxProvider === 'n8n-sandbox') {
            return {
                enabled: true,
                provider: 'n8n-sandbox',
                serviceUrl: n8nSandboxServiceUrl || undefined,
                apiKey: n8nSandboxServiceApiKey || undefined,
                timeout: sandboxTimeout,
            };
        }
        return {
            enabled: true,
            provider: 'local',
            timeout: sandboxTimeout,
        };
    }
    async resolveSandboxConfig(user) {
        const base = this.getSandboxConfigFromEnv();
        if (!base.enabled)
            return base;
        if (base.provider === 'daytona') {
            if (this.aiService.isProxyEnabled()) {
                const client = await this.aiService.getClient();
                const proxyConfig = await client.getSandboxProxyConfig();
                return {
                    ...base,
                    daytonaApiUrl: client.getSandboxProxyBaseUrl(),
                    image: proxyConfig.image,
                    logger: this.logger,
                    getAuthToken: async () => {
                        const token = await client.getBuilderApiProxyToken({ id: user.id }, { userMessageId: (0, nanoid_1.nanoid)() });
                        return token.accessToken;
                    },
                };
            }
            const daytona = await this.settingsService.resolveDaytonaConfig(user);
            return {
                ...base,
                daytonaApiUrl: daytona.apiUrl ?? base.daytonaApiUrl,
                daytonaApiKey: daytona.apiKey ?? base.daytonaApiKey,
            };
        }
        if (base.provider === 'n8n-sandbox') {
            const sandbox = await this.settingsService.resolveN8nSandboxConfig(user);
            return {
                ...base,
                serviceUrl: sandbox.serviceUrl ?? base.serviceUrl,
                apiKey: sandbox.apiKey ?? base.apiKey,
            };
        }
        return base;
    }
    async getOrCreateWorkspaceEntry(threadId, user, runId) {
        const existing = this.sandboxes.get(threadId);
        if (existing) {
            if (this.isSandboxEntryExpired(existing) && !this.isSandboxInUse(threadId)) {
                this.evictSandboxEntry(threadId, existing);
            }
            else {
                this.touchSandboxEntry(threadId, existing);
                return existing;
            }
        }
        const pending = this.sandboxCreations.get(threadId);
        if (pending)
            return await pending;
        const creation = this.createWorkspaceEntry(threadId, user, runId);
        this.sandboxCreations.set(threadId, creation);
        try {
            return await creation;
        }
        finally {
            this.sandboxCreations.delete(threadId);
        }
    }
    async getOrCreateWorkspace(threadId, user, context, runId) {
        const entry = await this.getOrCreateWorkspaceEntry(threadId, user, runId);
        if (entry)
            await this.ensureWorkspaceSetup(entry, context);
        return entry;
    }
    async ensureWorkspaceSetup(entry, context) {
        if (entry.setupComplete)
            return;
        entry.setupPromise ??= (0, instance_ai_1.setupSandboxWorkspace)(entry.workspace, context)
            .then(() => {
            entry.setupComplete = true;
        })
            .finally(() => {
            entry.setupPromise = undefined;
        });
        await entry.setupPromise;
    }
    async createWorkspaceEntry(threadId, user, runId) {
        const config = withThreadScopedSandboxIdentity(await this.resolveSandboxConfig(user), threadId, runId);
        if (!config.enabled)
            return undefined;
        const sandbox = await (0, instance_ai_1.createSandbox)(config, {
            logger: this.logger,
            errorReporter: this.errorReporter,
            useSnapshotFallback: true,
        });
        const workspace = (0, instance_ai_1.createWorkspace)(sandbox);
        if (!sandbox || !workspace)
            return undefined;
        try {
            await workspace.init();
        }
        catch (error) {
            try {
                await workspace.destroy();
            }
            catch {
            }
            throw error;
        }
        const entry = {
            sandbox,
            workspace,
            setupComplete: false,
            setupPromise: undefined,
            expiresAt: this.nextSandboxExpiry(),
        };
        this.sandboxes.set(threadId, entry);
        this.scheduleSandboxExpiry(threadId, entry);
        return entry;
    }
    evictSandboxEntry(threadId, entry) {
        if (this.sandboxes.get(threadId) !== entry)
            return;
        this.sandboxes.delete(threadId);
        if (entry.cleanupTimer) {
            clearTimeout(entry.cleanupTimer);
            entry.cleanupTimer = undefined;
        }
    }
    async destroySandbox(threadId, reason = 'thread_cleanup') {
        const entry = this.sandboxes.get(threadId);
        if (!entry?.sandbox)
            return;
        this.evictSandboxEntry(threadId, entry);
        try {
            await entry.workspace?.destroy();
        }
        catch (error) {
            this.logger.warn('Failed to destroy sandbox', {
                threadId,
                reason,
                error: error instanceof Error ? error.message : String(error),
            });
        }
    }
    get sandboxTtlMs() {
        return this.instanceAiConfig?.builderSandboxTtlMs ?? DEFAULT_SANDBOX_TTL_MS;
    }
    nextSandboxExpiry() {
        return Date.now() + this.sandboxTtlMs;
    }
    isSandboxEntryExpired(entry) {
        return this.sandboxTtlMs > 0 && entry.expiresAt <= Date.now();
    }
    touchSandboxEntry(threadId, entry) {
        if (this.sandboxTtlMs <= 0)
            return;
        entry.expiresAt = this.nextSandboxExpiry();
        this.scheduleSandboxExpiry(threadId, entry);
    }
    isSandboxInUse(threadId) {
        return Boolean(this.runState.getActiveRunId(threadId) ||
            this.runState.hasSuspendedRun(threadId) ||
            this.backgroundTasks.getRunningTasks(threadId).length > 0);
    }
    scheduleSandboxExpiry(threadId, entry) {
        if (this.sandboxTtlMs <= 0)
            return;
        if (entry.cleanupTimer)
            clearTimeout(entry.cleanupTimer);
        const delay = Math.max(0, entry.expiresAt - Date.now());
        entry.cleanupTimer = setTimeout(() => {
            const current = this.sandboxes.get(threadId);
            if (current !== entry)
                return;
            if (this.isSandboxInUse(threadId)) {
                this.touchSandboxEntry(threadId, entry);
                return;
            }
            this.evictSandboxEntry(threadId, entry);
        }, delay);
        entry.cleanupTimer.unref();
    }
    stopSandboxExpiryTimers() {
        for (const entry of this.sandboxes.values()) {
            if (!entry.cleanupTimer)
                continue;
            clearTimeout(entry.cleanupTimer);
            entry.cleanupTimer = undefined;
        }
    }
    async getProxyAuth(user) {
        const client = await this.aiService.getClient();
        const token = await client.getBuilderApiProxyToken({ id: user.id }, { userMessageId: (0, nanoid_1.nanoid)() });
        return {
            client,
            headers: { Authorization: `${token.tokenType} ${token.accessToken}` },
        };
    }
    async resolveAgentModelConfig(user) {
        if (this.aiService.isProxyEnabled()) {
            const client = await this.aiService.getClient();
            const proxyBaseUrl = client.getApiProxyBaseUrl();
            const tokenManager = new proxy_token_manager_1.ProxyTokenManager(async () => {
                return await client.getBuilderApiProxyToken({ id: user.id }, { userMessageId: (0, nanoid_1.nanoid)() });
            });
            return await this.resolveProxyModel(user, proxyBaseUrl, tokenManager);
        }
        const httpProxyModel = await this.resolveHttpProxyModel(user);
        if (httpProxyModel)
            return httpProxyModel;
        return await this.settingsService.resolveModelConfig(user);
    }
    async resolveProxyModel(user, proxyBaseUrl, tokenManager) {
        const modelName = this.settingsService.resolveModelName(user);
        const { createAnthropic } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/anthropic')));
        const provider = createAnthropic({
            baseURL: proxyBaseUrl + '/anthropic/v1',
            apiKey: 'proxy-managed',
            fetch: async (input, init) => {
                const headers = new Headers(init?.headers);
                const auth = await tokenManager.getAuthHeaders();
                for (const [k, v] of Object.entries(auth)) {
                    headers.set(k, v);
                }
                for (const [k, v] of Object.entries((0, api_types_1.buildProxyHeaders)({ feature: 'instance-ai', n8nVersion: constants_1.N8N_VERSION }))) {
                    headers.set(k, v);
                }
                return await globalThis.fetch(input, { ...init, headers });
            },
        });
        return provider(modelName);
    }
    async resolveHttpProxyModel(user) {
        const proxyFetch = getProxyFetch();
        if (!proxyFetch)
            return undefined;
        const config = await this.settingsService.resolveModelConfig(user);
        const modelId = typeof config === 'string' ? config : 'id' in config ? config.id : null;
        if (!modelId)
            return undefined;
        const [provider, ...rest] = modelId.split('/');
        const modelName = rest.join('/');
        const apiKey = typeof config === 'object' && 'apiKey' in config ? config.apiKey : undefined;
        const baseURL = typeof config === 'object' && 'url' in config ? config.url : undefined;
        if (provider !== 'anthropic')
            return undefined;
        const { createAnthropic } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/anthropic')));
        return createAnthropic({
            apiKey,
            baseURL: baseURL || undefined,
            fetch: proxyFetch,
        })(modelName);
    }
    async countCreditsIfFirst(user, threadId, runId) {
        if (!this.aiService.isProxyEnabled())
            return;
        if (this.creditedThreads.has(threadId))
            return;
        let thread;
        try {
            thread = await this.threadRepo.findOneBy({ id: threadId });
        }
        catch (error) {
            this.logger.warn('Failed to check Instance AI credit status', {
                threadId,
                runId,
                error: getErrorMessage(error),
            });
            return;
        }
        if (!thread)
            return;
        if (thread.metadata?.creditCounted) {
            this.creditedThreads.add(threadId);
            return;
        }
        try {
            this.creditedThreads.add(threadId);
            const { client, headers: authHeaders } = await this.getProxyAuth(user);
            const info = await client.markBuilderSuccess({ id: user.id }, authHeaders);
            if (info) {
                thread.metadata = { ...thread.metadata, creditCounted: true };
                await this.threadRepo.save(thread);
                this.push.sendToUsers({
                    type: 'updateInstanceAiCredits',
                    data: { creditsQuota: info.creditsQuota, creditsClaimed: info.creditsClaimed },
                }, [user.id]);
            }
        }
        catch (error) {
            this.creditedThreads.delete(threadId);
            this.logger.warn('Failed to count Instance AI credits', {
                error: getErrorMessage(error),
                threadId,
                runId,
            });
        }
    }
    isProxyEnabled() {
        return this.aiService.isProxyEnabled();
    }
    async getCredits(user) {
        if (!this.aiService.isProxyEnabled()) {
            return { creditsQuota: api_types_1.UNLIMITED_CREDITS, creditsClaimed: 0 };
        }
        const client = await this.aiService.getClient();
        return await client.getBuilderInstanceCredits({ id: user.id });
    }
    isEnabled() {
        return this.settingsService.isAgentEnabled() && !!this.instanceAiConfig.model;
    }
    hasActiveRun(threadId) {
        return this.runState.hasLiveRun(threadId);
    }
    getThreadStatus(threadId) {
        return this.runState.getThreadStatus(threadId, this.backgroundTasks.getTaskSnapshots(threadId));
    }
    storeTraceContext(runId, threadId, tracing, messageGroupId) {
        this.traceContextsByRunId.set(runId, {
            threadId,
            messageGroupId,
            tracing,
            traceSlug: this.traceReplay.getActiveSlug(),
        });
    }
    getTraceContext(runId) {
        return this.traceContextsByRunId.get(runId)?.tracing;
    }
    getTraceContextForContinuation(threadId, messageGroupId) {
        const entries = [...this.traceContextsByRunId.values()].reverse();
        const sameGroup = messageGroupId === undefined
            ? undefined
            : entries.find((entry) => entry.threadId === threadId && entry.messageGroupId === messageGroupId)?.tracing;
        return sameGroup ?? entries.find((entry) => entry.threadId === threadId)?.tracing;
    }
    async createOrchestratorResumeTraceContext(options) {
        const baseTracing = options.baseTracing ??
            this.getTraceContextForContinuation(options.threadId, options.messageGroupId);
        if (!baseTracing)
            return undefined;
        const tracing = await (0, instance_ai_1.continueInstanceAiTraceContext)(baseTracing, {
            threadId: options.threadId,
            messageId: options.messageId,
            messageGroupId: options.messageGroupId,
            runId: options.runId,
            userId: options.userId,
            modelId: options.modelId,
            input: options.input,
            proxyConfig: options.proxyConfig ?? baseTracing?.proxyConfig,
            metadata: {
                resume_reason: options.resumeReason,
                agent_id: ORCHESTRATOR_AGENT_ID,
                ...options.metadata,
            },
            n8nVersion: constants_1.N8N_VERSION,
            workflowSdkVersion: constants_1.WORKFLOW_SDK_VERSION,
        });
        if (tracing) {
            await this.configureTraceReplayMode(tracing);
            this.storeTraceContext(options.runId, options.threadId, tracing, options.messageGroupId);
            this.runState.attachTracing(options.threadId, tracing);
        }
        return tracing;
    }
    async configureTraceReplayMode(tracing) {
        await this.traceReplay.configureReplayMode(tracing);
    }
    async finalizeMessageTraceRoot(runId, tracing, options) {
        if (tracing.rootRun.endTime)
            return;
        const outputs = options.outputs ?? {
            status: options.status,
            runId,
            ...(options.outputText ? { response: options.outputText } : {}),
            ...(options.reason ? { reason: options.reason } : {}),
        };
        const metadata = {
            final_status: options.status,
            ...(options.modelId !== undefined ? { model_id: options.modelId } : {}),
            ...options.metadata,
        };
        try {
            await tracing.finishRun(tracing.rootRun, {
                outputs,
                metadata,
                ...(options.error
                    ? { error: options.error }
                    : options.status === 'error' && options.reason
                        ? { error: options.reason }
                        : {}),
            });
        }
        catch (error) {
            this.logger.warn('Failed to finalize Instance AI message trace root', {
                runId,
                threadId: tracing.rootRun.metadata?.thread_id,
                error: getErrorMessage(error),
            });
        }
        finally {
            (0, instance_ai_1.releaseTraceClient)(tracing.rootRun.traceId);
        }
    }
    async maybeFinalizeRunTraceRoot(runId, options) {
        const tracing = this.getTraceContext(runId);
        if (!tracing)
            return;
        await this.finalizeMessageTraceRoot(runId, tracing, options);
    }
    buildMessageTraceMetadata(threadId, runId, options) {
        const traceOptions = {
            status: options.status,
            ...(options.cancellationReason !== undefined
                ? { cancellationReason: options.cancellationReason }
                : {}),
            ...(options.runTimeout !== undefined ? { runTimeout: options.runTimeout } : {}),
        };
        return {
            completion_source: 'orchestrator',
            ...(0, run_trace_metadata_1.buildInstanceAiRunTraceMetadata)(this.eventBus.getEventsForRun(threadId, runId), traceOptions),
        };
    }
    async finalizeRemainingMessageTraceRoots(threadId, options) {
        const finalizedMessageRuns = new Set();
        for (const [runId, entry] of this.traceContextsByRunId) {
            if (entry.threadId !== threadId)
                continue;
            if (finalizedMessageRuns.has(entry.tracing.rootRun.id))
                continue;
            finalizedMessageRuns.add(entry.tracing.rootRun.id);
            await this.finalizeMessageTraceRoot(runId, entry.tracing, options);
        }
    }
    deleteTraceContextsForThread(threadId) {
        for (const [runId, entry] of this.traceContextsByRunId) {
            if (entry.threadId === threadId) {
                (0, instance_ai_1.releaseTraceClient)(entry.tracing.rootRun.traceId);
                if (entry.tracing.traceWriter && entry.traceSlug) {
                    this.traceReplay.preserveWriterEvents(entry.traceSlug, entry.tracing.traceWriter.getEvents());
                }
                this.traceContextsByRunId.delete(runId);
            }
        }
    }
    async finalizeDetachedTraceRun(taskId, traceContext, options) {
        if (!traceContext)
            return;
        try {
            if (traceContext.actorRun.id !== traceContext.rootRun.id &&
                traceContext.actorRun.endTime === undefined) {
                await traceContext.finishRun(traceContext.actorRun, {
                    outputs: {
                        status: options.status,
                        ...options.outputs,
                    },
                    metadata: {
                        final_status: options.status,
                        ...options.metadata,
                    },
                    ...(options.error ? { error: options.error } : {}),
                });
            }
            await traceContext.finishRun(traceContext.rootRun, {
                outputs: {
                    status: options.status,
                    ...options.outputs,
                },
                metadata: {
                    final_status: options.status,
                    ...options.metadata,
                },
                ...(options.error ? { error: options.error } : {}),
            });
        }
        catch (error) {
            this.logger.warn('Failed to finalize Instance AI detached trace run', {
                taskId,
                traceRunId: traceContext.rootRun.id,
                error: getErrorMessage(error),
            });
        }
        finally {
            (0, instance_ai_1.releaseTraceClient)(traceContext.rootRun.traceId);
        }
    }
    async finalizeRunTracing(runId, tracing, options) {
        if (!tracing)
            return;
        if (tracing.actorRun.endTime)
            return;
        const outputs = options.outputs ?? {
            status: options.status,
            runId,
            ...(options.outputText ? { response: options.outputText } : {}),
            ...(options.reason ? { reason: options.reason } : {}),
        };
        const metadata = {
            final_status: options.status,
            ...(options.modelId !== undefined ? { model_id: options.modelId } : {}),
            ...options.metadata,
        };
        try {
            await tracing.finishRun(tracing.actorRun, {
                outputs,
                metadata,
                ...(options.status === 'error' && options.reason ? { error: options.reason } : {}),
            });
        }
        catch (error) {
            this.logger.warn('Failed to finalize Instance AI run tracing', {
                runId,
                threadId: tracing.actorRun.metadata?.thread_id,
                error: getErrorMessage(error),
            });
        }
    }
    async finalizeBackgroundTaskTracing(task, status) {
        await this.finalizeDetachedTraceRun(task.taskId, task.traceContext, {
            status,
            outputs: {
                taskId: task.taskId,
                agentId: task.agentId,
                role: task.role,
                ...(task.result ? { result: task.result } : {}),
            },
            ...(status === 'failed' && task.error ? { error: task.error } : {}),
            metadata: {
                ...(task.plannedTaskId ? { planned_task_id: task.plannedTaskId } : {}),
                ...(task.workItemId ? { work_item_id: task.workItemId } : {}),
            },
        });
    }
    async submitLangsmithFeedback(user, threadId, responseId, payload) {
        const anchor = await this.dbSnapshotStorage.findLangsmithAnchor(threadId, responseId);
        if (!anchor) {
            this.logger.debug('No LangSmith anchor for feedback; skipping annotation', {
                threadId,
                responseId,
            });
            return;
        }
        let tracingProxyConfig;
        if (this.aiService.isProxyEnabled()) {
            try {
                const client = await this.aiService.getClient();
                const baseUrl = client.getApiProxyBaseUrl();
                const manager = new proxy_token_manager_1.ProxyTokenManager(async () => await client.getBuilderApiProxyToken({ id: user.id }, { userMessageId: (0, nanoid_1.nanoid)() }));
                tracingProxyConfig = {
                    apiUrl: baseUrl + '/langsmith',
                    getAuthHeaders: async () => await manager.getAuthHeaders(),
                };
            }
            catch (error) {
                this.logger.warn('Failed to build LangSmith proxy config for feedback', {
                    threadId,
                    responseId,
                    error: getErrorMessage(error),
                });
                return;
            }
        }
        const key = 'user_score';
        const feedbackId = (0, uuid_1.v5)(`${key}:${responseId}`, INSTANCE_AI_FEEDBACK_NAMESPACE);
        try {
            await (0, instance_ai_1.submitLangsmithUserFeedback)({
                langsmithRunId: anchor.langsmithRunId,
                langsmithTraceId: anchor.langsmithTraceId,
                key,
                score: payload.rating === 'up' ? 1 : 0,
                value: payload.rating,
                comment: payload.comment,
                feedbackId,
                sourceInfo: {
                    thread_id: threadId,
                    response_id: responseId,
                    user_id: user.id,
                    rating: payload.rating,
                },
                proxyConfig: tracingProxyConfig,
            });
        }
        catch (error) {
            this.logger.warn('Failed to submit LangSmith feedback', {
                threadId,
                responseId,
                error: getErrorMessage(error),
            });
        }
    }
    startRun(user, threadId, message, attachments, timeZone, pushRef) {
        this.liveness.clearThreadState(threadId);
        const { runId, abortController, messageGroupId } = this.runState.startRun({
            threadId,
            user,
        });
        if (timeZone) {
            this.runState.setTimeZone(threadId, timeZone);
        }
        if (pushRef !== undefined) {
            this.threadPushRef.set(threadId, pushRef);
        }
        void this.executeRun(user, threadId, runId, message, abortController, attachments, messageGroupId, timeZone);
        return runId;
    }
    getMessageGroupId(threadId) {
        return this.runState.getMessageGroupId(threadId);
    }
    getLiveMessageGroupId(threadId) {
        return this.runState.getLiveMessageGroupId(threadId, this.backgroundTasks.getTaskSnapshots(threadId));
    }
    getRunIdsForMessageGroup(messageGroupId) {
        return this.runState.getRunIdsForMessageGroup(messageGroupId);
    }
    getActiveRunId(threadId) {
        return this.runState.getActiveRunId(threadId);
    }
    cancelRun(threadId, reason = 'user_cancelled') {
        const cancelledTasks = this.backgroundTasks.cancelThread(threadId);
        const user = this.runState.getThreadUser(threadId);
        for (const task of cancelledTasks) {
            void this.finalizeBackgroundTaskTracing(task, 'cancelled');
            this.eventBus.publish(threadId, {
                type: 'agent-completed',
                runId: task.runId,
                agentId: task.agentId,
                payload: {
                    role: task.role,
                    result: '',
                    error: reason === liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON ? 'Timed out' : 'Cancelled by user',
                },
            });
            void this.recordBackgroundTerminalOutcome(task).finally(() => {
                void this.saveAgentTreeSnapshot(threadId, task.runId, this.dbSnapshotStorage, true, task.messageGroupId);
            });
            if (user) {
                void this.handlePlannedTaskSettlement(user, task, 'cancelled');
            }
        }
        void this.cancelAwaitingApprovalPlan(threadId);
        const { active, suspended } = this.runState.cancelThread(threadId);
        if (active) {
            if (reason === liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON)
                this.liveness.markRunTimedOut(active.runId);
            active.abortController.abort();
            return;
        }
        if (suspended) {
            if (reason === liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON)
                this.liveness.markRunTimedOut(suspended.runId);
            suspended.abortController.abort();
            void this.finalizeCancelledSuspendedRun(suspended, reason);
        }
    }
    sendCorrectionToTask(threadId, taskId, correction) {
        return this.backgroundTasks.queueCorrection(threadId, taskId, correction);
    }
    cancelBackgroundTask(threadId, taskId) {
        const task = this.backgroundTasks.cancelTask(threadId, taskId);
        if (!task)
            return;
        void this.finalizeBackgroundTaskTracing(task, 'cancelled');
        this.eventBus.publish(threadId, {
            type: 'agent-completed',
            runId: task.runId,
            agentId: task.agentId,
            payload: { role: task.role, result: '', error: 'Cancelled by user' },
        });
        void this.recordBackgroundTerminalOutcome(task).finally(() => {
            void this.saveAgentTreeSnapshot(threadId, task.runId, this.dbSnapshotStorage, true, task.messageGroupId);
        });
        const user = this.runState.getThreadUser(threadId);
        if (user) {
            void this.handlePlannedTaskSettlement(user, task, 'cancelled');
        }
    }
    cancelAllBackgroundTasks() {
        const cancelled = this.backgroundTasks.cancelAll();
        for (const task of cancelled) {
            void this.finalizeBackgroundTaskTracing(task, 'cancelled');
        }
        return cancelled.length;
    }
    async startStuckBackgroundTaskForTest(user, threadId) {
        const messageId = `msg_${(0, nanoid_1.nanoid)()}`;
        const messageText = 'I started a background workflow-builder task.';
        const { runId, messageGroupId } = this.runState.startRun({ threadId, user });
        if (!messageGroupId) {
            throw new n8n_workflow_1.UnexpectedError('Failed to create message group for timeout simulation');
        }
        const taskId = `task_${(0, nanoid_1.nanoid)()}`;
        const agentId = `agent_${(0, nanoid_1.nanoid)()}`;
        this.eventBus.publish(threadId, {
            type: 'run-start',
            runId,
            agentId: ORCHESTRATOR_AGENT_ID,
            userId: user.id,
            payload: { messageId, messageGroupId },
        });
        this.eventBus.publish(threadId, {
            type: 'text-delta',
            runId,
            agentId: ORCHESTRATOR_AGENT_ID,
            responseId: `test-background-start:${runId}`,
            payload: { text: messageText },
        });
        this.eventBus.publish(threadId, {
            type: 'agent-spawned',
            runId,
            agentId,
            payload: {
                parentId: ORCHESTRATOR_AGENT_ID,
                role: 'workflow-builder',
                tools: [],
                taskId,
                kind: 'builder',
                title: 'Building workflow',
                subtitle: 'Timeout simulation',
                goal: 'Simulate a stuck background task timeout',
            },
        });
        await this.agentMemory.saveMessages({
            threadId,
            resourceId: user.id,
            messages: [
                {
                    id: messageId,
                    createdAt: new Date(),
                    type: 'llm',
                    role: 'assistant',
                    content: [{ type: 'text', text: messageText }],
                },
            ],
        });
        const outcome = this.backgroundTasks.spawn({
            taskId,
            threadId,
            runId,
            role: 'workflow-builder',
            agentId,
            messageGroupId,
            run: async (signal) => await new Promise((resolve) => {
                signal.addEventListener('abort', () => resolve('aborted'), { once: true });
            }),
            onFailed: (task) => {
                this.eventBus.publish(threadId, {
                    type: 'agent-completed',
                    runId,
                    agentId,
                    payload: {
                        role: task.role,
                        result: '',
                        error: task.error ?? 'Unknown error',
                    },
                });
            },
            onSettled: async (task) => {
                await this.recordBackgroundTerminalOutcome(task);
                await this.saveAgentTreeSnapshot(threadId, runId, this.dbSnapshotStorage, true, messageGroupId);
            },
        });
        if (outcome.status !== 'started') {
            throw new n8n_workflow_1.UnexpectedError('Failed to start stuck background task simulation');
        }
        this.runState.clearActiveRun(threadId);
        this.eventBus.publish(threadId, {
            type: 'run-finish',
            runId,
            agentId: ORCHESTRATOR_AGENT_ID,
            userId: user.id,
            payload: { status: 'completed' },
        });
        return {
            threadId,
            runId,
            messageGroupId,
            taskId,
            agentId,
            timeoutAt: outcome.task.lastActivityAt + this.liveness.backgroundTaskIdleTimeoutMs + 1,
        };
    }
    async runLivenessSweepForTest(now) {
        await this.liveness.sweepTimedOutWork(now);
    }
    loadTraceEvents(slug, events) {
        this.traceReplay.loadEvents(slug, events);
    }
    getTraceEvents(slug) {
        return this.traceReplay.getEventsWithWriterFallback(slug, this.traceContextsByRunId.values());
    }
    activateTraceSlug(slug) {
        this.traceReplay.activateSlug(slug);
    }
    clearTraceEvents(slug) {
        this.traceReplay.clearEvents(slug);
    }
    getUserIdForApiKey(key) {
        return this.gatewayRegistry.getUserIdForApiKey(key);
    }
    generatePairingToken(userId) {
        return this.gatewayRegistry.generatePairingToken(userId);
    }
    getGatewayApiKeyExpiresAt(userId, key) {
        return this.gatewayRegistry.getApiKeyExpiresAt(userId, key);
    }
    getPairingToken(userId) {
        return this.gatewayRegistry.getPairingToken(userId);
    }
    consumePairingToken(userId, token) {
        return this.gatewayRegistry.consumePairingToken(userId, token);
    }
    getActiveSessionKey(userId) {
        return this.gatewayRegistry.getActiveSessionKey(userId);
    }
    clearActiveSessionKey(userId) {
        this.gatewayRegistry.clearActiveSessionKey(userId);
    }
    getLocalGateway(userId) {
        return this.gatewayRegistry.getGateway(userId);
    }
    initGateway(userId, data) {
        this.gatewayRegistry.initGateway(userId, data);
        this.telemetry.track('User connected to Computer Use', {
            user_id: userId,
            tool_groups: data.toolCategories.filter((c) => c.enabled).map((c) => c.name),
        });
    }
    resolveGatewayRequest(userId, requestId, result, error) {
        return this.gatewayRegistry.resolveGatewayRequest(userId, requestId, result, error);
    }
    disconnectGateway(userId) {
        this.gatewayRegistry.disconnectGateway(userId);
    }
    disconnectAllGateways() {
        const connectedUserIds = this.gatewayRegistry.getConnectedUserIds();
        this.gatewayRegistry.disconnectAll();
        return connectedUserIds;
    }
    isLocalGatewayDisabled() {
        return this.settingsService.isLocalGatewayDisabled();
    }
    getGatewayStatus(userId) {
        return this.gatewayRegistry.getGatewayStatus(userId);
    }
    startDisconnectTimer(userId, onDisconnect) {
        this.gatewayRegistry.startDisconnectTimer(userId, onDisconnect);
    }
    clearDisconnectTimer(userId) {
        this.gatewayRegistry.clearDisconnectTimer(userId);
    }
    async clearThreadState(threadId) {
        this.liveness.clearThreadState(threadId);
        const { active, suspended } = this.runState.clearThread(threadId);
        if (active) {
            active.abortController.abort();
            await this.finalizeRunTracing(active.runId, active.tracing, {
                status: 'cancelled',
                reason: 'thread_cleared',
            });
        }
        if (suspended) {
            suspended.abortController.abort();
            await this.finalizeRunTracing(suspended.runId, suspended.tracing, {
                status: 'cancelled',
                reason: 'thread_cleared',
            });
        }
        for (const task of this.backgroundTasks.cancelThread(threadId)) {
            task.abortController.abort();
            await this.finalizeBackgroundTaskTracing(task, 'cancelled');
        }
        await this.finalizeRemainingMessageTraceRoots(threadId, {
            status: 'cancelled',
            reason: 'thread_cleared',
            metadata: { completion_source: 'service_cleanup' },
        });
        this.creditedThreads.delete(threadId);
        this.schedulerLocks.delete(threadId);
        this.domainAccessTrackersByThread.delete(threadId);
        this.threadPushRef.delete(threadId);
        this.deleteTraceContextsForThread(threadId);
        await this.destroySandbox(threadId);
        await this.reapAiTemporaryForThreadCleanup(threadId);
        this.eventBus.clearThread(threadId);
    }
    async shutdown() {
        this.stopCheckpointPruning();
        this.liveness.shutdown();
        const { activeRuns, suspendedRuns } = this.runState.shutdown();
        for (const run of activeRuns) {
            run.abortController.abort();
            await this.finalizeRunTracing(run.runId, run.tracing, {
                status: 'cancelled',
                reason: 'service_shutdown',
            });
        }
        for (const run of suspendedRuns) {
            run.abortController.abort();
            await this.finalizeRunTracing(run.runId, run.tracing, {
                status: 'cancelled',
                reason: 'service_shutdown',
            });
        }
        for (const task of this.backgroundTasks.cancelAll()) {
            task.abortController.abort();
            await this.finalizeBackgroundTaskTracing(task, 'cancelled');
        }
        const threadsWithTraces = new Set([...this.traceContextsByRunId.values()].map((entry) => entry.threadId));
        for (const threadId of threadsWithTraces) {
            await this.finalizeRemainingMessageTraceRoots(threadId, {
                status: 'cancelled',
                reason: 'service_shutdown',
                metadata: { completion_source: 'service_cleanup' },
            });
        }
        this.gatewayRegistry.disconnectAll();
        this.stopSandboxExpiryTimers();
        this.domainAccessTrackersByThread.clear();
        this.traceContextsByRunId.clear();
        this.eventBus.clear();
        await this._mcpClientManager?.disconnect();
        this.logger.debug('Instance AI service shut down');
    }
    startCheckpointPruning() {
        if (this.checkpointPruneTimer || this.instanceAiConfig.snapshotPruneInterval <= 0)
            return;
        this.checkpointPruningStopped = false;
        this.scheduleCheckpointPrune(0);
    }
    stopCheckpointPruning() {
        this.checkpointPruningStopped = true;
        clearTimeout(this.checkpointPruneTimer);
        this.checkpointPruneTimer = undefined;
    }
    scheduleCheckpointPrune(delayMs = this.instanceAiConfig.snapshotPruneInterval) {
        if (this.checkpointPruningStopped)
            return;
        this.checkpointPruneTimer = setTimeout(() => {
            void this.pruneStaleCheckpoints();
        }, delayMs);
        this.checkpointPruneTimer.unref();
    }
    async pruneStaleCheckpoints(now = Date.now()) {
        const olderThan = new Date(now - this.instanceAiConfig.snapshotRetention);
        try {
            const count = await this.checkpointStore.deleteOlderThan(olderThan);
            if (count > 0) {
                this.logger.info('Deleted stale Instance AI checkpoints', { count });
            }
            else {
                this.logger.debug('No stale Instance AI checkpoints to delete');
            }
            this.scheduleCheckpointPrune();
        }
        catch (error) {
            this.logger.warn('Failed to delete stale Instance AI checkpoints', {
                error: getErrorMessage(error),
            });
            this.scheduleCheckpointPrune(INSTANCE_AI_CHECKPOINT_PRUNE_RETRY_MS);
        }
    }
    createAgentMemoryOptions() {
        return {
            lastMessages: this.instanceAiConfig.lastMessages,
            observationalMemory: {
                observerThresholdTokens: this.instanceAiConfig.observerMessageTokens,
                reflectorThresholdTokens: this.instanceAiConfig.reflectorObservationTokens,
            },
        };
    }
    async ensureThreadExists(memory, threadId, resourceId) {
        const existingThread = await memory.getThread(threadId);
        if (existingThread)
            return;
        await memory.saveThread({
            id: threadId,
            resourceId,
            title: '',
        });
    }
    projectPlannedTaskList(graph) {
        return {
            tasks: graph.tasks.map((task) => ({
                id: task.id,
                description: task.title,
                status: task.status === 'planned'
                    ? 'todo'
                    : task.status === 'running'
                        ? 'in_progress'
                        : task.status === 'succeeded'
                            ? 'done'
                            : task.status,
            })),
        };
    }
    buildPlannedTaskFollowUpMessage(type, graph, options = {}) {
        const payload = {
            tasks: graph.tasks.map((task) => ({
                id: task.id,
                title: task.title,
                kind: task.kind,
                status: task.status,
                result: task.result,
                error: task.error,
                outcome: task.outcome,
            })),
        };
        if (options.failedTask) {
            payload.failedTask = {
                id: options.failedTask.id,
                title: options.failedTask.title,
                kind: options.failedTask.kind,
                error: options.failedTask.error,
                result: options.failedTask.result,
            };
        }
        if (options.checkpoint) {
            const depOutcomes = graph.tasks
                .filter((t) => options.checkpoint.deps.includes(t.id))
                .map((t) => ({
                id: t.id,
                title: t.title,
                kind: t.kind,
                status: t.status,
                result: t.result,
                outcome: t.outcome,
            }));
            payload.checkpoint = {
                id: options.checkpoint.id,
                title: options.checkpoint.title,
                instructions: options.checkpoint.spec,
                dependsOn: depOutcomes,
            };
        }
        return `<planned-task-follow-up type="${type}">\n${JSON.stringify(payload, null, 2)}\n</planned-task-follow-up>\n\n${internal_messages_1.AUTO_FOLLOW_UP_MESSAGE}`;
    }
    async createPlannedTaskState() {
        const memory = this.agentMemory;
        const taskStorage = new instance_ai_1.ThreadTaskStorage(memory);
        const plannedTaskStorage = new instance_ai_1.PlannedTaskStorage(memory);
        const plannedTaskService = new instance_ai_1.PlannedTaskCoordinator(plannedTaskStorage);
        return { memory, taskStorage, plannedTaskService };
    }
    evaluateTerminalResponse(threadId, runId, status, options = {}) {
        const guard = new instance_ai_1.InstanceAiTerminalResponseGuard({
            runId,
            rootAgentId: ORCHESTRATOR_AGENT_ID,
            messageGroupId: options.messageGroupId,
            correlationId: options.correlationId,
        });
        const decision = guard.evaluateTerminal(this.getTerminalGuardEvents(threadId, runId, options.messageGroupId), status, {
            workSummary: options.workSummary,
            errorMessage: options.errorMessage,
        });
        this.handleTerminalResponseDecision(threadId, runId, decision, options.messageGroupId);
        return decision;
    }
    evaluateWaitingResponse(threadId, runId, confirmationEvent, options = {}) {
        const guard = new instance_ai_1.InstanceAiTerminalResponseGuard({
            runId,
            rootAgentId: ORCHESTRATOR_AGENT_ID,
            messageGroupId: options.messageGroupId,
            correlationId: options.correlationId,
        });
        const decision = guard.evaluateWaiting(this.getTerminalGuardEvents(threadId, runId, options.messageGroupId), confirmationEvent);
        this.handleTerminalResponseDecision(threadId, runId, decision, options.messageGroupId);
        return decision;
    }
    getTerminalGuardEvents(threadId, runId, messageGroupId) {
        if (!messageGroupId)
            return this.eventBus.getEventsForRun(threadId, runId);
        const groupRunIds = this.getRunIdsForMessageGroup(messageGroupId);
        return groupRunIds.length > 0
            ? this.eventBus.getEventsForRuns(threadId, groupRunIds)
            : this.eventBus.getEventsForRun(threadId, runId);
    }
    handleTerminalResponseDecision(threadId, runId, decision, messageGroupId) {
        this.telemetry.track('instance_ai_terminal_response_decision', {
            thread_id: threadId,
            run_id: runId,
            message_group_id: messageGroupId,
            source: 'terminal_guard',
            status: decision.status,
            action: decision.action,
            reason: decision.reason,
            visibility_source: decision.visibilitySource,
        });
        if (decision.reason === 'completed-after-error') {
            this.logger.warn('completed_after_error_event', {
                threadId,
                runId,
                messageGroupId,
            });
        }
        if (decision.reason === 'confirmation-invalid') {
            this.logger.warn('invalid_confirmation_payload', {
                threadId,
                runId,
                messageGroupId,
            });
        }
        if (decision.action === 'emit' && decision.event) {
            this.eventBus.publish(threadId, decision.event);
        }
    }
    createTerminalOutcomeStorage() {
        this.terminalOutcomeStorage ??= new instance_ai_1.TerminalOutcomeStorage(this.agentMemory);
        return this.terminalOutcomeStorage;
    }
    async finishInvalidConfirmationRun(args) {
        this.runState.cancelThread(args.threadId);
        args.abortController.abort();
        await this.finalizeRunTracing(args.runId, args.tracing, {
            status: 'error',
            reason: 'invalid_confirmation_payload',
        });
        this.publishRunFinish(args.threadId, args.runId, 'errored', 'I need your input to continue, but I could not display the prompt. Please try again.');
        await this.saveAgentTreeSnapshot(args.threadId, args.runId, args.snapshotStorage);
        return {
            status: 'error',
            reason: 'invalid_confirmation_payload',
            metadata: this.buildMessageTraceMetadata(args.threadId, args.runId, {
                status: 'error',
            }),
        };
    }
    buildBackgroundTerminalOutcome(task) {
        const status = task.status === 'failed' ? 'failed' : task.status === 'cancelled' ? 'cancelled' : 'completed';
        const userFacingMessage = status === 'completed'
            ? `The background ${task.role} task finished.`
            : status === 'cancelled'
                ? `The background ${task.role} task was cancelled.`
                : `The background ${task.role} task failed before I could complete that part.`;
        return {
            id: `${task.messageGroupId ?? task.runId}:${task.taskId}:${status}`,
            threadId: task.threadId,
            runId: task.runId,
            messageGroupId: task.messageGroupId,
            correlationId: task.messageGroupId,
            taskId: task.taskId,
            agentId: task.agentId,
            status,
            userFacingMessage,
            createdAt: new Date().toISOString(),
        };
    }
    async replayUndeliveredTerminalOutcomes(threadId, options = {}) {
        const storage = this.createTerminalOutcomeStorage();
        const persistedOutcomes = await storage.getUndelivered(threadId).catch((error) => {
            this.logger.warn('Failed to load undelivered Instance AI terminal outcomes', {
                threadId,
                error: getErrorMessage(error),
            });
            return [];
        });
        const inMemoryOutcomes = [...this.pendingTerminalOutcomes.values()].filter((outcome) => outcome.threadId === threadId);
        const outcomes = new Map();
        for (const outcome of [...persistedOutcomes, ...inMemoryOutcomes]) {
            outcomes.set(outcome.id, outcome);
        }
        const persistedOutcomeIds = new Set(persistedOutcomes.map((outcome) => outcome.id));
        const delivery = options.delivery ?? 'snapshot';
        for (const outcome of outcomes.values()) {
            const responseId = getBackgroundOutcomeResponseId(outcome);
            let snapshotDelivered = false;
            try {
                snapshotDelivered = await this.persistTerminalOutcomeLineToSnapshot(outcome, responseId);
            }
            catch (error) {
                this.logger.warn('Failed to replay Instance AI terminal outcome', {
                    threadId,
                    runId: outcome.runId,
                    taskId: outcome.taskId,
                    error: getErrorMessage(error),
                });
                if (delivery === 'event') {
                    const published = this.publishTerminalOutcomeLine(outcome, responseId);
                    this.telemetry.track('instance_ai_terminal_response_decision', {
                        thread_id: threadId,
                        run_id: outcome.runId,
                        message_group_id: outcome.messageGroupId,
                        task_id: outcome.taskId,
                        source: 'terminal_outcome_replay',
                        status: outcome.status,
                        action: published ? 'replay_event' : 'already-emitted',
                        visibility_source: 'background-outcome',
                    });
                }
                continue;
            }
            if (!snapshotDelivered)
                continue;
            let action = 'replay_snapshot';
            if (delivery === 'event') {
                const published = this.publishTerminalOutcomeLine(outcome, responseId);
                action = published ? 'replay_event' : 'already-emitted';
            }
            if (persistedOutcomeIds.has(outcome.id)) {
                await storage
                    .markDelivered(threadId, outcome.id, new Date().toISOString())
                    .catch((error) => {
                    this.logger.warn('Failed to mark Instance AI terminal outcome as delivered', {
                        threadId,
                        runId: outcome.runId,
                        taskId: outcome.taskId,
                        error: getErrorMessage(error),
                    });
                });
            }
            this.pendingTerminalOutcomes.delete(outcome.id);
            this.telemetry.track('instance_ai_terminal_response_decision', {
                thread_id: threadId,
                run_id: outcome.runId,
                message_group_id: outcome.messageGroupId,
                task_id: outcome.taskId,
                source: 'terminal_outcome_replay',
                status: outcome.status,
                action,
                visibility_source: 'background-outcome',
            });
        }
    }
    async persistTerminalOutcomeLineToSnapshot(outcome, responseId) {
        const snapshot = await this.dbSnapshotStorage.getLatest(outcome.threadId, {
            messageGroupId: outcome.messageGroupId,
            runId: outcome.runId,
        });
        if (!snapshot) {
            await this.dbSnapshotStorage.save(outcome.threadId, createTerminalOutcomeAgentTree(outcome, responseId), outcome.runId, {
                messageGroupId: outcome.messageGroupId,
                runIds: [outcome.runId],
            });
            return true;
        }
        const { tree } = appendTerminalOutcomeToAgentTree(snapshot.tree, outcome, responseId);
        const runIds = new Set(snapshot.runIds ?? [snapshot.runId]);
        runIds.add(outcome.runId);
        await this.dbSnapshotStorage.updateLast(outcome.threadId, tree, snapshot.runId, {
            messageGroupId: snapshot.messageGroupId ?? outcome.messageGroupId,
            runIds: [...runIds],
            langsmithRunId: snapshot.langsmithRunId,
            langsmithTraceId: snapshot.langsmithTraceId,
        });
        return true;
    }
    publishTerminalOutcomeLine(outcome, responseId) {
        const alreadyPublished = this.eventBus
            .getEventsForRun(outcome.threadId, outcome.runId)
            .some((event) => event.responseId === responseId);
        if (alreadyPublished)
            return false;
        this.eventBus.publish(outcome.threadId, {
            type: 'text-delta',
            runId: outcome.runId,
            agentId: ORCHESTRATOR_AGENT_ID,
            responseId,
            payload: { text: outcome.userFacingMessage },
        });
        return true;
    }
    async recordBackgroundTerminalOutcome(task) {
        const outcome = this.buildBackgroundTerminalOutcome(task);
        let persisted = false;
        try {
            await this.createTerminalOutcomeStorage().upsert(task.threadId, outcome);
            persisted = true;
        }
        catch (error) {
            this.pendingTerminalOutcomes.set(outcome.id, outcome);
            this.logger.warn('Failed to persist Instance AI terminal outcome', {
                threadId: task.threadId,
                runId: task.runId,
                taskId: task.taskId,
                error: getErrorMessage(error),
            });
            this.telemetry.track('instance_ai_terminal_outcome_persistence_failure', {
                thread_id: task.threadId,
                run_id: task.runId,
                task_id: task.taskId,
                status: outcome.status,
                phase: 'metadata',
            });
        }
        const responseId = getBackgroundOutcomeResponseId(outcome);
        const published = this.publishTerminalOutcomeLine(outcome, responseId);
        this.telemetry.track('instance_ai_terminal_response_decision', {
            thread_id: task.threadId,
            run_id: task.runId,
            message_group_id: task.messageGroupId,
            task_id: task.taskId,
            source: 'background_outcome',
            status: outcome.status,
            action: published ? 'emit' : 'already-emitted',
            visibility_source: 'background-outcome',
        });
        let snapshotDelivered = false;
        try {
            snapshotDelivered = await this.persistTerminalOutcomeLineToSnapshot(outcome, responseId);
        }
        catch (error) {
            this.logger.warn('Failed to persist Instance AI terminal outcome line to snapshot', {
                threadId: task.threadId,
                runId: task.runId,
                taskId: task.taskId,
                error: getErrorMessage(error),
            });
            this.telemetry.track('instance_ai_terminal_outcome_persistence_failure', {
                thread_id: task.threadId,
                run_id: task.runId,
                task_id: task.taskId,
                status: outcome.status,
                phase: 'snapshot',
            });
        }
        if (!persisted || !snapshotDelivered)
            return;
        try {
            await this.createTerminalOutcomeStorage().markDelivered(task.threadId, outcome.id, new Date().toISOString());
            this.pendingTerminalOutcomes.delete(outcome.id);
        }
        catch (error) {
            this.logger.warn('Failed to mark Instance AI terminal outcome as delivered', {
                threadId: task.threadId,
                runId: task.runId,
                taskId: task.taskId,
                error: getErrorMessage(error),
            });
        }
    }
    async syncPlannedTasksToUi(threadId, graph) {
        const { taskStorage } = await this.createPlannedTaskState();
        const tasks = this.projectPlannedTaskList(graph);
        await taskStorage.save(threadId, tasks);
        this.eventBus.publish(threadId, {
            type: 'tasks-update',
            runId: graph.planRunId,
            agentId: ORCHESTRATOR_AGENT_ID,
            payload: { tasks },
        });
    }
    async cancelAwaitingApprovalPlan(threadId) {
        try {
            const { plannedTaskService, taskStorage } = await this.createPlannedTaskState();
            const graph = await plannedTaskService.getGraph(threadId);
            if (!graph || graph.status !== 'awaiting_approval')
                return;
            await plannedTaskService.clear(threadId);
            await taskStorage.save(threadId, { tasks: [] });
            this.eventBus.publish(threadId, {
                type: 'tasks-update',
                runId: graph.planRunId,
                agentId: ORCHESTRATOR_AGENT_ID,
                payload: { tasks: { tasks: [] }, planItems: [] },
            });
        }
        catch (error) {
            this.logger.warn('Failed to clean up awaiting_approval plan on cancel', {
                threadId,
                error: error instanceof Error ? error.message : String(error),
            });
        }
    }
    async createExecutionEnvironment(user, threadId, runId, abortSignal, messageGroupId, pushRef) {
        const adminSettings = this.settingsService.getAdminSettings();
        const localGatewayDisabledGlobally = adminSettings.localGatewayDisabled;
        const localGatewayDisabledForUser = await this.settingsService.isLocalGatewayDisabledForUser(user.id);
        const userGateway = this.gatewayRegistry.findGateway(user.id);
        let searchProxyConfig;
        let tracingProxyConfig;
        let tokenManager;
        let proxyBaseUrl;
        if (this.aiService.isProxyEnabled()) {
            const client = await this.aiService.getClient();
            proxyBaseUrl = client.getApiProxyBaseUrl();
            const manager = new proxy_token_manager_1.ProxyTokenManager(async () => {
                return await client.getBuilderApiProxyToken({ id: user.id }, { userMessageId: (0, nanoid_1.nanoid)() });
            });
            tokenManager = manager;
            const featureHeaders = (0, api_types_1.buildProxyHeaders)({
                feature: 'instance-ai',
                n8nVersion: constants_1.N8N_VERSION,
            });
            searchProxyConfig = {
                apiUrl: proxyBaseUrl + '/brave-search',
                getAuthHeaders: async () => ({
                    ...(await manager.getAuthHeaders()),
                    ...featureHeaders,
                }),
            };
            tracingProxyConfig = {
                apiUrl: proxyBaseUrl + '/langsmith',
                getAuthHeaders: async () => ({
                    ...(await manager.getAuthHeaders()),
                    ...featureHeaders,
                }),
            };
        }
        const context = this.adapterService.createContext(user, {
            searchProxyConfig,
            pushRef,
            threadId,
        });
        if (!localGatewayDisabledForUser && userGateway?.isConnected) {
            context.localMcpServer = userGateway;
        }
        context.permissions = this.settingsService.getPermissions();
        if (this.sourceControlPreferencesService.getPreferences().branchReadOnly) {
            context.permissions = (0, api_types_1.applyBranchReadOnlyOverrides)(context.permissions);
            context.branchReadOnly = true;
        }
        let domainTracker = this.domainAccessTrackersByThread.get(threadId);
        if (!domainTracker) {
            domainTracker = (0, instance_ai_1.createDomainAccessTracker)();
            this.domainAccessTrackersByThread.set(threadId, domainTracker);
        }
        context.domainAccessTracker = domainTracker;
        context.runId = runId;
        if (localGatewayDisabledGlobally) {
            context.localGatewayStatus = { status: 'disabledGlobally' };
        }
        else if (!localGatewayDisabledForUser && userGateway?.isConnected) {
            context.localGatewayStatus = {
                status: 'connected',
                capabilities: userGateway
                    .getStatus()
                    .toolCategories.filter(({ enabled }) => enabled)
                    .map(({ name }) => name),
            };
        }
        else {
            context.localGatewayStatus = {
                status: localGatewayDisabledForUser ? 'disabled' : 'disconnected',
            };
        }
        const modelId = proxyBaseUrl && tokenManager
            ? await this.resolveProxyModel(user, proxyBaseUrl, tokenManager)
            : await this.resolveAgentModelConfig(user);
        const memory = this.agentMemory;
        await this.ensureThreadExists(memory, threadId, user.id);
        const taskStorage = new instance_ai_1.ThreadTaskStorage(memory);
        const iterationLog = this.dbIterationLogStorage;
        const snapshotStorage = this.dbSnapshotStorage;
        const workflowLoopStorage = new instance_ai_1.WorkflowLoopStorage(memory);
        const workflowTasks = new instance_ai_1.WorkflowTaskCoordinator(threadId, workflowLoopStorage);
        const plannedTaskStorage = new instance_ai_1.PlannedTaskStorage(memory);
        const plannedTaskService = new instance_ai_1.PlannedTaskCoordinator(plannedTaskStorage);
        const nodeDefDirs = this.adapterService.getNodeDefinitionDirs();
        if (nodeDefDirs.length > 0) {
            (0, workflow_sdk_1.setSchemaBaseDirs)(nodeDefDirs);
        }
        const domainTools = (0, instance_ai_1.createAllTools)(context);
        const baseRuntimeSkills = (0, instance_ai_1.loadInstanceAiRuntimeSkillSource)();
        let runtimeSkills = baseRuntimeSkills;
        let runtimeWorkspace;
        if (adminSettings.sandboxEnabled) {
            let sandboxEntryPromise;
            const getSandboxEntry = async () => {
                sandboxEntryPromise ??= this.getOrCreateWorkspaceEntry(threadId, user, runId).catch((error) => {
                    sandboxEntryPromise = undefined;
                    throw error;
                });
                return await sandboxEntryPromise;
            };
            const getSetupSandboxEntry = async () => {
                return await this.getOrCreateWorkspace(threadId, user, context, runId);
            };
            runtimeWorkspace = (0, instance_ai_1.createLazyRuntimeWorkspace)({
                ensureWorkspace: async () => (await getSetupSandboxEntry())?.workspace,
            });
            const runtimeSkillWorkspace = (0, instance_ai_1.createLazyRuntimeWorkspace)({
                id: 'instance-ai-runtime-skill-workspace',
                name: 'Instance AI runtime skill workspace',
                ensureWorkspace: async () => (await getSandboxEntry())?.workspace,
            });
            runtimeSkills = (0, instance_ai_1.createLazyWorkspaceRuntimeSkillSource)({
                source: baseRuntimeSkills,
                workspace: runtimeSkillWorkspace,
                logger: this.logger,
            });
        }
        const orchestrationContext = {
            threadId,
            runId,
            messageGroupId,
            userId: user.id,
            orchestratorAgentId: ORCHESTRATOR_AGENT_ID,
            modelId,
            checkpointStore: this.checkpointStore,
            subAgentMaxSteps: this.instanceAiConfig.subAgentMaxSteps,
            eventBus: this.eventBus,
            logger: this.logger,
            trackTelemetry: (eventName, properties) => {
                this.telemetry.track(eventName, properties);
            },
            domainTools,
            abortSignal,
            taskStorage,
            timeZone: this.defaultTimeZone,
            browserMcpConfig: this.instanceAiConfig.browserMcp
                ? { name: 'chrome-devtools', command: 'npx', args: ['-y', 'chrome-devtools-mcp@latest'] }
                : undefined,
            localMcpServer: context.localMcpServer,
            runtimeSkills,
            runtimeSkillCatalog: baseRuntimeSkills,
            oauth2CallbackUrl: this.oauth2CallbackUrl,
            webhookBaseUrl: this.webhookBaseUrl,
            formBaseUrl: this.formBaseUrl,
            waitForConfirmation: async (requestId) => {
                this.runState.touchActiveRun(threadId);
                return await new Promise((resolve) => {
                    this.runState.registerPendingConfirmation(requestId, {
                        resolve,
                        threadId,
                        userId: user.id,
                        createdAt: Date.now(),
                    });
                    queueMicrotask(() => {
                        void this.saveAgentTreeSnapshot(threadId, runId, snapshotStorage);
                    });
                });
            },
            cancelBackgroundTask: async (taskId) => this.cancelBackgroundTask(threadId, taskId),
            spawnBackgroundTask: (opts) => this.spawnBackgroundTask(runId, opts, snapshotStorage, messageGroupId),
            touchRun: () => this.runState.touchActiveRun(threadId),
            touchBackgroundTask: (taskId) => this.backgroundTasks.touchTask(threadId, taskId),
            plannedTaskService,
            schedulePlannedTasks: async () => await this.schedulePlannedTasks(user, threadId),
            iterationLog,
            sendCorrectionToTask: (taskId, correction) => this.sendCorrectionToTask(threadId, taskId, correction),
            workflowTaskService: workflowTasks,
            workspace: runtimeWorkspace,
            nodeDefinitionDirs: nodeDefDirs.length > 0 ? nodeDefDirs : undefined,
            domainContext: context,
            tracingProxyConfig,
            memory,
        };
        return {
            context,
            memory,
            taskStorage,
            iterationLog,
            snapshotStorage,
            workflowTasks,
            plannedTaskService,
            modelId,
            orchestrationContext,
        };
    }
    async dispatchPlannedTask(task, context, graph) {
        const taskContext = this.createPlannedTaskContext(task.kind, context);
        const conversationContext = buildPlannedTaskConversationContext(task, graph);
        let started = null;
        switch (task.kind) {
            case 'build-workflow':
                started = await (0, instance_ai_1.startBuildWorkflowAgentTask)(taskContext, {
                    task: task.spec,
                    workflowId: task.workflowId,
                    plannedTaskId: task.id,
                    conversationContext,
                });
                break;
            case 'delegate':
                started = await (0, instance_ai_1.startDetachedDelegateTask)(taskContext, {
                    title: task.title,
                    spec: task.spec,
                    tools: task.tools ?? [],
                    plannedTaskId: task.id,
                    conversationContext,
                });
                break;
        }
        if (!started?.taskId) {
            await context.plannedTaskService?.markFailed(context.threadId, task.id, {
                error: started?.result || `Failed to start planned task "${task.title}"`,
            });
            return;
        }
        await context.plannedTaskService?.markRunning(context.threadId, task.id, {
            agentId: started.agentId,
            backgroundTaskId: started.taskId,
        });
        const nextGraph = await context.plannedTaskService?.getGraph(context.threadId);
        if (nextGraph) {
            await this.syncPlannedTasksToUi(context.threadId, nextGraph);
        }
    }
    createPlannedTaskContext(kind, context) {
        if (!context.domainContext)
            return context;
        const taskDomainContext = (0, instance_ai_1.applyPlannedTaskPermissions)(context.domainContext, kind);
        if (taskDomainContext === context.domainContext)
            return context;
        return {
            ...context,
            domainContext: taskDomainContext,
            domainTools: (0, instance_ai_1.createAllTools)(taskDomainContext),
        };
    }
    async getCheckpointAllowedWorkflowIds(threadId, checkpointTaskId) {
        try {
            const { plannedTaskService } = await this.createPlannedTaskState();
            const graph = await plannedTaskService.getGraph(threadId);
            const checkpoint = graph?.tasks.find((t) => t.id === checkpointTaskId);
            if (!graph || !checkpoint)
                return new Set();
            const deps = new Set(checkpoint.deps);
            const allowed = new Set();
            for (const task of graph.tasks) {
                if (!deps.has(task.id))
                    continue;
                const workflowId = task.outcome?.workflowId;
                if (typeof workflowId === 'string' && workflowId.length > 0) {
                    allowed.add(workflowId);
                }
            }
            return allowed;
        }
        catch (error) {
            this.logger.warn('Failed to resolve checkpoint allowed workflow IDs', {
                threadId,
                checkpointTaskId,
                error: error instanceof Error ? error.message : String(error),
            });
            return new Set();
        }
    }
    async handlePlannedTaskSettlement(user, task, status) {
        if (!task.plannedTaskId)
            return;
        const { plannedTaskService } = await this.createPlannedTaskState();
        let graph = null;
        if (status === 'succeeded') {
            graph = await plannedTaskService.markSucceeded(task.threadId, task.plannedTaskId, {
                result: task.result,
                outcome: task.outcome,
            });
        }
        else if (status === 'failed') {
            graph = await plannedTaskService.markFailed(task.threadId, task.plannedTaskId, {
                error: task.error,
            });
        }
        else {
            graph = await plannedTaskService.markCancelled(task.threadId, task.plannedTaskId, {
                error: task.error,
            });
        }
        if (graph) {
            await this.syncPlannedTasksToUi(task.threadId, graph);
        }
        await this.schedulePlannedTasks(user, task.threadId);
    }
    async startInternalFollowUpRun(user, threadId, message, messageGroupId, isReplanFollowUp = false, checkpoint) {
        if (this.runState.hasLiveRun(threadId)) {
            this.logger.warn('Skipping internal follow-up: active run exists', { threadId });
            return '';
        }
        const { runId, abortController } = this.runState.startRun({
            threadId,
            user,
            messageGroupId,
        });
        const timeZone = this.runState.getTimeZone(threadId) ?? this.defaultTimeZone;
        const resumeReason = checkpoint
            ? 'planned_checkpoint'
            : isReplanFollowUp
                ? 'replan'
                : 'background_task_completed';
        void this.executeRun(user, threadId, runId, message, abortController, undefined, messageGroupId, timeZone, isReplanFollowUp, checkpoint, resumeReason);
        return runId;
    }
    async schedulePlannedTasks(user, threadId) {
        const prev = this.schedulerLocks.get(threadId) ?? Promise.resolve();
        const current = prev.then(() => this.doSchedulePlannedTasks(user, threadId)).catch(() => { });
        this.schedulerLocks.set(threadId, current);
        await current;
    }
    async doSchedulePlannedTasks(user, threadId) {
        const revalidated = await this.revalidateActiveUser(user.id);
        if (!revalidated) {
            this.logger.warn('Cancelling run: user no longer authorized for AI Assistant', {
                userId: user.id,
                threadId,
            });
            this.cancelRun(threadId);
            return;
        }
        const activeUser = revalidated;
        const { plannedTaskService } = await this.createPlannedTaskState();
        const graph = await plannedTaskService.getGraph(threadId);
        if (!graph)
            return;
        await this.syncPlannedTasksToUi(threadId, graph);
        const availableSlots = Math.max(0, MAX_CONCURRENT_BACKGROUND_TASKS_PER_THREAD -
            this.backgroundTasks.getRunningTasks(threadId).length);
        const action = await plannedTaskService.tick(threadId, { availableSlots });
        if (action.type === 'none')
            return;
        if (action.type === 'replan') {
            await this.syncPlannedTasksToUi(threadId, action.graph);
            const startedRunId = await this.startInternalFollowUpRun(activeUser, threadId, this.buildPlannedTaskFollowUpMessage('replan', action.graph, {
                failedTask: action.failedTask,
            }), action.graph.messageGroupId, true);
            if (!startedRunId) {
                await plannedTaskService.revertToActive(threadId);
            }
            return;
        }
        if (action.type === 'synthesize') {
            await this.syncPlannedTasksToUi(threadId, action.graph);
            const startedRunId = await this.startInternalFollowUpRun(activeUser, threadId, this.buildPlannedTaskFollowUpMessage('synthesize', action.graph), action.graph.messageGroupId);
            if (!startedRunId) {
                await plannedTaskService.revertToActive(threadId);
            }
            return;
        }
        if (action.type === 'orchestrate-checkpoint') {
            if (this.runState.hasLiveRun(threadId)) {
                return;
            }
            const checkpoint = action.tasks[0];
            await plannedTaskService.markRunning(threadId, checkpoint.id, {
                agentId: ORCHESTRATOR_AGENT_ID,
            });
            const graphAfterMark = (await plannedTaskService.getGraph(threadId)) ?? action.graph;
            await this.syncPlannedTasksToUi(threadId, graphAfterMark);
            const checkpointRecord = graphAfterMark.tasks.find((t) => t.id === checkpoint.id) ?? checkpoint;
            const startedRunId = await this.startInternalFollowUpRun(activeUser, threadId, this.buildPlannedTaskFollowUpMessage('checkpoint', graphAfterMark, {
                checkpoint: checkpointRecord,
            }), action.graph.messageGroupId, false, { isCheckpointFollowUp: true, checkpointTaskId: checkpoint.id });
            if (!startedRunId) {
                this.logger.warn('Checkpoint follow-up run did not start — reverting checkpoint to planned for retry', { threadId, checkpointTaskId: checkpoint.id });
                await plannedTaskService.revertCheckpointToPlanned(threadId, checkpoint.id);
            }
            return;
        }
        const environment = await this.createExecutionEnvironment(activeUser, threadId, action.graph.planRunId, createInertAbortSignal(), action.graph.messageGroupId, this.threadPushRef.get(threadId));
        environment.orchestrationContext.tracing = this.getTraceContext(action.graph.planRunId);
        for (const task of action.tasks) {
            await this.dispatchPlannedTask(task, environment.orchestrationContext, action.graph);
        }
        await this.doSchedulePlannedTasks(activeUser, threadId);
    }
    async executeRun(user, threadId, runId, message, abortController, attachments, messageGroupId, timeZone, isReplanFollowUp = false, checkpoint, resumeReason) {
        const signal = abortController.signal;
        let tracing;
        let messageTraceFinalization;
        let aiCreatedWorkflowIds;
        let activeSnapshotStorage;
        let messageId = '';
        try {
            messageId = (0, nanoid_1.nanoid)();
            this.eventBus.publish(threadId, {
                type: 'run-start',
                runId,
                agentId: ORCHESTRATOR_AGENT_ID,
                userId: user.id,
                payload: { messageId, messageGroupId },
            });
            if (signal.aborted) {
                this.evaluateTerminalResponse(threadId, runId, 'cancelled', {
                    messageGroupId,
                    correlationId: messageId,
                });
                this.eventBus.publish(threadId, {
                    type: 'run-finish',
                    runId,
                    agentId: ORCHESTRATOR_AGENT_ID,
                    payload: { status: 'cancelled', reason: 'user_cancelled' },
                });
                return;
            }
            const mcpServers = this.parseMcpServers(this.instanceAiConfig.mcpServers);
            const executionPushRef = this.threadPushRef.get(threadId);
            const environment = await this.createExecutionEnvironment(user, threadId, runId, signal, messageGroupId, executionPushRef);
            activeSnapshotStorage = environment.snapshotStorage;
            const { context, memory, taskStorage, snapshotStorage, modelId, orchestrationContext } = environment;
            aiCreatedWorkflowIds = context.aiCreatedWorkflowIds ??= new Set();
            orchestrationContext.currentUserMessage = message;
            orchestrationContext.isReplanFollowUp = isReplanFollowUp;
            orchestrationContext.timeZone = timeZone ?? this.defaultTimeZone;
            if (checkpoint?.isCheckpointFollowUp) {
                orchestrationContext.isCheckpointFollowUp = true;
                orchestrationContext.checkpointTaskId = checkpoint.checkpointTaskId;
                context.permissions = {
                    ...context.permissions,
                    ...(instance_ai_1.PLANNED_TASK_PERMISSION_OVERRIDES.checkpoint ?? {}),
                };
                context.allowedRunWorkflowIds = await this.getCheckpointAllowedWorkflowIds(threadId, checkpoint.checkpointTaskId);
            }
            if (attachments && attachments.length > 0) {
                context.currentUserAttachments = attachments;
            }
            const memoryConfig = this.createAgentMemoryOptions();
            const traceInput = {
                message,
                ...(attachments?.length
                    ? {
                        attachments: attachments.map((attachment) => ({
                            mimeType: attachment.mimeType,
                            size: attachment.data.length,
                        })),
                    }
                    : {}),
                ...(messageGroupId ? { messageGroupId } : {}),
            };
            tracing = resumeReason
                ? await this.createOrchestratorResumeTraceContext({
                    threadId,
                    messageId,
                    messageGroupId,
                    runId,
                    userId: user.id,
                    modelId,
                    input: traceInput,
                    proxyConfig: orchestrationContext.tracingProxyConfig,
                    resumeReason,
                    metadata: {
                        ...(checkpoint?.isCheckpointFollowUp
                            ? { checkpoint_task_id: checkpoint.checkpointTaskId }
                            : {}),
                    },
                })
                : await (0, instance_ai_1.createInstanceAiTraceContext)({
                    threadId,
                    messageId,
                    messageGroupId,
                    runId,
                    userId: user.id,
                    modelId,
                    input: traceInput,
                    proxyConfig: orchestrationContext.tracingProxyConfig,
                    n8nVersion: constants_1.N8N_VERSION,
                    workflowSdkVersion: constants_1.WORKFLOW_SDK_VERSION,
                });
            if (!tracing && process.env.E2E_TESTS === 'true') {
                const { createTraceReplayOnlyContext } = await Promise.resolve().then(() => __importStar(require('@n8n/instance-ai')));
                tracing = createTraceReplayOnlyContext();
            }
            if (tracing) {
                orchestrationContext.tracing = tracing;
                if (this.getTraceContext(runId) !== tracing) {
                    await this.configureTraceReplayMode(tracing);
                    this.runState.attachTracing(threadId, tracing);
                    this.storeTraceContext(runId, threadId, tracing, messageGroupId);
                }
            }
            const thread = await memory.getThread(threadId);
            if (thread && !thread.title) {
                await (0, instance_ai_1.patchThread)(memory, {
                    threadId,
                    update: () => ({ title: (0, instance_ai_1.truncateToTitle)(message) }),
                });
            }
            const existingTasks = await taskStorage.get(threadId);
            if (existingTasks) {
                this.eventBus.publish(threadId, {
                    type: 'tasks-update',
                    runId,
                    agentId: ORCHESTRATOR_AGENT_ID,
                    payload: { tasks: existingTasks },
                });
            }
            const enrichedMessage = await this.buildMessageWithRunningTasks(threadId, message);
            let nonStructuredAttachments = [];
            let attachmentManifest = '';
            let hasParseableAttachment = false;
            if (attachments && attachments.length > 0) {
                const classifiedAttachments = (0, instance_ai_1.classifyAttachments)(attachments);
                nonStructuredAttachments = attachments.filter((attachment) => !(0, instance_ai_1.isParseableAttachment)(attachment));
                hasParseableAttachment = classifiedAttachments.some((attachment) => attachment.parseable);
                attachmentManifest = (0, instance_ai_1.buildAttachmentManifest)(classifiedAttachments);
            }
            const fullMessage = !message && hasParseableAttachment
                ? `The user attached file(s) without a message. Inspect the first parseable attachment with parse-file and provide a concise summary.\n\n${attachmentManifest}`
                : attachmentManifest
                    ? `${enrichedMessage}\n\n${attachmentManifest}`
                    : enrichedMessage;
            const promptBuildRun = tracing
                ? await tracing.startChildRun(tracing.messageRun, {
                    name: 'prepare: prompt',
                    canonicalName: 'instance-ai.prompt_build',
                    tags: ['prompt'],
                    metadata: { agent_role: 'prompt_build' },
                    inputs: {
                        message,
                        attachmentCount: attachments?.length ?? 0,
                    },
                })
                : undefined;
            let streamInput;
            try {
                if (nonStructuredAttachments.length > 0) {
                    streamInput = [
                        {
                            role: 'user',
                            content: [
                                { type: 'text', text: fullMessage },
                                ...nonStructuredAttachments.map((attachment) => ({
                                    type: 'file',
                                    data: attachment.data,
                                    mediaType: attachment.mimeType,
                                })),
                            ],
                        },
                    ];
                }
                else {
                    streamInput = fullMessage;
                }
                if (promptBuildRun && tracing) {
                    const traceOutput = typeof streamInput === 'string'
                        ? { fullMessage: streamInput }
                        : {
                            fullMessage,
                            attachmentCount: attachments?.length ?? 0,
                            nonStructuredAttachmentCount: nonStructuredAttachments.length,
                        };
                    await tracing.finishRun(promptBuildRun, {
                        outputs: traceOutput,
                        metadata: { final_status: 'completed' },
                    });
                }
            }
            catch (error) {
                if (promptBuildRun && tracing) {
                    await tracing.failRun(promptBuildRun, error, {
                        final_status: 'error',
                    });
                }
                throw error;
            }
            if (tracing && tracing.actorRun.id === tracing.rootRun.id) {
                const actorRun = await tracing.startChildRun(tracing.rootRun, {
                    name: 'agent: orchestrator',
                    canonicalName: 'instance-ai.agent.orchestrator',
                    tags: ['orchestrator'],
                    metadata: {
                        agent_role: 'orchestrator',
                        agent_id: ORCHESTRATOR_AGENT_ID,
                        execution_mode: 'foreground',
                        trace_kind: tracing.traceKind,
                    },
                    inputs: traceInput,
                });
                tracing.actorRun = actorRun;
                tracing.orchestratorRun = actorRun;
            }
            const agent = await (0, instance_ai_1.createInstanceAgent)({
                modelId,
                context,
                orchestrationContext,
                mcpServers,
                mcpManager: this.mcpClientManager,
                memoryConfig,
                memory,
                checkpointStore: this.checkpointStore,
                timeZone: timeZone ?? this.defaultTimeZone,
            });
            const result = tracing
                ? await tracing.withActiveSpan(tracing.actorRun, async () => {
                    return await (0, instance_ai_1.streamAgentRun)(agent, streamInput, {
                        maxIterations: instance_ai_1.MAX_STEPS.ORCHESTRATOR,
                        abortSignal: signal,
                        persistence: {
                            resourceId: user.id,
                            threadId,
                        },
                        providerOptions: {
                            anthropic: { cacheControl: { type: 'ephemeral' } },
                        },
                    }, {
                        threadId,
                        runId,
                        agentId: ORCHESTRATOR_AGENT_ID,
                        signal,
                        eventBus: this.eventBus,
                        logger: this.logger,
                        onActivity: () => this.runState.touchActiveRun(threadId),
                    });
                })
                : await (0, instance_ai_1.streamAgentRun)(agent, streamInput, {
                    maxIterations: instance_ai_1.MAX_STEPS.ORCHESTRATOR,
                    abortSignal: signal,
                    persistence: {
                        resourceId: user.id,
                        threadId,
                    },
                    providerOptions: {
                        anthropic: { cacheControl: { type: 'ephemeral' } },
                    },
                }, {
                    threadId,
                    runId,
                    agentId: ORCHESTRATOR_AGENT_ID,
                    signal,
                    eventBus: this.eventBus,
                    logger: this.logger,
                    onActivity: () => this.runState.touchActiveRun(threadId),
                });
            if (result.status === 'suspended') {
                if (result.suspension) {
                    this.runState.suspendRun(threadId, {
                        runId,
                        agentRunId: result.agentRunId,
                        agent,
                        threadId,
                        user,
                        toolCallId: result.suspension.toolCallId,
                        requestId: result.suspension.requestId,
                        abortController,
                        messageGroupId,
                        createdAt: Date.now(),
                        tracing,
                        modelId,
                        checkpoint,
                    });
                }
                const intermediateText = await (result.text ?? Promise.resolve(''));
                if (intermediateText) {
                    this.telemetry.track('Builder sent message', {
                        thread_id: threadId,
                        message: intermediateText,
                        is_intermediate: true,
                    });
                }
                const waitingDecision = this.evaluateWaitingResponse(threadId, runId, result.confirmationEvent, {
                    messageGroupId,
                    correlationId: messageId,
                });
                if (waitingDecision?.reason === 'confirmation-invalid') {
                    messageTraceFinalization = await this.finishInvalidConfirmationRun({
                        threadId,
                        runId,
                        abortController,
                        snapshotStorage,
                        tracing,
                    });
                    return;
                }
                if (result.confirmationEvent) {
                    this.trackConfirmationRequest(threadId, result.confirmationEvent);
                    this.eventBus.publish(threadId, result.confirmationEvent);
                }
                await this.saveAgentTreeSnapshot(threadId, runId, snapshotStorage);
                const suspensionOutputs = {
                    status: 'suspended',
                    runId,
                    ...(result.suspension?.requestId ? { requestId: result.suspension.requestId } : {}),
                    ...(result.suspension?.toolCallId
                        ? { pendingToolCallId: result.suspension.toolCallId }
                        : {}),
                    ...(result.suspension?.toolName ? { toolName: result.suspension.toolName } : {}),
                };
                await this.finalizeRunTracing(runId, tracing, {
                    status: 'suspended',
                    outputs: suspensionOutputs,
                    metadata: {
                        completion_source: 'orchestrator',
                        ...(result.suspension?.requestId ? { request_id: result.suspension.requestId } : {}),
                        ...(result.suspension?.toolCallId
                            ? { pending_tool_call_id: result.suspension.toolCallId }
                            : {}),
                        ...(result.suspension?.toolName
                            ? { pending_tool_name: result.suspension.toolName }
                            : {}),
                    },
                });
                messageTraceFinalization = {
                    status: 'suspended',
                    outputs: suspensionOutputs,
                    metadata: {
                        completion_source: 'orchestrator',
                        ...(result.suspension?.requestId ? { request_id: result.suspension.requestId } : {}),
                        ...(result.suspension?.toolCallId
                            ? { pending_tool_call_id: result.suspension.toolCallId }
                            : {}),
                        ...(result.suspension?.toolName
                            ? { pending_tool_name: result.suspension.toolName }
                            : {}),
                    },
                };
                return;
            }
            const outputText = await (result.text ?? Promise.resolve(''));
            this.evaluateTerminalResponse(threadId, runId, result.status, {
                messageGroupId,
                correlationId: messageId,
                workSummary: result.workSummary,
            });
            const finalStatus = result.status === 'errored' ? 'error' : result.status;
            await this.finalizeRunTracing(runId, tracing, {
                status: finalStatus,
                outputText,
                modelId,
            });
            messageTraceFinalization = {
                status: finalStatus,
                outputText,
                modelId,
                metadata: this.buildMessageTraceMetadata(threadId, runId, { status: finalStatus }),
            };
            const archivedWorkflowIds = await this.reapAiTemporaryFromRun(threadId, user, aiCreatedWorkflowIds);
            await this.finalizeRun(threadId, runId, result.status, snapshotStorage, {
                userId: user.id,
                modelId,
                archivedWorkflowIds,
            });
            if (result.status === 'completed') {
                await this.countCreditsIfFirst(user, threadId, runId);
                this.telemetry.track('Builder sent message', {
                    thread_id: threadId,
                    message: outputText,
                });
                this.telemetry.track('Builder satisfied user intent', {
                    thread_id: threadId,
                });
            }
        }
        catch (error) {
            if (signal.aborted) {
                const runTimeout = this.liveness.consumeRunTimeout(runId);
                const cancellationReason = runTimeout.timedOut
                    ? liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON
                    : getAbortReason(signal);
                if (cancellationReason === liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON) {
                    this.liveness.publishRunTimeoutNotice(threadId, runId);
                }
                this.evaluateTerminalResponse(threadId, runId, 'cancelled', {
                    messageGroupId,
                    correlationId: messageId,
                });
                await this.finalizeRunTracing(runId, tracing, {
                    status: 'cancelled',
                    reason: cancellationReason,
                });
                messageTraceFinalization = {
                    status: 'cancelled',
                    reason: cancellationReason,
                    metadata: this.buildMessageTraceMetadata(threadId, runId, {
                        status: 'cancelled',
                        cancellationReason,
                        runTimeout,
                    }),
                };
                const archivedWorkflowIds = await this.reapAiTemporaryFromRun(threadId, user, aiCreatedWorkflowIds);
                this.publishRunFinish(threadId, runId, 'cancelled', cancellationReason, archivedWorkflowIds);
                if (activeSnapshotStorage) {
                    await this.saveAgentTreeSnapshot(threadId, runId, activeSnapshotStorage);
                }
                return;
            }
            const errorMessage = getErrorMessage(error);
            const userFacingErrorMessage = getUserFacingErrorMessage(error);
            this.logger.error('Instance AI run error', {
                error: errorMessage,
                threadId,
                runId,
            });
            this.evaluateTerminalResponse(threadId, runId, 'errored', {
                messageGroupId,
                correlationId: messageId,
                errorMessage: userFacingErrorMessage,
            });
            await this.finalizeRunTracing(runId, tracing, {
                status: 'error',
                reason: errorMessage,
            });
            messageTraceFinalization = {
                status: 'error',
                reason: errorMessage,
                metadata: this.buildMessageTraceMetadata(threadId, runId, { status: 'error' }),
            };
            const archivedWorkflowIds = await this.reapAiTemporaryFromRun(threadId, user, aiCreatedWorkflowIds);
            this.eventBus.publish(threadId, {
                type: 'run-finish',
                runId,
                agentId: ORCHESTRATOR_AGENT_ID,
                payload: {
                    status: 'error',
                    reason: userFacingErrorMessage,
                    ...(archivedWorkflowIds.length > 0 ? { archivedWorkflowIds } : {}),
                },
            });
            if (activeSnapshotStorage) {
                await this.saveAgentTreeSnapshot(threadId, runId, activeSnapshotStorage);
            }
        }
        finally {
            this.runState.clearActiveRun(threadId);
            this.domainAccessTrackersByThread.get(threadId)?.clearRun(runId);
            if (messageTraceFinalization) {
                await this.maybeFinalizeRunTraceRoot(runId, messageTraceFinalization);
                if (messageTraceFinalization.status !== 'cancelled') {
                    this.liveness.consumeRunTimeout(runId);
                }
            }
            if (!this.runState.hasSuspendedRun(threadId)) {
                if (checkpoint?.isCheckpointFollowUp) {
                    await this.finalizeCheckpointFollowUp(user, threadId, checkpoint.checkpointTaskId);
                }
                else {
                    await this.schedulePlannedTasks(user, threadId);
                }
                await this.drainPendingCheckpointReentries(user, threadId);
            }
        }
    }
    queuePendingCheckpointReentry(threadId, checkpointTaskId) {
        let set = this.pendingCheckpointReentries.get(threadId);
        if (!set) {
            set = new Set();
            this.pendingCheckpointReentries.set(threadId, set);
        }
        set.add(checkpointTaskId);
    }
    async drainPendingCheckpointReentries(user, threadId) {
        const set = this.pendingCheckpointReentries.get(threadId);
        if (!set || set.size === 0)
            return;
        const snapshot = [...set];
        for (const checkpointTaskId of snapshot) {
            if (this.runState.getActiveRunId(threadId) || this.runState.hasSuspendedRun(threadId)) {
                return;
            }
            const siblings = this.backgroundTasks.getRunningTasksByParentCheckpoint(threadId, checkpointTaskId);
            if (siblings.length > 0)
                continue;
            set.delete(checkpointTaskId);
            await this.reenterCheckpointById(user, threadId, checkpointTaskId);
        }
        if (set.size === 0)
            this.pendingCheckpointReentries.delete(threadId);
    }
    async reenterCheckpointById(user, threadId, checkpointTaskId, messageGroupId) {
        try {
            const { plannedTaskService } = await this.createPlannedTaskState();
            const graph = await plannedTaskService.getGraph(threadId);
            const checkpoint = graph?.tasks.find((t) => t.id === checkpointTaskId);
            if (!graph || !checkpoint || checkpoint.kind !== 'checkpoint')
                return false;
            if (checkpoint.status !== 'running')
                return false;
            const startedRunId = await this.startInternalFollowUpRun(user, threadId, this.buildPlannedTaskFollowUpMessage('checkpoint', graph, { checkpoint }), messageGroupId, false, { isCheckpointFollowUp: true, checkpointTaskId });
            if (!startedRunId)
                return false;
            this.logger.debug('Re-entered checkpoint follow-up', {
                threadId,
                checkpointTaskId,
                messageGroupId,
            });
            return true;
        }
        catch (error) {
            this.logger.error('Failed to re-enter checkpoint follow-up', {
                threadId,
                checkpointTaskId,
                error: error instanceof Error ? error.message : String(error),
            });
            return false;
        }
    }
    async maybeReenterParentCheckpoint(user, threadId, task) {
        const parentCheckpointId = task.parentCheckpointId;
        if (!parentCheckpointId)
            return false;
        const siblings = this.backgroundTasks
            .getRunningTasksByParentCheckpoint(threadId, parentCheckpointId)
            .filter((t) => t.taskId !== task.taskId);
        if (siblings.length > 0)
            return false;
        if (this.runState.getActiveRunId(threadId) || this.runState.hasSuspendedRun(threadId)) {
            return false;
        }
        return await this.reenterCheckpointById(user, threadId, parentCheckpointId, task.messageGroupId);
    }
    async finalizeCheckpointFollowUp(user, threadId, checkpointTaskId) {
        try {
            const { plannedTaskService } = await this.createPlannedTaskState();
            const graph = await plannedTaskService.getGraph(threadId);
            const task = graph?.tasks.find((t) => t.id === checkpointTaskId);
            if (task && task.status === 'running') {
                const inflightChildren = this.backgroundTasks.getRunningTasksByParentCheckpoint(threadId, checkpointTaskId);
                if (inflightChildren.length > 0) {
                    this.logger.debug('Checkpoint run ended with in-flight child tasks — deferring finalization', {
                        threadId,
                        checkpointTaskId,
                        inflightTaskIds: inflightChildren.map((t) => t.taskId),
                    });
                }
                else {
                    this.logger.warn('Checkpoint run ended without reporting completion — marking failed', {
                        threadId,
                        checkpointTaskId,
                    });
                    await plannedTaskService.markCheckpointFailed(threadId, checkpointTaskId, {
                        error: 'Checkpoint run ended without reporting completion',
                    });
                    const nextGraph = await plannedTaskService.getGraph(threadId);
                    if (nextGraph) {
                        await this.syncPlannedTasksToUi(threadId, nextGraph);
                    }
                }
            }
        }
        catch (error) {
            this.logger.error('Checkpoint finalization failed', {
                threadId,
                checkpointTaskId,
                error: error instanceof Error ? error.message : String(error),
            });
        }
        await this.schedulePlannedTasks(user, threadId);
    }
    async resolveConfirmation(requestingUserId, requestId, request) {
        const data = toConfirmationData(request);
        const freshUser = await this.revalidateActiveUser(requestingUserId);
        if (!freshUser) {
            this.runState.rejectPendingConfirmation(requestId);
            const suspended = this.runState.findSuspendedByRequestId(requestId);
            if (suspended?.user.id === requestingUserId) {
                this.cancelRun(suspended.threadId);
            }
            this.logger.warn('Rejecting confirmation: user no longer authorized for AI Assistant', {
                userId: requestingUserId,
                requestId,
            });
            return false;
        }
        if (this.runState.resolvePendingConfirmation(freshUser.id, requestId, data)) {
            this.logger.debug('Resolved pending confirmation (sub-agent HITL)', {
                requestId,
                approved: data.approved,
            });
            return true;
        }
        this.logger.debug('Pending confirmation not found, trying suspended run resume', {
            requestId,
            approved: data.approved,
        });
        return await this.resumeSuspendedRun(requestingUserId, requestId, data);
    }
    async revalidateActiveUser(userId) {
        try {
            const user = await this.userRepository.findOne({
                where: { id: userId },
                relations: ['role'],
            });
            if (!user || user.disabled)
                return null;
            const hasInstanceAiMessageScope = user.role?.scopes?.some((scope) => scope.slug === 'instanceAi:message') ?? false;
            return hasInstanceAiMessageScope ? user : null;
        }
        catch (error) {
            this.logger.warn('Failed to revalidate user', {
                userId,
                error: getErrorMessage(error),
            });
            return null;
        }
    }
    async resumeSuspendedRun(requestingUserId, requestId, data) {
        const suspended = this.runState.findSuspendedByRequestId(requestId);
        if (!suspended) {
            this.logger.warn('Confirmation target not found: no pending confirmation or suspended run', {
                requestId,
                approved: data.approved,
            });
            return false;
        }
        const { agent, runId, agentRunId, threadId, user, toolCallId, abortController, tracing, modelId, messageGroupId, checkpoint, } = suspended;
        if (user.id !== requestingUserId)
            return false;
        const activeUser = await this.revalidateActiveUser(user.id);
        if (!activeUser) {
            this.logger.warn('Cancelling suspended run: user no longer authorized for AI Assistant', {
                userId: user.id,
                threadId,
                requestId,
            });
            this.cancelRun(threadId);
            return false;
        }
        this.runState.activateSuspendedRun(threadId);
        const credentialsPayload = data.nodeCredentials ?? data.credentials;
        const resumeData = {
            approved: data.approved,
            ...(credentialsPayload ? { credentials: credentialsPayload } : {}),
            ...(data.userInput !== undefined ? { userInput: data.userInput } : {}),
            ...(data.domainAccessAction ? { domainAccessAction: data.domainAccessAction } : {}),
            ...(data.action ? { action: data.action } : {}),
            ...(data.nodeParameters ? { nodeParameters: data.nodeParameters } : {}),
            ...(data.testTriggerNode ? { testTriggerNode: data.testTriggerNode } : {}),
            ...(data.answers ? { answers: data.answers } : {}),
            ...(data.resourceDecision ? { resourceDecision: data.resourceDecision } : {}),
        };
        const resumeTracing = await this.createOrchestratorResumeTraceContext({
            baseTracing: tracing,
            threadId,
            messageId: (0, nanoid_1.nanoid)(),
            messageGroupId,
            runId,
            userId: activeUser.id,
            modelId,
            input: {
                requestId,
                toolCallId,
                approved: data.approved,
                resumeFields: Object.keys(resumeData),
            },
            resumeReason: 'approval',
            metadata: {
                request_id: requestId,
                pending_tool_call_id: toolCallId,
                approved: data.approved,
                ...(checkpoint?.isCheckpointFollowUp
                    ? { checkpoint_task_id: checkpoint.checkpointTaskId }
                    : {}),
            },
        });
        void this.processResumedStream(agent, resumeData, {
            runId,
            agentRunId,
            threadId,
            user: activeUser,
            toolCallId,
            signal: abortController.signal,
            abortController,
            snapshotStorage: this.dbSnapshotStorage,
            tracing: resumeTracing ?? tracing,
            modelId,
            checkpoint,
        });
        return true;
    }
    async processResumedStream(agent, resumeData, opts) {
        let messageTraceFinalization;
        try {
            if (opts.tracing?.getTelemetry && isTelemetryConfigurableAgent(agent)) {
                try {
                    agent.telemetry(opts.tracing.getTelemetry({
                        agentRole: 'orchestrator',
                        functionId: 'instance-ai.orchestrator',
                        executionMode: opts.tracing.traceKind === 'orchestrator_resume' ? 'resume' : 'foreground',
                    }));
                }
                catch (error) {
                    this.logger.warn('Failed to configure Instance AI resume tracing', {
                        error: getErrorMessage(error),
                        threadId: opts.threadId,
                        runId: opts.runId,
                    });
                }
            }
            const result = opts.tracing
                ? await opts.tracing.withActiveSpan(opts.tracing.actorRun, async () => {
                    return await (0, instance_ai_1.resumeAgentRun)(agent, resumeData, {
                        runId: opts.agentRunId,
                        toolCallId: opts.toolCallId,
                        persistence: { resourceId: opts.user.id, threadId: opts.threadId },
                    }, {
                        threadId: opts.threadId,
                        runId: opts.runId,
                        agentId: ORCHESTRATOR_AGENT_ID,
                        signal: opts.signal,
                        eventBus: this.eventBus,
                        logger: this.logger,
                        agentRunId: opts.agentRunId,
                        onActivity: () => this.runState.touchActiveRun(opts.threadId),
                    });
                })
                : await (0, instance_ai_1.resumeAgentRun)(agent, resumeData, {
                    runId: opts.agentRunId,
                    toolCallId: opts.toolCallId,
                    persistence: { resourceId: opts.user.id, threadId: opts.threadId },
                }, {
                    threadId: opts.threadId,
                    runId: opts.runId,
                    agentId: ORCHESTRATOR_AGENT_ID,
                    signal: opts.signal,
                    eventBus: this.eventBus,
                    logger: this.logger,
                    agentRunId: opts.agentRunId,
                    onActivity: () => this.runState.touchActiveRun(opts.threadId),
                });
            if (result.status === 'suspended') {
                if (result.suspension) {
                    this.runState.suspendRun(opts.threadId, {
                        runId: opts.runId,
                        agentRunId: result.agentRunId,
                        agent,
                        threadId: opts.threadId,
                        user: opts.user,
                        toolCallId: result.suspension.toolCallId,
                        requestId: result.suspension.requestId,
                        abortController: opts.abortController,
                        messageGroupId: this.traceContextsByRunId.get(opts.runId)?.messageGroupId,
                        createdAt: Date.now(),
                        tracing: opts.tracing,
                        ...(opts.modelId !== undefined ? { modelId: opts.modelId } : {}),
                        checkpoint: opts.checkpoint,
                    });
                }
                const intermediateText = await (result.text ?? Promise.resolve(''));
                if (intermediateText) {
                    this.telemetry.track('Builder sent message', {
                        thread_id: opts.threadId,
                        message: intermediateText,
                        is_intermediate: true,
                    });
                }
                const messageGroupId = this.traceContextsByRunId.get(opts.runId)?.messageGroupId;
                const waitingDecision = this.evaluateWaitingResponse(opts.threadId, opts.runId, result.confirmationEvent, { messageGroupId });
                if (waitingDecision?.reason === 'confirmation-invalid') {
                    messageTraceFinalization = await this.finishInvalidConfirmationRun({
                        threadId: opts.threadId,
                        runId: opts.runId,
                        abortController: opts.abortController,
                        snapshotStorage: opts.snapshotStorage,
                        tracing: opts.tracing,
                    });
                    return;
                }
                if (result.confirmationEvent) {
                    this.trackConfirmationRequest(opts.threadId, result.confirmationEvent);
                    this.eventBus.publish(opts.threadId, result.confirmationEvent);
                }
                await this.saveAgentTreeSnapshot(opts.threadId, opts.runId, opts.snapshotStorage);
                const suspensionOutputs = {
                    status: 'suspended',
                    runId: opts.runId,
                    ...(result.suspension?.requestId ? { requestId: result.suspension.requestId } : {}),
                    ...(result.suspension?.toolCallId
                        ? { pendingToolCallId: result.suspension.toolCallId }
                        : {}),
                    ...(result.suspension?.toolName ? { toolName: result.suspension.toolName } : {}),
                };
                await this.finalizeRunTracing(opts.runId, opts.tracing, {
                    status: 'suspended',
                    outputs: suspensionOutputs,
                    metadata: {
                        completion_source: 'orchestrator',
                        ...(result.suspension?.requestId ? { request_id: result.suspension.requestId } : {}),
                        ...(result.suspension?.toolCallId
                            ? { pending_tool_call_id: result.suspension.toolCallId }
                            : {}),
                        ...(result.suspension?.toolName
                            ? { pending_tool_name: result.suspension.toolName }
                            : {}),
                    },
                });
                messageTraceFinalization = {
                    status: 'suspended',
                    outputs: suspensionOutputs,
                    metadata: {
                        completion_source: 'orchestrator',
                        ...(result.suspension?.requestId ? { request_id: result.suspension.requestId } : {}),
                        ...(result.suspension?.toolCallId
                            ? { pending_tool_call_id: result.suspension.toolCallId }
                            : {}),
                        ...(result.suspension?.toolName
                            ? { pending_tool_name: result.suspension.toolName }
                            : {}),
                    },
                };
                return;
            }
            const outputText = await (result.text ?? Promise.resolve(''));
            const messageGroupId = this.traceContextsByRunId.get(opts.runId)?.messageGroupId;
            this.evaluateTerminalResponse(opts.threadId, opts.runId, result.status, {
                messageGroupId,
                workSummary: result.workSummary,
            });
            const finalStatus = result.status === 'errored' ? 'error' : result.status;
            await this.finalizeRunTracing(opts.runId, opts.tracing, {
                status: finalStatus,
                outputText,
            });
            messageTraceFinalization = {
                status: finalStatus,
                outputText,
                metadata: this.buildMessageTraceMetadata(opts.threadId, opts.runId, {
                    status: finalStatus,
                }),
            };
            const archivedWorkflowIds = await this.reapAiTemporaryFromRun(opts.threadId, opts.user, undefined);
            await this.finalizeRun(opts.threadId, opts.runId, result.status, opts.snapshotStorage, {
                archivedWorkflowIds,
            });
            if (result.status === 'completed') {
                await this.countCreditsIfFirst(opts.user, opts.threadId, opts.runId);
                this.telemetry.track('Builder sent message', {
                    thread_id: opts.threadId,
                    message: outputText,
                });
                this.telemetry.track('Builder satisfied user intent', {
                    thread_id: opts.threadId,
                });
            }
        }
        catch (error) {
            if (opts.signal.aborted) {
                const messageGroupId = this.traceContextsByRunId.get(opts.runId)?.messageGroupId;
                const runTimeout = this.liveness.consumeRunTimeout(opts.runId);
                const cancellationReason = runTimeout.timedOut
                    ? liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON
                    : getAbortReason(opts.signal);
                if (cancellationReason === liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON) {
                    this.liveness.publishRunTimeoutNotice(opts.threadId, opts.runId);
                }
                this.evaluateTerminalResponse(opts.threadId, opts.runId, 'cancelled', {
                    messageGroupId,
                });
                await this.finalizeRunTracing(opts.runId, opts.tracing, {
                    status: 'cancelled',
                    reason: cancellationReason,
                });
                messageTraceFinalization = {
                    status: 'cancelled',
                    reason: cancellationReason,
                    metadata: this.buildMessageTraceMetadata(opts.threadId, opts.runId, {
                        status: 'cancelled',
                        cancellationReason,
                        runTimeout,
                    }),
                };
                const archivedWorkflowIds = await this.reapAiTemporaryFromRun(opts.threadId, opts.user, undefined);
                this.publishRunFinish(opts.threadId, opts.runId, 'cancelled', cancellationReason, archivedWorkflowIds);
                await this.saveAgentTreeSnapshot(opts.threadId, opts.runId, opts.snapshotStorage);
                return;
            }
            const errorMessage = getErrorMessage(error);
            const userFacingErrorMessage = getUserFacingErrorMessage(error);
            this.logger.error('Instance AI resumed run error', {
                error: errorMessage,
                threadId: opts.threadId,
                runId: opts.runId,
            });
            const messageGroupId = this.traceContextsByRunId.get(opts.runId)?.messageGroupId;
            this.evaluateTerminalResponse(opts.threadId, opts.runId, 'errored', {
                messageGroupId,
                errorMessage: userFacingErrorMessage,
            });
            await this.finalizeRunTracing(opts.runId, opts.tracing, {
                status: 'error',
                reason: errorMessage,
            });
            messageTraceFinalization = {
                status: 'error',
                reason: errorMessage,
                metadata: this.buildMessageTraceMetadata(opts.threadId, opts.runId, {
                    status: 'error',
                }),
            };
            const archivedWorkflowIds = await this.reapAiTemporaryFromRun(opts.threadId, opts.user, undefined);
            this.eventBus.publish(opts.threadId, {
                type: 'run-finish',
                runId: opts.runId,
                agentId: ORCHESTRATOR_AGENT_ID,
                payload: {
                    status: 'error',
                    reason: userFacingErrorMessage,
                    ...(archivedWorkflowIds.length > 0 ? { archivedWorkflowIds } : {}),
                },
            });
            await this.saveAgentTreeSnapshot(opts.threadId, opts.runId, opts.snapshotStorage);
        }
        finally {
            this.runState.clearActiveRun(opts.threadId);
            if (messageTraceFinalization) {
                await this.maybeFinalizeRunTraceRoot(opts.runId, messageTraceFinalization);
                if (messageTraceFinalization.status !== 'cancelled') {
                    this.liveness.consumeRunTimeout(opts.runId);
                }
            }
            if (!this.runState.hasSuspendedRun(opts.threadId)) {
                if (opts.checkpoint?.isCheckpointFollowUp) {
                    await this.finalizeCheckpointFollowUp(opts.user, opts.threadId, opts.checkpoint.checkpointTaskId);
                }
                else {
                    await this.schedulePlannedTasks(opts.user, opts.threadId);
                }
                await this.drainPendingCheckpointReentries(opts.user, opts.threadId);
            }
        }
    }
    spawnBackgroundTask(runId, opts, snapshotStorage, messageGroupIdOverride) {
        const outcome = this.backgroundTasks.spawn({
            taskId: opts.taskId,
            threadId: opts.threadId,
            runId,
            role: opts.role,
            agentId: opts.agentId,
            messageGroupId: messageGroupIdOverride ?? this.runState.getMessageGroupId(opts.threadId),
            plannedTaskId: opts.plannedTaskId,
            workItemId: opts.workItemId,
            traceContext: opts.traceContext,
            createTraceContext: opts.createTraceContext,
            dedupeKey: opts.dedupeKey,
            parentCheckpointId: opts.parentCheckpointId,
            run: opts.run,
            onLimitReached: async (errorMessage) => {
                await this.finalizeDetachedTraceRun(opts.taskId, opts.traceContext, {
                    status: 'failed',
                    outputs: {
                        taskId: opts.taskId,
                        agentId: opts.agentId,
                        role: opts.role,
                    },
                    error: errorMessage,
                    metadata: {
                        ...(opts.plannedTaskId ? { planned_task_id: opts.plannedTaskId } : {}),
                        ...(opts.workItemId ? { work_item_id: opts.workItemId } : {}),
                    },
                });
                this.eventBus.publish(opts.threadId, {
                    type: 'agent-completed',
                    runId,
                    agentId: opts.agentId,
                    payload: {
                        role: opts.role,
                        result: '',
                        error: errorMessage,
                    },
                });
            },
            onCompleted: async (task) => {
                await this.finalizeBackgroundTaskTracing(task, 'completed');
                this.eventBus.publish(opts.threadId, {
                    type: 'agent-completed',
                    runId,
                    agentId: opts.agentId,
                    payload: { role: opts.role, result: task.result ?? '' },
                });
                const user = this.runState.getThreadUser(opts.threadId);
                if (user) {
                    await this.handlePlannedTaskSettlement(user, task, 'succeeded');
                }
            },
            onFailed: async (task) => {
                await this.finalizeBackgroundTaskTracing(task, 'failed');
                this.eventBus.publish(opts.threadId, {
                    type: 'agent-completed',
                    runId,
                    agentId: opts.agentId,
                    payload: { role: opts.role, result: '', error: task.error ?? 'Unknown error' },
                });
                const user = this.runState.getThreadUser(opts.threadId);
                if (user) {
                    await this.handlePlannedTaskSettlement(user, task, 'failed');
                }
            },
            onSettled: async (task) => {
                await this.recordBackgroundTerminalOutcome(task);
                await this.saveAgentTreeSnapshot(opts.threadId, runId, snapshotStorage, true, task.messageGroupId);
                if (task.plannedTaskId)
                    return;
                const parentCheckpointId = task.parentCheckpointId;
                if (parentCheckpointId) {
                    const user = this.runState.getThreadUser(opts.threadId);
                    if (!user) {
                        this.queuePendingCheckpointReentry(opts.threadId, parentCheckpointId);
                        return;
                    }
                    const reentered = await this.maybeReenterParentCheckpoint(user, opts.threadId, task);
                    if (!reentered) {
                        this.queuePendingCheckpointReentry(opts.threadId, parentCheckpointId);
                    }
                    return;
                }
                const remaining = this.backgroundTasks.getRunningTasks(opts.threadId);
                const hasActiveRun = !!this.runState.getActiveRunId(opts.threadId);
                const hasSuspendedRun = this.runState.hasSuspendedRun(opts.threadId);
                if (remaining.length === 0 && !hasActiveRun && !hasSuspendedRun) {
                    if (this.liveness.hasTimedOutActiveRunThread(opts.threadId)) {
                        this.logger.debug('Skipping background auto-follow-up after active run timeout', {
                            threadId: opts.threadId,
                            taskId: task.taskId,
                        });
                        return;
                    }
                    const user = this.runState.getThreadUser(opts.threadId);
                    if (user) {
                        const payload = JSON.stringify({
                            role: opts.role,
                            status: task.result ? 'completed' : task.error ? 'failed' : 'finished',
                            result: task.result ?? undefined,
                            outcome: task.outcome ?? undefined,
                            error: task.error ?? undefined,
                        }, null, 2);
                        await this.startInternalFollowUpRun(user, opts.threadId, `<background-task-completed>\n${payload}\n</background-task-completed>\n\n${internal_messages_1.AUTO_FOLLOW_UP_MESSAGE}`, task.messageGroupId);
                    }
                }
            },
        });
        if (outcome.status === 'started') {
            return { status: 'started', taskId: outcome.task.taskId, agentId: outcome.task.agentId };
        }
        if (outcome.status === 'duplicate') {
            this.logger.warn('Background task dispatch deduped — task already in flight', {
                threadId: opts.threadId,
                requestedTaskId: opts.taskId,
                existingTaskId: outcome.existing.taskId,
                plannedTaskId: opts.dedupeKey?.plannedTaskId,
                workflowId: opts.dedupeKey?.workflowId,
                role: opts.role,
            });
            void this.finalizeDetachedTraceRun(opts.taskId, opts.traceContext, {
                status: 'cancelled',
                outputs: {
                    taskId: opts.taskId,
                    agentId: opts.agentId,
                    role: opts.role,
                    deduped_to: outcome.existing.taskId,
                },
                metadata: {
                    deduped: true,
                    existing_task_id: outcome.existing.taskId,
                    ...(opts.plannedTaskId ? { planned_task_id: opts.plannedTaskId } : {}),
                    ...(opts.workItemId ? { work_item_id: opts.workItemId } : {}),
                },
            });
            this.eventBus.publish(opts.threadId, {
                type: 'agent-completed',
                runId,
                agentId: opts.agentId,
                payload: {
                    role: opts.role,
                    result: '',
                    error: `Deduped: task already in flight as ${outcome.existing.taskId}`,
                },
            });
            return {
                status: 'duplicate',
                existing: {
                    taskId: outcome.existing.taskId,
                    agentId: outcome.existing.agentId,
                    role: outcome.existing.role,
                    plannedTaskId: outcome.existing.plannedTaskId,
                    workItemId: outcome.existing.workItemId,
                },
            };
        }
        return { status: 'limit-reached' };
    }
    async buildMessageWithRunningTasks(threadId, message) {
        return await (0, instance_ai_1.enrichMessageWithBackgroundTasks)(message, this.backgroundTasks.getRunningTasks(threadId), {
            formatTask: async (task) => `[Running task — ${task.role}]: taskId=${task.taskId}`,
        });
    }
    trackConfirmationRequest(threadId, confirmationEvent) {
        const payload = confirmationEvent.payload;
        const inputThreadId = (0, nanoid_1.nanoid)();
        payload.inputThreadId = inputThreadId;
        const inputType = payload.inputType;
        let type;
        if (inputType) {
            type = inputType;
        }
        else if (Array.isArray(payload.setupRequests) && payload.setupRequests.length > 0) {
            type = 'setup';
        }
        else if (Array.isArray(payload.credentialRequests) && payload.credentialRequests.length > 0) {
            type = 'credential-setup';
        }
        else {
            type = 'approval';
        }
        let numSteps = 1;
        if (Array.isArray(payload.questions)) {
            numSteps = payload.questions.length;
        }
        else if (Array.isArray(payload.setupRequests)) {
            numSteps = payload.setupRequests.length;
        }
        else if (Array.isArray(payload.credentialRequests)) {
            numSteps = payload.credentialRequests.length;
        }
        this.telemetry.track('Builder asked for input', {
            thread_id: threadId,
            input_thread_id: inputThreadId,
            type,
            num_steps: numSteps,
        });
    }
    async reapAiTemporaryFromRun(threadId, user, createdWorkflowIds) {
        const runningTaskCount = this.backgroundTasks.getRunningTasks(threadId).length;
        if (runningTaskCount > 0) {
            this.logger.debug('Deferring AI-builder temporary workflow cleanup until tasks settle', {
                threadId,
                runningTaskCount,
            });
            return [];
        }
        let markedWorkflows = [];
        try {
            markedWorkflows = await this.aiBuilderTemporaryWorkflowRepository.findByThread(threadId);
        }
        catch (error) {
            this.logger.warn('Failed to inspect AI-builder temporary workflows during run finish', {
                threadId,
                error: getErrorMessage(error),
            });
        }
        const workflowIds = new Set([
            ...markedWorkflows.map(({ workflowId }) => workflowId),
            ...(createdWorkflowIds ?? []),
        ]);
        if (workflowIds.size === 0)
            return [];
        return await this.archiveAiTemporaryWorkflows(threadId, user, workflowIds);
    }
    async archiveAiTemporaryWorkflows(threadId, user, workflowIds) {
        const adapter = this.adapterService.createContext(user, { threadId });
        const archived = [];
        for (const workflowId of workflowIds) {
            try {
                const didArchive = await adapter.workflowService.archiveIfAiTemporary(workflowId);
                if (didArchive)
                    archived.push(workflowId);
            }
            catch (error) {
                this.logger.warn('Failed to reap AI-builder temporary workflow', {
                    threadId,
                    workflowId,
                    error: getErrorMessage(error),
                });
            }
        }
        return archived;
    }
    async finalizeCancelledSuspendedRun(suspended, reason = 'user_cancelled') {
        const runTimeout = reason === liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON
            ? this.liveness.consumeRunTimeout(suspended.runId)
            : undefined;
        if (reason === liveness_1.INSTANCE_AI_RUN_TIMEOUT_REASON) {
            this.liveness.publishRunTimeoutNotice(suspended.threadId, suspended.runId);
        }
        await this.finalizeRunTracing(suspended.runId, suspended.tracing, {
            status: 'cancelled',
            reason,
        });
        const archivedWorkflowIds = await this.reapAiTemporaryFromRun(suspended.threadId, suspended.user, undefined);
        this.publishRunFinish(suspended.threadId, suspended.runId, 'cancelled', reason, archivedWorkflowIds);
        await this.saveAgentTreeSnapshot(suspended.threadId, suspended.runId, this.dbSnapshotStorage, true);
        await this.maybeFinalizeRunTraceRoot(suspended.runId, {
            status: 'cancelled',
            reason,
            metadata: this.buildMessageTraceMetadata(suspended.threadId, suspended.runId, {
                status: 'cancelled',
                cancellationReason: reason,
                ...(runTimeout ? { runTimeout } : {}),
            }),
        });
    }
    async reapAiTemporaryForThreadCleanup(threadId) {
        let markedWorkflows;
        try {
            markedWorkflows = await this.aiBuilderTemporaryWorkflowRepository.findByThread(threadId);
        }
        catch (error) {
            this.logger.warn('Failed to inspect AI-builder temporary workflows during thread cleanup', {
                threadId,
                error: getErrorMessage(error),
            });
            return;
        }
        if (markedWorkflows.length === 0)
            return;
        let thread;
        try {
            thread = await this.threadRepo.findOneBy({ id: threadId });
        }
        catch (error) {
            this.logger.warn('Failed to load thread owner for AI-builder temporary workflow cleanup', {
                threadId,
                markedWorkflowCount: markedWorkflows.length,
                error: getErrorMessage(error),
            });
            return;
        }
        if (!thread?.resourceId) {
            this.logger.warn('Skipping AI-builder temporary workflow cleanup for thread without owner', {
                threadId,
                markedWorkflowCount: markedWorkflows.length,
            });
            return;
        }
        let user;
        try {
            user = await this.userRepository.findOneBy({ id: thread.resourceId });
        }
        catch (error) {
            this.logger.warn('Failed to load user for AI-builder temporary workflow cleanup', {
                threadId,
                userId: thread.resourceId,
                markedWorkflowCount: markedWorkflows.length,
                error: getErrorMessage(error),
            });
            return;
        }
        if (!user) {
            this.logger.warn('Skipping AI-builder temporary workflow cleanup for missing thread owner', {
                threadId,
                userId: thread.resourceId,
                markedWorkflowCount: markedWorkflows.length,
            });
            return;
        }
        await this.archiveAiTemporaryWorkflows(threadId, user, new Set(markedWorkflows.map(({ workflowId }) => workflowId)));
    }
    publishRunFinish(threadId, runId, status, reason, archivedWorkflowIds) {
        const effectiveStatus = status === 'errored' ? 'error' : status;
        const hasArchived = archivedWorkflowIds && archivedWorkflowIds.length > 0;
        this.eventBus.publish(threadId, {
            type: 'run-finish',
            runId,
            agentId: ORCHESTRATOR_AGENT_ID,
            payload: {
                status: effectiveStatus,
                ...(status === 'cancelled' ? { reason: reason ?? 'user_cancelled' } : {}),
                ...(hasArchived ? { archivedWorkflowIds } : {}),
            },
        });
    }
    async finalizeRun(threadId, runId, status, snapshotStorage, options) {
        this.publishRunFinish(threadId, runId, status, undefined, options?.archivedWorkflowIds);
        await this.saveAgentTreeSnapshot(threadId, runId, snapshotStorage);
        if (status === 'completed' && options?.userId && options?.modelId) {
            void this.refineTitleIfNeeded(threadId, options.userId, options.modelId);
        }
    }
    async refineTitleIfNeeded(threadId, userId, modelId) {
        try {
            const memory = this.agentMemory;
            const thread = await memory.getThread(threadId);
            if (!thread?.title)
                return;
            if (thread.metadata?.titleRefined)
                return;
            const history = await memory.getMessages(threadId, { limit: 5 });
            const userTexts = history.flatMap((m) => {
                if (!('role' in m) || m.role !== 'user')
                    return [];
                const text = this.extractStoredMessageText(m.content);
                return text.length > 0 ? [text] : [];
            });
            if (userTexts.length === 0)
                return;
            const userText = userTexts.join('\n');
            const baseTracing = this.getTraceContextForContinuation(threadId);
            const titleTracing = await (0, instance_ai_1.createInternalOperationTraceContext)({
                threadId,
                conversationId: threadId,
                messageId: `internal:title:${threadId}`,
                runId: `title-${(0, nanoid_1.nanoid)()}`,
                userId,
                modelId,
                operationName: 'thread_title',
                input: {
                    message_count: userTexts.length,
                    source: 'thread_title_refinement',
                },
                proxyConfig: baseTracing?.proxyConfig,
                metadata: {
                    n8n_version: constants_1.N8N_VERSION || undefined,
                    operation_name: 'thread_title',
                    trigger: 'run_completed',
                },
            });
            const titleTelemetry = titleTracing?.getTelemetry?.({
                agentRole: 'thread_title',
                functionId: 'instance-ai.thread_title',
                executionMode: 'internal',
                metadata: {
                    operation_name: 'thread_title',
                },
            });
            let llmTitle;
            if (titleTracing) {
                try {
                    llmTitle = await titleTracing.withActiveSpan(titleTracing.rootRun, async () => {
                        const title = await (0, instance_ai_1.generateTitleForRun)(modelId, userText, {
                            ...(titleTelemetry ? { telemetry: titleTelemetry } : {}),
                        });
                        if (title) {
                            await titleTracing.finishRun(titleTracing.rootRun, {
                                outputs: { title },
                                metadata: { final_status: 'completed' },
                            });
                        }
                        else {
                            await titleTracing.finishRun(titleTracing.rootRun, {
                                outputs: { title: null },
                                metadata: { final_status: 'skipped' },
                            });
                        }
                        return title;
                    });
                }
                finally {
                    (0, instance_ai_1.releaseTraceClient)(titleTracing.rootRun.traceId);
                }
            }
            else {
                llmTitle = await (0, instance_ai_1.generateTitleForRun)(modelId, userText);
            }
            if (!llmTitle)
                return;
            await (0, instance_ai_1.patchThread)(memory, {
                threadId,
                update: ({ metadata }) => ({
                    title: llmTitle,
                    metadata: { ...metadata, titleRefined: true },
                }),
            });
            this.eventBus.publish(threadId, {
                type: 'thread-title-updated',
                runId: '',
                agentId: ORCHESTRATOR_AGENT_ID,
                payload: { title: llmTitle },
            });
        }
        catch (error) {
            this.logger.warn('Failed to refine thread title', {
                threadId,
                error: getErrorMessage(error),
            });
        }
    }
    extractStoredMessageText(content) {
        if (typeof content === 'string')
            return content;
        if (Array.isArray(content)) {
            return content.flatMap((part) => (isTextMessagePart(part) ? [part.text] : [])).join('\n');
        }
        return '';
    }
    async saveAgentTreeSnapshot(threadId, runId, snapshotStorage, isUpdate = false, overrideMessageGroupId) {
        try {
            const messageGroupId = overrideMessageGroupId ?? this.runState.getMessageGroupId(threadId);
            let events;
            let groupRunIds;
            if (messageGroupId) {
                groupRunIds = this.getRunIdsForMessageGroup(messageGroupId);
                if (groupRunIds.length === 0) {
                    const snapshot = await snapshotStorage.getLatest(threadId, { messageGroupId, runId });
                    groupRunIds = snapshot?.runIds?.length ? snapshot.runIds : [runId];
                }
                events = this.eventBus.getEventsForRuns(threadId, groupRunIds);
            }
            else {
                events = this.eventBus.getEventsForRun(threadId, runId);
            }
            if (isUpdate && events.length === 0) {
                this.logger.warn('Skipped updating empty Instance AI agent tree snapshot', {
                    threadId,
                    runId,
                    messageGroupId,
                });
                return;
            }
            const agentTree = (0, instance_ai_1.buildAgentTreeFromEvents)(events);
            const tracing = this.traceContextsByRunId.get(runId)?.tracing;
            const saveOptions = {
                messageGroupId,
                runIds: groupRunIds,
                traceId: tracing?.rootRun.otelTraceId,
                spanId: tracing?.rootRun.otelSpanId,
                langsmithRunId: tracing?.rootRun.id,
                langsmithTraceId: tracing?.rootRun.traceId,
            };
            if (isUpdate) {
                await snapshotStorage.updateLast(threadId, agentTree, runId, saveOptions);
            }
            else {
                await snapshotStorage.save(threadId, agentTree, runId, saveOptions);
            }
        }
        catch (error) {
            this.logger.warn('Failed to save agent tree snapshot', {
                threadId,
                runId,
                error: error instanceof Error ? error.message : String(error),
            });
        }
    }
    parseMcpServers(raw) {
        if (!raw.trim())
            return [];
        return raw.split(',').map((entry) => {
            const [name, url] = entry.trim().split('=');
            return { name: name.trim(), url: url?.trim() };
        });
    }
};
exports.InstanceAiService = InstanceAiService;
__decorate([
    (0, decorators_1.OnLeaderTakeover)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], InstanceAiService.prototype, "startCheckpointPruning", null);
__decorate([
    (0, decorators_1.OnLeaderStepdown)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], InstanceAiService.prototype, "stopCheckpointPruning", null);
exports.InstanceAiService = InstanceAiService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [backend_common_1.Logger,
        config_1.GlobalConfig,
        n8n_core_1.InstanceSettings,
        instance_ai_adapter_service_1.InstanceAiAdapterService,
        in_process_event_bus_1.InProcessEventBus,
        instance_ai_settings_service_1.InstanceAiSettingsService,
        typeorm_agent_memory_1.TypeORMAgentMemory,
        typeorm_agent_checkpoint_store_1.TypeORMAgentCheckpointStore,
        ai_service_1.AiService,
        push_1.Push,
        instance_ai_thread_repository_1.InstanceAiThreadRepository,
        url_service_1.UrlService,
        db_snapshot_storage_1.DbSnapshotStorage,
        db_iteration_log_storage_1.DbIterationLogStorage,
        source_control_preferences_service_ee_1.SourceControlPreferencesService,
        telemetry_1.Telemetry,
        db_1.UserRepository,
        db_1.AiBuilderTemporaryWorkflowRepository,
        n8n_core_1.ErrorReporter,
        config_1.SsrfProtectionConfig,
        ssrf_protection_service_1.SsrfProtectionService,
        event_service_1.EventService])
], InstanceAiService);
//# sourceMappingURL=instance-ai.service.js.map