@n8n-plus/n8n-plus
Version:
n8n Workflow Automation Tool (plus edition)
3,671 lines • 161 kB
JavaScript
"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