/** * Webhook Auto-Bind System — NEW * Automatically detects trigger type from intent and assigns correct trigger node * Auto-generates webhook paths, auto-configures trigger settings * User NEVER manually defines triggers */ import type { WorkflowIntent, WorkflowGraph } from '../types/workflow'; import { getNodeDef } from '../knowledge/nodeRegistry'; export interface TriggerConfig { nodeType: string; displayName: string; parameters: Record; credentials?: Record; webhookPath?: string; notes: string; } // ─── Trigger detection rules (ordered by specificity) ───────────────────────── const TRIGGER_RULES: Array<{ keywords: string[]; integrations?: string[]; domain?: string[]; triggerType: string; nodeType: string; priority: number; }> = [ // Telegram { keywords: ['telegram', 'bot', 'message', 'chat'], integrations: ['telegram'], triggerType: 'telegram', nodeType: 'n8n-nodes-base.telegramTrigger', priority: 100, }, // Slack { keywords: ['slack', 'channel', 'workspace'], integrations: ['slack'], triggerType: 'slack', nodeType: 'n8n-nodes-base.slackTrigger', priority: 100, }, // GitHub { keywords: ['github', 'push', 'pull request', 'issue', 'commit', 'repository'], integrations: ['github'], triggerType: 'github', nodeType: 'n8n-nodes-base.githubTrigger', priority: 100, }, // Email (IMAP) { keywords: ['email', 'inbox', 'imap', 'mail received', 'new email', 'receive email'], integrations: ['email', 'imap', 'gmail'], triggerType: 'email', nodeType: 'n8n-nodes-base.emailReadImap', priority: 90, }, // Notion { keywords: ['notion', 'database page', 'notion page'], integrations: ['notion'], triggerType: 'notion', nodeType: 'n8n-nodes-base.notionTrigger', priority: 90, }, // Schedule / Cron { keywords: [ 'every hour', 'every day', 'daily', 'hourly', 'weekly', 'monthly', 'schedule', 'cron', 'at midnight', 'every morning', 'every night', 'periodic', 'interval', 'every minute', 'every 5 minutes', 'nightly', 'scheduled', ], triggerType: 'schedule', nodeType: 'n8n-nodes-base.scheduleTrigger', priority: 80, }, // Webhook (general HTTP) { keywords: [ 'webhook', 'api call', 'http request', 'post request', 'form submission', 'when called', 'endpoint', 'receive data', 'api endpoint', 'rest api', 'when a request', 'when triggered via', ], triggerType: 'webhook', nodeType: 'n8n-nodes-base.webhook', priority: 70, }, // Manual (fallback) { keywords: ['manually', 'manual trigger', 'on demand', 'test', 'demo'], triggerType: 'manual', nodeType: 'n8n-nodes-base.manualTrigger', priority: 10, }, ]; export class WebhookAutoBindSystem { /** * Detect the appropriate trigger node type from intent and request text */ detectTrigger( userRequest: string, intent: WorkflowIntent, ): { nodeType: string; triggerType: string; confidence: 'high' | 'medium' | 'low' } { const requestLower = userRequest.toLowerCase(); const integrationLower = intent.integrations.map((i) => i.toLowerCase()); let bestMatch: (typeof TRIGGER_RULES)[0] | undefined; let bestScore = 0; for (const rule of TRIGGER_RULES) { let score = 0; // Keyword matches in request const keywordMatches = rule.keywords.filter((kw) => requestLower.includes(kw)); score += keywordMatches.length * 10; // Integration matches if (rule.integrations) { const integrationMatches = rule.integrations.filter((i) => integrationLower.some((intent_i) => intent_i.includes(i) || i.includes(intent_i)), ); score += integrationMatches.length * 20; } // Exact trigger type hint from intent if (intent.triggerType && intent.triggerType === rule.triggerType) { score += 50; } // Apply rule priority as tiebreaker score += rule.priority * 0.1; if (score > bestScore) { bestScore = score; bestMatch = rule; } } // Default to webhook if nothing matched const nodeType = bestMatch?.nodeType ?? 'n8n-nodes-base.webhook'; const triggerType = bestMatch?.triggerType ?? 'webhook'; const confidence: 'high' | 'medium' | 'low' = bestScore >= 20 ? 'high' : bestScore >= 10 ? 'medium' : 'low'; return { nodeType, triggerType, confidence }; } /** * Generate complete trigger node configuration */ buildTriggerConfig( nodeType: string, userRequest: string, intent: WorkflowIntent, workflowName: string, ): TriggerConfig { switch (nodeType) { case 'n8n-nodes-base.webhook': return this.buildWebhookConfig(workflowName, intent); case 'n8n-nodes-base.telegramTrigger': return this.buildTelegramTriggerConfig(); case 'n8n-nodes-base.scheduleTrigger': return this.buildScheduleConfig(userRequest); case 'n8n-nodes-base.emailReadImap': return this.buildEmailTriggerConfig(); case 'n8n-nodes-base.slackTrigger': return this.buildSlackTriggerConfig(); case 'n8n-nodes-base.githubTrigger': return this.buildGitHubTriggerConfig(); case 'n8n-nodes-base.notionTrigger': return this.buildNotionTriggerConfig(); case 'n8n-nodes-base.manualTrigger': return this.buildManualTriggerConfig(); default: return this.buildWebhookConfig(workflowName, intent); } } /** * Inject auto-configured trigger into a WorkflowGraph as the first node */ injectTriggerIntoGraph( graph: WorkflowGraph, triggerConfig: TriggerConfig, triggerNodeType: string, ): WorkflowGraph { const def = getNodeDef(triggerNodeType); const triggerId = 'trigger-auto-' + Date.now(); // Check if graph already has a trigger node from the detected type const existingTrigger = graph.nodes.find((n) => n.layer === 'trigger'); if (existingTrigger) { // Update existing trigger with proper config const updatedNodes = graph.nodes.map((node) => { if (node.layer !== 'trigger') return node; return { ...node, n8nNodeType: triggerNodeType, label: triggerConfig.displayName, autoConfigured: true, webhookPath: triggerConfig.webhookPath, }; }); return { ...graph, nodes: updatedNodes }; } // Insert new trigger at front const newTriggerNode = { id: triggerId, label: triggerConfig.displayName, n8nNodeType: triggerNodeType, layer: 'trigger' as const, description: triggerConfig.notes, isCritical: true, autoConfigured: true, webhookPath: triggerConfig.webhookPath, position: { x: 0, y: 300 }, parameters: triggerConfig.parameters, retryPolicy: undefined, dataContract: { inputs: [], outputs: ['body', 'headers', 'query', 'params'], }, }; // Shift existing nodes to the right const shiftedNodes = graph.nodes.map((node) => ({ ...node, position: { x: (node.position?.x ?? 0) + 220, y: node.position?.y ?? 300 }, })); // Connect trigger to first non-trigger node const firstNode = shiftedNodes[0]; const newEdge = firstNode ? { id: `edge-trigger-to-${firstNode.id}`, sourceNodeId: triggerId, targetNodeId: firstNode.id, outputIndex: 0, inputIndex: 0, label: 'on receive', } : undefined; return { ...graph, nodes: [newTriggerNode, ...shiftedNodes], edges: newEdge ? [newEdge, ...graph.edges] : graph.edges, }; } // ─── Trigger config builders ────────────────────────────────────────────── private buildWebhookConfig(workflowName: string, intent: WorkflowIntent): TriggerConfig { const slug = workflowName .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 40); const path = `wfo/${slug}`; return { nodeType: 'n8n-nodes-base.webhook', displayName: 'Webhook Trigger', parameters: { httpMethod: intent.syncVsAsync === 'sync' ? 'POST' : 'POST', path, responseMode: intent.syncVsAsync === 'sync' ? 'lastNode' : 'onReceived', responseData: 'allEntries', options: {}, }, webhookPath: path, notes: `Auto-configured webhook trigger. Path: /webhook/${path}. Method: POST. Response: ${intent.syncVsAsync === 'sync' ? 'synchronous (after processing)' : 'immediate acknowledgement'}.`, }; } private buildTelegramTriggerConfig(): TriggerConfig { return { nodeType: 'n8n-nodes-base.telegramTrigger', displayName: 'Telegram Trigger', parameters: { updates: ['message', 'callback_query'], }, credentials: { telegramApi: { id: '', name: 'Telegram Bot API' }, }, notes: 'Auto-configured Telegram trigger. Listens for messages and callback queries. Requires telegramApi credential.', }; } private buildScheduleConfig(userRequest: string): TriggerConfig { // Detect schedule from request text const req = userRequest.toLowerCase(); let interval: Record[] = [{ field: 'hours', hoursInterval: 1 }]; if (req.includes('every minute')) interval = [{ field: 'minutes', minutesInterval: 1 }]; else if (req.includes('every 5 minute')) interval = [{ field: 'minutes', minutesInterval: 5 }]; else if (req.includes('every 15 minute')) interval = [{ field: 'minutes', minutesInterval: 15 }]; else if (req.includes('every 30 minute')) interval = [{ field: 'minutes', minutesInterval: 30 }]; else if (req.includes('every hour') || req.includes('hourly')) interval = [{ field: 'hours', hoursInterval: 1 }]; else if (req.includes('every 6 hour')) interval = [{ field: 'hours', hoursInterval: 6 }]; else if (req.includes('every 12 hour')) interval = [{ field: 'hours', hoursInterval: 12 }]; else if (req.includes('daily') || req.includes('every day') || req.includes('nightly') || req.includes('midnight')) { interval = [{ field: 'cronExpression', expression: '0 0 * * *' }]; } else if (req.includes('weekly') || req.includes('every week')) { interval = [{ field: 'cronExpression', expression: '0 9 * * 1' }]; // Monday 9am } else if (req.includes('monthly') || req.includes('every month')) { interval = [{ field: 'cronExpression', expression: '0 9 1 * *' }]; // 1st of month 9am } return { nodeType: 'n8n-nodes-base.scheduleTrigger', displayName: 'Schedule Trigger', parameters: { rule: { interval }, }, notes: `Auto-configured schedule trigger. Interval detected from request: ${JSON.stringify(interval)}.`, }; } private buildEmailTriggerConfig(): TriggerConfig { return { nodeType: 'n8n-nodes-base.emailReadImap', displayName: 'Email Trigger (IMAP)', parameters: { mailbox: 'INBOX', action: 'read', downloadAttachments: false, format: 'simple', options: {}, }, credentials: { imap: { id: '', name: 'IMAP Account' }, }, notes: 'Auto-configured IMAP email trigger. Monitors INBOX for new emails. Requires IMAP credential.', }; } private buildSlackTriggerConfig(): TriggerConfig { return { nodeType: 'n8n-nodes-base.slackTrigger', displayName: 'Slack Trigger', parameters: { trigger: 'any_message', }, credentials: { slackOAuth2Api: { id: '', name: 'Slack OAuth2' }, }, notes: 'Auto-configured Slack trigger. Listens for messages in configured channels. Requires Slack OAuth2 credential.', }; } private buildGitHubTriggerConfig(): TriggerConfig { return { nodeType: 'n8n-nodes-base.githubTrigger', displayName: 'GitHub Trigger', parameters: { owner: '', repository: '', events: ['push'], }, credentials: { githubApi: { id: '', name: 'GitHub API' }, }, notes: 'Auto-configured GitHub trigger. Listens for push events. Set owner and repository in parameters.', }; } private buildNotionTriggerConfig(): TriggerConfig { return { nodeType: 'n8n-nodes-base.notionTrigger', displayName: 'Notion Trigger', parameters: { databaseId: '', event: 'page_added', simple: true, }, credentials: { notionApi: { id: '', name: 'Notion API' }, }, notes: 'Auto-configured Notion trigger. Monitors a database for new pages. Set databaseId in parameters.', }; } private buildManualTriggerConfig(): TriggerConfig { return { nodeType: 'n8n-nodes-base.manualTrigger', displayName: 'Manual Trigger', parameters: {}, notes: 'Manual trigger — start this workflow by clicking "Execute Workflow" in n8n.', }; } } // ─── Singleton export ───────────────────────────────────────────────────────── export const webhookAutoBind = new WebhookAutoBindSystem();