UNPKG

n8n

Version:

n8n Workflow Automation Tool

431 lines • 22.8 kB
"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