n8n
Version:
n8n Workflow Automation Tool
431 lines • 22.8 kB
JavaScript
"use strict";
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 __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentPublishService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const telemetry_1 = require("@n8n/telemetry");
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const n8n_workflow_1 = require("n8n-workflow");
const uuid_1 = require("uuid");
const credentials_service_1 = require("../../credentials/credentials.service");
const conflict_error_1 = require("../../errors/response-errors/conflict.error");
const not_found_error_1 = require("../../errors/response-errors/not-found.error");
const event_service_1 = require("../../events/event.service");
const agent_missing_skill_ids_1 = require("../../modules/agents/utils/agent-missing-skill-ids");
const telemetry_2 = require("../../telemetry");
const agents_credential_provider_1 = require("./adapters/agents-credential-provider");
const agent_custom_tools_service_1 = require("./agent-custom-tools.service");
const agent_telemetry_1 = require("./agent-telemetry");
const agent_modification_telemetry_service_1 = require("./agent-modification-telemetry.service");
const agent_runtime_cache_service_1 = require("./agent-runtime-cache.service");
const agent_setup_completion_service_1 = require("./agent-setup-completion.service");
const agent_validation_service_1 = require("./agent-validation.service");
const agent_task_entity_1 = require("./entities/agent-task.entity");
const chat_integration_service_1 = require("./integrations/chat-integration.service");
const agent_history_repository_1 = require("./repositories/agent-history.repository");
const agent_task_snapshot_repository_1 = require("./repositories/agent-task-snapshot.repository");
const agent_task_repository_1 = require("./repositories/agent-task.repository");
const agent_repository_1 = require("./repositories/agent.repository");
const sub_agent_cleanup_service_1 = require("./sub-agents/sub-agent-cleanup.service");
const agent_capabilities_1 = require("./utils/agent-capabilities");
function requireValidValidation(validation) {
if (validation.status !== 'valid') {
throw new n8n_workflow_1.UserError('Agent configuration has errors that must be resolved before publishing');
}
}
let AgentPublishService = class AgentPublishService {
constructor(logger, agentRepository, agentHistoryRepository, agentTaskSnapshotRepository, agentTaskRepository, customToolsService, runtimeCacheService, subAgentCleanupService, agentValidationService, credentialsService, telemetry, eventService, setupCompletionService, modificationTelemetry) {
this.logger = logger;
this.agentRepository = agentRepository;
this.agentHistoryRepository = agentHistoryRepository;
this.agentTaskSnapshotRepository = agentTaskSnapshotRepository;
this.agentTaskRepository = agentTaskRepository;
this.customToolsService = customToolsService;
this.runtimeCacheService = runtimeCacheService;
this.subAgentCleanupService = subAgentCleanupService;
this.agentValidationService = agentValidationService;
this.credentialsService = credentialsService;
this.telemetry = telemetry;
this.eventService = eventService;
this.setupCompletionService = setupCompletionService;
this.modificationTelemetry = modificationTelemetry;
}
async publishAgent(agentId, projectId, user, emitter, versionId) {
const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
if (!agent) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
if (!versionId && agent.versionId !== null && agent.versionId === agent.activeVersionId) {
return { agent };
}
if (versionId !== undefined && versionId === agent.activeVersionId) {
return { agent };
}
let targetHistory;
if (versionId) {
const target = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agent.id);
if (!target) {
throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found for agent "${agent.id}"`);
}
targetHistory = target;
}
const tasks = versionId
? new Map()
: new Map((await this.agentTaskRepository.findByAgentId(agentId)).map((task) => [task.id, task]));
const validation = await this.assertPublishable(agent, projectId, user, tasks, targetHistory);
const emitSetupCompleted = this.setupCompletionService.recordPublishedSetupComplete(agent, projectId, user, targetHistory ? targetHistory.schema : agent.schema);
await this.agentRepository.manager.transaction(async (trx) => {
if (targetHistory) {
agent.activeVersionId = targetHistory.versionId;
agent.activeVersion = targetHistory;
agent.versionId = (0, uuid_1.v4)();
}
else {
agent.versionId ??= (0, uuid_1.v4)();
agent.activeVersion = await this.agentHistoryRepository.saveVersion({
versionId: agent.versionId,
agentId: agent.id,
schema: agent.schema,
tools: this.customToolsService.snapshotConfiguredTools(agent.schema, agent.tools ?? {}),
skills: this.pickConfiguredSkillBodies(agent.schema, agent.skills ?? {}),
publishedBy: user,
}, trx);
await this.snapshotConfiguredTasks(trx, agent.versionId, agent.schema, tasks);
agent.activeVersionId = agent.versionId;
}
await trx.save(agent);
});
this.eventService.emit('agent-saved', { agentId });
this.runtimeCacheService.clearRuntimes(agentId);
this.trackPublished(agent, projectId, user, emitter, targetHistory);
await emitSetupCompleted?.();
const credentialIntegrations = agent.integrations ?? [];
if (credentialIntegrations.length > 0) {
await di_1.Container.get(chat_integration_service_1.ChatIntegrationService)
.syncToConfig(agent, [], credentialIntegrations)
.catch((error) => this.logger.warn('Failed to connect integrations on publish', {
agentId,
error,
}));
}
const { AgentTaskService } = await import('./agent-task.service.js');
await di_1.Container.get(AgentTaskService)
.requestReconcile(agentId)
.catch((error) => this.logger.warn('Failed to register agent tasks on publish', { agentId, error }));
this.logger.debug('Published SDK agent', { agentId, projectId, userId: user.id });
return versionId ? { agent } : { agent, draftValidation: validation };
}
async assertPublishable(agent, projectId, user, tasks, targetHistory) {
const credentialProvider = new agents_credential_provider_1.AgentsCredentialProvider(this.credentialsService, projectId, user);
const validation = targetHistory
? await this.agentValidationService.validateAgentHistoryConfiguration(agent.id, projectId, targetHistory, agent.integrations ?? [], credentialProvider)
: await this.agentValidationService.validateAgentEntityConfiguration(agent, projectId, tasks, credentialProvider);
requireValidValidation(validation);
return validation;
}
async unpublishAgent(agentId, projectId, user, by) {
const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
if (!agent) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
await this.agentRepository.manager.transaction(async (trx) => {
agent.activeVersionId = null;
agent.activeVersion = null;
agent.versionId = (0, uuid_1.v4)();
await trx.save(agent);
});
this.eventService.emit('agent-saved', { agentId });
this.runtimeCacheService.clearRuntimes(agentId);
this.trackUnpublished(agentId, projectId, user, by);
await this.subAgentCleanupService.removeSubAgentFromParents(agentId, projectId);
const chatIntegrationService = di_1.Container.get(chat_integration_service_1.ChatIntegrationService);
for (const integration of agent.integrations ?? []) {
await chatIntegrationService.disconnectChannel(agentId, integration, {
deleteSubscriptions: false,
});
}
const { AgentTaskService } = await import('./agent-task.service.js');
await di_1.Container.get(AgentTaskService)
.requestReconcile(agentId)
.catch((error) => this.logger.warn('Failed to stop agent tasks on unpublish', { agentId, error }));
this.logger.debug('Unpublished SDK agent', { agentId, projectId });
return agent;
}
trackPublished(agent, projectId, user, emitter, targetHistory) {
const published = targetHistory ? targetHistory.schema : agent.schema;
const counts = (0, agent_capabilities_1.countAgentCapabilities)(published, agent.integrations);
const { model, tool_types } = (0, agent_telemetry_1.buildAgentConfigurationTelemetryFromConfig)(published, agent.integrations);
const properties = {
agent_id: agent.id,
project_id: projectId,
user_id: user.id,
trigger: targetHistory ? 'republish' : emitter.trigger,
version_id: agent.activeVersionId,
capability_kinds: (0, agent_capabilities_1.configuredCapabilityKinds)(counts),
capability_count: (0, agent_capabilities_1.totalAgentCapabilities)(counts),
tool_count: counts.tool,
skill_count: counts.skill,
sub_agent_count: counts.subAgent,
mcp_server_count: counts.mcpServer,
vector_store_count: counts.vectorStore,
task_count: counts.task,
trigger_count: counts.channel,
model,
tool_types,
};
switch (emitter.by) {
case 'user':
this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.USER_PUBLISHED_AGENT, {
...properties,
event_version: '2',
});
return;
case 'builder':
this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.BUILDER_PUBLISHED_AGENT, {
...properties,
event_version: '1',
});
return;
case 'mcp':
this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.MCP_PUBLISHED_AGENT, {
...properties,
event_version: '1',
});
}
}
trackUnpublished(agentId, projectId, user, by) {
const properties = { agent_id: agentId, project_id: projectId, user_id: user.id };
switch (by) {
case 'user':
this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.USER_UNPUBLISHED_AGENT, {
...properties,
event_version: '2',
});
return;
case 'builder':
this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.BUILDER_UNPUBLISHED_AGENT, {
...properties,
event_version: '1',
});
return;
case 'mcp':
this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.MCP_UNPUBLISHED_AGENT, {
...properties,
event_version: '1',
});
}
}
async revertToPublishedAgent(agentId, projectId, user, modifiedBy) {
const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
if (!agent) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
const activeVersion = agent.activeVersion;
if (!activeVersion) {
throw new conflict_error_1.ConflictError(`Agent "${agentId}" is not published`);
}
const previousSchema = agent.schema;
const previousTools = agent.tools ?? {};
const previousSkills = agent.skills ?? {};
let tasksChanged = false;
await this.agentRepository.manager.transaction(async (trx) => {
agent.schema = activeVersion.schema ? (0, n8n_workflow_1.deepCopy)(activeVersion.schema) : null;
agent.tools = (0, n8n_workflow_1.deepCopy)(activeVersion.tools ?? {});
agent.skills = (0, n8n_workflow_1.deepCopy)(activeVersion.skills ?? {});
agent.versionId = activeVersion.versionId;
if (agent.schema) {
agent.name = agent.schema.name;
}
await trx.save(agent);
tasksChanged = await this.restoreTasksFromSnapshot(trx, agentId, activeVersion.versionId);
});
this.eventService.emit('agent-saved', { agentId });
this.runtimeCacheService.clearRuntimes(agentId);
await this.recordRevert(agent, projectId, user, modifiedBy, previousSchema, {
tools: !(0, isEqual_1.default)(previousTools, agent.tools ?? {}),
skills: !(0, isEqual_1.default)(previousSkills, agent.skills ?? {}),
tasks: tasksChanged,
});
this.logger.debug('Reverted SDK agent to published version', { agentId, projectId });
return agent;
}
async revertToVersion(agentId, projectId, versionId, user, modifiedBy) {
const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
if (!agent) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
const previousSchema = agent.schema;
const previousTools = agent.tools ?? {};
const previousSkills = agent.skills ?? {};
let tasksChanged = false;
await this.agentRepository.manager.transaction(async (trx) => {
const target = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agentId, trx);
if (!target) {
throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found`);
}
agent.schema = target.schema ? (0, n8n_workflow_1.deepCopy)(target.schema) : null;
agent.tools = (0, n8n_workflow_1.deepCopy)(target.tools ?? {});
agent.skills = (0, n8n_workflow_1.deepCopy)(target.skills ?? {});
agent.versionId = (0, uuid_1.v4)();
if (agent.schema) {
agent.name = agent.schema.name;
}
await trx.save(agent);
tasksChanged = await this.restoreTasksFromSnapshot(trx, agentId, target.versionId);
});
this.eventService.emit('agent-saved', { agentId });
this.runtimeCacheService.clearRuntimes(agentId);
await this.recordRevert(agent, projectId, user, modifiedBy, previousSchema, {
tools: !(0, isEqual_1.default)(previousTools, agent.tools ?? {}),
skills: !(0, isEqual_1.default)(previousSkills, agent.skills ?? {}),
tasks: tasksChanged,
});
this.logger.debug('Reverted SDK agent to a specific version', {
agentId,
projectId,
versionId,
});
return agent;
}
async recordRevert(agent, projectId, user, modifiedBy, previousSchema, sidecarChanges) {
const integrations = agent.integrations ?? [];
this.modificationTelemetry.record({
agent,
projectId,
user,
by: modifiedBy,
changedParts: (0, agent_modification_telemetry_service_1.diffAgentConfigParts)(previousSchema, agent.schema, integrations, integrations, sidecarChanges),
wasUnconfigured: false,
});
}
async hasPublishHistory(agentId) {
return await this.agentHistoryRepository.existsForAgent(agentId);
}
async getVersion(agentId, projectId, versionId) {
const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
if (!agent) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
const version = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agentId);
if (!version) {
throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found for agent "${agentId}"`);
}
const tasks = await this.agentTaskSnapshotRepository.findByVersionId(versionId);
return { agent, version, tasks };
}
async listPublishHistory(agentId, projectId, take, skip) {
const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
if (!agent) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
const versions = await this.agentHistoryRepository.findByAgentId(agentId, take, skip);
return versions.map((v) => ({
versionId: v.versionId,
agentId: v.agentId,
createdAt: v.createdAt.toISOString(),
updatedAt: v.updatedAt.toISOString(),
author: v.author,
isActive: v.versionId === agent.activeVersionId,
}));
}
async snapshotConfiguredTasks(trx, versionId, config, tasks) {
if (!config)
return;
const refs = config.tasks ?? [];
if (refs.length === 0)
return;
const missing = refs.filter((ref) => !tasks.has(ref.id)).map((ref) => ref.id);
if (missing.length > 0) {
throw new n8n_workflow_1.UserError(`Cannot publish agent with missing task bodies: ${missing.join(', ')}`);
}
await this.agentTaskSnapshotRepository.saveForVersion(refs.map((ref) => {
const body = tasks.get(ref.id);
if (!body) {
throw new n8n_workflow_1.UserError(`Cannot publish agent with missing task body: ${ref.id}`);
}
return {
versionId,
taskId: ref.id,
enabled: ref.enabled,
name: body.name,
objective: body.objective,
cronExpression: body.cronExpression,
};
}), trx);
}
pickConfiguredSkillBodies(config, skills) {
if (!config)
return null;
const missing = (0, agent_missing_skill_ids_1.getMissingSkillIds)(config, skills);
if (missing.length > 0) {
throw new n8n_workflow_1.UserError(`Cannot publish agent with missing skill bodies: ${missing.join(', ')}`);
}
const snapshot = {};
for (const ref of config.skills ?? []) {
const skill = skills[ref.id];
if (skill)
snapshot[ref.id] = (0, n8n_workflow_1.deepCopy)(skill);
}
return snapshot;
}
async restoreTasksFromSnapshot(trx, agentId, versionId) {
const repo = trx.getRepository(agent_task_entity_1.AgentTask);
const existing = await repo.findBy({ agentId });
const snapshots = await this.agentTaskSnapshotRepository.findByVersionId(versionId, trx);
const existingBodies = Object.fromEntries(existing.map((row) => [
row.id,
{ name: row.name, objective: row.objective, cronExpression: row.cronExpression },
]));
const snapshotBodies = Object.fromEntries(snapshots.map((snapshot) => [
snapshot.taskId,
{
name: snapshot.name,
objective: snapshot.objective,
cronExpression: snapshot.cronExpression,
},
]));
const tasksChanged = !(0, isEqual_1.default)(existingBodies, snapshotBodies);
const snapshotIds = new Set(snapshots.map((snapshot) => snapshot.taskId));
const orphanIds = existing.filter((row) => !snapshotIds.has(row.id)).map((row) => row.id);
if (orphanIds.length > 0)
await repo.delete(orphanIds);
const existingIds = new Set(existing.map((row) => row.id));
for (const snapshot of snapshots) {
if (existingIds.has(snapshot.taskId)) {
await repo.update(snapshot.taskId, {
name: snapshot.name,
objective: snapshot.objective,
cronExpression: snapshot.cronExpression,
});
}
else {
await repo.insert({
id: snapshot.taskId,
agentId,
name: snapshot.name,
objective: snapshot.objective,
cronExpression: snapshot.cronExpression,
});
}
}
return tasksChanged;
}
};
exports.AgentPublishService = AgentPublishService;
exports.AgentPublishService = AgentPublishService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger, agent_repository_1.AgentRepository, agent_history_repository_1.AgentHistoryRepository, agent_task_snapshot_repository_1.AgentTaskSnapshotRepository, agent_task_repository_1.AgentTaskRepository, agent_custom_tools_service_1.AgentCustomToolsService, agent_runtime_cache_service_1.AgentRuntimeCacheService, sub_agent_cleanup_service_1.SubAgentCleanupService, agent_validation_service_1.AgentValidationService, credentials_service_1.CredentialsService, telemetry_2.Telemetry, event_service_1.EventService, agent_setup_completion_service_1.AgentSetupCompletionService, agent_modification_telemetry_service_1.AgentModificationTelemetryService])
], AgentPublishService);
//# sourceMappingURL=agent-publish.service.js.map