UNPKG

n8n

Version:

n8n Workflow Automation Tool

327 lines 13.9 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentScheduleService = void 0; const api_types_1 = require("@n8n/api-types"); const db_1 = require("@n8n/db"); const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const decorators_1 = require("@n8n/decorators"); const di_1 = require("@n8n/di"); const cron_1 = require("cron"); const crypto_1 = require("crypto"); const luxon_1 = require("luxon"); const bad_request_error_1 = require("../../../errors/response-errors/bad-request.error"); const conflict_error_1 = require("../../../errors/response-errors/conflict.error"); const agents_service_1 = require("../agents.service"); const agent_repository_1 = require("../repositories/agent.repository"); const cron_validation_1 = require("./cron-validation"); let AgentScheduleService = class AgentScheduleService { constructor(logger, globalConfig, agentRepository, agentsService, projectRelationRepository) { this.logger = logger; this.globalConfig = globalConfig; this.agentRepository = agentRepository; this.agentsService = agentsService; this.projectRelationRepository = projectRelationRepository; this.jobs = new Map(); } getConfig(agent) { const integration = this.getScheduleIntegration(agent); return { active: integration?.active ?? false, cronExpression: integration?.cronExpression ?? '', wakeUpPrompt: integration?.wakeUpPrompt ?? api_types_1.DEFAULT_AGENT_SCHEDULE_WAKE_UP_PROMPT, }; } async saveConfig(agent, cronExpression, wakeUpPrompt) { const existing = this.getScheduleIntegration(agent); const normalizedCronExpression = cronExpression.trim(); const nextWakeUpPrompt = wakeUpPrompt ?? existing?.wakeUpPrompt ?? api_types_1.DEFAULT_AGENT_SCHEDULE_WAKE_UP_PROMPT; if (normalizedCronExpression !== '') { this.assertCronExpressionIsValid(normalizedCronExpression); } else if (existing?.active) { throw new bad_request_error_1.BadRequestError('Cron expression is required while the schedule is active'); } const saved = await this.saveScheduleIntegration(agent, { type: api_types_1.AGENT_SCHEDULE_TRIGGER_TYPE, active: existing?.active ?? false, cronExpression: normalizedCronExpression, wakeUpPrompt: nextWakeUpPrompt, }); if (this.getScheduleIntegration(saved)?.active) { await this.registerOrRefresh(saved); } this.logger.debug('[AgentScheduleService] Saved schedule config', { agentId: agent.id, projectId: agent.projectId, active: saved.integrations.find(api_types_1.isAgentScheduleIntegration)?.active ?? false, cronExpression: normalizedCronExpression || null, }); return this.getConfig(saved); } async activate(agent) { if (!agent.publishedVersion) { throw new conflict_error_1.ConflictError(`Agent "${agent.id}" must be published before activating the schedule trigger`); } const existing = this.getScheduleIntegration(agent); const cronExpression = existing?.cronExpression.trim() ?? ''; if (cronExpression === '') { throw new bad_request_error_1.BadRequestError('Cron expression is required before activation'); } this.assertCronExpressionIsValid(cronExpression); const saved = await this.saveScheduleIntegration(agent, { type: api_types_1.AGENT_SCHEDULE_TRIGGER_TYPE, active: true, cronExpression, wakeUpPrompt: existing?.wakeUpPrompt ?? api_types_1.DEFAULT_AGENT_SCHEDULE_WAKE_UP_PROMPT, }); await this.registerOrRefresh(saved); this.logger.info('[AgentScheduleService] Activated schedule trigger', { agentId: agent.id, projectId: agent.projectId, cronExpression, }); return this.getConfig(saved); } async deactivate(agent) { const existing = this.getScheduleIntegration(agent); if (!existing) { this.deregister(agent.id); return this.getConfig(agent); } const saved = await this.saveScheduleIntegration(agent, { ...existing, active: false, }); this.deregister(agent.id); this.logger.info('[AgentScheduleService] Deactivated schedule trigger', { agentId: agent.id, projectId: agent.projectId, }); return this.getConfig(saved); } async reconnectAll() { const agents = await this.agentRepository.findPublished(); this.logger.debug('[AgentScheduleService] Reconnecting active schedules', { publishedAgentCount: agents.length, }); for (const agent of agents) { const schedule = this.getScheduleIntegration(agent); if (!schedule?.active) { continue; } try { await this.registerOrRefresh(agent); } catch (error) { this.logger.error('[AgentScheduleService] Failed to reconnect agent schedule', { agentId: agent.id, error: error instanceof Error ? error.message : String(error), }); } } } async applyConfig(agent) { const schedule = this.getScheduleIntegration(agent); if (schedule?.active) { await this.registerOrRefresh(agent); } else { this.deregister(agent.id); } } async registerOrRefresh(agent) { const schedule = this.getScheduleIntegration(agent); if (!schedule?.active) { this.logger.debug('[AgentScheduleService] Skipping schedule registration for inactive agent', { agentId: agent.id, projectId: agent.projectId, }); this.deregister(agent.id); return; } if (!agent.publishedVersion) { this.logger.warn('[AgentScheduleService] Skipping schedule registration for unpublished agent', { agentId: agent.id, projectId: agent.projectId, }); this.deregister(agent.id); return; } this.assertCronExpressionIsValid(schedule.cronExpression); this.deregister(agent.id); const timezone = this.globalConfig.generic.timezone; const job = new cron_1.CronJob(schedule.cronExpression, () => { void this.runScheduled(agent.id); }, null, false, timezone); job.start(); this.jobs.set(agent.id, job); this.logger.info('[AgentScheduleService] Registered schedule trigger', { agentId: agent.id, projectId: agent.projectId, cronExpression: schedule.cronExpression, timezone, }); } deregister(agentId) { const existing = this.jobs.get(agentId); if (!existing) { return; } void existing.stop(); this.jobs.delete(agentId); this.logger.info('[AgentScheduleService] Deregistered schedule trigger', { agentId }); } stopAll() { for (const [agentId] of this.jobs) { this.deregister(agentId); } } getScheduleIntegration(agent) { return (agent.integrations ?? []).find(api_types_1.isAgentScheduleIntegration); } async saveScheduleIntegration(agent, schedule) { agent.integrations = [ ...(agent.integrations ?? []).filter((integration) => !(0, api_types_1.isAgentScheduleIntegration)(integration)), schedule, ]; return await this.agentRepository.save(agent); } assertCronExpressionIsValid(cronExpression) { if (!(0, cron_validation_1.isValidCronExpression)(cronExpression)) { throw new bad_request_error_1.BadRequestError('Invalid cron expression'); } } async runScheduled(agentId) { let projectId; let threadId; let cronExpression; const startedAt = Date.now(); try { const agent = await this.agentRepository.findOne({ where: { id: agentId }, relations: { publishedVersion: true }, }); if (!agent) { this.logger.warn('[AgentScheduleService] Scheduled trigger fired for missing agent', { agentId, }); this.deregister(agentId); return; } projectId = agent.projectId; const schedule = this.getScheduleIntegration(agent); cronExpression = schedule?.cronExpression; if (!agent.publishedVersion) { this.logger.warn('[AgentScheduleService] Scheduled trigger fired for unpublished agent', { agentId, projectId, }); this.deregister(agentId); return; } if (!schedule?.active) { this.logger.warn('[AgentScheduleService] Scheduled trigger fired for inactive agent', { agentId, projectId, }); this.deregister(agentId); return; } const executionUserId = await this.resolveExecutionUserId(agent); if (!executionUserId) { this.logger.warn('[AgentScheduleService] No project member available for scheduled run', { agentId, projectId: agent.projectId, }); return; } threadId = `schedule-${agentId}-${(0, crypto_1.randomUUID)()}`; const timezone = this.globalConfig.generic.timezone; const timestamp = luxon_1.DateTime.now().setZone(timezone).toISO() ?? new Date().toISOString(); const message = `${schedule.wakeUpPrompt}\n\nCurrent date and time: ${timestamp} (timezone: ${timezone})`; this.logger.info('[AgentScheduleService] Scheduled trigger fired', { agentId, projectId, threadId, cronExpression: schedule.cronExpression, timezone, }); this.logger.debug('[AgentScheduleService] Starting scheduled agent run', { agentId, projectId, threadId, messageLength: message.length, }); let chunkCount = 0; for await (const _chunk of this.agentsService.executeForSchedulePublished({ agentId: agent.id, projectId: agent.projectId, message, memory: { threadId, resourceId: executionUserId }, })) { chunkCount += 1; } this.logger.info('[AgentScheduleService] Scheduled agent run completed', { agentId, projectId, threadId, chunkCount, durationMs: Date.now() - startedAt, }); } catch (error) { this.logger.error('[AgentScheduleService] Scheduled agent run failed', { agentId, projectId, threadId, cronExpression, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); } } async resolveExecutionUserId(agent) { const userIds = await this.projectRelationRepository.findUserIdsByProjectId(agent.projectId); if (userIds.length === 0) { return undefined; } const publishedById = agent.publishedVersion?.publishedById; if (publishedById && userIds.includes(publishedById)) { return publishedById; } return undefined; } }; exports.AgentScheduleService = AgentScheduleService; __decorate([ (0, decorators_1.OnLeaderTakeover)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], AgentScheduleService.prototype, "reconnectAll", null); __decorate([ (0, decorators_1.OnLeaderStepdown)(), (0, decorators_1.OnShutdown)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], AgentScheduleService.prototype, "stopAll", null); exports.AgentScheduleService = AgentScheduleService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, config_1.GlobalConfig, agent_repository_1.AgentRepository, agents_service_1.AgentsService, db_1.ProjectRelationRepository]) ], AgentScheduleService); //# sourceMappingURL=agent-schedule.service.js.map