/** * Workflow Compiler Agent — UPGRADED * Converts WorkflowGraph (IR) → valid n8n JSON * Schema-aware, version-aware, credential-aware * Provider-agnostic LLM Gateway — NO direct OpenAI dependency * STRICT: Only uses nodes from registry. Zero hallucinated params. */ import { type LLMGateway } from '@wfo/integrations/llm-providers/index'; import type { WorkflowGraph, WorkflowIntent, N8nWorkflow } from '../types/workflow'; import { COMPILER_PROMPT } from '../prompts/compiler'; import { N8N_NODE_REGISTRY, isValidNodeType } from '../knowledge/nodeRegistry'; export class WorkflowCompiler { private llm: LLMGateway; constructor(llm: LLMGateway) { this.llm = llm; } async compile(graph: WorkflowGraph, intent: WorkflowIntent): Promise { // Build node schema context from registry (ONLY nodes used in graph) const nodeTypes = [...new Set(graph.nodes.map((n) => n.n8nNodeType))]; const validNodeTypes = nodeTypes.filter(isValidNodeType); const nodeSchemas = validNodeTypes .map((type) => N8N_NODE_REGISTRY[type]) .filter(Boolean) .map((s) => JSON.stringify(s, null, 2)) .join('\n\n'); const workflow = await this.llm.completeJSON([ { role: 'system', content: COMPILER_PROMPT }, { role: 'user', content: `Compile this WorkflowGraph to a valid n8n JSON workflow. GRAPH: ${JSON.stringify(graph, null, 2)} INTENT: ${JSON.stringify(intent, null, 2)} REGISTERED NODE SCHEMAS (use ONLY these — parameters MUST match schema): ${nodeSchemas || '(No pre-built schemas — use standard n8n defaults)'} CRITICAL COMPILATION RULES: 1. active MUST be false — NEVER true 2. All node IDs must be unique UUID strings (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) 3. connections MUST wire ALL graph edges correctly 4. NEVER invent parameters not in the node schema above 5. Every node MUST have real expressions for dynamic fields — NO static placeholder text 6. Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 on all critical/external nodes 7. Add meaningful node notes explaining what each node does 8. USE these expression patterns: - Input from trigger: {{$json?.body?.fieldName ?? $json?.fieldName ?? ""}} - Previous node: {{$node["NodeName"].json?.field ?? ""}} - Array item: {{$json?.items?.[0]?.value ?? ""}} - Conditional: {{$json?.status === "active" ? "yes" : "no"}} 9. SET nodes MUST have explicit field mappings — NOT empty values array 10. IF nodes MUST have real boolean conditions referencing actual fields 11. CODE nodes MUST contain real JavaScript logic — NOT placeholder comments 12. AI Agent nodes MUST include system message AND dynamic user message from previous node data 13. Return ONLY complete, importable n8n JSON workflow`, }, ], { temperature: 0.0, retries: 3, }); return this.ensureSafeDefaults(workflow); } /** * Enforce safety defaults regardless of LLM output * CRITICAL: active = false, proper settings, error handling */ private ensureSafeDefaults(workflow: N8nWorkflow): N8nWorkflow { return { ...workflow, active: false, // NEVER activate on compile — ABSOLUTE safety rule settings: { callerPolicy: 'workflowsFromSameOwner', errorWorkflow: workflow.settings?.errorWorkflow, timezone: workflow.settings?.timezone ?? 'UTC', ...workflow.settings, // Force these even if LLM overrides: executionOrder: 'v1', saveManualExecutions: true, }, nodes: workflow.nodes.map((node) => ({ ...node, onError: node.onError ?? 'continueErrorOutput', // Ensure critical external nodes have retry retryOnFail: node.retryOnFail ?? false, maxTries: node.maxTries ?? 3, waitBetweenTries: node.waitBetweenTries ?? 1000, })), }; } }