PYAE1994 commited on
Commit
dd480ef
Β·
verified Β·
1 Parent(s): a4c6da2

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. Dockerfile +32 -0
  2. README.md +393 -7
  3. apps/simulator/.dockerignore +9 -0
  4. apps/simulator/Dockerfile +25 -0
  5. apps/simulator/README.md +36 -0
  6. apps/simulator/package.json +31 -0
  7. apps/simulator/pnpm-lock.yaml +1492 -0
  8. apps/simulator/pnpm-workspace.yaml +2 -0
  9. apps/simulator/src/agents/compiler.ts +99 -0
  10. apps/simulator/src/agents/credentialIntelligence.ts +177 -0
  11. apps/simulator/src/agents/graphEngine.ts +110 -0
  12. apps/simulator/src/agents/intentInterpreter.ts +48 -0
  13. apps/simulator/src/agents/simulator.ts +89 -0
  14. apps/simulator/src/agents/validator.ts +486 -0
  15. apps/simulator/src/agents/workflowPlanner.ts +36 -0
  16. apps/simulator/src/index.ts +96 -0
  17. apps/simulator/src/knowledge/nodeRegistry.ts +1078 -0
  18. apps/simulator/src/middleware/internalAuth.ts +27 -0
  19. apps/simulator/src/prompts/compiler.ts +128 -0
  20. apps/simulator/src/prompts/graphEngine.ts +81 -0
  21. apps/simulator/src/prompts/intentInterpreter.ts +51 -0
  22. apps/simulator/src/prompts/simulator.ts +103 -0
  23. apps/simulator/src/prompts/workflowPlanner.ts +80 -0
  24. apps/simulator/src/routes/generate.ts +244 -0
  25. apps/simulator/src/routes/simulate.ts +60 -0
  26. apps/simulator/src/routes/validate.ts +61 -0
  27. apps/simulator/src/services/llmClient.ts +69 -0
  28. apps/simulator/src/services/memorySystem.ts +333 -0
  29. apps/simulator/src/services/mockDataGenerator.ts +61 -0
  30. apps/simulator/src/services/qualityScorer.ts +155 -0
  31. apps/simulator/src/services/selfHealing.ts +377 -0
  32. apps/simulator/src/services/swarmOrchestrator.ts +228 -0
  33. apps/simulator/src/services/webhookAutoBind.ts +409 -0
  34. apps/simulator/src/types/workflow.ts +50 -0
  35. apps/simulator/tsconfig.json +26 -0
  36. apps/worker/package.json +21 -0
  37. apps/worker/src/index.ts +77 -0
  38. apps/worker/src/middleware/auth.ts +32 -0
  39. apps/worker/src/middleware/errorHandler.ts +16 -0
  40. apps/worker/src/middleware/rateLimit.ts +50 -0
  41. apps/worker/src/routes/activate.ts +167 -0
  42. apps/worker/src/routes/approval.ts +169 -0
  43. apps/worker/src/routes/deploy.ts +173 -0
  44. apps/worker/src/routes/generate.ts +149 -0
  45. apps/worker/src/routes/health.ts +51 -0
  46. apps/worker/src/routes/reports.ts +73 -0
  47. apps/worker/src/routes/simulate.ts +89 -0
  48. apps/worker/src/routes/validate.ts +77 -0
  49. apps/worker/src/types/env.ts +33 -0
  50. apps/worker/src/types/workflow.ts +499 -0
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-slim
2
+
3
+ # Install pnpm
4
+ RUN npm install -g pnpm
5
+
6
+ WORKDIR /app
7
+
8
+ # Copy all package.json files for dependency installation
9
+ COPY package.json pnpm-lock.yaml* ./
10
+ COPY apps/simulator/package.json ./apps/simulator/
11
+ COPY apps/worker/package.json ./apps/worker/
12
+
13
+ # Install all dependencies in the monorepo
14
+ RUN pnpm install
15
+
16
+ # Copy the rest of the monorepo files
17
+ COPY . .
18
+
19
+ # Build the simulator service
20
+ RUN cd apps/simulator && pnpm install && ./node_modules/.bin/tsc
21
+
22
+ # Hugging Face Spaces run on port 7860 by default
23
+ EXPOSE 7860
24
+
25
+ # Set environment variables
26
+ ENV PORT=7860
27
+ ENV NODE_ENV=production
28
+
29
+ # Ensure the correct entry point is used
30
+ # Based on the original Dockerfile: CMD ["node", "apps/simulator/dist/apps/simulator/src/index.js"]
31
+ # But we need to make sure the path is correct after build
32
+ CMD ["node", "apps/simulator/dist/apps/simulator/src/index.js"]
README.md CHANGED
@@ -1,10 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Workflow Factor Os Simulator
3
- emoji: ⚑
4
- colorFrom: purple
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Workflow Factor OS
2
+
3
+ > **Production-Grade AI Workflow Orchestrator for n8n**
4
+ > Built on GitHub + Cloudflare Workers Free Plan Architecture
5
+
6
+ ---
7
+
8
+ ## What Is This?
9
+
10
+ Workflow Factor OS is a **production-grade AI Workflow Engineering Platform** that designs, validates, simulates, deploys, and safely activates n8n workflows with professional engineering quality.
11
+
12
+ This is **not** a simple JSON generator or prompt-to-workflow toy.
13
+
14
+ It behaves like:
15
+ - πŸ—οΈ A **workflow engineering team**
16
+ - πŸ–₯️ A **workflow operating system**
17
+ - πŸŽ›οΈ An **AI orchestration platform**
18
+
19
+ ---
20
+
21
+ ## Architecture Overview
22
+
23
+ ```
24
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
25
+ β”‚ USER / FRONTEND β”‚
26
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
27
+ β”‚ HTTPS
28
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
29
+ β”‚ CLOUDFLARE WORKER (apps/worker) β”‚
30
+ β”‚ β€’ Thin API Gateway + Orchestrator β”‚
31
+ β”‚ β€’ Auth, Rate Limiting, KV State Storage β”‚
32
+ β”‚ β€’ Routes: /generate /validate /simulate /deploy /approve /activate β”‚
33
+ β”‚ β€’ CPU budget: 10ms (Free Plan) β”‚
34
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
35
+ β”‚ Internal API (X-Internal-Secret)
36
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
37
+ β”‚ SIMULATOR SERVICE (apps/simulator) β€” External Node.js/Express β”‚
38
+ β”‚ Heavy CPU work offloaded here: β”‚
39
+ β”‚ β€’ Intent Interpreter Agent (GPT-4o) β”‚
40
+ β”‚ β€’ Workflow Planner Agent (GPT-4o) β”‚
41
+ β”‚ β€’ Graph Engine (IR Layer) (GPT-4o) β”‚
42
+ β”‚ β€’ Workflow Compiler (GPT-4o) β”‚
43
+ β”‚ β€’ Validation Engine (multi-stage) β”‚
44
+ β”‚ β€’ Dry Run Simulator (GPT-4o + chaos testing) β”‚
45
+ β”‚ β€’ Credential Intelligence (n8n API scan) β”‚
46
+ β”‚ β€’ Quality Scorer (multi-dimension scoring) β”‚
47
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
48
+ β”‚ n8n REST API
49
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
50
+ β”‚ n8n INSTANCE β”‚
51
+ β”‚ β€’ Workflow storage (deployed but NOT activated) β”‚
52
+ β”‚ β€’ Credential management β”‚
53
+ β”‚ β€’ Workflow activation (only after human approval) β”‚
54
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Workflow Lifecycle
60
+
61
+ ```
62
+ User Request
63
+ β”‚
64
+ β–Ό
65
+ [1] Intent Interpretation β†’ classify type, integrations, risk
66
+ β”‚
67
+ β–Ό
68
+ [2] Architecture Planning β†’ layered design, risk analysis
69
+ β”‚
70
+ β–Ό
71
+ [3] Graph IR Generation β†’ internal WorkflowGraph (source of truth)
72
+ β”‚
73
+ β–Ό
74
+ [4] Compile to n8n JSON β†’ valid, schema-compliant n8n workflow
75
+ β”‚
76
+ β–Ό
77
+ [5] Multi-Stage Validation β†’ schema, credentials, graph, expressions, reliability
78
+ β”‚
79
+ β–Ό
80
+ [6] Dry-Run Simulation β†’ mock data, chaos tests, failure prediction
81
+ β”‚
82
+ β–Ό
83
+ [7] Deploy to n8n β†’ active: false (NEVER auto-activate)
84
+ β”‚
85
+ β–Ό
86
+ [8] Credential Check β†’ verify all required credentials exist
87
+ β”‚
88
+ β–Ό
89
+ [9] Human Approval Request β†’ reviewer sees: architecture, risks, scores
90
+ β”‚
91
+ β–Ό
92
+ [10] Human Decision β†’ approve / reject / modify
93
+ β”‚
94
+ β–Ό
95
+ [11] Activation Engine β†’ final validation + activate via n8n API
96
+ β”‚
97
+ β–Ό
98
+ [12] Audit Log β†’ full trail of every lifecycle event
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Repository Structure
104
+
105
+ ```
106
+ workflow-factor-os/
107
+ β”‚
108
+ β”œβ”€β”€ apps/
109
+ β”‚ β”œβ”€β”€ worker/ # Cloudflare Worker (API Gateway)
110
+ β”‚ β”‚ └── src/
111
+ β”‚ β”‚ β”œβ”€β”€ index.ts # Hono app entry point
112
+ β”‚ β”‚ β”œβ”€β”€ routes/ # generate, validate, simulate, deploy, approval, activate, reports, health
113
+ β”‚ β”‚ β”œβ”€β”€ middleware/ # auth, rateLimit, errorHandler
114
+ β”‚ β”‚ β”œβ”€β”€ types/ # env.ts, workflow.ts (all shared types)
115
+ β”‚ β”‚ └── utils/ # audit.ts, ids.ts
116
+ β”‚ β”‚
117
+ β”‚ └── simulator/ # External Compute Service (Node.js/Express)
118
+ β”‚ └── src/
119
+ β”‚ β”œβ”€β”€ index.ts # Express app entry point
120
+ β”‚ β”œβ”€β”€ routes/ # generate, validate, simulate
121
+ β”‚ β”œβ”€β”€ middleware/ # internalAuth
122
+ β”‚ β”œβ”€β”€ agents/ # intentInterpreter, workflowPlanner, graphEngine, compiler, validator, simulator, credentialIntelligence
123
+ β”‚ β”œβ”€β”€ services/ # mockDataGenerator, qualityScorer
124
+ β”‚ β”œβ”€β”€ prompts/ # intentInterpreter, workflowPlanner, graphEngine, compiler, simulator
125
+ β”‚ β”œβ”€β”€ knowledge/ # nodeRegistry (real n8n node schemas)
126
+ β”‚ └── types/ # workflow types (re-export)
127
+ β”‚
128
+ β”œβ”€β”€ core/ # Domain core modules (extensible)
129
+ β”‚ β”œβ”€β”€ interpreter/ # Intent Interpreter logic
130
+ β”‚ β”œβ”€β”€ planner/ # Architecture Planner logic
131
+ β”‚ β”œβ”€β”€ graph/ # Graph IR Engine
132
+ β”‚ β”œβ”€β”€ compiler/ # Workflow Compiler
133
+ β”‚ β”œβ”€β”€ validator/ # Validation Engine
134
+ β”‚ β”œβ”€β”€ simulator/ # Dry Run Simulator
135
+ β”‚ β”œβ”€β”€ deployer/ # Deployment Engine
136
+ β”‚ β”œβ”€β”€ credentials/ # Credential Intelligence
137
+ β”‚ β”œβ”€β”€ approval/ # Human Approval System
138
+ β”‚ └── activation/ # Activation Engine
139
+ β”‚
140
+ β”œβ”€β”€ knowledge/
141
+ β”‚ β”œβ”€β”€ n8n-schema/ # n8n JSON schema definitions
142
+ β”‚ β”œβ”€β”€ node-definitions/ # Extracted node registry
143
+ β”‚ β”œβ”€β”€ workflow-patterns/ # Retry, webhook safety, AI, error handling patterns
144
+ β”‚ β”œβ”€β”€ anti-patterns/ # Known bad patterns to avoid
145
+ β”‚ └── execution-rules/ # n8n runtime rules
146
+ β”‚
147
+ β”œβ”€β”€ integrations/
148
+ β”‚ β”œβ”€β”€ n8n-client/ # Typed n8n REST API client
149
+ β”‚ β”œβ”€β”€ llm-providers/ # Provider-agnostic LLM abstraction
150
+ β”‚ β”œβ”€β”€ database/ # External DB client (future)
151
+ β”‚ └── queue/ # External queue client (future)
152
+ β”‚
153
+ β”œβ”€β”€ validation/
154
+ β”‚ β”œβ”€β”€ schema/ # Zod schemas for all types
155
+ β”‚ β”œβ”€β”€ graph/ # Graph validation utilities
156
+ β”‚ β”œβ”€β”€ credentials/ # Credential validation
157
+ β”‚ β”œβ”€β”€ expressions/ # Expression safety analysis
158
+ β”‚ └── reliability/ # Retry, fallback, monitoring checks
159
+ β”‚
160
+ β”œβ”€β”€ observability/
161
+ β”‚ β”œβ”€β”€ logging/ # Pino structured logger
162
+ β”‚ β”œβ”€β”€ metrics/ # Metrics collection (future)
163
+ β”‚ β”œβ”€β”€ tracing/ # Distributed tracing (future)
164
+ β”‚ └── audit/ # Audit trail
165
+ β”‚
166
+ β”œβ”€β”€ config/
167
+ β”‚ β”œβ”€β”€ prompts/ # Central prompt registry
168
+ β”‚ β”œβ”€β”€ env/ # .env.example
169
+ β”‚ └── n8n/ # n8n API endpoint config
170
+ β”‚
171
+ β”œβ”€β”€ scripts/
172
+ β”‚ β”œβ”€β”€ build-knowledge-base.js # Extract n8n node schemas
173
+ β”‚ └── validate-env.js # Pre-deploy env validation
174
+ β”‚
175
+ └── docs/ # Architecture diagrams, API docs
176
+ ```
177
+
178
  ---
179
+
180
+ ## API Endpoints
181
+
182
+ ### Cloudflare Worker (Public API)
183
+
184
+ | Method | Endpoint | Description |
185
+ |--------|----------|-------------|
186
+ | `GET` | `/health` | Health check (public) |
187
+ | `POST` | `/api/workflows/generate` | Full pipeline: intent β†’ plan β†’ graph β†’ compile β†’ validate β†’ simulate |
188
+ | `POST` | `/api/workflows/validate` | Re-run validation on existing job |
189
+ | `POST` | `/api/workflows/simulate` | Re-run dry-run simulation |
190
+ | `POST` | `/api/workflows/deploy` | Deploy to n8n (active: false) |
191
+ | `POST` | `/api/workflows/request-approval` | Create human approval request |
192
+ | `GET` | `/api/workflows/approve/:jobId` | Get approval request details |
193
+ | `POST` | `/api/workflows/approve` | Submit approval decision |
194
+ | `POST` | `/api/workflows/activate` | Activate approved workflow |
195
+ | `GET` | `/api/reports/workflow/:id` | Full job report |
196
+ | `GET` | `/api/reports/validation/:id` | Validation report |
197
+ | `GET` | `/api/reports/simulation/:id` | Simulation report |
198
+ | `GET` | `/api/reports/credential/:id` | Credential analysis |
199
+ | `GET` | `/api/reports/audit/:id` | Full audit trail |
200
+
201
+ ### Simulator Service (Internal β€” not public)
202
+
203
+ | Method | Endpoint | Description |
204
+ |--------|----------|-------------|
205
+ | `GET` | `/health` | Service health |
206
+ | `POST` | `/process/generate` | Full AI pipeline |
207
+ | `POST` | `/process/validate` | Validation engine |
208
+ | `POST` | `/process/simulate` | Dry-run simulation |
209
+
210
  ---
211
 
212
+ ## Quick Start
213
+
214
+ ### 1. Prerequisites
215
+
216
+ - Node.js >= 18
217
+ - A running n8n instance with API key
218
+ - OpenAI API key
219
+ - Cloudflare account (free plan OK)
220
+
221
+ ### 2. Clone and Install
222
+
223
+ ```bash
224
+ git clone https://github.com/pyaesonegtckglay-dotcom/workflow-factor-os.git
225
+ cd workflow-factor-os
226
+ npm install
227
+ ```
228
+
229
+ ### 3. Configure Simulator Service
230
+
231
+ ```bash
232
+ cp config/env/.env.example apps/simulator/.env
233
+ # Edit .env with your values
234
+ ```
235
+
236
+ ### 4. Start Simulator Service
237
+
238
+ ```bash
239
+ npm run dev:simulator
240
+ # Runs on http://localhost:3001
241
+ ```
242
+
243
+ ### 5. Configure & Deploy Cloudflare Worker
244
+
245
+ ```bash
246
+ cd apps/worker
247
+
248
+ # Set secrets
249
+ wrangler secret put OPENAI_API_KEY
250
+ wrangler secret put N8N_API_KEY
251
+ wrangler secret put INTERNAL_API_SECRET
252
+
253
+ # Create KV namespace
254
+ wrangler kv:namespace create "WFO_CACHE"
255
+ # Copy the ID into wrangler.toml
256
+
257
+ # Update wrangler.toml with your SIMULATOR_URL and N8N_BASE_URL
258
+
259
+ # Deploy
260
+ npm run deploy
261
+ ```
262
+
263
+ ### 6. Test
264
+
265
+ ```bash
266
+ # Generate a workflow
267
+ curl -X POST https://your-worker.workers.dev/api/workflows/generate \
268
+ -H "X-API-Key: your-secret" \
269
+ -H "Content-Type: application/json" \
270
+ -d '{
271
+ "request": "Build a Telegram bot that receives messages, processes them with GPT-4o, and logs results to Google Sheets"
272
+ }'
273
+
274
+ # Get the jobId from response, then check full report:
275
+ curl https://your-worker.workers.dev/api/reports/workflow/<jobId> \
276
+ -H "X-API-Key: your-secret"
277
+ ```
278
+
279
+ ---
280
+
281
+ ## 12 Core Engineering Systems
282
+
283
+ | System | Location | Description |
284
+ |--------|----------|-------------|
285
+ | Intent Interpreter | `apps/simulator/src/agents/intentInterpreter.ts` | Classifies request, identifies integrations, risk, domain |
286
+ | Workflow Planner | `apps/simulator/src/agents/workflowPlanner.ts` | Layered architecture design, risk analysis |
287
+ | Graph Engine (IR) | `apps/simulator/src/agents/graphEngine.ts` | WorkflowGraph IR - source of truth |
288
+ | Pattern Knowledge Engine | `knowledge/workflow-patterns/` | Retry, webhook safety, AI, error patterns |
289
+ | n8n Repository Intelligence | `apps/simulator/src/knowledge/nodeRegistry.ts` | Real n8n node schemas |
290
+ | Workflow Compiler | `apps/simulator/src/agents/compiler.ts` | Graph IR β†’ valid n8n JSON |
291
+ | Validation Engine | `apps/simulator/src/agents/validator.ts` | 5-stage validation |
292
+ | Dry Run Simulation | `apps/simulator/src/agents/simulator.ts` | Mock execution + chaos testing |
293
+ | Deployment Engine | `apps/worker/src/routes/deploy.ts` | Safe n8n deployment (never auto-activate) |
294
+ | Credential Intelligence | `apps/simulator/src/agents/credentialIntelligence.ts` | Credential scan + missing detection |
295
+ | Human Approval System | `apps/worker/src/routes/approval.ts` | Mandatory human gate before activation |
296
+ | Activation Engine | `apps/worker/src/routes/activate.ts` | Final safety checks + n8n activation |
297
+
298
+ ---
299
+
300
+ ## Engineering Philosophy
301
+
302
+ ### NEVER:
303
+ - ❌ Instantly activate workflows
304
+ - ❌ Skip validation
305
+ - ❌ Skip dry-run simulation
306
+ - ❌ Assume credentials exist
307
+ - ❌ Hallucinate node parameters
308
+ - ❌ Generate monolithic spaghetti workflows
309
+
310
+ ### ALWAYS:
311
+ - βœ… Validate before deploying
312
+ - βœ… Simulate before deploying
313
+ - βœ… Check credentials
314
+ - βœ… Require human approval
315
+ - βœ… Use modular workflow design
316
+ - βœ… Think about retries and fallbacks
317
+ - βœ… Think about observability
318
+ - βœ… Use optional chaining in expressions
319
+
320
+ ---
321
+
322
+ ## Cloudflare Workers Free Plan Adaptations
323
+
324
+ Per the architecture addendum, the CF Worker is a **thin API gateway** only:
325
+
326
+ | Component | Free Plan Approach |
327
+ |-----------|-------------------|
328
+ | Intent Interpreter | Offloaded to Simulator service |
329
+ | Workflow Planner | Offloaded to Simulator service |
330
+ | Graph Engine | Offloaded to Simulator service |
331
+ | Compiler | Offloaded to Simulator service |
332
+ | Validation Engine | Offloaded to Simulator service |
333
+ | Dry Run Simulator | Offloaded to Simulator service |
334
+ | State Storage | Workers KV (job records, 7-day TTL) |
335
+ | Credential Check | Worker calls n8n API directly |
336
+ | Deployment | Worker calls n8n API directly |
337
+ | Activation | Worker calls n8n API directly |
338
+ | Auth & Rate Limiting | Worker handles (minimal CPU) |
339
+
340
+ ---
341
+
342
+ ## Workflow Quality Scoring
343
+
344
+ Every generated workflow is scored across 9 dimensions:
345
+
346
+ | Dimension | Weight | What It Measures |
347
+ |-----------|--------|-----------------|
348
+ | Schema Correctness | 25% | Valid n8n JSON structure |
349
+ | Credential Readiness | 20% | All required credentials present |
350
+ | Graph Validity | 20% | No orphans, proper connections |
351
+ | Expression Safety | 15% | Null-safe expressions |
352
+ | Reliability | 20% | Retry policies, fallbacks, monitoring |
353
+
354
+ **Deployment Readiness:**
355
+ - 🟒 85-100: Low risk β€” safe to deploy
356
+ - 🟑 65-84: Medium risk β€” review warnings
357
+ - 🟠 40-64: High risk β€” fix issues first
358
+ - πŸ”΄ 0-39: Critical β€” do not deploy
359
+
360
+ ---
361
+
362
+ ## Workflow Lifecycle States
363
+
364
+ ```
365
+ draft β†’ planned β†’ generated β†’ validated β†’ simulated β†’
366
+ deployed β†’ awaiting_approval β†’ approved/rejected β†’ activated β†’ monitored β†’ deprecated
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Security
372
+
373
+ - All API endpoints protected by `X-API-Key` header
374
+ - Worker ↔ Simulator communication uses shared `INTERNAL_API_SECRET`
375
+ - All secrets stored as Cloudflare Worker Secrets (never in wrangler.toml)
376
+ - Credentials never logged or exposed in responses
377
+ - Rate limiting: 20 requests/minute per IP via KV
378
+
379
+ ---
380
+
381
+ ## Future Roadmap
382
+
383
+ - [ ] Multi-user / RBAC support
384
+ - [ ] Workflow template marketplace
385
+ - [ ] AI workflow memory (remember successful patterns)
386
+ - [ ] Autonomous workflow optimization
387
+ - [ ] Self-healing workflows
388
+ - [ ] Runtime anomaly detection
389
+ - [ ] Web UI for human approval dashboard
390
+ - [ ] D1 database for persistent job storage (beyond KV)
391
+
392
+ ---
393
+
394
+ ## License
395
+
396
+ MIT
apps/simulator/.dockerignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ .env
4
+ .git
5
+ .github
6
+ .vscode
7
+ .DS_Store
8
+ apps/*/node_modules
9
+ apps/*/dist
apps/simulator/Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install pnpm
6
+ RUN npm install -g pnpm
7
+
8
+ # Copy all package.json files for dependency installation
9
+ COPY package.json pnpm-lock.yaml* ./
10
+ COPY apps/simulator/package.json ./apps/simulator/
11
+ COPY apps/worker/package.json ./apps/worker/
12
+
13
+ # Install all dependencies in the monorepo
14
+ RUN pnpm install
15
+
16
+ # Copy the rest of the monorepo files
17
+ COPY . .
18
+
19
+ # Build the simulator service
20
+ RUN cd apps/simulator && npm install && ./node_modules/.bin/tsc
21
+
22
+ EXPOSE 7860
23
+
24
+ # Ensure the correct entry point is used
25
+ CMD ["node", "apps/simulator/dist/apps/simulator/src/index.js"]
apps/simulator/README.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Workflow Factor OS - Simulator Service
2
+
3
+ This is the Simulator Service component of the Workflow Factor OS monorepo, deployed to Hugging Face Spaces.
4
+
5
+ ## Environment Variables
6
+
7
+ The following environment variables **MUST** be set in your Hugging Face Space settings:
8
+
9
+ * `LLM_GATEWAY_URL`: The URL of your custom LLM gateway (e.g., `https://llm-routing-system.pyaesone-n8n.workers.dev/v1/chat/completions`)
10
+ * `LLM_API_KEY`: Your API key for the LLM gateway. This will be used as the primary LLM key.
11
+ * `N8N_BASE_URL`: The base URL of your n8n instance (e.g., `https://pyae1994-n8n-aiven-v1.hf.space/`)
12
+ * `N8N_API_KEY`: Your API key for the n8n instance.
13
+ * `INTERNAL_API_SECRET`: A shared secret between the Cloudflare Worker and this Simulator service for internal API authentication (e.g., `super-secret-PSA`).
14
+
15
+ ## Deployment
16
+
17
+ This service is designed to be deployed as a Docker image to Hugging Face Spaces. The `Dockerfile` in this directory handles the build process.
18
+
19
+ ### Build and Run Locally (for testing)
20
+
21
+ To build and run this service locally using Docker, navigate to the `apps/simulator` directory and execute:
22
+
23
+ ```bash
24
+ docker build -t wfo-simulator .
25
+ docker run -p 7860:7860 -e LLM_GATEWAY_URL="your_llm_gateway_url" -e LLM_API_KEY="your_llm_api_key" -e N8N_BASE_URL="your_n8n_base_url" -e N8N_API_KEY="your_n8n_api_key" -e INTERNAL_API_SECRET="your_internal_api_secret" wfo-simulator
26
+ ```
27
+
28
+ ### Hugging Face Spaces Deployment
29
+
30
+ 1. Create a new Space on Hugging Face.
31
+ 2. Select "Docker" as the Space SDK.
32
+ 3. Upload the contents of the `apps/simulator` directory (including the `Dockerfile` and `.dockerignore`) to your Hugging Face Space repository.
33
+ 4. Configure the environment variables listed above in the Space settings.
34
+ 5. Hugging Face Spaces will automatically build and deploy your Docker image.
35
+
36
+ **Note:** The service exposes port `7860` as required by Hugging Face Spaces for custom Docker applications.
apps/simulator/package.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@wfo/simulator",
3
+ "version": "1.0.0",
4
+ "description": "External Compute Service - Heavy AI orchestration logic (Intent, Planner, Graph, Compiler, Validator, Simulator)",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "tsx watch src/index.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "lint": "eslint src --ext .ts"
11
+ },
12
+ "dependencies": {
13
+ "cors": "^2.8.5",
14
+ "dotenv": "^16.4.0",
15
+ "express": "^4.18.0",
16
+ "express-rate-limit": "^7.2.0",
17
+ "helmet": "^7.1.0",
18
+ "module-alias": "^2.3.4",
19
+ "openai": "^4.28.0",
20
+ "pino": "^8.17.0",
21
+ "pino-pretty": "^10.3.0",
22
+ "zod": "^3.22.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/cors": "^2.8.17",
26
+ "@types/express": "^4.17.21",
27
+ "@types/node": "^20.11.0",
28
+ "tsx": "^4.7.0",
29
+ "typescript": "^5.3.0"
30
+ }
31
+ }
apps/simulator/pnpm-lock.yaml ADDED
@@ -0,0 +1,1492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ lockfileVersion: '9.0'
2
+
3
+ settings:
4
+ autoInstallPeers: true
5
+ excludeLinksFromLockfile: false
6
+
7
+ importers:
8
+
9
+ .:
10
+ dependencies:
11
+ cors:
12
+ specifier: ^2.8.5
13
+ version: 2.8.6
14
+ dotenv:
15
+ specifier: ^16.4.0
16
+ version: 16.6.1
17
+ express:
18
+ specifier: ^4.18.0
19
+ version: 4.22.1
20
+ express-rate-limit:
21
+ specifier: ^7.2.0
22
+ version: 7.5.1(express@4.22.1)
23
+ helmet:
24
+ specifier: ^7.1.0
25
+ version: 7.2.0
26
+ openai:
27
+ specifier: ^4.28.0
28
+ version: 4.104.0(zod@3.25.76)
29
+ pino:
30
+ specifier: ^8.17.0
31
+ version: 8.21.0
32
+ pino-pretty:
33
+ specifier: ^10.3.0
34
+ version: 10.3.1
35
+ zod:
36
+ specifier: ^3.22.0
37
+ version: 3.25.76
38
+ devDependencies:
39
+ '@types/cors':
40
+ specifier: ^2.8.17
41
+ version: 2.8.19
42
+ '@types/express':
43
+ specifier: ^4.17.21
44
+ version: 4.17.25
45
+ '@types/node':
46
+ specifier: ^20.11.0
47
+ version: 20.19.40
48
+ tsx:
49
+ specifier: ^4.7.0
50
+ version: 4.21.0
51
+ typescript:
52
+ specifier: ^5.3.0
53
+ version: 5.9.3
54
+
55
+ packages:
56
+
57
+ '@esbuild/aix-ppc64@0.27.7':
58
+ resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
59
+ engines: {node: '>=18'}
60
+ cpu: [ppc64]
61
+ os: [aix]
62
+
63
+ '@esbuild/android-arm64@0.27.7':
64
+ resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
65
+ engines: {node: '>=18'}
66
+ cpu: [arm64]
67
+ os: [android]
68
+
69
+ '@esbuild/android-arm@0.27.7':
70
+ resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
71
+ engines: {node: '>=18'}
72
+ cpu: [arm]
73
+ os: [android]
74
+
75
+ '@esbuild/android-x64@0.27.7':
76
+ resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
77
+ engines: {node: '>=18'}
78
+ cpu: [x64]
79
+ os: [android]
80
+
81
+ '@esbuild/darwin-arm64@0.27.7':
82
+ resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
83
+ engines: {node: '>=18'}
84
+ cpu: [arm64]
85
+ os: [darwin]
86
+
87
+ '@esbuild/darwin-x64@0.27.7':
88
+ resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
89
+ engines: {node: '>=18'}
90
+ cpu: [x64]
91
+ os: [darwin]
92
+
93
+ '@esbuild/freebsd-arm64@0.27.7':
94
+ resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
95
+ engines: {node: '>=18'}
96
+ cpu: [arm64]
97
+ os: [freebsd]
98
+
99
+ '@esbuild/freebsd-x64@0.27.7':
100
+ resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
101
+ engines: {node: '>=18'}
102
+ cpu: [x64]
103
+ os: [freebsd]
104
+
105
+ '@esbuild/linux-arm64@0.27.7':
106
+ resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
107
+ engines: {node: '>=18'}
108
+ cpu: [arm64]
109
+ os: [linux]
110
+
111
+ '@esbuild/linux-arm@0.27.7':
112
+ resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
113
+ engines: {node: '>=18'}
114
+ cpu: [arm]
115
+ os: [linux]
116
+
117
+ '@esbuild/linux-ia32@0.27.7':
118
+ resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
119
+ engines: {node: '>=18'}
120
+ cpu: [ia32]
121
+ os: [linux]
122
+
123
+ '@esbuild/linux-loong64@0.27.7':
124
+ resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
125
+ engines: {node: '>=18'}
126
+ cpu: [loong64]
127
+ os: [linux]
128
+
129
+ '@esbuild/linux-mips64el@0.27.7':
130
+ resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
131
+ engines: {node: '>=18'}
132
+ cpu: [mips64el]
133
+ os: [linux]
134
+
135
+ '@esbuild/linux-ppc64@0.27.7':
136
+ resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
137
+ engines: {node: '>=18'}
138
+ cpu: [ppc64]
139
+ os: [linux]
140
+
141
+ '@esbuild/linux-riscv64@0.27.7':
142
+ resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
143
+ engines: {node: '>=18'}
144
+ cpu: [riscv64]
145
+ os: [linux]
146
+
147
+ '@esbuild/linux-s390x@0.27.7':
148
+ resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
149
+ engines: {node: '>=18'}
150
+ cpu: [s390x]
151
+ os: [linux]
152
+
153
+ '@esbuild/linux-x64@0.27.7':
154
+ resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
155
+ engines: {node: '>=18'}
156
+ cpu: [x64]
157
+ os: [linux]
158
+
159
+ '@esbuild/netbsd-arm64@0.27.7':
160
+ resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
161
+ engines: {node: '>=18'}
162
+ cpu: [arm64]
163
+ os: [netbsd]
164
+
165
+ '@esbuild/netbsd-x64@0.27.7':
166
+ resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
167
+ engines: {node: '>=18'}
168
+ cpu: [x64]
169
+ os: [netbsd]
170
+
171
+ '@esbuild/openbsd-arm64@0.27.7':
172
+ resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
173
+ engines: {node: '>=18'}
174
+ cpu: [arm64]
175
+ os: [openbsd]
176
+
177
+ '@esbuild/openbsd-x64@0.27.7':
178
+ resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
179
+ engines: {node: '>=18'}
180
+ cpu: [x64]
181
+ os: [openbsd]
182
+
183
+ '@esbuild/openharmony-arm64@0.27.7':
184
+ resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
185
+ engines: {node: '>=18'}
186
+ cpu: [arm64]
187
+ os: [openharmony]
188
+
189
+ '@esbuild/sunos-x64@0.27.7':
190
+ resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
191
+ engines: {node: '>=18'}
192
+ cpu: [x64]
193
+ os: [sunos]
194
+
195
+ '@esbuild/win32-arm64@0.27.7':
196
+ resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
197
+ engines: {node: '>=18'}
198
+ cpu: [arm64]
199
+ os: [win32]
200
+
201
+ '@esbuild/win32-ia32@0.27.7':
202
+ resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
203
+ engines: {node: '>=18'}
204
+ cpu: [ia32]
205
+ os: [win32]
206
+
207
+ '@esbuild/win32-x64@0.27.7':
208
+ resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
209
+ engines: {node: '>=18'}
210
+ cpu: [x64]
211
+ os: [win32]
212
+
213
+ '@types/body-parser@1.19.6':
214
+ resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
215
+
216
+ '@types/connect@3.4.38':
217
+ resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
218
+
219
+ '@types/cors@2.8.19':
220
+ resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
221
+
222
+ '@types/express-serve-static-core@4.19.8':
223
+ resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==}
224
+
225
+ '@types/express@4.17.25':
226
+ resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==}
227
+
228
+ '@types/http-errors@2.0.5':
229
+ resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
230
+
231
+ '@types/mime@1.3.5':
232
+ resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
233
+
234
+ '@types/node-fetch@2.6.13':
235
+ resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==}
236
+
237
+ '@types/node@18.19.130':
238
+ resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==}
239
+
240
+ '@types/node@20.19.40':
241
+ resolution: {integrity: sha512-xxx6M2IpSTnnKcR0cMvIiohkiCx20/oRPtWGbenFygKCGl3zqUzdNjQ/1V4solq1LU+dgv0nQzeGOuqkqZGg0Q==}
242
+
243
+ '@types/qs@6.15.1':
244
+ resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
245
+
246
+ '@types/range-parser@1.2.7':
247
+ resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
248
+
249
+ '@types/send@0.17.6':
250
+ resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
251
+
252
+ '@types/send@1.2.1':
253
+ resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
254
+
255
+ '@types/serve-static@1.15.10':
256
+ resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==}
257
+
258
+ abort-controller@3.0.0:
259
+ resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
260
+ engines: {node: '>=6.5'}
261
+
262
+ accepts@1.3.8:
263
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
264
+ engines: {node: '>= 0.6'}
265
+
266
+ agentkeepalive@4.6.0:
267
+ resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
268
+ engines: {node: '>= 8.0.0'}
269
+
270
+ array-flatten@1.1.1:
271
+ resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
272
+
273
+ asynckit@0.4.0:
274
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
275
+
276
+ atomic-sleep@1.0.0:
277
+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
278
+ engines: {node: '>=8.0.0'}
279
+
280
+ base64-js@1.5.1:
281
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
282
+
283
+ body-parser@1.20.5:
284
+ resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==}
285
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
286
+
287
+ buffer@6.0.3:
288
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
289
+
290
+ bytes@3.1.2:
291
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
292
+ engines: {node: '>= 0.8'}
293
+
294
+ call-bind-apply-helpers@1.0.2:
295
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
296
+ engines: {node: '>= 0.4'}
297
+
298
+ call-bound@1.0.4:
299
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
300
+ engines: {node: '>= 0.4'}
301
+
302
+ colorette@2.0.20:
303
+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
304
+
305
+ combined-stream@1.0.8:
306
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
307
+ engines: {node: '>= 0.8'}
308
+
309
+ content-disposition@0.5.4:
310
+ resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
311
+ engines: {node: '>= 0.6'}
312
+
313
+ content-type@1.0.5:
314
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
315
+ engines: {node: '>= 0.6'}
316
+
317
+ cookie-signature@1.0.7:
318
+ resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
319
+
320
+ cookie@0.7.2:
321
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
322
+ engines: {node: '>= 0.6'}
323
+
324
+ cors@2.8.6:
325
+ resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
326
+ engines: {node: '>= 0.10'}
327
+
328
+ dateformat@4.6.3:
329
+ resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
330
+
331
+ debug@2.6.9:
332
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
333
+ peerDependencies:
334
+ supports-color: '*'
335
+ peerDependenciesMeta:
336
+ supports-color:
337
+ optional: true
338
+
339
+ delayed-stream@1.0.0:
340
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
341
+ engines: {node: '>=0.4.0'}
342
+
343
+ depd@2.0.0:
344
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
345
+ engines: {node: '>= 0.8'}
346
+
347
+ destroy@1.2.0:
348
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
349
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
350
+
351
+ dotenv@16.6.1:
352
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
353
+ engines: {node: '>=12'}
354
+
355
+ dunder-proto@1.0.1:
356
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
357
+ engines: {node: '>= 0.4'}
358
+
359
+ ee-first@1.1.1:
360
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
361
+
362
+ encodeurl@2.0.0:
363
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
364
+ engines: {node: '>= 0.8'}
365
+
366
+ end-of-stream@1.4.5:
367
+ resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
368
+
369
+ es-define-property@1.0.1:
370
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
371
+ engines: {node: '>= 0.4'}
372
+
373
+ es-errors@1.3.0:
374
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
375
+ engines: {node: '>= 0.4'}
376
+
377
+ es-object-atoms@1.1.1:
378
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
379
+ engines: {node: '>= 0.4'}
380
+
381
+ es-set-tostringtag@2.1.0:
382
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
383
+ engines: {node: '>= 0.4'}
384
+
385
+ esbuild@0.27.7:
386
+ resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
387
+ engines: {node: '>=18'}
388
+ hasBin: true
389
+
390
+ escape-html@1.0.3:
391
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
392
+
393
+ etag@1.8.1:
394
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
395
+ engines: {node: '>= 0.6'}
396
+
397
+ event-target-shim@5.0.1:
398
+ resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
399
+ engines: {node: '>=6'}
400
+
401
+ events@3.3.0:
402
+ resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
403
+ engines: {node: '>=0.8.x'}
404
+
405
+ express-rate-limit@7.5.1:
406
+ resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==}
407
+ engines: {node: '>= 16'}
408
+ peerDependencies:
409
+ express: '>= 4.11'
410
+
411
+ express@4.22.1:
412
+ resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
413
+ engines: {node: '>= 0.10.0'}
414
+
415
+ fast-copy@3.0.2:
416
+ resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
417
+
418
+ fast-redact@3.5.0:
419
+ resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
420
+ engines: {node: '>=6'}
421
+
422
+ fast-safe-stringify@2.1.1:
423
+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
424
+
425
+ finalhandler@1.3.2:
426
+ resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
427
+ engines: {node: '>= 0.8'}
428
+
429
+ form-data-encoder@1.7.2:
430
+ resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
431
+
432
+ form-data@4.0.5:
433
+ resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
434
+ engines: {node: '>= 6'}
435
+
436
+ formdata-node@4.4.1:
437
+ resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
438
+ engines: {node: '>= 12.20'}
439
+
440
+ forwarded@0.2.0:
441
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
442
+ engines: {node: '>= 0.6'}
443
+
444
+ fresh@0.5.2:
445
+ resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
446
+ engines: {node: '>= 0.6'}
447
+
448
+ fsevents@2.3.3:
449
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
450
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
451
+ os: [darwin]
452
+
453
+ function-bind@1.1.2:
454
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
455
+
456
+ get-intrinsic@1.3.0:
457
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
458
+ engines: {node: '>= 0.4'}
459
+
460
+ get-proto@1.0.1:
461
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
462
+ engines: {node: '>= 0.4'}
463
+
464
+ get-tsconfig@4.14.0:
465
+ resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
466
+
467
+ gopd@1.2.0:
468
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
469
+ engines: {node: '>= 0.4'}
470
+
471
+ has-symbols@1.1.0:
472
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
473
+ engines: {node: '>= 0.4'}
474
+
475
+ has-tostringtag@1.0.2:
476
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
477
+ engines: {node: '>= 0.4'}
478
+
479
+ hasown@2.0.3:
480
+ resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
481
+ engines: {node: '>= 0.4'}
482
+
483
+ helmet@7.2.0:
484
+ resolution: {integrity: sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==}
485
+ engines: {node: '>=16.0.0'}
486
+
487
+ help-me@5.0.0:
488
+ resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
489
+
490
+ http-errors@2.0.1:
491
+ resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
492
+ engines: {node: '>= 0.8'}
493
+
494
+ humanize-ms@1.2.1:
495
+ resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
496
+
497
+ iconv-lite@0.4.24:
498
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
499
+ engines: {node: '>=0.10.0'}
500
+
501
+ ieee754@1.2.1:
502
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
503
+
504
+ inherits@2.0.4:
505
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
506
+
507
+ ipaddr.js@1.9.1:
508
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
509
+ engines: {node: '>= 0.10'}
510
+
511
+ joycon@3.1.1:
512
+ resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
513
+ engines: {node: '>=10'}
514
+
515
+ math-intrinsics@1.1.0:
516
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
517
+ engines: {node: '>= 0.4'}
518
+
519
+ media-typer@0.3.0:
520
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
521
+ engines: {node: '>= 0.6'}
522
+
523
+ merge-descriptors@1.0.3:
524
+ resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
525
+
526
+ methods@1.1.2:
527
+ resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
528
+ engines: {node: '>= 0.6'}
529
+
530
+ mime-db@1.52.0:
531
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
532
+ engines: {node: '>= 0.6'}
533
+
534
+ mime-types@2.1.35:
535
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
536
+ engines: {node: '>= 0.6'}
537
+
538
+ mime@1.6.0:
539
+ resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
540
+ engines: {node: '>=4'}
541
+ hasBin: true
542
+
543
+ minimist@1.2.8:
544
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
545
+
546
+ ms@2.0.0:
547
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
548
+
549
+ ms@2.1.3:
550
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
551
+
552
+ negotiator@0.6.3:
553
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
554
+ engines: {node: '>= 0.6'}
555
+
556
+ node-domexception@1.0.0:
557
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
558
+ engines: {node: '>=10.5.0'}
559
+ deprecated: Use your platform's native DOMException instead
560
+
561
+ node-fetch@2.7.0:
562
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
563
+ engines: {node: 4.x || >=6.0.0}
564
+ peerDependencies:
565
+ encoding: ^0.1.0
566
+ peerDependenciesMeta:
567
+ encoding:
568
+ optional: true
569
+
570
+ object-assign@4.1.1:
571
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
572
+ engines: {node: '>=0.10.0'}
573
+
574
+ object-inspect@1.13.4:
575
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
576
+ engines: {node: '>= 0.4'}
577
+
578
+ on-exit-leak-free@2.1.2:
579
+ resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
580
+ engines: {node: '>=14.0.0'}
581
+
582
+ on-finished@2.4.1:
583
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
584
+ engines: {node: '>= 0.8'}
585
+
586
+ once@1.4.0:
587
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
588
+
589
+ openai@4.104.0:
590
+ resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==}
591
+ hasBin: true
592
+ peerDependencies:
593
+ ws: ^8.18.0
594
+ zod: ^3.23.8
595
+ peerDependenciesMeta:
596
+ ws:
597
+ optional: true
598
+ zod:
599
+ optional: true
600
+
601
+ parseurl@1.3.3:
602
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
603
+ engines: {node: '>= 0.8'}
604
+
605
+ path-to-regexp@0.1.13:
606
+ resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==}
607
+
608
+ pino-abstract-transport@1.2.0:
609
+ resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
610
+
611
+ pino-pretty@10.3.1:
612
+ resolution: {integrity: sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==}
613
+ hasBin: true
614
+
615
+ pino-std-serializers@6.2.2:
616
+ resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==}
617
+
618
+ pino@8.21.0:
619
+ resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==}
620
+ hasBin: true
621
+
622
+ process-warning@3.0.0:
623
+ resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
624
+
625
+ process@0.11.10:
626
+ resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
627
+ engines: {node: '>= 0.6.0'}
628
+
629
+ proxy-addr@2.0.7:
630
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
631
+ engines: {node: '>= 0.10'}
632
+
633
+ pump@3.0.4:
634
+ resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
635
+
636
+ qs@6.14.2:
637
+ resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
638
+ engines: {node: '>=0.6'}
639
+
640
+ qs@6.15.1:
641
+ resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
642
+ engines: {node: '>=0.6'}
643
+
644
+ quick-format-unescaped@4.0.4:
645
+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
646
+
647
+ range-parser@1.2.1:
648
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
649
+ engines: {node: '>= 0.6'}
650
+
651
+ raw-body@2.5.3:
652
+ resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
653
+ engines: {node: '>= 0.8'}
654
+
655
+ readable-stream@4.7.0:
656
+ resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
657
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
658
+
659
+ real-require@0.2.0:
660
+ resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
661
+ engines: {node: '>= 12.13.0'}
662
+
663
+ resolve-pkg-maps@1.0.0:
664
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
665
+
666
+ safe-buffer@5.2.1:
667
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
668
+
669
+ safe-stable-stringify@2.5.0:
670
+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
671
+ engines: {node: '>=10'}
672
+
673
+ safer-buffer@2.1.2:
674
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
675
+
676
+ secure-json-parse@2.7.0:
677
+ resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
678
+
679
+ send@0.19.2:
680
+ resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==}
681
+ engines: {node: '>= 0.8.0'}
682
+
683
+ serve-static@1.16.3:
684
+ resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
685
+ engines: {node: '>= 0.8.0'}
686
+
687
+ setprototypeof@1.2.0:
688
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
689
+
690
+ side-channel-list@1.0.1:
691
+ resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
692
+ engines: {node: '>= 0.4'}
693
+
694
+ side-channel-map@1.0.1:
695
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
696
+ engines: {node: '>= 0.4'}
697
+
698
+ side-channel-weakmap@1.0.2:
699
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
700
+ engines: {node: '>= 0.4'}
701
+
702
+ side-channel@1.1.0:
703
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
704
+ engines: {node: '>= 0.4'}
705
+
706
+ sonic-boom@3.8.1:
707
+ resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==}
708
+
709
+ split2@4.2.0:
710
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
711
+ engines: {node: '>= 10.x'}
712
+
713
+ statuses@2.0.2:
714
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
715
+ engines: {node: '>= 0.8'}
716
+
717
+ string_decoder@1.3.0:
718
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
719
+
720
+ strip-json-comments@3.1.1:
721
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
722
+ engines: {node: '>=8'}
723
+
724
+ thread-stream@2.7.0:
725
+ resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==}
726
+
727
+ toidentifier@1.0.1:
728
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
729
+ engines: {node: '>=0.6'}
730
+
731
+ tr46@0.0.3:
732
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
733
+
734
+ tsx@4.21.0:
735
+ resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
736
+ engines: {node: '>=18.0.0'}
737
+ hasBin: true
738
+
739
+ type-is@1.6.18:
740
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
741
+ engines: {node: '>= 0.6'}
742
+
743
+ typescript@5.9.3:
744
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
745
+ engines: {node: '>=14.17'}
746
+ hasBin: true
747
+
748
+ undici-types@5.26.5:
749
+ resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
750
+
751
+ undici-types@6.21.0:
752
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
753
+
754
+ unpipe@1.0.0:
755
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
756
+ engines: {node: '>= 0.8'}
757
+
758
+ utils-merge@1.0.1:
759
+ resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
760
+ engines: {node: '>= 0.4.0'}
761
+
762
+ vary@1.1.2:
763
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
764
+ engines: {node: '>= 0.8'}
765
+
766
+ web-streams-polyfill@4.0.0-beta.3:
767
+ resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
768
+ engines: {node: '>= 14'}
769
+
770
+ webidl-conversions@3.0.1:
771
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
772
+
773
+ whatwg-url@5.0.0:
774
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
775
+
776
+ wrappy@1.0.2:
777
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
778
+
779
+ zod@3.25.76:
780
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
781
+
782
+ snapshots:
783
+
784
+ '@esbuild/aix-ppc64@0.27.7':
785
+ optional: true
786
+
787
+ '@esbuild/android-arm64@0.27.7':
788
+ optional: true
789
+
790
+ '@esbuild/android-arm@0.27.7':
791
+ optional: true
792
+
793
+ '@esbuild/android-x64@0.27.7':
794
+ optional: true
795
+
796
+ '@esbuild/darwin-arm64@0.27.7':
797
+ optional: true
798
+
799
+ '@esbuild/darwin-x64@0.27.7':
800
+ optional: true
801
+
802
+ '@esbuild/freebsd-arm64@0.27.7':
803
+ optional: true
804
+
805
+ '@esbuild/freebsd-x64@0.27.7':
806
+ optional: true
807
+
808
+ '@esbuild/linux-arm64@0.27.7':
809
+ optional: true
810
+
811
+ '@esbuild/linux-arm@0.27.7':
812
+ optional: true
813
+
814
+ '@esbuild/linux-ia32@0.27.7':
815
+ optional: true
816
+
817
+ '@esbuild/linux-loong64@0.27.7':
818
+ optional: true
819
+
820
+ '@esbuild/linux-mips64el@0.27.7':
821
+ optional: true
822
+
823
+ '@esbuild/linux-ppc64@0.27.7':
824
+ optional: true
825
+
826
+ '@esbuild/linux-riscv64@0.27.7':
827
+ optional: true
828
+
829
+ '@esbuild/linux-s390x@0.27.7':
830
+ optional: true
831
+
832
+ '@esbuild/linux-x64@0.27.7':
833
+ optional: true
834
+
835
+ '@esbuild/netbsd-arm64@0.27.7':
836
+ optional: true
837
+
838
+ '@esbuild/netbsd-x64@0.27.7':
839
+ optional: true
840
+
841
+ '@esbuild/openbsd-arm64@0.27.7':
842
+ optional: true
843
+
844
+ '@esbuild/openbsd-x64@0.27.7':
845
+ optional: true
846
+
847
+ '@esbuild/openharmony-arm64@0.27.7':
848
+ optional: true
849
+
850
+ '@esbuild/sunos-x64@0.27.7':
851
+ optional: true
852
+
853
+ '@esbuild/win32-arm64@0.27.7':
854
+ optional: true
855
+
856
+ '@esbuild/win32-ia32@0.27.7':
857
+ optional: true
858
+
859
+ '@esbuild/win32-x64@0.27.7':
860
+ optional: true
861
+
862
+ '@types/body-parser@1.19.6':
863
+ dependencies:
864
+ '@types/connect': 3.4.38
865
+ '@types/node': 20.19.40
866
+
867
+ '@types/connect@3.4.38':
868
+ dependencies:
869
+ '@types/node': 20.19.40
870
+
871
+ '@types/cors@2.8.19':
872
+ dependencies:
873
+ '@types/node': 20.19.40
874
+
875
+ '@types/express-serve-static-core@4.19.8':
876
+ dependencies:
877
+ '@types/node': 20.19.40
878
+ '@types/qs': 6.15.1
879
+ '@types/range-parser': 1.2.7
880
+ '@types/send': 1.2.1
881
+
882
+ '@types/express@4.17.25':
883
+ dependencies:
884
+ '@types/body-parser': 1.19.6
885
+ '@types/express-serve-static-core': 4.19.8
886
+ '@types/qs': 6.15.1
887
+ '@types/serve-static': 1.15.10
888
+
889
+ '@types/http-errors@2.0.5': {}
890
+
891
+ '@types/mime@1.3.5': {}
892
+
893
+ '@types/node-fetch@2.6.13':
894
+ dependencies:
895
+ '@types/node': 20.19.40
896
+ form-data: 4.0.5
897
+
898
+ '@types/node@18.19.130':
899
+ dependencies:
900
+ undici-types: 5.26.5
901
+
902
+ '@types/node@20.19.40':
903
+ dependencies:
904
+ undici-types: 6.21.0
905
+
906
+ '@types/qs@6.15.1': {}
907
+
908
+ '@types/range-parser@1.2.7': {}
909
+
910
+ '@types/send@0.17.6':
911
+ dependencies:
912
+ '@types/mime': 1.3.5
913
+ '@types/node': 20.19.40
914
+
915
+ '@types/send@1.2.1':
916
+ dependencies:
917
+ '@types/node': 20.19.40
918
+
919
+ '@types/serve-static@1.15.10':
920
+ dependencies:
921
+ '@types/http-errors': 2.0.5
922
+ '@types/node': 20.19.40
923
+ '@types/send': 0.17.6
924
+
925
+ abort-controller@3.0.0:
926
+ dependencies:
927
+ event-target-shim: 5.0.1
928
+
929
+ accepts@1.3.8:
930
+ dependencies:
931
+ mime-types: 2.1.35
932
+ negotiator: 0.6.3
933
+
934
+ agentkeepalive@4.6.0:
935
+ dependencies:
936
+ humanize-ms: 1.2.1
937
+
938
+ array-flatten@1.1.1: {}
939
+
940
+ asynckit@0.4.0: {}
941
+
942
+ atomic-sleep@1.0.0: {}
943
+
944
+ base64-js@1.5.1: {}
945
+
946
+ body-parser@1.20.5:
947
+ dependencies:
948
+ bytes: 3.1.2
949
+ content-type: 1.0.5
950
+ debug: 2.6.9
951
+ depd: 2.0.0
952
+ destroy: 1.2.0
953
+ http-errors: 2.0.1
954
+ iconv-lite: 0.4.24
955
+ on-finished: 2.4.1
956
+ qs: 6.15.1
957
+ raw-body: 2.5.3
958
+ type-is: 1.6.18
959
+ unpipe: 1.0.0
960
+ transitivePeerDependencies:
961
+ - supports-color
962
+
963
+ buffer@6.0.3:
964
+ dependencies:
965
+ base64-js: 1.5.1
966
+ ieee754: 1.2.1
967
+
968
+ bytes@3.1.2: {}
969
+
970
+ call-bind-apply-helpers@1.0.2:
971
+ dependencies:
972
+ es-errors: 1.3.0
973
+ function-bind: 1.1.2
974
+
975
+ call-bound@1.0.4:
976
+ dependencies:
977
+ call-bind-apply-helpers: 1.0.2
978
+ get-intrinsic: 1.3.0
979
+
980
+ colorette@2.0.20: {}
981
+
982
+ combined-stream@1.0.8:
983
+ dependencies:
984
+ delayed-stream: 1.0.0
985
+
986
+ content-disposition@0.5.4:
987
+ dependencies:
988
+ safe-buffer: 5.2.1
989
+
990
+ content-type@1.0.5: {}
991
+
992
+ cookie-signature@1.0.7: {}
993
+
994
+ cookie@0.7.2: {}
995
+
996
+ cors@2.8.6:
997
+ dependencies:
998
+ object-assign: 4.1.1
999
+ vary: 1.1.2
1000
+
1001
+ dateformat@4.6.3: {}
1002
+
1003
+ debug@2.6.9:
1004
+ dependencies:
1005
+ ms: 2.0.0
1006
+
1007
+ delayed-stream@1.0.0: {}
1008
+
1009
+ depd@2.0.0: {}
1010
+
1011
+ destroy@1.2.0: {}
1012
+
1013
+ dotenv@16.6.1: {}
1014
+
1015
+ dunder-proto@1.0.1:
1016
+ dependencies:
1017
+ call-bind-apply-helpers: 1.0.2
1018
+ es-errors: 1.3.0
1019
+ gopd: 1.2.0
1020
+
1021
+ ee-first@1.1.1: {}
1022
+
1023
+ encodeurl@2.0.0: {}
1024
+
1025
+ end-of-stream@1.4.5:
1026
+ dependencies:
1027
+ once: 1.4.0
1028
+
1029
+ es-define-property@1.0.1: {}
1030
+
1031
+ es-errors@1.3.0: {}
1032
+
1033
+ es-object-atoms@1.1.1:
1034
+ dependencies:
1035
+ es-errors: 1.3.0
1036
+
1037
+ es-set-tostringtag@2.1.0:
1038
+ dependencies:
1039
+ es-errors: 1.3.0
1040
+ get-intrinsic: 1.3.0
1041
+ has-tostringtag: 1.0.2
1042
+ hasown: 2.0.3
1043
+
1044
+ esbuild@0.27.7:
1045
+ optionalDependencies:
1046
+ '@esbuild/aix-ppc64': 0.27.7
1047
+ '@esbuild/android-arm': 0.27.7
1048
+ '@esbuild/android-arm64': 0.27.7
1049
+ '@esbuild/android-x64': 0.27.7
1050
+ '@esbuild/darwin-arm64': 0.27.7
1051
+ '@esbuild/darwin-x64': 0.27.7
1052
+ '@esbuild/freebsd-arm64': 0.27.7
1053
+ '@esbuild/freebsd-x64': 0.27.7
1054
+ '@esbuild/linux-arm': 0.27.7
1055
+ '@esbuild/linux-arm64': 0.27.7
1056
+ '@esbuild/linux-ia32': 0.27.7
1057
+ '@esbuild/linux-loong64': 0.27.7
1058
+ '@esbuild/linux-mips64el': 0.27.7
1059
+ '@esbuild/linux-ppc64': 0.27.7
1060
+ '@esbuild/linux-riscv64': 0.27.7
1061
+ '@esbuild/linux-s390x': 0.27.7
1062
+ '@esbuild/linux-x64': 0.27.7
1063
+ '@esbuild/netbsd-arm64': 0.27.7
1064
+ '@esbuild/netbsd-x64': 0.27.7
1065
+ '@esbuild/openbsd-arm64': 0.27.7
1066
+ '@esbuild/openbsd-x64': 0.27.7
1067
+ '@esbuild/openharmony-arm64': 0.27.7
1068
+ '@esbuild/sunos-x64': 0.27.7
1069
+ '@esbuild/win32-arm64': 0.27.7
1070
+ '@esbuild/win32-ia32': 0.27.7
1071
+ '@esbuild/win32-x64': 0.27.7
1072
+
1073
+ escape-html@1.0.3: {}
1074
+
1075
+ etag@1.8.1: {}
1076
+
1077
+ event-target-shim@5.0.1: {}
1078
+
1079
+ events@3.3.0: {}
1080
+
1081
+ express-rate-limit@7.5.1(express@4.22.1):
1082
+ dependencies:
1083
+ express: 4.22.1
1084
+
1085
+ express@4.22.1:
1086
+ dependencies:
1087
+ accepts: 1.3.8
1088
+ array-flatten: 1.1.1
1089
+ body-parser: 1.20.5
1090
+ content-disposition: 0.5.4
1091
+ content-type: 1.0.5
1092
+ cookie: 0.7.2
1093
+ cookie-signature: 1.0.7
1094
+ debug: 2.6.9
1095
+ depd: 2.0.0
1096
+ encodeurl: 2.0.0
1097
+ escape-html: 1.0.3
1098
+ etag: 1.8.1
1099
+ finalhandler: 1.3.2
1100
+ fresh: 0.5.2
1101
+ http-errors: 2.0.1
1102
+ merge-descriptors: 1.0.3
1103
+ methods: 1.1.2
1104
+ on-finished: 2.4.1
1105
+ parseurl: 1.3.3
1106
+ path-to-regexp: 0.1.13
1107
+ proxy-addr: 2.0.7
1108
+ qs: 6.14.2
1109
+ range-parser: 1.2.1
1110
+ safe-buffer: 5.2.1
1111
+ send: 0.19.2
1112
+ serve-static: 1.16.3
1113
+ setprototypeof: 1.2.0
1114
+ statuses: 2.0.2
1115
+ type-is: 1.6.18
1116
+ utils-merge: 1.0.1
1117
+ vary: 1.1.2
1118
+ transitivePeerDependencies:
1119
+ - supports-color
1120
+
1121
+ fast-copy@3.0.2: {}
1122
+
1123
+ fast-redact@3.5.0: {}
1124
+
1125
+ fast-safe-stringify@2.1.1: {}
1126
+
1127
+ finalhandler@1.3.2:
1128
+ dependencies:
1129
+ debug: 2.6.9
1130
+ encodeurl: 2.0.0
1131
+ escape-html: 1.0.3
1132
+ on-finished: 2.4.1
1133
+ parseurl: 1.3.3
1134
+ statuses: 2.0.2
1135
+ unpipe: 1.0.0
1136
+ transitivePeerDependencies:
1137
+ - supports-color
1138
+
1139
+ form-data-encoder@1.7.2: {}
1140
+
1141
+ form-data@4.0.5:
1142
+ dependencies:
1143
+ asynckit: 0.4.0
1144
+ combined-stream: 1.0.8
1145
+ es-set-tostringtag: 2.1.0
1146
+ hasown: 2.0.3
1147
+ mime-types: 2.1.35
1148
+
1149
+ formdata-node@4.4.1:
1150
+ dependencies:
1151
+ node-domexception: 1.0.0
1152
+ web-streams-polyfill: 4.0.0-beta.3
1153
+
1154
+ forwarded@0.2.0: {}
1155
+
1156
+ fresh@0.5.2: {}
1157
+
1158
+ fsevents@2.3.3:
1159
+ optional: true
1160
+
1161
+ function-bind@1.1.2: {}
1162
+
1163
+ get-intrinsic@1.3.0:
1164
+ dependencies:
1165
+ call-bind-apply-helpers: 1.0.2
1166
+ es-define-property: 1.0.1
1167
+ es-errors: 1.3.0
1168
+ es-object-atoms: 1.1.1
1169
+ function-bind: 1.1.2
1170
+ get-proto: 1.0.1
1171
+ gopd: 1.2.0
1172
+ has-symbols: 1.1.0
1173
+ hasown: 2.0.3
1174
+ math-intrinsics: 1.1.0
1175
+
1176
+ get-proto@1.0.1:
1177
+ dependencies:
1178
+ dunder-proto: 1.0.1
1179
+ es-object-atoms: 1.1.1
1180
+
1181
+ get-tsconfig@4.14.0:
1182
+ dependencies:
1183
+ resolve-pkg-maps: 1.0.0
1184
+
1185
+ gopd@1.2.0: {}
1186
+
1187
+ has-symbols@1.1.0: {}
1188
+
1189
+ has-tostringtag@1.0.2:
1190
+ dependencies:
1191
+ has-symbols: 1.1.0
1192
+
1193
+ hasown@2.0.3:
1194
+ dependencies:
1195
+ function-bind: 1.1.2
1196
+
1197
+ helmet@7.2.0: {}
1198
+
1199
+ help-me@5.0.0: {}
1200
+
1201
+ http-errors@2.0.1:
1202
+ dependencies:
1203
+ depd: 2.0.0
1204
+ inherits: 2.0.4
1205
+ setprototypeof: 1.2.0
1206
+ statuses: 2.0.2
1207
+ toidentifier: 1.0.1
1208
+
1209
+ humanize-ms@1.2.1:
1210
+ dependencies:
1211
+ ms: 2.1.3
1212
+
1213
+ iconv-lite@0.4.24:
1214
+ dependencies:
1215
+ safer-buffer: 2.1.2
1216
+
1217
+ ieee754@1.2.1: {}
1218
+
1219
+ inherits@2.0.4: {}
1220
+
1221
+ ipaddr.js@1.9.1: {}
1222
+
1223
+ joycon@3.1.1: {}
1224
+
1225
+ math-intrinsics@1.1.0: {}
1226
+
1227
+ media-typer@0.3.0: {}
1228
+
1229
+ merge-descriptors@1.0.3: {}
1230
+
1231
+ methods@1.1.2: {}
1232
+
1233
+ mime-db@1.52.0: {}
1234
+
1235
+ mime-types@2.1.35:
1236
+ dependencies:
1237
+ mime-db: 1.52.0
1238
+
1239
+ mime@1.6.0: {}
1240
+
1241
+ minimist@1.2.8: {}
1242
+
1243
+ ms@2.0.0: {}
1244
+
1245
+ ms@2.1.3: {}
1246
+
1247
+ negotiator@0.6.3: {}
1248
+
1249
+ node-domexception@1.0.0: {}
1250
+
1251
+ node-fetch@2.7.0:
1252
+ dependencies:
1253
+ whatwg-url: 5.0.0
1254
+
1255
+ object-assign@4.1.1: {}
1256
+
1257
+ object-inspect@1.13.4: {}
1258
+
1259
+ on-exit-leak-free@2.1.2: {}
1260
+
1261
+ on-finished@2.4.1:
1262
+ dependencies:
1263
+ ee-first: 1.1.1
1264
+
1265
+ once@1.4.0:
1266
+ dependencies:
1267
+ wrappy: 1.0.2
1268
+
1269
+ openai@4.104.0(zod@3.25.76):
1270
+ dependencies:
1271
+ '@types/node': 18.19.130
1272
+ '@types/node-fetch': 2.6.13
1273
+ abort-controller: 3.0.0
1274
+ agentkeepalive: 4.6.0
1275
+ form-data-encoder: 1.7.2
1276
+ formdata-node: 4.4.1
1277
+ node-fetch: 2.7.0
1278
+ optionalDependencies:
1279
+ zod: 3.25.76
1280
+ transitivePeerDependencies:
1281
+ - encoding
1282
+
1283
+ parseurl@1.3.3: {}
1284
+
1285
+ path-to-regexp@0.1.13: {}
1286
+
1287
+ pino-abstract-transport@1.2.0:
1288
+ dependencies:
1289
+ readable-stream: 4.7.0
1290
+ split2: 4.2.0
1291
+
1292
+ pino-pretty@10.3.1:
1293
+ dependencies:
1294
+ colorette: 2.0.20
1295
+ dateformat: 4.6.3
1296
+ fast-copy: 3.0.2
1297
+ fast-safe-stringify: 2.1.1
1298
+ help-me: 5.0.0
1299
+ joycon: 3.1.1
1300
+ minimist: 1.2.8
1301
+ on-exit-leak-free: 2.1.2
1302
+ pino-abstract-transport: 1.2.0
1303
+ pump: 3.0.4
1304
+ readable-stream: 4.7.0
1305
+ secure-json-parse: 2.7.0
1306
+ sonic-boom: 3.8.1
1307
+ strip-json-comments: 3.1.1
1308
+
1309
+ pino-std-serializers@6.2.2: {}
1310
+
1311
+ pino@8.21.0:
1312
+ dependencies:
1313
+ atomic-sleep: 1.0.0
1314
+ fast-redact: 3.5.0
1315
+ on-exit-leak-free: 2.1.2
1316
+ pino-abstract-transport: 1.2.0
1317
+ pino-std-serializers: 6.2.2
1318
+ process-warning: 3.0.0
1319
+ quick-format-unescaped: 4.0.4
1320
+ real-require: 0.2.0
1321
+ safe-stable-stringify: 2.5.0
1322
+ sonic-boom: 3.8.1
1323
+ thread-stream: 2.7.0
1324
+
1325
+ process-warning@3.0.0: {}
1326
+
1327
+ process@0.11.10: {}
1328
+
1329
+ proxy-addr@2.0.7:
1330
+ dependencies:
1331
+ forwarded: 0.2.0
1332
+ ipaddr.js: 1.9.1
1333
+
1334
+ pump@3.0.4:
1335
+ dependencies:
1336
+ end-of-stream: 1.4.5
1337
+ once: 1.4.0
1338
+
1339
+ qs@6.14.2:
1340
+ dependencies:
1341
+ side-channel: 1.1.0
1342
+
1343
+ qs@6.15.1:
1344
+ dependencies:
1345
+ side-channel: 1.1.0
1346
+
1347
+ quick-format-unescaped@4.0.4: {}
1348
+
1349
+ range-parser@1.2.1: {}
1350
+
1351
+ raw-body@2.5.3:
1352
+ dependencies:
1353
+ bytes: 3.1.2
1354
+ http-errors: 2.0.1
1355
+ iconv-lite: 0.4.24
1356
+ unpipe: 1.0.0
1357
+
1358
+ readable-stream@4.7.0:
1359
+ dependencies:
1360
+ abort-controller: 3.0.0
1361
+ buffer: 6.0.3
1362
+ events: 3.3.0
1363
+ process: 0.11.10
1364
+ string_decoder: 1.3.0
1365
+
1366
+ real-require@0.2.0: {}
1367
+
1368
+ resolve-pkg-maps@1.0.0: {}
1369
+
1370
+ safe-buffer@5.2.1: {}
1371
+
1372
+ safe-stable-stringify@2.5.0: {}
1373
+
1374
+ safer-buffer@2.1.2: {}
1375
+
1376
+ secure-json-parse@2.7.0: {}
1377
+
1378
+ send@0.19.2:
1379
+ dependencies:
1380
+ debug: 2.6.9
1381
+ depd: 2.0.0
1382
+ destroy: 1.2.0
1383
+ encodeurl: 2.0.0
1384
+ escape-html: 1.0.3
1385
+ etag: 1.8.1
1386
+ fresh: 0.5.2
1387
+ http-errors: 2.0.1
1388
+ mime: 1.6.0
1389
+ ms: 2.1.3
1390
+ on-finished: 2.4.1
1391
+ range-parser: 1.2.1
1392
+ statuses: 2.0.2
1393
+ transitivePeerDependencies:
1394
+ - supports-color
1395
+
1396
+ serve-static@1.16.3:
1397
+ dependencies:
1398
+ encodeurl: 2.0.0
1399
+ escape-html: 1.0.3
1400
+ parseurl: 1.3.3
1401
+ send: 0.19.2
1402
+ transitivePeerDependencies:
1403
+ - supports-color
1404
+
1405
+ setprototypeof@1.2.0: {}
1406
+
1407
+ side-channel-list@1.0.1:
1408
+ dependencies:
1409
+ es-errors: 1.3.0
1410
+ object-inspect: 1.13.4
1411
+
1412
+ side-channel-map@1.0.1:
1413
+ dependencies:
1414
+ call-bound: 1.0.4
1415
+ es-errors: 1.3.0
1416
+ get-intrinsic: 1.3.0
1417
+ object-inspect: 1.13.4
1418
+
1419
+ side-channel-weakmap@1.0.2:
1420
+ dependencies:
1421
+ call-bound: 1.0.4
1422
+ es-errors: 1.3.0
1423
+ get-intrinsic: 1.3.0
1424
+ object-inspect: 1.13.4
1425
+ side-channel-map: 1.0.1
1426
+
1427
+ side-channel@1.1.0:
1428
+ dependencies:
1429
+ es-errors: 1.3.0
1430
+ object-inspect: 1.13.4
1431
+ side-channel-list: 1.0.1
1432
+ side-channel-map: 1.0.1
1433
+ side-channel-weakmap: 1.0.2
1434
+
1435
+ sonic-boom@3.8.1:
1436
+ dependencies:
1437
+ atomic-sleep: 1.0.0
1438
+
1439
+ split2@4.2.0: {}
1440
+
1441
+ statuses@2.0.2: {}
1442
+
1443
+ string_decoder@1.3.0:
1444
+ dependencies:
1445
+ safe-buffer: 5.2.1
1446
+
1447
+ strip-json-comments@3.1.1: {}
1448
+
1449
+ thread-stream@2.7.0:
1450
+ dependencies:
1451
+ real-require: 0.2.0
1452
+
1453
+ toidentifier@1.0.1: {}
1454
+
1455
+ tr46@0.0.3: {}
1456
+
1457
+ tsx@4.21.0:
1458
+ dependencies:
1459
+ esbuild: 0.27.7
1460
+ get-tsconfig: 4.14.0
1461
+ optionalDependencies:
1462
+ fsevents: 2.3.3
1463
+
1464
+ type-is@1.6.18:
1465
+ dependencies:
1466
+ media-typer: 0.3.0
1467
+ mime-types: 2.1.35
1468
+
1469
+ typescript@5.9.3: {}
1470
+
1471
+ undici-types@5.26.5: {}
1472
+
1473
+ undici-types@6.21.0: {}
1474
+
1475
+ unpipe@1.0.0: {}
1476
+
1477
+ utils-merge@1.0.1: {}
1478
+
1479
+ vary@1.1.2: {}
1480
+
1481
+ web-streams-polyfill@4.0.0-beta.3: {}
1482
+
1483
+ webidl-conversions@3.0.1: {}
1484
+
1485
+ whatwg-url@5.0.0:
1486
+ dependencies:
1487
+ tr46: 0.0.3
1488
+ webidl-conversions: 3.0.1
1489
+
1490
+ wrappy@1.0.2: {}
1491
+
1492
+ zod@3.25.76: {}
apps/simulator/pnpm-workspace.yaml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ allowBuilds:
2
+ esbuild: set this to true or false
apps/simulator/src/agents/compiler.ts ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Workflow Compiler Agent β€” UPGRADED
3
+ * Converts WorkflowGraph (IR) β†’ valid n8n JSON
4
+ * Schema-aware, version-aware, credential-aware
5
+ * Provider-agnostic LLM Gateway β€” NO direct OpenAI dependency
6
+ * STRICT: Only uses nodes from registry. Zero hallucinated params.
7
+ */
8
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
9
+ import type { WorkflowGraph, WorkflowIntent, N8nWorkflow } from '../types/workflow';
10
+ import { COMPILER_PROMPT } from '../prompts/compiler';
11
+ import { N8N_NODE_REGISTRY, isValidNodeType } from '../knowledge/nodeRegistry';
12
+
13
+ export class WorkflowCompiler {
14
+ private llm: LLMGateway;
15
+
16
+ constructor(llm: LLMGateway) {
17
+ this.llm = llm;
18
+ }
19
+
20
+ async compile(graph: WorkflowGraph, intent: WorkflowIntent): Promise<N8nWorkflow> {
21
+ // Build node schema context from registry (ONLY nodes used in graph)
22
+ const nodeTypes = [...new Set(graph.nodes.map((n) => n.n8nNodeType))];
23
+ const validNodeTypes = nodeTypes.filter(isValidNodeType);
24
+ const nodeSchemas = validNodeTypes
25
+ .map((type) => N8N_NODE_REGISTRY[type])
26
+ .filter(Boolean)
27
+ .map((s) => JSON.stringify(s, null, 2))
28
+ .join('\n\n');
29
+
30
+ const workflow = await this.llm.completeJSON<N8nWorkflow>([
31
+ { role: 'system', content: COMPILER_PROMPT },
32
+ {
33
+ role: 'user',
34
+ content: `Compile this WorkflowGraph to a valid n8n JSON workflow.
35
+
36
+ GRAPH:
37
+ ${JSON.stringify(graph, null, 2)}
38
+
39
+ INTENT:
40
+ ${JSON.stringify(intent, null, 2)}
41
+
42
+ REGISTERED NODE SCHEMAS (use ONLY these β€” parameters MUST match schema):
43
+ ${nodeSchemas || '(No pre-built schemas β€” use standard n8n defaults)'}
44
+
45
+ CRITICAL COMPILATION RULES:
46
+ 1. active MUST be false β€” NEVER true
47
+ 2. All node IDs must be unique UUID strings (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
48
+ 3. connections MUST wire ALL graph edges correctly
49
+ 4. NEVER invent parameters not in the node schema above
50
+ 5. Every node MUST have real expressions for dynamic fields β€” NO static placeholder text
51
+ 6. Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 on all critical/external nodes
52
+ 7. Add meaningful node notes explaining what each node does
53
+ 8. USE these expression patterns:
54
+ - Input from trigger: {{$json?.body?.fieldName ?? $json?.fieldName ?? ""}}
55
+ - Previous node: {{$node["NodeName"].json?.field ?? ""}}
56
+ - Array item: {{$json?.items?.[0]?.value ?? ""}}
57
+ - Conditional: {{$json?.status === "active" ? "yes" : "no"}}
58
+ 9. SET nodes MUST have explicit field mappings β€” NOT empty values array
59
+ 10. IF nodes MUST have real boolean conditions referencing actual fields
60
+ 11. CODE nodes MUST contain real JavaScript logic β€” NOT placeholder comments
61
+ 12. AI Agent nodes MUST include system message AND dynamic user message from previous node data
62
+ 13. Return ONLY complete, importable n8n JSON workflow`,
63
+ },
64
+ ], {
65
+ temperature: 0.0,
66
+ retries: 3,
67
+ });
68
+
69
+ return this.ensureSafeDefaults(workflow);
70
+ }
71
+
72
+ /**
73
+ * Enforce safety defaults regardless of LLM output
74
+ * CRITICAL: active = false, proper settings, error handling
75
+ */
76
+ private ensureSafeDefaults(workflow: N8nWorkflow): N8nWorkflow {
77
+ return {
78
+ ...workflow,
79
+ active: false, // NEVER activate on compile β€” ABSOLUTE safety rule
80
+ settings: {
81
+ callerPolicy: 'workflowsFromSameOwner',
82
+ errorWorkflow: workflow.settings?.errorWorkflow,
83
+ timezone: workflow.settings?.timezone ?? 'UTC',
84
+ ...workflow.settings,
85
+ // Force these even if LLM overrides:
86
+ executionOrder: 'v1',
87
+ saveManualExecutions: true,
88
+ },
89
+ nodes: workflow.nodes.map((node) => ({
90
+ ...node,
91
+ onError: node.onError ?? 'continueErrorOutput',
92
+ // Ensure critical external nodes have retry
93
+ retryOnFail: node.retryOnFail ?? false,
94
+ maxTries: node.maxTries ?? 3,
95
+ waitBetweenTries: node.waitBetweenTries ?? 1000,
96
+ })),
97
+ };
98
+ }
99
+ }
apps/simulator/src/agents/credentialIntelligence.ts ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Credential Intelligence System β€” UPGRADED
3
+ * Provider-agnostic, uses Node Registry for credential mapping
4
+ * Inspects workflow to identify required credentials
5
+ * Scans existing n8n credentials and maps types
6
+ * Detects missing credentials and blocks activation if needed
7
+ */
8
+ import type {
9
+ N8nWorkflow,
10
+ CredentialAnalysis,
11
+ RequiredCredential,
12
+ AvailableCredential,
13
+ MissingCredential,
14
+ } from '../types/workflow';
15
+ import { N8N_NODE_REGISTRY } from '../knowledge/nodeRegistry';
16
+
17
+ // Extended node β†’ credential map (derived from registry + additional mappings)
18
+ const NODE_CREDENTIAL_MAP: Record<string, string | undefined> = {
19
+ 'n8n-nodes-base.telegram': 'telegramApi',
20
+ 'n8n-nodes-base.telegramTrigger': 'telegramApi',
21
+ 'n8n-nodes-base.openAi': 'openAiApi',
22
+ '@n8n/n8n-nodes-langchain.openAi': 'openAiApi',
23
+ '@n8n/n8n-nodes-langchain.agent': undefined, // depends on sub-node
24
+ '@n8n/n8n-nodes-langchain.lmChatAnthropic': 'anthropicApi',
25
+ '@n8n/n8n-nodes-langchain.memoryBufferWindow': undefined,
26
+ '@n8n/n8n-nodes-langchain.toolCode': undefined,
27
+ 'n8n-nodes-base.gmail': 'googleOAuth2Api',
28
+ 'n8n-nodes-base.emailReadImap': 'imap',
29
+ 'n8n-nodes-base.googleSheets': 'googleSheetsOAuth2Api',
30
+ 'n8n-nodes-base.slack': 'slackApi',
31
+ 'n8n-nodes-base.slackTrigger': 'slackOAuth2Api',
32
+ 'n8n-nodes-base.airtable': 'airtableTokenApi',
33
+ 'n8n-nodes-base.notion': 'notionApi',
34
+ 'n8n-nodes-base.notionTrigger': 'notionApi',
35
+ 'n8n-nodes-base.github': 'githubApi',
36
+ 'n8n-nodes-base.githubTrigger': 'githubApi',
37
+ 'n8n-nodes-base.stripe': 'stripeApi',
38
+ 'n8n-nodes-base.hubspot': 'hubspotApi',
39
+ 'n8n-nodes-base.postgres': 'postgres',
40
+ 'n8n-nodes-base.mysql': 'mySql',
41
+ 'n8n-nodes-base.mongodb': 'mongoDb',
42
+ 'n8n-nodes-base.redis': 'redis',
43
+ // No credential needed
44
+ 'n8n-nodes-base.httpRequest': undefined,
45
+ 'n8n-nodes-base.webhook': undefined,
46
+ 'n8n-nodes-base.scheduleTrigger': undefined,
47
+ 'n8n-nodes-base.manualTrigger': undefined,
48
+ 'n8n-nodes-base.code': undefined,
49
+ 'n8n-nodes-base.set': undefined,
50
+ 'n8n-nodes-base.if': undefined,
51
+ 'n8n-nodes-base.switch': undefined,
52
+ 'n8n-nodes-base.merge': undefined,
53
+ 'n8n-nodes-base.splitInBatches': undefined,
54
+ 'n8n-nodes-base.itemLists': undefined,
55
+ 'n8n-nodes-base.filter': undefined,
56
+ 'n8n-nodes-base.noOp': undefined,
57
+ 'n8n-nodes-base.stopAndError': undefined,
58
+ 'n8n-nodes-base.wait': undefined,
59
+ 'n8n-nodes-base.respondToWebhook': undefined,
60
+ 'n8n-nodes-base.stickyNote': undefined,
61
+ };
62
+
63
+ const CREDENTIAL_SETUP_INSTRUCTIONS: Record<string, string> = {
64
+ telegramApi: 'Create a Telegram Bot via @BotFather, copy the bot token, then add "Telegram API" credential in n8n Settings β†’ Credentials.',
65
+ openAiApi: 'Generate an API key at https://platform.openai.com/api-keys, then add "OpenAI" credential in n8n.',
66
+ anthropicApi: 'Get your API key at https://console.anthropic.com/, then add "Anthropic" credential in n8n.',
67
+ googleOAuth2Api: 'Set up a Google Cloud project, enable Gmail API, create OAuth2 credentials, then add "Google OAuth2 API" in n8n.',
68
+ googleSheetsOAuth2Api: 'Set up Google Cloud project, enable Sheets API, create OAuth2 credentials, then add "Google Sheets OAuth2 API" in n8n.',
69
+ slackApi: 'Create a Slack App at https://api.slack.com/apps with required scopes, then add "Slack API" credential in n8n.',
70
+ slackOAuth2Api: 'Create a Slack App with OAuth2, then add "Slack OAuth2 API" credential in n8n.',
71
+ airtableTokenApi: 'Generate a Personal Access Token at https://airtable.com/account, then add "Airtable Token API" in n8n.',
72
+ notionApi: 'Create an integration at https://www.notion.so/my-integrations, then add "Notion API" credential in n8n.',
73
+ githubApi: 'Generate a Personal Access Token at https://github.com/settings/tokens, then add "GitHub API" in n8n.',
74
+ stripeApi: 'Get your API key from https://dashboard.stripe.com/apikeys, then add "Stripe API" in n8n.',
75
+ hubspotApi: 'Create a Private App in HubSpot settings, copy the access token, then add "HubSpot API" in n8n.',
76
+ postgres: 'Provide your PostgreSQL host, port, database, username, and password in "Postgres" credential in n8n.',
77
+ mySql: 'Provide your MySQL host, port, database, username, and password in "MySQL" credential in n8n.',
78
+ mongoDb: 'Provide your MongoDB connection string in "MongoDB" credential in n8n.',
79
+ redis: 'Provide your Redis host, port, and optional password in "Redis" credential in n8n.',
80
+ imap: 'Provide your IMAP server host, port, username, and password in "IMAP" credential in n8n.',
81
+ };
82
+
83
+ export class CredentialIntelligence {
84
+ private n8nBaseUrl: string;
85
+ private n8nApiKey: string;
86
+
87
+ constructor(n8nBaseUrl: string, n8nApiKey: string) {
88
+ this.n8nBaseUrl = n8nBaseUrl;
89
+ this.n8nApiKey = n8nApiKey;
90
+ }
91
+
92
+ async analyse(jobId: string, workflow: N8nWorkflow): Promise<CredentialAnalysis> {
93
+ // ─── 1. Identify required credentials from workflow nodes ──────────────
94
+ const required: RequiredCredential[] = [];
95
+ const requiredTypes = new Set<string>();
96
+
97
+ workflow.nodes.forEach((node) => {
98
+ // Check registry-based credential (more accurate)
99
+ const registryDef = N8N_NODE_REGISTRY[node.type];
100
+ const credType = registryDef?.credentialType ?? NODE_CREDENTIAL_MAP[node.type];
101
+
102
+ if (credType !== undefined && credType !== '') {
103
+ if (!requiredTypes.has(credType)) {
104
+ required.push({
105
+ nodeId: node.id,
106
+ nodeName: node.name,
107
+ credentialType: credType,
108
+ credentialDisplayName: registryDef?.credentialDisplayName,
109
+ required: true,
110
+ });
111
+ requiredTypes.add(credType);
112
+ }
113
+ }
114
+
115
+ // Also check node.credentials field (explicitly set by compiler)
116
+ if (node.credentials) {
117
+ Object.keys(node.credentials).forEach((cType) => {
118
+ if (!requiredTypes.has(cType)) {
119
+ required.push({
120
+ nodeId: node.id,
121
+ nodeName: node.name,
122
+ credentialType: cType,
123
+ required: true,
124
+ });
125
+ requiredTypes.add(cType);
126
+ }
127
+ });
128
+ }
129
+ });
130
+
131
+ // ─── 2. Fetch available credentials from n8n ───────────────────────────
132
+ let available: AvailableCredential[] = [];
133
+ let n8nReachable = false;
134
+ try {
135
+ const resp = await fetch(`${this.n8nBaseUrl}/api/v1/credentials`, {
136
+ headers: { 'X-N8N-API-KEY': this.n8nApiKey },
137
+ signal: AbortSignal.timeout(8000),
138
+ });
139
+ if (resp.ok) {
140
+ const data = await resp.json() as { data: AvailableCredential[] };
141
+ available = data.data ?? [];
142
+ n8nReachable = true;
143
+ }
144
+ } catch {
145
+ // n8n not accessible β€” non-fatal, report as warning
146
+ }
147
+
148
+ const availableTypes = new Set(available.map((c) => c.type));
149
+
150
+ // ─── 3. Identify missing credentials ──────────────────────────────────
151
+ const missingTypeMap = new Map<string, string[]>();
152
+ required.forEach((req) => {
153
+ // Only report as missing if n8n is reachable and credential is not there
154
+ if (n8nReachable && !availableTypes.has(req.credentialType)) {
155
+ const existing = missingTypeMap.get(req.credentialType) ?? [];
156
+ if (!existing.includes(req.nodeName)) existing.push(req.nodeName);
157
+ missingTypeMap.set(req.credentialType, existing);
158
+ }
159
+ });
160
+
161
+ const missing: MissingCredential[] = [...missingTypeMap.entries()].map(([credType, nodeNames]) => ({
162
+ credentialType: credType,
163
+ requiredByNodes: nodeNames,
164
+ setupInstructions: CREDENTIAL_SETUP_INSTRUCTIONS[credType] ?? `Set up "${credType}" credential in n8n Settings β†’ Credentials.`,
165
+ }));
166
+
167
+ return {
168
+ jobId,
169
+ allCredentialsPresent: missing.length === 0,
170
+ n8nReachable,
171
+ required,
172
+ available,
173
+ missing,
174
+ analysedAt: new Date().toISOString(),
175
+ };
176
+ }
177
+ }
apps/simulator/src/agents/graphEngine.ts ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Workflow Graph Engine (IR Layer) β€” UPGRADED
3
+ * Builds an internal graph representation BEFORE compiling to n8n JSON
4
+ * Provider-agnostic LLM Gateway β€” NO direct OpenAI dependency
5
+ * Validates all node types against the real Node Registry before returning
6
+ */
7
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
8
+ import type { WorkflowIntent, WorkflowArchitecturePlan, WorkflowGraph } from '../types/workflow';
9
+ import { GRAPH_ENGINE_PROMPT } from '../prompts/graphEngine';
10
+ import { isValidNodeType, getRegistryNodeList } from '../knowledge/nodeRegistry';
11
+
12
+ export class WorkflowGraphEngine {
13
+ private llm: LLMGateway;
14
+
15
+ constructor(llm: LLMGateway) {
16
+ this.llm = llm;
17
+ }
18
+
19
+ async buildGraph(
20
+ userRequest: string,
21
+ intent: WorkflowIntent,
22
+ plan: WorkflowArchitecturePlan,
23
+ ): Promise<WorkflowGraph> {
24
+ const registryList = getRegistryNodeList();
25
+
26
+ const graph = await this.llm.completeJSON<WorkflowGraph>([
27
+ { role: 'system', content: GRAPH_ENGINE_PROMPT },
28
+ {
29
+ role: 'user',
30
+ content: `Build a WorkflowGraph IR for this workflow:
31
+
32
+ REQUEST: ${userRequest}
33
+
34
+ INTENT:
35
+ ${JSON.stringify(intent, null, 2)}
36
+
37
+ ARCHITECTURE PLAN:
38
+ ${JSON.stringify(plan, null, 2)}
39
+
40
+ AVAILABLE NODE TYPES (USE ONLY THESE β€” NO OTHERS):
41
+ ${registryList}
42
+
43
+ CRITICAL RULES:
44
+ - Every node MUST have n8nNodeType from the list above ONLY
45
+ - NEVER invent node types not in the list
46
+ - Every non-trigger node MUST have at least one incoming edge
47
+ - Every node MUST have meaningful parameters β€” NO empty nodes
48
+ - DataContracts must define what JSON fields flow between nodes
49
+ - Use real expressions: {{$json?.field ?? ""}} β€” NOT placeholder text
50
+ - Position nodes in clean left-to-right layout (x increases by 220 per step)
51
+ - Return a complete WorkflowGraph JSON`,
52
+ },
53
+ ], {
54
+ temperature: 0.0,
55
+ retries: 3,
56
+ });
57
+
58
+ return this.validateAndOptimizeGraph(graph);
59
+ }
60
+
61
+ /**
62
+ * Validates all node types against registry, removes unknown nodes,
63
+ * optimises graph structure (orphan removal, position cleanup)
64
+ */
65
+ private validateAndOptimizeGraph(graph: WorkflowGraph): WorkflowGraph {
66
+ const unknownNodes: string[] = [];
67
+
68
+ // Filter out any hallucinated node types
69
+ const validNodes = graph.nodes.filter((node) => {
70
+ if (!isValidNodeType(node.n8nNodeType)) {
71
+ unknownNodes.push(`${node.label} (${node.n8nNodeType})`);
72
+ return false;
73
+ }
74
+ return true;
75
+ });
76
+
77
+ if (unknownNodes.length > 0) {
78
+ console.warn(
79
+ `[GraphEngine] REJECTED ${unknownNodes.length} unknown node type(s): ${unknownNodes.join(', ')}`,
80
+ );
81
+ }
82
+
83
+ const validNodeIds = new Set(validNodes.map((n) => n.id));
84
+
85
+ // Remove edges that reference removed nodes
86
+ const validEdges = graph.edges.filter(
87
+ (e) => validNodeIds.has(e.sourceNodeId) && validNodeIds.has(e.targetNodeId),
88
+ );
89
+
90
+ // Remove orphaned non-trigger nodes
91
+ const optimizedNodes = validNodes.filter((node) => {
92
+ const hasIncoming = validEdges.some((e) => e.targetNodeId === node.id);
93
+ const hasOutgoing = validEdges.some((e) => e.sourceNodeId === node.id);
94
+ const isTrigger = node.layer === 'trigger';
95
+ return isTrigger || hasIncoming || hasOutgoing;
96
+ });
97
+
98
+ return {
99
+ ...graph,
100
+ nodes: optimizedNodes,
101
+ edges: validEdges,
102
+ metadata: {
103
+ ...graph.metadata,
104
+ version: '2.0.0',
105
+ createdAt: new Date().toISOString(),
106
+ unknownNodesRejected: unknownNodes,
107
+ },
108
+ };
109
+ }
110
+ }
apps/simulator/src/agents/intentInterpreter.ts ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Intent Interpreter Agent β€” UPGRADED
3
+ * Deeply understands user's workflow request using provider-agnostic LLM Gateway
4
+ * NO direct OpenAI dependency
5
+ */
6
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
7
+ import type { WorkflowIntent } from '../types/workflow';
8
+ import { INTENT_INTERPRETER_PROMPT } from '../prompts/intentInterpreter';
9
+
10
+ export class IntentInterpreterAgent {
11
+ private llm: LLMGateway;
12
+
13
+ constructor(llm: LLMGateway) {
14
+ this.llm = llm;
15
+ }
16
+
17
+ async interpret(userRequest: string): Promise<WorkflowIntent> {
18
+ const raw = await this.llm.completeJSON<Partial<WorkflowIntent>>([
19
+ { role: 'system', content: INTENT_INTERPRETER_PROMPT },
20
+ {
21
+ role: 'user',
22
+ content: `Analyse this workflow request and return structured JSON:\n\n${userRequest}`,
23
+ },
24
+ ], {
25
+ temperature: 0.1,
26
+ retries: 3,
27
+ });
28
+
29
+ return this.validateAndNormalize(raw);
30
+ }
31
+
32
+ private validateAndNormalize(raw: Partial<WorkflowIntent>): WorkflowIntent {
33
+ return {
34
+ workflowType: raw.workflowType ?? 'general',
35
+ requiresAI: raw.requiresAI ?? false,
36
+ integrations: Array.isArray(raw.integrations) ? raw.integrations : [],
37
+ riskLevel: raw.riskLevel ?? 'medium',
38
+ requiresHumanApproval: raw.requiresHumanApproval ?? true,
39
+ syncVsAsync: raw.syncVsAsync ?? 'sync',
40
+ scalingRequirements: raw.scalingRequirements ?? 'low',
41
+ identifiedRisks: Array.isArray(raw.identifiedRisks) ? raw.identifiedRisks : [],
42
+ estimatedComplexity: raw.estimatedComplexity ?? 'moderate',
43
+ domain: raw.domain ?? 'general',
44
+ triggerType: raw.triggerType ?? 'webhook',
45
+ triggerHint: raw.triggerHint,
46
+ };
47
+ }
48
+ }
apps/simulator/src/agents/simulator.ts ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Dry Run Simulation Engine β€” UPGRADED
3
+ * Simulates workflow execution with mock data BEFORE deployment
4
+ * Provider-agnostic LLM Gateway β€” NO direct OpenAI dependency
5
+ * Detects: runtime issues, null references, failure points, chaos scenarios
6
+ */
7
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
8
+ import type {
9
+ N8nWorkflow,
10
+ WorkflowGraph,
11
+ WorkflowArchitecturePlan,
12
+ SimulationReport,
13
+ } from '../types/workflow';
14
+ import { SIMULATOR_PROMPT } from '../prompts/simulator';
15
+ import { MockDataGenerator } from '../services/mockDataGenerator';
16
+
17
+ export class DryRunSimulator {
18
+ private llm: LLMGateway;
19
+ private mockDataGen: MockDataGenerator;
20
+
21
+ constructor(llm: LLMGateway) {
22
+ this.llm = llm;
23
+ this.mockDataGen = new MockDataGenerator(llm);
24
+ }
25
+
26
+ async simulate(
27
+ jobId: string,
28
+ workflow: N8nWorkflow,
29
+ graph: WorkflowGraph,
30
+ plan: WorkflowArchitecturePlan,
31
+ ): Promise<SimulationReport> {
32
+ // Generate realistic mock data for trigger node
33
+ const mockData = await this.mockDataGen.generate(workflow, graph);
34
+
35
+ const result = await this.llm.completeJSON<Partial<SimulationReport>>([
36
+ { role: 'system', content: SIMULATOR_PROMPT },
37
+ {
38
+ role: 'user',
39
+ content: `Simulate the execution of this n8n workflow using the provided mock data.
40
+
41
+ WORKFLOW:
42
+ ${JSON.stringify(workflow, null, 2)}
43
+
44
+ GRAPH (IR):
45
+ ${JSON.stringify(graph, null, 2)}
46
+
47
+ ARCHITECTURE PLAN:
48
+ ${JSON.stringify(plan, null, 2)}
49
+
50
+ MOCK INPUT DATA:
51
+ ${JSON.stringify(mockData, null, 2)}
52
+
53
+ Produce a SimulationReport JSON with:
54
+ - Full executionTrace (one step per node, with realistic input/output data)
55
+ - Identified predictedFailurePoints (null references, missing fields, credential errors, API limits)
56
+ - ChaosTestResults: simulate timeout, null_data, malformed_input, expired_credentials, rate_limit, network_error
57
+ - readinessScore (0-100): based on trace success rate, failure severity, chaos resilience
58
+ - riskAnalysis: overallRisk, specific risks, mitigations
59
+ - passed: true only if readinessScore >= 70 AND no critical failure points
60
+ - deploymentBlockers: list of issues that MUST be fixed before deployment`,
61
+ },
62
+ ], {
63
+ temperature: 0.1,
64
+ retries: 3,
65
+ });
66
+
67
+ return this.normalizeReport(jobId, result, mockData);
68
+ }
69
+
70
+ private normalizeReport(
71
+ jobId: string,
72
+ raw: Partial<SimulationReport>,
73
+ mockData: Record<string, unknown>,
74
+ ): SimulationReport {
75
+ const readinessScore = Math.min(100, Math.max(0, raw.readinessScore ?? 0));
76
+ return {
77
+ jobId,
78
+ passed: raw.passed ?? readinessScore >= 70,
79
+ readinessScore,
80
+ executionTrace: Array.isArray(raw.executionTrace) ? raw.executionTrace : [],
81
+ riskAnalysis: raw.riskAnalysis ?? { overallRisk: 'medium', risks: [], mitigations: [] },
82
+ predictedFailurePoints: Array.isArray(raw.predictedFailurePoints) ? raw.predictedFailurePoints : [],
83
+ chaosTestResults: Array.isArray(raw.chaosTestResults) ? raw.chaosTestResults : [],
84
+ deploymentBlockers: Array.isArray(raw.deploymentBlockers) ? raw.deploymentBlockers : [],
85
+ mockDataUsed: mockData,
86
+ simulatedAt: new Date().toISOString(),
87
+ };
88
+ }
89
+ }
apps/simulator/src/agents/validator.ts ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Validation Engine Agent β€” UPGRADED
3
+ * Multi-stage: Schema β†’ Credential β†’ Graph β†’ Expression β†’ Reliability β†’ NodeRegistry
4
+ * Provider-agnostic LLM Gateway β€” NO direct OpenAI dependency
5
+ * Strict: rejects unknown node types, validates real expressions, checks completeness
6
+ */
7
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
8
+ import type {
9
+ N8nWorkflow,
10
+ WorkflowGraph,
11
+ ValidationReport,
12
+ ValidationSection,
13
+ ValidationIssue,
14
+ } from '../types/workflow';
15
+ import { isValidNodeType, getNodeDef } from '../knowledge/nodeRegistry';
16
+
17
+ export class ValidationEngine {
18
+ private llm: LLMGateway;
19
+ private n8nBaseUrl: string;
20
+ private n8nApiKey: string;
21
+
22
+ constructor(llm: LLMGateway, n8nBaseUrl: string, n8nApiKey: string) {
23
+ this.llm = llm;
24
+ this.n8nBaseUrl = n8nBaseUrl;
25
+ this.n8nApiKey = n8nApiKey;
26
+ }
27
+
28
+ async validate(jobId: string, workflow: N8nWorkflow, graph: WorkflowGraph): Promise<ValidationReport> {
29
+ const [
30
+ schemaValidation,
31
+ nodeRegistryValidation,
32
+ graphValidation,
33
+ expressionValidation,
34
+ dataFlowValidation,
35
+ reliabilityValidation,
36
+ credentialValidation,
37
+ ] = await Promise.all([
38
+ this.validateSchema(workflow),
39
+ this.validateNodeRegistry(workflow),
40
+ this.validateGraph(graph),
41
+ this.validateExpressions(workflow),
42
+ this.validateDataFlow(workflow, graph),
43
+ this.validateReliability(workflow, graph),
44
+ this.validateCredentials(workflow),
45
+ ]);
46
+
47
+ const allIssues = [
48
+ ...schemaValidation.issues,
49
+ ...nodeRegistryValidation.issues,
50
+ ...graphValidation.issues,
51
+ ...expressionValidation.issues,
52
+ ...dataFlowValidation.issues,
53
+ ...reliabilityValidation.issues,
54
+ ...credentialValidation.issues,
55
+ ];
56
+
57
+ const errors = allIssues.filter((i) => i.severity === 'error');
58
+ const warnings = allIssues.filter((i) => i.severity === 'warning');
59
+
60
+ const overallScore = Math.round(
61
+ schemaValidation.score * 0.15 +
62
+ nodeRegistryValidation.score * 0.20 +
63
+ credentialValidation.score * 0.15 +
64
+ graphValidation.score * 0.15 +
65
+ expressionValidation.score * 0.15 +
66
+ dataFlowValidation.score * 0.10 +
67
+ reliabilityValidation.score * 0.10,
68
+ );
69
+
70
+ return {
71
+ jobId,
72
+ valid: errors.length === 0,
73
+ overallScore,
74
+ schemaValidation,
75
+ credentialValidation,
76
+ graphValidation,
77
+ expressionValidation,
78
+ reliabilityValidation,
79
+ nodeRegistryValidation,
80
+ dataFlowValidation,
81
+ issues: errors,
82
+ warnings,
83
+ validatedAt: new Date().toISOString(),
84
+ };
85
+ }
86
+
87
+ // ─── A. Schema Validation ─────────────────────────────────────────────────
88
+ private async validateSchema(workflow: N8nWorkflow): Promise<ValidationSection> {
89
+ const issues: ValidationIssue[] = [];
90
+
91
+ if (!workflow.name || workflow.name.trim() === '') {
92
+ issues.push(this.issue('schema-001', 'error', 'Schema', undefined,
93
+ 'Workflow must have a name', 'Add a descriptive workflow name'));
94
+ }
95
+
96
+ if (!workflow.nodes || workflow.nodes.length === 0) {
97
+ issues.push(this.issue('schema-002', 'error', 'Schema', undefined,
98
+ 'Workflow has no nodes', 'Add at least one trigger node'));
99
+ }
100
+
101
+ if ((workflow.active as any) === true) {
102
+ issues.push(this.issue('schema-active', 'error', 'Schema', undefined,
103
+ 'Workflow active must be false β€” NEVER deploy as active',
104
+ 'Set active: false β€” activation requires human approval'));
105
+ }
106
+
107
+ workflow.nodes?.forEach((node) => {
108
+ if (!node.type) {
109
+ issues.push(this.issue('schema-003', 'error', 'Schema', node.id,
110
+ `Node "${node.name}" is missing type`, 'Provide a valid n8n node type'));
111
+ }
112
+ if (!node.typeVersion || node.typeVersion < 1) {
113
+ issues.push(this.issue('schema-004', 'warning', 'Schema', node.id,
114
+ `Node "${node.name}" missing typeVersion`, 'Add typeVersion: 1 or higher'));
115
+ }
116
+ if (!node.position || !Array.isArray(node.position) || node.position.length !== 2) {
117
+ issues.push(this.issue('schema-005', 'warning', 'Schema', node.id,
118
+ `Node "${node.name}" missing position [x, y]`, 'Add [x, y] position array'));
119
+ }
120
+ if (!node.id || node.id.trim() === '') {
121
+ issues.push(this.issue('schema-006', 'error', 'Schema', undefined,
122
+ `Node "${node.name}" is missing an ID`, 'Add a unique UUID string as node ID'));
123
+ }
124
+ });
125
+
126
+ // Check for duplicate node IDs
127
+ const nodeIds = workflow.nodes?.map((n) => n.id) ?? [];
128
+ const seen = new Set<string>();
129
+ nodeIds.forEach((id) => {
130
+ if (seen.has(id)) {
131
+ issues.push(this.issue('schema-007', 'error', 'Schema', id,
132
+ `Duplicate node ID detected: "${id}"`, 'Ensure all node IDs are unique UUIDs'));
133
+ }
134
+ seen.add(id);
135
+ });
136
+
137
+ const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 5);
138
+ return { passed: errors(issues) === 0, score, issues };
139
+ }
140
+
141
+ // ─── B. Node Registry Validation (CRITICAL) ──────────────────────────────
142
+ private async validateNodeRegistry(workflow: N8nWorkflow): Promise<ValidationSection> {
143
+ const issues: ValidationIssue[] = [];
144
+
145
+ workflow.nodes?.forEach((node) => {
146
+ if (!node.type) return;
147
+
148
+ if (!isValidNodeType(node.type)) {
149
+ issues.push(this.issue(`registry-unknown-${node.id}`, 'error', 'NodeRegistry', node.id,
150
+ `UNKNOWN node type "${node.type}" β€” not in n8n registry. This will fail on import.`,
151
+ 'Replace with a valid node type from the registry'));
152
+ return;
153
+ }
154
+
155
+ const def = getNodeDef(node.type);
156
+ if (!def) return;
157
+
158
+ // Check required parameters are present
159
+ def.requiredParameters.forEach((param) => {
160
+ const paramsStr = JSON.stringify(node.parameters ?? {});
161
+ if (!paramsStr.includes(`"${param}"`)) {
162
+ issues.push(this.issue(`registry-param-${node.id}-${param}`, 'warning', 'NodeRegistry', node.id,
163
+ `Node "${node.name}" may be missing required parameter: "${param}"`,
164
+ `Add "${param}" parameter to node "${node.name}"`));
165
+ }
166
+ });
167
+
168
+ // Check node has parameters (no completely empty nodes)
169
+ if (!node.parameters || Object.keys(node.parameters).length === 0) {
170
+ if (def.requiredParameters.length > 0) {
171
+ issues.push(this.issue(`registry-empty-${node.id}`, 'error', 'NodeRegistry', node.id,
172
+ `Node "${node.name}" has empty parameters β€” this will not execute correctly`,
173
+ `Add required parameters: ${def.requiredParameters.join(', ')}`));
174
+ }
175
+ }
176
+
177
+ // Verify typeVersion matches registry
178
+ if (node.typeVersion && node.typeVersion > def.typeVersion) {
179
+ issues.push(this.issue(`registry-version-${node.id}`, 'warning', 'NodeRegistry', node.id,
180
+ `Node "${node.name}" uses typeVersion ${node.typeVersion} but registry has v${def.typeVersion}`,
181
+ `Use typeVersion: ${def.typeVersion} for "${node.type}"`));
182
+ }
183
+ });
184
+
185
+ const score = Math.max(0, 100 - errors(issues) * 25 - warnings(issues) * 8);
186
+ return { passed: errors(issues) === 0, score, issues };
187
+ }
188
+
189
+ // ─── C. Credential Validation ─────────────────────────────────────────────
190
+ private async validateCredentials(workflow: N8nWorkflow): Promise<ValidationSection> {
191
+ const issues: ValidationIssue[] = [];
192
+
193
+ let availableCredTypes = new Set<string>();
194
+ try {
195
+ const resp = await fetch(`${this.n8nBaseUrl}/api/v1/credentials`, {
196
+ headers: { 'X-N8N-API-KEY': this.n8nApiKey },
197
+ signal: AbortSignal.timeout(5000),
198
+ });
199
+ if (resp.ok) {
200
+ const data = await resp.json() as { data: Array<{ type: string }> };
201
+ availableCredTypes = new Set(data.data.map((c) => c.type));
202
+ }
203
+ } catch {
204
+ issues.push(this.issue('cred-001', 'warning', 'Credentials', undefined,
205
+ 'Could not connect to n8n to verify credentials β€” verify manually',
206
+ 'Ensure n8n is accessible at configured URL'));
207
+ }
208
+
209
+ workflow.nodes?.forEach((node) => {
210
+ // Check registry-defined credential
211
+ const def = getNodeDef(node.type);
212
+ if (def?.credentialType) {
213
+ if (availableCredTypes.size > 0 && !availableCredTypes.has(def.credentialType)) {
214
+ issues.push(this.issue(`cred-missing-${node.id}`, 'error', 'Credentials', node.id,
215
+ `Node "${node.name}" requires credential type "${def.credentialType}" which is not in n8n`,
216
+ `Create a "${def.credentialDisplayName ?? def.credentialType}" credential in n8n`));
217
+ }
218
+ }
219
+
220
+ // Also check node.credentials object
221
+ if (node.credentials) {
222
+ Object.entries(node.credentials).forEach(([credType]) => {
223
+ if (availableCredTypes.size > 0 && !availableCredTypes.has(credType)) {
224
+ issues.push(this.issue(`cred-node-${node.id}-${credType}`, 'error', 'Credentials', node.id,
225
+ `Node "${node.name}" references credential "${credType}" not found in n8n`,
226
+ `Create a "${credType}" credential in n8n`));
227
+ }
228
+ });
229
+ }
230
+ });
231
+
232
+ const score = Math.max(0, 100 - errors(issues) * 25);
233
+ return { passed: errors(issues) === 0, score, issues };
234
+ }
235
+
236
+ // ─── D. Graph Validation ──────────────────────────────────────────────────
237
+ private async validateGraph(graph: WorkflowGraph): Promise<ValidationSection> {
238
+ const issues: ValidationIssue[] = [];
239
+ const nodeIds = new Set(graph.nodes.map((n) => n.id));
240
+
241
+ // Orphan detection (non-trigger nodes with no incoming edges)
242
+ graph.nodes.forEach((node) => {
243
+ if (node.layer !== 'trigger') {
244
+ const hasIncoming = graph.edges.some((e) => e.targetNodeId === node.id);
245
+ if (!hasIncoming) {
246
+ issues.push(this.issue('graph-orphan-' + node.id, 'error', 'Graph', node.id,
247
+ `Node "${node.label}" is orphaned β€” no incoming edges`,
248
+ 'Connect this node to the workflow or remove it'));
249
+ }
250
+ }
251
+ });
252
+
253
+ // Dead-end detection (non-terminal nodes with no outgoing edges)
254
+ graph.nodes.forEach((node) => {
255
+ const isTerminal = ['monitoring', 'utility'].includes(node.layer ?? '');
256
+ const hasOutgoing = graph.edges.some((e) => e.sourceNodeId === node.id);
257
+ if (!isTerminal && !hasOutgoing && node.layer !== 'trigger') {
258
+ // Warning only β€” some nodes legitimately end flows
259
+ issues.push(this.issue('graph-deadend-' + node.id, 'warning', 'Graph', node.id,
260
+ `Node "${node.label}" has no outgoing edges β€” is this intentional?`,
261
+ 'Connect to a next step or add a terminal node'));
262
+ }
263
+ });
264
+
265
+ // Edge validity
266
+ graph.edges.forEach((edge) => {
267
+ if (!nodeIds.has(edge.sourceNodeId)) {
268
+ issues.push(this.issue('graph-edge-src-' + edge.id, 'error', 'Graph', undefined,
269
+ `Edge "${edge.id}" references missing source node "${edge.sourceNodeId}"`,
270
+ 'Fix edge source reference'));
271
+ }
272
+ if (!nodeIds.has(edge.targetNodeId)) {
273
+ issues.push(this.issue('graph-edge-tgt-' + edge.id, 'error', 'Graph', undefined,
274
+ `Edge "${edge.id}" references missing target node "${edge.targetNodeId}"`,
275
+ 'Fix edge target reference'));
276
+ }
277
+ });
278
+
279
+ // Trigger check
280
+ const hasTrigger = graph.nodes.some((n) => n.layer === 'trigger');
281
+ if (!hasTrigger) {
282
+ issues.push(this.issue('graph-no-trigger', 'error', 'Graph', undefined,
283
+ 'Workflow has no trigger node',
284
+ 'Add a trigger layer node: Webhook, Schedule, Telegram Trigger, etc.'));
285
+ }
286
+
287
+ const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 5);
288
+ return { passed: errors(issues) === 0, score, issues };
289
+ }
290
+
291
+ // ─── E. Expression Validation (UPGRADED) ─────────────────────────────────
292
+ private async validateExpressions(workflow: N8nWorkflow): Promise<ValidationSection> {
293
+ const issues: ValidationIssue[] = [];
294
+ const expressionRegex = /\{\{(.+?)\}\}/g;
295
+
296
+ workflow.nodes?.forEach((node) => {
297
+ const paramsStr = JSON.stringify(node.parameters ?? {});
298
+ const matches = [...paramsStr.matchAll(expressionRegex)];
299
+
300
+ // Check for completely static nodes that should have expressions
301
+ const def = getNodeDef(node.type);
302
+ if (def && matches.length === 0 && def.requiredParameters.length > 0 && node.type !== 'n8n-nodes-base.manualTrigger') {
303
+ const isTrigger = def.isTrigger;
304
+ if (!isTrigger && !['n8n-nodes-base.noOp', 'n8n-nodes-base.stickyNote'].includes(node.type)) {
305
+ issues.push(this.issue(`expr-static-${node.id}`, 'warning', 'Expressions', node.id,
306
+ `Node "${node.name}" has no dynamic expressions β€” likely using static/hardcoded values`,
307
+ 'Add dynamic expressions like {{$json?.field ?? ""}} to reference upstream data'));
308
+ }
309
+ }
310
+
311
+ matches.forEach((match) => {
312
+ const expr = match[1]?.trim() ?? '';
313
+
314
+ // Detect unsafe dot-access without optional chaining
315
+ if (
316
+ expr.includes('$json.') ||
317
+ expr.includes('$node[') && !expr.includes('?.') && !expr.includes(' ?? ')
318
+ ) {
319
+ issues.push(this.issue(
320
+ `expr-unsafe-${node.id}-${Buffer.from(expr).toString('base64').slice(0, 8)}`,
321
+ 'warning', 'Expressions', node.id,
322
+ `Expression "{{${expr}}}" may throw if value is null β€” use optional chaining`,
323
+ `Change to: {{${expr.replace(/\./g, '?.')} ?? ''}}`));
324
+ }
325
+
326
+ // Detect empty expressions
327
+ if (expr === '' || expr === '""' || expr === "''") {
328
+ issues.push(this.issue(`expr-empty-${node.id}`, 'error', 'Expressions', node.id,
329
+ `Node "${node.name}" has empty expression {{${expr}}}`,
330
+ 'Replace with a real expression referencing upstream data'));
331
+ }
332
+
333
+ // Detect placeholder text patterns
334
+ const placeholderPatterns = ['YOUR_', 'REPLACE_ME', 'TODO', 'PLACEHOLDER', 'example.com', 'XXXX'];
335
+ placeholderPatterns.forEach((p) => {
336
+ if (expr.toUpperCase().includes(p)) {
337
+ issues.push(this.issue(`expr-placeholder-${node.id}`, 'error', 'Expressions', node.id,
338
+ `Node "${node.name}" contains placeholder text in expression: "${expr}"`,
339
+ 'Replace placeholder with actual value or expression'));
340
+ }
341
+ });
342
+ });
343
+
344
+ // Detect placeholder strings in parameters (outside expressions)
345
+ const plainParams = JSON.stringify(node.parameters ?? {});
346
+ const placeholderStrings = ['YOUR_', 'REPLACE_ME', 'example.com', 'your-', 'my-webhook-path'];
347
+ placeholderStrings.forEach((p) => {
348
+ if (plainParams.includes(p)) {
349
+ issues.push(this.issue(`param-placeholder-${node.id}-${p}`, 'warning', 'Expressions', node.id,
350
+ `Node "${node.name}" may contain placeholder value: "${p}"`,
351
+ 'Replace placeholder with real configuration value'));
352
+ }
353
+ });
354
+ });
355
+
356
+ const score = Math.max(0, 100 - errors(issues) * 25 - warnings(issues) * 5);
357
+ return { passed: errors(issues) === 0, score, issues };
358
+ }
359
+
360
+ // ─── F. Data Flow Validation (NEW) ───────────────────────────────────────
361
+ private async validateDataFlow(workflow: N8nWorkflow, graph: WorkflowGraph): Promise<ValidationSection> {
362
+ const issues: ValidationIssue[] = [];
363
+
364
+ // Check that connected nodes actually reference upstream data
365
+ graph.edges.forEach((edge) => {
366
+ const targetNode = workflow.nodes.find((n) =>
367
+ n.name === graph.nodes.find((gn) => gn.id === edge.targetNodeId)?.label,
368
+ );
369
+
370
+ if (targetNode?.parameters) {
371
+ const paramsStr = JSON.stringify(targetNode.parameters);
372
+ const hasExpression = paramsStr.includes('{{') || paramsStr.includes('$json') || paramsStr.includes('$node');
373
+
374
+ // If node has inputs but no expressions at all, it's likely disconnected data-flow
375
+ const def = getNodeDef(targetNode.type);
376
+ if (
377
+ def &&
378
+ !def.isTrigger &&
379
+ !hasExpression &&
380
+ !['n8n-nodes-base.noOp', 'n8n-nodes-base.stickyNote', 'n8n-nodes-base.wait'].includes(targetNode.type)
381
+ ) {
382
+ issues.push(this.issue(`dataflow-${edge.id}`, 'warning', 'DataFlow', targetNode.id,
383
+ `Node "${targetNode.name}" receives data but has no expressions referencing upstream fields`,
384
+ 'Add expressions like {{$json?.fieldName ?? ""}} to consume upstream data'));
385
+ }
386
+ }
387
+ });
388
+
389
+ // SET node β€” verify values array is not empty
390
+ workflow.nodes?.filter((n) => n.type === 'n8n-nodes-base.set').forEach((node) => {
391
+ const values = (node.parameters as Record<string, unknown>)?.fields;
392
+ if (!values || JSON.stringify(values) === '{"values":[]}' || JSON.stringify(values) === '[]') {
393
+ issues.push(this.issue(`dataflow-set-empty-${node.id}`, 'error', 'DataFlow', node.id,
394
+ `SET node "${node.name}" has empty fields β€” it will output nothing`,
395
+ 'Add field mappings: name β†’ expression pairs'));
396
+ }
397
+ });
398
+
399
+ // CODE node β€” verify jsCode is not a placeholder
400
+ workflow.nodes?.filter((n) => n.type === 'n8n-nodes-base.code').forEach((node) => {
401
+ const jsCode = (node.parameters as Record<string, unknown>)?.jsCode as string | undefined;
402
+ if (!jsCode || jsCode.trim() === '' || jsCode.includes('// TODO') || jsCode === 'return [];') {
403
+ issues.push(this.issue(`dataflow-code-${node.id}`, 'error', 'DataFlow', node.id,
404
+ `Code node "${node.name}" has empty or placeholder code`,
405
+ 'Add real JavaScript logic that processes $input.all() items'));
406
+ }
407
+ });
408
+
409
+ // IF node β€” verify conditions reference real fields
410
+ workflow.nodes?.filter((n) => n.type === 'n8n-nodes-base.if').forEach((node) => {
411
+ const conditions = (node.parameters as Record<string, unknown>)?.conditions as Record<string, unknown> | undefined;
412
+ const condList = (conditions?.conditions as unknown[]) ?? [];
413
+ if (condList.length === 0) {
414
+ issues.push(this.issue(`dataflow-if-${node.id}`, 'error', 'DataFlow', node.id,
415
+ `IF node "${node.name}" has no conditions defined`,
416
+ 'Add at least one condition referencing a real upstream JSON field'));
417
+ }
418
+ });
419
+
420
+ const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 8);
421
+ return { passed: errors(issues) === 0, score, issues };
422
+ }
423
+
424
+ // ─── G. Reliability Validation ───────────────────────────────────────────
425
+ private async validateReliability(workflow: N8nWorkflow, graph: WorkflowGraph): Promise<ValidationSection> {
426
+ const issues: ValidationIssue[] = [];
427
+
428
+ // Retry policy on critical/external nodes
429
+ const criticalNodes = graph.nodes.filter((n) => n.isCritical);
430
+ criticalNodes.forEach((node) => {
431
+ const n8nNode = workflow.nodes.find((n) => n.name === node.label);
432
+ if (n8nNode && !n8nNode.retryOnFail) {
433
+ issues.push(this.issue(`rel-retry-${node.id}`, 'warning', 'Reliability', node.id,
434
+ `Critical node "${node.label}" has no retry policy`,
435
+ 'Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000'));
436
+ }
437
+ });
438
+
439
+ // Check external API nodes have retry
440
+ workflow.nodes?.forEach((node) => {
441
+ const def = getNodeDef(node.type);
442
+ if (def?.supportsRetry && !node.retryOnFail) {
443
+ issues.push(this.issue(`rel-external-retry-${node.id}`, 'warning', 'Reliability', node.id,
444
+ `External node "${node.name}" (${node.type}) has no retry β€” may fail on transient errors`,
445
+ 'Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000'));
446
+ }
447
+ });
448
+
449
+ // Error workflow
450
+ if (!workflow.settings?.errorWorkflow) {
451
+ issues.push(this.issue('rel-error-workflow', 'warning', 'Reliability', undefined,
452
+ 'No error workflow configured',
453
+ 'Set settings.errorWorkflow to an alerting/notification workflow ID'));
454
+ }
455
+
456
+ // Monitoring nodes
457
+ const hasMonitoring = graph.nodes.some((n) => n.layer === 'monitoring');
458
+ if (!hasMonitoring) {
459
+ issues.push(this.issue('rel-monitoring', 'warning', 'Reliability', undefined,
460
+ 'No monitoring/observability layer nodes detected',
461
+ 'Add logging or notification nodes for observability'));
462
+ }
463
+
464
+ const score = Math.max(0, 100 - errors(issues) * 20 - warnings(issues) * 8);
465
+ return { passed: errors(issues) === 0, score, issues };
466
+ }
467
+
468
+ private issue(
469
+ id: string,
470
+ severity: ValidationIssue['severity'],
471
+ category: string,
472
+ nodeId: string | undefined,
473
+ message: string,
474
+ suggestion: string,
475
+ ): ValidationIssue {
476
+ return { id, severity, category, nodeId, message, suggestion };
477
+ }
478
+ }
479
+
480
+ // helpers
481
+ function errors(issues: ValidationIssue[]): number {
482
+ return issues.filter((i) => i.severity === 'error').length;
483
+ }
484
+ function warnings(issues: ValidationIssue[]): number {
485
+ return issues.filter((i) => i.severity === 'warning').length;
486
+ }
apps/simulator/src/agents/workflowPlanner.ts ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Workflow Planner Agent β€” UPGRADED
3
+ * Designs workflows like a senior engineer using layered architecture
4
+ * Provider-agnostic LLM Gateway (no direct OpenAI dependency)
5
+ */
6
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
7
+ import type { WorkflowIntent, WorkflowArchitecturePlan } from '../types/workflow';
8
+ import { WORKFLOW_PLANNER_PROMPT } from '../prompts/workflowPlanner';
9
+
10
+ export class WorkflowPlannerAgent {
11
+ private llm: LLMGateway;
12
+
13
+ constructor(llm: LLMGateway) {
14
+ this.llm = llm;
15
+ }
16
+
17
+ async plan(userRequest: string, intent: WorkflowIntent): Promise<WorkflowArchitecturePlan> {
18
+ return this.llm.completeJSON<WorkflowArchitecturePlan>([
19
+ { role: 'system', content: WORKFLOW_PLANNER_PROMPT },
20
+ {
21
+ role: 'user',
22
+ content: `Design a production-grade workflow architecture for this request:
23
+
24
+ REQUEST: ${userRequest}
25
+
26
+ INTENT ANALYSIS:
27
+ ${JSON.stringify(intent, null, 2)}
28
+
29
+ Return a complete WorkflowArchitecturePlan JSON following the specification.`,
30
+ },
31
+ ], {
32
+ temperature: 0.1,
33
+ retries: 3,
34
+ });
35
+ }
36
+ }
apps/simulator/src/index.ts ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Simulator Service - Main Entry Point β€” UPGRADED
3
+ * External Node.js compute service (Express.js)
4
+ * Handles all CPU-intensive AI orchestration logic offloaded from Cloudflare Worker
5
+ * Provider-agnostic LLM Gateway β€” NO direct OpenAI SDK dependency
6
+ */
7
+ import * as moduleAlias from 'module-alias';
8
+ import path from 'path';
9
+
10
+ // Register module aliases for production
11
+ moduleAlias.addAliases({
12
+ '@wfo/core': path.join(__dirname, '../../../core'),
13
+ '@wfo/integrations': path.join(__dirname, '../../../integrations'),
14
+ '@wfo/validation': path.join(__dirname, '../../../validation'),
15
+ '@wfo/observability': path.join(__dirname, '../../../observability'),
16
+ '@wfo/knowledge': path.join(__dirname, '../../../knowledge'),
17
+ '@wfo/config': path.join(__dirname, '../../../config'),
18
+ '@wfo/apps/worker': path.join(__dirname, '../../../apps/worker'),
19
+ });
20
+
21
+ import express from 'express';
22
+ import helmet from 'helmet';
23
+ import cors from 'cors';
24
+ import { pino } from 'pino';
25
+ import 'dotenv/config';
26
+
27
+ import { internalAuthMiddleware } from './middleware/internalAuth';
28
+ import { generateRoute } from './routes/generate';
29
+ import { validateRoute } from './routes/validate';
30
+ import { simulateRoute } from './routes/simulate';
31
+
32
+ export const logger = pino({
33
+ transport: {
34
+ target: 'pino-pretty',
35
+ options: { colorize: true },
36
+ },
37
+ level: process.env['LOG_LEVEL'] ?? 'info',
38
+ });
39
+
40
+ const app: express.Application = express();
41
+
42
+ // ─── Security Middleware ──────────────────────────────────────────────────────
43
+ app.use(helmet());
44
+ app.use(cors({ origin: process.env['WORKER_ORIGIN'] ?? '*' }));
45
+ app.use(express.json({ limit: '4mb' }));
46
+
47
+ // ─── Internal Auth (shared secret with CF Worker) ────────────────────────────
48
+ app.use('/process', internalAuthMiddleware);
49
+
50
+ // ─── Routes ───────────────────────────────────────────────────────────────────
51
+ app.use('/process/generate', generateRoute);
52
+ app.use('/process/validate', validateRoute);
53
+ app.use('/process/simulate', simulateRoute);
54
+
55
+ // ─── Health (public) ──────────────────────────────────────────────────────────
56
+ app.get('/health', (_req, res) => {
57
+ res.json({
58
+ status: 'ok',
59
+ service: 'wfo-simulator',
60
+ version: '2.0.0',
61
+ uptime: process.uptime(),
62
+ capabilities: {
63
+ llmGateway: 'provider-agnostic (openai | anthropic | openai-compatible | custom)',
64
+ swarmOrchestration: 'parallel multi-agent workflow design (up to 5 candidates)',
65
+ selfHealing: 'automatic validation fix + AI-assisted healing',
66
+ webhookAutoBind: 'auto trigger detection and configuration',
67
+ memorySystem: 'in-process pattern learning and anti-pattern prevention',
68
+ nodeRegistry: `strict β€” ${Object.keys(require('./knowledge/nodeRegistry').N8N_NODE_REGISTRY).length} verified node types`,
69
+ },
70
+ config: {
71
+ llmProvider: process.env['LLM_PROVIDER'] ?? 'openai',
72
+ llmModel: process.env['LLM_MODEL'] ?? 'gpt-4o',
73
+ llmGatewayUrl: process.env['LLM_GATEWAY_URL'] ?? 'https://api.openai.com/v1',
74
+ hasFallback: !!(process.env['LLM_FALLBACK_URL'] && process.env['LLM_FALLBACK_KEY']),
75
+ },
76
+ });
77
+ });
78
+
79
+ // ─── Global Error Handler ─────────────────────────────────────────────────────
80
+ app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
81
+ logger.error({ err }, 'Unhandled error');
82
+ res.status(500).json({ success: false, error: err.message });
83
+ });
84
+
85
+ const PORT = Number(process.env["PORT"] ?? 7860);
86
+ app.listen(PORT, () => {
87
+ logger.info(`[Simulator v2.0] Running on port ${PORT}`);
88
+ logger.info(`[Simulator] LLM provider: ${process.env['LLM_PROVIDER'] ?? 'openai'}`);
89
+ logger.info(`[Simulator] LLM model: ${process.env['LLM_MODEL'] ?? 'gpt-4o'}`);
90
+ logger.info(`[Simulator] Swarm: enabled (up to 5 parallel designs)`);
91
+ logger.info(`[Simulator] Self-Healing: enabled`);
92
+ logger.info(`[Simulator] Memory: enabled`);
93
+ logger.info(`[Simulator] Webhook Auto-Bind: enabled`);
94
+ });
95
+
96
+ export default app;
apps/simulator/src/knowledge/nodeRegistry.ts ADDED
@@ -0,0 +1,1078 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * n8n Node Registry β€” PRODUCTION GRADE
3
+ * Real node definitions: type, version, parameters schema, credentials, I/O structure
4
+ * ZERO hallucinated nodes. ZERO placeholder types. ZERO unknown nodes.
5
+ * Source: n8n official node definitions (github.com/n8n-io/n8n)
6
+ */
7
+
8
+ export interface NodeRegistryEntry {
9
+ type: string;
10
+ displayName: string;
11
+ typeVersion: number;
12
+ group: string[];
13
+ description: string;
14
+ isTrigger: boolean;
15
+ credentialType?: string;
16
+ credentialDisplayName?: string;
17
+ inputs: string[];
18
+ outputs: string[];
19
+ outputNames?: string[];
20
+ requiredParameters: string[];
21
+ optionalParameters: string[];
22
+ defaultParameters: Record<string, unknown>;
23
+ executionBehavior: 'once' | 'perItem' | 'trigger' | 'webhook';
24
+ supportsRetry: boolean;
25
+ layer: 'trigger' | 'transform' | 'ai' | 'integration' | 'control' | 'database' | 'utility';
26
+ tags: string[];
27
+ }
28
+
29
+ export const N8N_NODE_REGISTRY: Record<string, NodeRegistryEntry> = {
30
+
31
+ // ─── TRIGGER NODES ──────────────────────────────────────────────────────────
32
+
33
+ 'n8n-nodes-base.webhook': {
34
+ type: 'n8n-nodes-base.webhook',
35
+ displayName: 'Webhook',
36
+ typeVersion: 2,
37
+ group: ['trigger'],
38
+ description: 'Starts the workflow when a webhook is received',
39
+ isTrigger: true,
40
+ inputs: [],
41
+ outputs: ['main'],
42
+ requiredParameters: ['httpMethod', 'path'],
43
+ optionalParameters: ['responseMode', 'responseData', 'responseCode', 'responseHeaders', 'rawBody', 'options'],
44
+ defaultParameters: {
45
+ httpMethod: 'POST',
46
+ path: '={{$runIndex === 0 ? "auto-webhook-" + $workflow.id : ""}}',
47
+ responseMode: 'onReceived',
48
+ responseData: 'allEntries',
49
+ },
50
+ executionBehavior: 'webhook',
51
+ supportsRetry: false,
52
+ layer: 'trigger',
53
+ tags: ['trigger', 'http', 'webhook'],
54
+ },
55
+
56
+ 'n8n-nodes-base.scheduleTrigger': {
57
+ type: 'n8n-nodes-base.scheduleTrigger',
58
+ displayName: 'Schedule Trigger',
59
+ typeVersion: 1,
60
+ group: ['trigger'],
61
+ description: 'Triggers workflow on a schedule (cron or interval)',
62
+ isTrigger: true,
63
+ inputs: [],
64
+ outputs: ['main'],
65
+ requiredParameters: ['rule'],
66
+ optionalParameters: [],
67
+ defaultParameters: {
68
+ rule: {
69
+ interval: [{ field: 'hours', hoursInterval: 1 }],
70
+ },
71
+ },
72
+ executionBehavior: 'trigger',
73
+ supportsRetry: false,
74
+ layer: 'trigger',
75
+ tags: ['trigger', 'cron', 'schedule'],
76
+ },
77
+
78
+ 'n8n-nodes-base.manualTrigger': {
79
+ type: 'n8n-nodes-base.manualTrigger',
80
+ displayName: 'Manual Trigger',
81
+ typeVersion: 1,
82
+ group: ['trigger'],
83
+ description: 'Starts workflow execution manually via the UI',
84
+ isTrigger: true,
85
+ inputs: [],
86
+ outputs: ['main'],
87
+ requiredParameters: [],
88
+ optionalParameters: [],
89
+ defaultParameters: {},
90
+ executionBehavior: 'trigger',
91
+ supportsRetry: false,
92
+ layer: 'trigger',
93
+ tags: ['trigger', 'manual'],
94
+ },
95
+
96
+ 'n8n-nodes-base.telegramTrigger': {
97
+ type: 'n8n-nodes-base.telegramTrigger',
98
+ displayName: 'Telegram Trigger',
99
+ typeVersion: 1,
100
+ group: ['trigger'],
101
+ description: 'Starts workflow when a Telegram message or event is received',
102
+ isTrigger: true,
103
+ credentialType: 'telegramApi',
104
+ credentialDisplayName: 'Telegram API',
105
+ inputs: [],
106
+ outputs: ['main'],
107
+ requiredParameters: ['updates'],
108
+ optionalParameters: ['additionalFields'],
109
+ defaultParameters: {
110
+ updates: ['message'],
111
+ },
112
+ executionBehavior: 'webhook',
113
+ supportsRetry: false,
114
+ layer: 'trigger',
115
+ tags: ['trigger', 'telegram', 'messaging'],
116
+ },
117
+
118
+ 'n8n-nodes-base.emailReadImap': {
119
+ type: 'n8n-nodes-base.emailReadImap',
120
+ displayName: 'Email Trigger (IMAP)',
121
+ typeVersion: 2,
122
+ group: ['trigger'],
123
+ description: 'Triggers workflow when a new email is received via IMAP',
124
+ isTrigger: true,
125
+ credentialType: 'imap',
126
+ credentialDisplayName: 'IMAP',
127
+ inputs: [],
128
+ outputs: ['main'],
129
+ requiredParameters: ['mailbox', 'action'],
130
+ optionalParameters: ['downloadAttachments', 'format', 'options'],
131
+ defaultParameters: {
132
+ mailbox: 'INBOX',
133
+ action: 'read',
134
+ },
135
+ executionBehavior: 'trigger',
136
+ supportsRetry: false,
137
+ layer: 'trigger',
138
+ tags: ['trigger', 'email', 'imap'],
139
+ },
140
+
141
+ 'n8n-nodes-base.githubTrigger': {
142
+ type: 'n8n-nodes-base.githubTrigger',
143
+ displayName: 'GitHub Trigger',
144
+ typeVersion: 1,
145
+ group: ['trigger'],
146
+ description: 'Triggers on GitHub events (push, PR, issue, etc.)',
147
+ isTrigger: true,
148
+ credentialType: 'githubApi',
149
+ credentialDisplayName: 'GitHub API',
150
+ inputs: [],
151
+ outputs: ['main'],
152
+ requiredParameters: ['owner', 'repository', 'events'],
153
+ optionalParameters: [],
154
+ defaultParameters: {
155
+ events: ['push'],
156
+ },
157
+ executionBehavior: 'webhook',
158
+ supportsRetry: false,
159
+ layer: 'trigger',
160
+ tags: ['trigger', 'github', 'devops'],
161
+ },
162
+
163
+ 'n8n-nodes-base.slackTrigger': {
164
+ type: 'n8n-nodes-base.slackTrigger',
165
+ displayName: 'Slack Trigger',
166
+ typeVersion: 1,
167
+ group: ['trigger'],
168
+ description: 'Triggers on Slack events (messages, reactions, etc.)',
169
+ isTrigger: true,
170
+ credentialType: 'slackOAuth2Api',
171
+ credentialDisplayName: 'Slack OAuth2 API',
172
+ inputs: [],
173
+ outputs: ['main'],
174
+ requiredParameters: ['trigger'],
175
+ optionalParameters: ['channelId', 'watchedEvent'],
176
+ defaultParameters: {
177
+ trigger: 'any_message',
178
+ },
179
+ executionBehavior: 'webhook',
180
+ supportsRetry: false,
181
+ layer: 'trigger',
182
+ tags: ['trigger', 'slack', 'messaging'],
183
+ },
184
+
185
+ 'n8n-nodes-base.notionTrigger': {
186
+ type: 'n8n-nodes-base.notionTrigger',
187
+ displayName: 'Notion Trigger',
188
+ typeVersion: 1,
189
+ group: ['trigger'],
190
+ description: 'Triggers when a Notion database page is created or updated',
191
+ isTrigger: true,
192
+ credentialType: 'notionApi',
193
+ credentialDisplayName: 'Notion API',
194
+ inputs: [],
195
+ outputs: ['main'],
196
+ requiredParameters: ['databaseId', 'event'],
197
+ optionalParameters: ['simple'],
198
+ defaultParameters: {
199
+ event: 'page_added',
200
+ },
201
+ executionBehavior: 'trigger',
202
+ supportsRetry: false,
203
+ layer: 'trigger',
204
+ tags: ['trigger', 'notion', 'productivity'],
205
+ },
206
+
207
+ // ─── AI / LLM NODES ─────────────────────────────────────────────────────────
208
+
209
+ '@n8n/n8n-nodes-langchain.agent': {
210
+ type: '@n8n/n8n-nodes-langchain.agent',
211
+ displayName: 'AI Agent',
212
+ typeVersion: 1,
213
+ group: ['transform'],
214
+ description: 'AI Agent powered by LangChain with tools and memory',
215
+ isTrigger: false,
216
+ inputs: ['main', 'ai_tool', 'ai_memory', 'ai_languageModel'],
217
+ outputs: ['main'],
218
+ requiredParameters: ['text', 'options'],
219
+ optionalParameters: ['systemMessage', 'promptType'],
220
+ defaultParameters: {
221
+ text: '={{ $json?.message ?? $json?.text ?? $json?.body?.message ?? "" }}',
222
+ options: {
223
+ systemMessage: 'You are a helpful AI assistant. Be concise and accurate.',
224
+ },
225
+ },
226
+ executionBehavior: 'perItem',
227
+ supportsRetry: true,
228
+ layer: 'ai',
229
+ tags: ['ai', 'agent', 'langchain', 'llm'],
230
+ },
231
+
232
+ '@n8n/n8n-nodes-langchain.openAi': {
233
+ type: '@n8n/n8n-nodes-langchain.openAi',
234
+ displayName: 'OpenAI Chat Model',
235
+ typeVersion: 1,
236
+ group: ['transform'],
237
+ description: 'OpenAI Chat Model for use with AI Agent or Chain nodes',
238
+ isTrigger: false,
239
+ credentialType: 'openAiApi',
240
+ credentialDisplayName: 'OpenAI API',
241
+ inputs: ['ai_languageModel'],
242
+ outputs: ['ai_languageModel'],
243
+ requiredParameters: ['model'],
244
+ optionalParameters: ['options'],
245
+ defaultParameters: {
246
+ model: { __rl: true, value: 'gpt-4o', mode: 'list', cachedResultName: 'GPT-4o' },
247
+ options: {
248
+ maxTokens: 2048,
249
+ temperature: 0.7,
250
+ },
251
+ },
252
+ executionBehavior: 'once',
253
+ supportsRetry: true,
254
+ layer: 'ai',
255
+ tags: ['ai', 'openai', 'llm', 'langchain'],
256
+ },
257
+
258
+ '@n8n/n8n-nodes-langchain.lmChatAnthropic': {
259
+ type: '@n8n/n8n-nodes-langchain.lmChatAnthropic',
260
+ displayName: 'Anthropic Chat Model',
261
+ typeVersion: 1,
262
+ group: ['transform'],
263
+ description: 'Anthropic Claude model for use with AI Agent or Chain nodes',
264
+ isTrigger: false,
265
+ credentialType: 'anthropicApi',
266
+ credentialDisplayName: 'Anthropic API',
267
+ inputs: ['ai_languageModel'],
268
+ outputs: ['ai_languageModel'],
269
+ requiredParameters: ['model'],
270
+ optionalParameters: ['options'],
271
+ defaultParameters: {
272
+ model: 'claude-3-5-sonnet-20241022',
273
+ options: {
274
+ maxTokens: 2048,
275
+ temperature: 0.7,
276
+ },
277
+ },
278
+ executionBehavior: 'once',
279
+ supportsRetry: true,
280
+ layer: 'ai',
281
+ tags: ['ai', 'anthropic', 'claude', 'llm', 'langchain'],
282
+ },
283
+
284
+ '@n8n/n8n-nodes-langchain.memoryBufferWindow': {
285
+ type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
286
+ displayName: 'Window Buffer Memory',
287
+ typeVersion: 1,
288
+ group: ['transform'],
289
+ description: 'Keeps last N messages in memory for AI Agent context',
290
+ isTrigger: false,
291
+ inputs: ['ai_memory'],
292
+ outputs: ['ai_memory'],
293
+ requiredParameters: [],
294
+ optionalParameters: ['sessionKey', 'sessionIdType', 'contextWindowLength'],
295
+ defaultParameters: {
296
+ contextWindowLength: 10,
297
+ },
298
+ executionBehavior: 'once',
299
+ supportsRetry: false,
300
+ layer: 'ai',
301
+ tags: ['ai', 'memory', 'langchain'],
302
+ },
303
+
304
+ '@n8n/n8n-nodes-langchain.toolCode': {
305
+ type: '@n8n/n8n-nodes-langchain.toolCode',
306
+ displayName: 'Code Tool',
307
+ typeVersion: 1,
308
+ group: ['transform'],
309
+ description: 'Custom JavaScript tool for AI Agent',
310
+ isTrigger: false,
311
+ inputs: ['ai_tool'],
312
+ outputs: ['ai_tool'],
313
+ requiredParameters: ['name', 'description', 'jsCode'],
314
+ optionalParameters: [],
315
+ defaultParameters: {
316
+ name: 'custom_tool',
317
+ description: 'Describe what this tool does so the AI agent knows when to use it',
318
+ jsCode: '// Tool logic here\nreturn { result: "success" };',
319
+ },
320
+ executionBehavior: 'once',
321
+ supportsRetry: false,
322
+ layer: 'ai',
323
+ tags: ['ai', 'tool', 'langchain', 'code'],
324
+ },
325
+
326
+ 'n8n-nodes-base.openAi': {
327
+ type: 'n8n-nodes-base.openAi',
328
+ displayName: 'OpenAI',
329
+ typeVersion: 1,
330
+ group: ['transform'],
331
+ description: 'Use the OpenAI API (Chat, Image, Audio, etc.)',
332
+ isTrigger: false,
333
+ credentialType: 'openAiApi',
334
+ credentialDisplayName: 'OpenAI API',
335
+ inputs: ['main'],
336
+ outputs: ['main'],
337
+ requiredParameters: ['resource', 'operation'],
338
+ optionalParameters: ['model', 'messages', 'options'],
339
+ defaultParameters: {
340
+ resource: 'chat',
341
+ operation: 'complete',
342
+ model: { __rl: true, value: 'gpt-4o', mode: 'list', cachedResultName: 'GPT-4o' },
343
+ },
344
+ executionBehavior: 'perItem',
345
+ supportsRetry: true,
346
+ layer: 'ai',
347
+ tags: ['ai', 'openai', 'llm'],
348
+ },
349
+
350
+ // ─── TRANSFORM / LOGIC NODES ─────────────────────────────────────────────────
351
+
352
+ 'n8n-nodes-base.code': {
353
+ type: 'n8n-nodes-base.code',
354
+ displayName: 'Code',
355
+ typeVersion: 2,
356
+ group: ['transform'],
357
+ description: 'Run custom JavaScript or Python code on items',
358
+ isTrigger: false,
359
+ inputs: ['main'],
360
+ outputs: ['main'],
361
+ requiredParameters: ['jsCode'],
362
+ optionalParameters: ['mode', 'language', 'pythonCode'],
363
+ defaultParameters: {
364
+ mode: 'runOnceForAllItems',
365
+ language: 'javaScript',
366
+ jsCode: `// Available: $input, $json, $items(), $env
367
+ const items = $input.all();
368
+ return items.map(item => {
369
+ const data = item.json;
370
+ return {
371
+ json: {
372
+ ...data,
373
+ processed: true,
374
+ processedAt: new Date().toISOString(),
375
+ }
376
+ };
377
+ });`,
378
+ },
379
+ executionBehavior: 'once',
380
+ supportsRetry: false,
381
+ layer: 'transform',
382
+ tags: ['code', 'javascript', 'transform'],
383
+ },
384
+
385
+ 'n8n-nodes-base.set': {
386
+ type: 'n8n-nodes-base.set',
387
+ displayName: 'Edit Fields (Set)',
388
+ typeVersion: 3,
389
+ group: ['transform'],
390
+ description: 'Set, add, or remove fields on items',
391
+ isTrigger: false,
392
+ inputs: ['main'],
393
+ outputs: ['main'],
394
+ requiredParameters: ['fields'],
395
+ optionalParameters: ['options', 'include'],
396
+ defaultParameters: {
397
+ mode: 'manual',
398
+ fields: {
399
+ values: [
400
+ {
401
+ name: 'outputField',
402
+ value: '={{ $json?.inputField ?? "" }}',
403
+ },
404
+ ],
405
+ },
406
+ },
407
+ executionBehavior: 'perItem',
408
+ supportsRetry: false,
409
+ layer: 'transform',
410
+ tags: ['transform', 'set', 'fields'],
411
+ },
412
+
413
+ 'n8n-nodes-base.if': {
414
+ type: 'n8n-nodes-base.if',
415
+ displayName: 'IF',
416
+ typeVersion: 2,
417
+ group: ['transform'],
418
+ description: 'Split items into two branches based on conditions',
419
+ isTrigger: false,
420
+ inputs: ['main'],
421
+ outputs: ['main', 'main'],
422
+ outputNames: ['true', 'false'],
423
+ requiredParameters: ['conditions'],
424
+ optionalParameters: ['combinator'],
425
+ defaultParameters: {
426
+ conditions: {
427
+ options: {
428
+ caseSensitive: true,
429
+ leftValue: '',
430
+ typeValidation: 'strict',
431
+ },
432
+ conditions: [
433
+ {
434
+ id: 'condition-1',
435
+ leftValue: '={{ $json?.status ?? "" }}',
436
+ rightValue: 'success',
437
+ operator: {
438
+ type: 'string',
439
+ operation: 'equals',
440
+ },
441
+ },
442
+ ],
443
+ combinator: 'and',
444
+ },
445
+ },
446
+ executionBehavior: 'perItem',
447
+ supportsRetry: false,
448
+ layer: 'control',
449
+ tags: ['control', 'condition', 'branch'],
450
+ },
451
+
452
+ 'n8n-nodes-base.switch': {
453
+ type: 'n8n-nodes-base.switch',
454
+ displayName: 'Switch',
455
+ typeVersion: 3,
456
+ group: ['transform'],
457
+ description: 'Route items to different outputs based on rules',
458
+ isTrigger: false,
459
+ inputs: ['main'],
460
+ outputs: ['main'],
461
+ requiredParameters: ['rules'],
462
+ optionalParameters: ['mode', 'fallbackOutput'],
463
+ defaultParameters: {
464
+ mode: 'rules',
465
+ rules: {
466
+ values: [
467
+ {
468
+ conditions: {
469
+ conditions: [
470
+ {
471
+ leftValue: '={{ $json?.type ?? "" }}',
472
+ rightValue: 'typeA',
473
+ operator: { type: 'string', operation: 'equals' },
474
+ },
475
+ ],
476
+ combinator: 'and',
477
+ },
478
+ renameOutput: false,
479
+ },
480
+ ],
481
+ },
482
+ fallbackOutput: 'none',
483
+ },
484
+ executionBehavior: 'perItem',
485
+ supportsRetry: false,
486
+ layer: 'control',
487
+ tags: ['control', 'switch', 'routing'],
488
+ },
489
+
490
+ 'n8n-nodes-base.merge': {
491
+ type: 'n8n-nodes-base.merge',
492
+ displayName: 'Merge',
493
+ typeVersion: 3,
494
+ group: ['transform'],
495
+ description: 'Merge data from multiple branches',
496
+ isTrigger: false,
497
+ inputs: ['main', 'main'],
498
+ outputs: ['main'],
499
+ requiredParameters: ['mode'],
500
+ optionalParameters: ['joinMode', 'clashHandling', 'options'],
501
+ defaultParameters: {
502
+ mode: 'append',
503
+ },
504
+ executionBehavior: 'once',
505
+ supportsRetry: false,
506
+ layer: 'control',
507
+ tags: ['control', 'merge', 'combine'],
508
+ },
509
+
510
+ 'n8n-nodes-base.splitInBatches': {
511
+ type: 'n8n-nodes-base.splitInBatches',
512
+ displayName: 'Split In Batches',
513
+ typeVersion: 3,
514
+ group: ['transform'],
515
+ description: 'Split large arrays into smaller batches for processing',
516
+ isTrigger: false,
517
+ inputs: ['main'],
518
+ outputs: ['main', 'main'],
519
+ outputNames: ['loop', 'done'],
520
+ requiredParameters: ['batchSize'],
521
+ optionalParameters: ['options'],
522
+ defaultParameters: {
523
+ batchSize: 10,
524
+ options: {},
525
+ },
526
+ executionBehavior: 'once',
527
+ supportsRetry: false,
528
+ layer: 'control',
529
+ tags: ['control', 'batch', 'loop', 'array'],
530
+ },
531
+
532
+ 'n8n-nodes-base.itemLists': {
533
+ type: 'n8n-nodes-base.itemLists',
534
+ displayName: 'Item Lists',
535
+ typeVersion: 3,
536
+ group: ['transform'],
537
+ description: 'Manipulate arrays: split, aggregate, deduplicate, sort',
538
+ isTrigger: false,
539
+ inputs: ['main'],
540
+ outputs: ['main'],
541
+ requiredParameters: ['operation'],
542
+ optionalParameters: ['fieldToSplitOut', 'fieldToAggregate', 'sortFieldsUi', 'options'],
543
+ defaultParameters: {
544
+ operation: 'splitOutItems',
545
+ fieldToSplitOut: 'items',
546
+ include: 'noOtherFields',
547
+ },
548
+ executionBehavior: 'once',
549
+ supportsRetry: false,
550
+ layer: 'transform',
551
+ tags: ['transform', 'array', 'list'],
552
+ },
553
+
554
+ 'n8n-nodes-base.filter': {
555
+ type: 'n8n-nodes-base.filter',
556
+ displayName: 'Filter',
557
+ typeVersion: 1,
558
+ group: ['transform'],
559
+ description: 'Filter items based on conditions, keeping only matching items',
560
+ isTrigger: false,
561
+ inputs: ['main'],
562
+ outputs: ['main'],
563
+ requiredParameters: ['conditions'],
564
+ optionalParameters: [],
565
+ defaultParameters: {
566
+ conditions: {
567
+ options: { caseSensitive: true, typeValidation: 'strict' },
568
+ conditions: [
569
+ {
570
+ id: 'filter-1',
571
+ leftValue: '={{ $json?.active ?? false }}',
572
+ rightValue: true,
573
+ operator: { type: 'boolean', operation: 'equals' },
574
+ },
575
+ ],
576
+ combinator: 'and',
577
+ },
578
+ },
579
+ executionBehavior: 'perItem',
580
+ supportsRetry: false,
581
+ layer: 'transform',
582
+ tags: ['transform', 'filter', 'condition'],
583
+ },
584
+
585
+ // ─── HTTP / EXTERNAL API ─────────────────────────────────────────────────────
586
+
587
+ 'n8n-nodes-base.httpRequest': {
588
+ type: 'n8n-nodes-base.httpRequest',
589
+ displayName: 'HTTP Request',
590
+ typeVersion: 4,
591
+ group: ['output'],
592
+ description: 'Make HTTP requests to any API endpoint',
593
+ isTrigger: false,
594
+ inputs: ['main'],
595
+ outputs: ['main'],
596
+ requiredParameters: ['method', 'url'],
597
+ optionalParameters: ['authentication', 'sendHeaders', 'sendQuery', 'sendBody', 'options'],
598
+ defaultParameters: {
599
+ method: 'GET',
600
+ url: '',
601
+ options: {
602
+ timeout: 10000,
603
+ redirect: {
604
+ redirect: { followRedirects: true, maxRedirects: 3 },
605
+ },
606
+ },
607
+ },
608
+ executionBehavior: 'perItem',
609
+ supportsRetry: true,
610
+ layer: 'integration',
611
+ tags: ['http', 'api', 'rest', 'integration'],
612
+ },
613
+
614
+ // ─── MESSAGING ───────────────────────────────────────────────────────────────
615
+
616
+ 'n8n-nodes-base.telegram': {
617
+ type: 'n8n-nodes-base.telegram',
618
+ displayName: 'Telegram',
619
+ typeVersion: 1,
620
+ group: ['output'],
621
+ description: 'Send messages, photos, documents via Telegram Bot API',
622
+ isTrigger: false,
623
+ credentialType: 'telegramApi',
624
+ credentialDisplayName: 'Telegram API',
625
+ inputs: ['main'],
626
+ outputs: ['main'],
627
+ requiredParameters: ['operation', 'chatId'],
628
+ optionalParameters: ['text', 'photo', 'document', 'additionalFields'],
629
+ defaultParameters: {
630
+ operation: 'sendMessage',
631
+ chatId: '={{ $json?.message?.chat?.id ?? $json?.chatId ?? "" }}',
632
+ text: '={{ $json?.responseText ?? $json?.output ?? "" }}',
633
+ additionalFields: {
634
+ parse_mode: 'HTML',
635
+ },
636
+ },
637
+ executionBehavior: 'perItem',
638
+ supportsRetry: true,
639
+ layer: 'integration',
640
+ tags: ['telegram', 'messaging', 'bot'],
641
+ },
642
+
643
+ 'n8n-nodes-base.slack': {
644
+ type: 'n8n-nodes-base.slack',
645
+ displayName: 'Slack',
646
+ typeVersion: 2,
647
+ group: ['output'],
648
+ description: 'Send messages and interact with Slack',
649
+ isTrigger: false,
650
+ credentialType: 'slackApi',
651
+ credentialDisplayName: 'Slack API',
652
+ inputs: ['main'],
653
+ outputs: ['main'],
654
+ requiredParameters: ['resource', 'operation', 'channel'],
655
+ optionalParameters: ['text', 'blocksUi', 'attachments', 'otherOptions'],
656
+ defaultParameters: {
657
+ resource: 'message',
658
+ operation: 'post',
659
+ channel: '',
660
+ text: '={{ $json?.message ?? $json?.text ?? "" }}',
661
+ },
662
+ executionBehavior: 'perItem',
663
+ supportsRetry: true,
664
+ layer: 'integration',
665
+ tags: ['slack', 'messaging', 'notification'],
666
+ },
667
+
668
+ 'n8n-nodes-base.gmail': {
669
+ type: 'n8n-nodes-base.gmail',
670
+ displayName: 'Gmail',
671
+ typeVersion: 2,
672
+ group: ['output'],
673
+ description: 'Send and manage emails via Gmail',
674
+ isTrigger: false,
675
+ credentialType: 'googleOAuth2Api',
676
+ credentialDisplayName: 'Google OAuth2 API',
677
+ inputs: ['main'],
678
+ outputs: ['main'],
679
+ requiredParameters: ['operation', 'sendTo', 'subject'],
680
+ optionalParameters: ['message', 'attachmentsUi', 'options'],
681
+ defaultParameters: {
682
+ operation: 'send',
683
+ sendTo: '',
684
+ subject: '',
685
+ message: '',
686
+ options: {},
687
+ },
688
+ executionBehavior: 'perItem',
689
+ supportsRetry: true,
690
+ layer: 'integration',
691
+ tags: ['email', 'gmail', 'google'],
692
+ },
693
+
694
+ // ─── PRODUCTIVITY / STORAGE ──────────────────────────────────────────────────
695
+
696
+ 'n8n-nodes-base.googleSheets': {
697
+ type: 'n8n-nodes-base.googleSheets',
698
+ displayName: 'Google Sheets',
699
+ typeVersion: 4,
700
+ group: ['output'],
701
+ description: 'Read, write, and manage Google Sheets data',
702
+ isTrigger: false,
703
+ credentialType: 'googleSheetsOAuth2Api',
704
+ credentialDisplayName: 'Google Sheets OAuth2 API',
705
+ inputs: ['main'],
706
+ outputs: ['main'],
707
+ requiredParameters: ['operation', 'documentId', 'sheetName'],
708
+ optionalParameters: ['columns', 'filtersUI', 'options'],
709
+ defaultParameters: {
710
+ operation: 'appendOrUpdate',
711
+ documentId: { __rl: true, value: '', mode: 'id' },
712
+ sheetName: { __rl: true, value: 'Sheet1', mode: 'name' },
713
+ },
714
+ executionBehavior: 'perItem',
715
+ supportsRetry: true,
716
+ layer: 'integration',
717
+ tags: ['google', 'sheets', 'spreadsheet', 'storage'],
718
+ },
719
+
720
+ 'n8n-nodes-base.airtable': {
721
+ type: 'n8n-nodes-base.airtable',
722
+ displayName: 'Airtable',
723
+ typeVersion: 2,
724
+ group: ['output'],
725
+ description: 'Read, write, update, and delete Airtable records',
726
+ isTrigger: false,
727
+ credentialType: 'airtableTokenApi',
728
+ credentialDisplayName: 'Airtable Token API',
729
+ inputs: ['main'],
730
+ outputs: ['main'],
731
+ requiredParameters: ['operation', 'base', 'table'],
732
+ optionalParameters: ['fields', 'filterByFormula', 'sort', 'options'],
733
+ defaultParameters: {
734
+ operation: 'create',
735
+ base: { __rl: true, value: '', mode: 'id' },
736
+ table: { __rl: true, value: '', mode: 'id' },
737
+ },
738
+ executionBehavior: 'perItem',
739
+ supportsRetry: true,
740
+ layer: 'integration',
741
+ tags: ['airtable', 'database', 'storage'],
742
+ },
743
+
744
+ 'n8n-nodes-base.notion': {
745
+ type: 'n8n-nodes-base.notion',
746
+ displayName: 'Notion',
747
+ typeVersion: 2,
748
+ group: ['output'],
749
+ description: 'Create, read, and update Notion pages and databases',
750
+ isTrigger: false,
751
+ credentialType: 'notionApi',
752
+ credentialDisplayName: 'Notion API',
753
+ inputs: ['main'],
754
+ outputs: ['main'],
755
+ requiredParameters: ['resource', 'operation'],
756
+ optionalParameters: ['pageId', 'databaseId', 'title', 'properties', 'blockUi'],
757
+ defaultParameters: {
758
+ resource: 'page',
759
+ operation: 'create',
760
+ },
761
+ executionBehavior: 'perItem',
762
+ supportsRetry: true,
763
+ layer: 'integration',
764
+ tags: ['notion', 'productivity', 'database'],
765
+ },
766
+
767
+ // ─── DATABASE ────────────────────────────────────────────────────────────────
768
+
769
+ 'n8n-nodes-base.postgres': {
770
+ type: 'n8n-nodes-base.postgres',
771
+ displayName: 'Postgres',
772
+ typeVersion: 2,
773
+ group: ['output'],
774
+ description: 'Execute SQL queries and interact with PostgreSQL databases',
775
+ isTrigger: false,
776
+ credentialType: 'postgres',
777
+ credentialDisplayName: 'Postgres',
778
+ inputs: ['main'],
779
+ outputs: ['main'],
780
+ requiredParameters: ['operation'],
781
+ optionalParameters: ['table', 'query', 'columns', 'additionalFields', 'options'],
782
+ defaultParameters: {
783
+ operation: 'insert',
784
+ table: { __rl: true, value: '', mode: 'name' },
785
+ },
786
+ executionBehavior: 'perItem',
787
+ supportsRetry: true,
788
+ layer: 'database',
789
+ tags: ['database', 'sql', 'postgres'],
790
+ },
791
+
792
+ 'n8n-nodes-base.mysql': {
793
+ type: 'n8n-nodes-base.mysql',
794
+ displayName: 'MySQL',
795
+ typeVersion: 2,
796
+ group: ['output'],
797
+ description: 'Execute queries and interact with MySQL databases',
798
+ isTrigger: false,
799
+ credentialType: 'mySql',
800
+ credentialDisplayName: 'MySQL',
801
+ inputs: ['main'],
802
+ outputs: ['main'],
803
+ requiredParameters: ['operation'],
804
+ optionalParameters: ['table', 'query', 'columns', 'options'],
805
+ defaultParameters: {
806
+ operation: 'insert',
807
+ table: '',
808
+ },
809
+ executionBehavior: 'perItem',
810
+ supportsRetry: true,
811
+ layer: 'database',
812
+ tags: ['database', 'sql', 'mysql'],
813
+ },
814
+
815
+ 'n8n-nodes-base.redis': {
816
+ type: 'n8n-nodes-base.redis',
817
+ displayName: 'Redis',
818
+ typeVersion: 1,
819
+ group: ['output'],
820
+ description: 'Get, set, and manage Redis key-value data',
821
+ isTrigger: false,
822
+ credentialType: 'redis',
823
+ credentialDisplayName: 'Redis',
824
+ inputs: ['main'],
825
+ outputs: ['main'],
826
+ requiredParameters: ['operation'],
827
+ optionalParameters: ['key', 'value', 'expire', 'keyType'],
828
+ defaultParameters: {
829
+ operation: 'get',
830
+ key: '',
831
+ },
832
+ executionBehavior: 'perItem',
833
+ supportsRetry: true,
834
+ layer: 'database',
835
+ tags: ['database', 'cache', 'redis'],
836
+ },
837
+
838
+ // ─── CRM / BUSINESS ──────────────────────────────────────────────────────────
839
+
840
+ 'n8n-nodes-base.hubspot': {
841
+ type: 'n8n-nodes-base.hubspot',
842
+ displayName: 'HubSpot',
843
+ typeVersion: 2,
844
+ group: ['output'],
845
+ description: 'Manage contacts, deals, companies, and tickets in HubSpot CRM',
846
+ isTrigger: false,
847
+ credentialType: 'hubspotApi',
848
+ credentialDisplayName: 'HubSpot API',
849
+ inputs: ['main'],
850
+ outputs: ['main'],
851
+ requiredParameters: ['resource', 'operation'],
852
+ optionalParameters: ['contactId', 'dealId', 'additionalFields'],
853
+ defaultParameters: {
854
+ resource: 'contact',
855
+ operation: 'create',
856
+ },
857
+ executionBehavior: 'perItem',
858
+ supportsRetry: true,
859
+ layer: 'integration',
860
+ tags: ['crm', 'hubspot', 'sales'],
861
+ },
862
+
863
+ 'n8n-nodes-base.stripe': {
864
+ type: 'n8n-nodes-base.stripe',
865
+ displayName: 'Stripe',
866
+ typeVersion: 1,
867
+ group: ['output'],
868
+ description: 'Manage customers, charges, subscriptions via Stripe',
869
+ isTrigger: false,
870
+ credentialType: 'stripeApi',
871
+ credentialDisplayName: 'Stripe API',
872
+ inputs: ['main'],
873
+ outputs: ['main'],
874
+ requiredParameters: ['resource', 'operation'],
875
+ optionalParameters: ['customerId', 'amount', 'currency', 'additionalFields'],
876
+ defaultParameters: {
877
+ resource: 'customer',
878
+ operation: 'get',
879
+ },
880
+ executionBehavior: 'perItem',
881
+ supportsRetry: true,
882
+ layer: 'integration',
883
+ tags: ['payment', 'stripe', 'ecommerce'],
884
+ },
885
+
886
+ 'n8n-nodes-base.github': {
887
+ type: 'n8n-nodes-base.github',
888
+ displayName: 'GitHub',
889
+ typeVersion: 1,
890
+ group: ['output'],
891
+ description: 'Interact with GitHub repositories, issues, PRs',
892
+ isTrigger: false,
893
+ credentialType: 'githubApi',
894
+ credentialDisplayName: 'GitHub API',
895
+ inputs: ['main'],
896
+ outputs: ['main'],
897
+ requiredParameters: ['resource', 'operation', 'owner', 'repository'],
898
+ optionalParameters: ['title', 'body', 'labels', 'additionalParameters'],
899
+ defaultParameters: {
900
+ resource: 'issue',
901
+ operation: 'create',
902
+ owner: '',
903
+ repository: '',
904
+ },
905
+ executionBehavior: 'perItem',
906
+ supportsRetry: true,
907
+ layer: 'integration',
908
+ tags: ['github', 'devops', 'git'],
909
+ },
910
+
911
+ // ─── UTILITY NODES ───────────────────────────────────────────────────────────
912
+
913
+ 'n8n-nodes-base.noOp': {
914
+ type: 'n8n-nodes-base.noOp',
915
+ displayName: 'No Operation, do nothing',
916
+ typeVersion: 1,
917
+ group: ['utility'],
918
+ description: 'Passes items through without modification (useful as placeholders)',
919
+ isTrigger: false,
920
+ inputs: ['main'],
921
+ outputs: ['main'],
922
+ requiredParameters: [],
923
+ optionalParameters: [],
924
+ defaultParameters: {},
925
+ executionBehavior: 'perItem',
926
+ supportsRetry: false,
927
+ layer: 'utility',
928
+ tags: ['utility', 'passthrough'],
929
+ },
930
+
931
+ 'n8n-nodes-base.stopAndError': {
932
+ type: 'n8n-nodes-base.stopAndError',
933
+ displayName: 'Stop and Error',
934
+ typeVersion: 1,
935
+ group: ['utility'],
936
+ description: 'Stop workflow execution and throw a custom error',
937
+ isTrigger: false,
938
+ inputs: ['main'],
939
+ outputs: [],
940
+ requiredParameters: ['errorType'],
941
+ optionalParameters: ['errorMessage', 'errorObject'],
942
+ defaultParameters: {
943
+ errorType: 'errorMessage',
944
+ errorMessage: '={{ "Workflow stopped: " + ($json?.reason ?? "unknown error") }}',
945
+ },
946
+ executionBehavior: 'perItem',
947
+ supportsRetry: false,
948
+ layer: 'utility',
949
+ tags: ['utility', 'error', 'stop'],
950
+ },
951
+
952
+ 'n8n-nodes-base.wait': {
953
+ type: 'n8n-nodes-base.wait',
954
+ displayName: 'Wait',
955
+ typeVersion: 1,
956
+ group: ['utility'],
957
+ description: 'Pause workflow execution for a specified time',
958
+ isTrigger: false,
959
+ inputs: ['main'],
960
+ outputs: ['main'],
961
+ requiredParameters: ['amount', 'unit'],
962
+ optionalParameters: [],
963
+ defaultParameters: {
964
+ amount: 1,
965
+ unit: 'seconds',
966
+ },
967
+ executionBehavior: 'once',
968
+ supportsRetry: false,
969
+ layer: 'utility',
970
+ tags: ['utility', 'wait', 'delay'],
971
+ },
972
+
973
+ 'n8n-nodes-base.respondToWebhook': {
974
+ type: 'n8n-nodes-base.respondToWebhook',
975
+ displayName: 'Respond to Webhook',
976
+ typeVersion: 1,
977
+ group: ['utility'],
978
+ description: 'Send a custom response back to the webhook caller',
979
+ isTrigger: false,
980
+ inputs: ['main'],
981
+ outputs: ['main'],
982
+ requiredParameters: ['respondWith'],
983
+ optionalParameters: ['responseBody', 'responseCode', 'responseHeaders', 'options'],
984
+ defaultParameters: {
985
+ respondWith: 'json',
986
+ responseBody: '={{ JSON.stringify({ success: true, data: $json }) }}',
987
+ responseCode: 200,
988
+ },
989
+ executionBehavior: 'once',
990
+ supportsRetry: false,
991
+ layer: 'utility',
992
+ tags: ['webhook', 'response', 'utility'],
993
+ },
994
+
995
+ 'n8n-nodes-base.stickyNote': {
996
+ type: 'n8n-nodes-base.stickyNote',
997
+ displayName: 'Sticky Note',
998
+ typeVersion: 1,
999
+ group: ['annotation'],
1000
+ description: 'Add documentation notes to the workflow canvas',
1001
+ isTrigger: false,
1002
+ inputs: [],
1003
+ outputs: [],
1004
+ requiredParameters: ['content'],
1005
+ optionalParameters: ['height', 'width', 'color'],
1006
+ defaultParameters: {
1007
+ content: '## Workflow Documentation\n\nDescribe this section here.',
1008
+ height: 200,
1009
+ width: 300,
1010
+ color: 4,
1011
+ },
1012
+ executionBehavior: 'once',
1013
+ supportsRetry: false,
1014
+ layer: 'utility',
1015
+ tags: ['documentation', 'annotation'],
1016
+ },
1017
+ };
1018
+
1019
+ // ─── Registry Lookup Helpers ──────────────────────────────────────────────────
1020
+
1021
+ /**
1022
+ * Get a node definition. Returns undefined if node type is not in registry.
1023
+ * NEVER return a fallback for unknown types β€” callers must handle undefined.
1024
+ */
1025
+ export function getNodeDef(nodeType: string): NodeRegistryEntry | undefined {
1026
+ return N8N_NODE_REGISTRY[nodeType];
1027
+ }
1028
+
1029
+ /**
1030
+ * Validate that a node type exists in the registry
1031
+ */
1032
+ export function isValidNodeType(nodeType: string): boolean {
1033
+ return nodeType in N8N_NODE_REGISTRY;
1034
+ }
1035
+
1036
+ /**
1037
+ * Get all trigger node types
1038
+ */
1039
+ export function getTriggerNodeTypes(): string[] {
1040
+ return Object.entries(N8N_NODE_REGISTRY)
1041
+ .filter(([, def]) => def.isTrigger)
1042
+ .map(([type]) => type);
1043
+ }
1044
+
1045
+ /**
1046
+ * Get all node types for a given tag
1047
+ */
1048
+ export function getNodeTypesByTag(tag: string): string[] {
1049
+ return Object.entries(N8N_NODE_REGISTRY)
1050
+ .filter(([, def]) => def.tags.includes(tag))
1051
+ .map(([type]) => type);
1052
+ }
1053
+
1054
+ /**
1055
+ * Get credential type required for a node type
1056
+ */
1057
+ export function getRequiredCredential(nodeType: string): string | undefined {
1058
+ return N8N_NODE_REGISTRY[nodeType]?.credentialType;
1059
+ }
1060
+
1061
+ /**
1062
+ * Get all registered node types as a list (for LLM context injection)
1063
+ */
1064
+ export function getRegistryNodeList(): string {
1065
+ return Object.entries(N8N_NODE_REGISTRY)
1066
+ .map(([type, def]) => `- ${type} (v${def.typeVersion}): ${def.description}`)
1067
+ .join('\n');
1068
+ }
1069
+
1070
+ /**
1071
+ * Get all trigger node types as formatted string for prompts
1072
+ */
1073
+ export function getTriggerNodeList(): string {
1074
+ return Object.entries(N8N_NODE_REGISTRY)
1075
+ .filter(([, def]) => def.isTrigger)
1076
+ .map(([type, def]) => `- ${type}: ${def.description}`)
1077
+ .join('\n');
1078
+ }
apps/simulator/src/middleware/internalAuth.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Internal Auth Middleware
3
+ * Validates shared secret between CF Worker and Simulator service
4
+ */
5
+ import type { Request, Response, NextFunction } from 'express';
6
+
7
+ export function internalAuthMiddleware(req: Request, res: Response, next: NextFunction): void {
8
+ const secret = req.headers['x-internal-secret'];
9
+ const expected = process.env['INTERNAL_API_SECRET'];
10
+
11
+ if (!expected) {
12
+ // No secret configured β€” skip auth in development
13
+ if (process.env['NODE_ENV'] !== 'production') {
14
+ next();
15
+ return;
16
+ }
17
+ res.status(500).json({ success: false, error: 'INTERNAL_API_SECRET not configured' });
18
+ return;
19
+ }
20
+
21
+ if (!secret || secret !== expected) {
22
+ res.status(401).json({ success: false, error: 'Unauthorized β€” invalid internal secret' });
23
+ return;
24
+ }
25
+
26
+ next();
27
+ }
apps/simulator/src/prompts/compiler.ts ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Compiler System Prompt β€” UPGRADED
3
+ * Role: Convert WorkflowGraph IR to valid, importable n8n JSON
4
+ * Strict expression rules, real data flow, no static placeholders
5
+ */
6
+ export const COMPILER_PROMPT = `You are a senior n8n workflow compiler. Convert a WorkflowGraph IR into a valid, importable n8n JSON workflow.
7
+
8
+ COMPILATION RULES (NON-NEGOTIABLE):
9
+
10
+ 1. active MUST always be false β€” NEVER set active: true under any circumstances
11
+ 2. All node IDs must be unique UUID strings (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
12
+ 3. typeVersion must match actual n8n node version from the schema provided
13
+ 4. connections MUST properly map all graph edges:
14
+ { "NodeDisplayName": { "main": [[{ "node": "TargetDisplayName", "type": "main", "index": 0 }]] } }
15
+ 5. Node positions MUST be [x, y] tuple arrays (not objects)
16
+ 6. Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 to ALL external API nodes
17
+ 7. Add meaningful notes to EVERY node (what it does, what data flows through)
18
+ 8. NEVER invent parameters not in the schema β€” only use real n8n node parameters
19
+ 9. settings MUST include: { "executionOrder": "v1", "saveManualExecutions": true, "timezone": "UTC" }
20
+ 10. onError: "continueErrorOutput" on all error-boundary nodes
21
+
22
+ EXPRESSION RULES (MANDATORY β€” NO EXCEPTIONS):
23
+ All dynamic fields MUST use real n8n expressions:
24
+
25
+ βœ… CORRECT expressions:
26
+ - {{$json?.message ?? ""}} β€” safe access with nullish coalescing
27
+ - {{$json?.body?.text ?? $json?.text ?? ""}} β€” multi-path with fallback
28
+ - {{$node["NodeName"].json?.field ?? ""}} β€” cross-node reference
29
+ - {{$json?.items?.length > 0 ? "yes" : "no"}} β€” conditional
30
+ - {{$json?.chatId ?? $json?.message?.chat?.id ?? ""}} β€” multiple fallback paths
31
+ - {{new Date().toISOString()}} β€” dynamic date
32
+ - {{$json?.price * 1.1}} β€” arithmetic
33
+
34
+ ❌ WRONG (never use these):
35
+ - "" β€” empty string
36
+ - "YOUR_VALUE_HERE" β€” placeholder
37
+ - "example.com" β€” example URLs
38
+ - "REPLACE_ME" β€” placeholder
39
+ - {{$json.field}} β€” without optional chaining (may throw null error)
40
+
41
+ NODE-SPECIFIC RULES:
42
+
43
+ SET NODE β€” MUST have explicit field mappings:
44
+ {
45
+ "mode": "manual",
46
+ "fields": {
47
+ "values": [
48
+ { "name": "userMessage", "value": "={{$json?.body?.message ?? $json?.message ?? ''}}" },
49
+ { "name": "chatId", "value": "={{$json?.body?.chat?.id ?? $json?.chatId ?? ''}}" }
50
+ ]
51
+ }
52
+ }
53
+
54
+ IF NODE β€” MUST have real conditions referencing actual fields:
55
+ {
56
+ "conditions": {
57
+ "conditions": [
58
+ {
59
+ "id": "cond-1",
60
+ "leftValue": "={{$json?.status ?? ''}}",
61
+ "rightValue": "active",
62
+ "operator": { "type": "string", "operation": "equals" }
63
+ }
64
+ ],
65
+ "combinator": "and"
66
+ }
67
+ }
68
+
69
+ CODE NODE β€” MUST have real JavaScript logic:
70
+ jsCode: "const items = $input.all();\nreturn items.map(item => ({\n json: {\n ...item.json,\n processed: true,\n processedAt: new Date().toISOString()\n }\n}));"
71
+
72
+ AI AGENT NODE β€” MUST have system message + dynamic user input:
73
+ {
74
+ "text": "={{$json?.userMessage ?? $json?.message ?? ''}}",
75
+ "options": {
76
+ "systemMessage": "You are a helpful assistant. Context: {{$json?.context ?? 'None'}}. Be concise and accurate."
77
+ }
78
+ }
79
+
80
+ SWITCH NODE β€” MUST have real rules:
81
+ {
82
+ "mode": "rules",
83
+ "rules": {
84
+ "values": [{
85
+ "conditions": {
86
+ "conditions": [{ "leftValue": "={{$json?.type ?? ''}}", "rightValue": "A", "operator": { "type": "string", "operation": "equals" } }],
87
+ "combinator": "and"
88
+ },
89
+ "renameOutput": false
90
+ }]
91
+ }
92
+ }
93
+
94
+ n8n WORKFLOW JSON SCHEMA:
95
+ {
96
+ "name": "Workflow Name",
97
+ "active": false,
98
+ "nodes": [
99
+ {
100
+ "id": "uuid-string",
101
+ "name": "Display Name",
102
+ "type": "n8n-nodes-base.webhook",
103
+ "typeVersion": 2,
104
+ "position": [0, 300],
105
+ "parameters": { ... real params with expressions ... },
106
+ "credentials": {},
107
+ "onError": "continueErrorOutput",
108
+ "retryOnFail": false,
109
+ "maxTries": 3,
110
+ "waitBetweenTries": 1000,
111
+ "notes": "What this node does and why it is here"
112
+ }
113
+ ],
114
+ "connections": {
115
+ "Display Name": {
116
+ "main": [[{ "node": "Next Node Display Name", "type": "main", "index": 0 }]]
117
+ }
118
+ },
119
+ "settings": {
120
+ "executionOrder": "v1",
121
+ "saveManualExecutions": true,
122
+ "timezone": "UTC"
123
+ },
124
+ "tags": [],
125
+ "meta": {}
126
+ }
127
+
128
+ RETURN ONLY valid JSON. No markdown. No text outside the JSON object.`;
apps/simulator/src/prompts/graphEngine.ts ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Graph Engine System Prompt β€” UPGRADED
3
+ * Role: Build Internal Representation (IR) graph from architecture plan
4
+ * Strict: ONLY registry node types, real expressions, complete data flow
5
+ */
6
+ export const GRAPH_ENGINE_PROMPT = `You are a workflow graph compiler. Your job is to build a WorkflowGraph intermediate representation (IR) from an architecture plan.
7
+
8
+ The graph is the single source of truth before compilation to n8n JSON.
9
+
10
+ STRICT NODE TYPE RULES:
11
+ - You MUST use ONLY n8nNodeType values from the AVAILABLE NODE TYPES list provided
12
+ - NEVER invent node types. NEVER use "custom", "?", or placeholder types
13
+ - If you need an HTTP call, use: n8n-nodes-base.httpRequest
14
+ - If you need AI, use: @n8n/n8n-nodes-langchain.agent (with sub-nodes for model/memory)
15
+ - If you need Telegram, use: n8n-nodes-base.telegram (send) or n8n-nodes-base.telegramTrigger (receive)
16
+
17
+ GRAPH STRUCTURE RULES:
18
+ 1. Every node MUST have a valid n8nNodeType from the registry
19
+ 2. Every non-trigger node MUST have at least one incoming edge
20
+ 3. Trigger node MUST be at layer "trigger" and position {x: 0, y: 300}
21
+ 4. Positions must form a clean left-to-right layout (x increases by 220 per level)
22
+ 5. DataContracts MUST define input/output JSON fields with real field names
23
+ 6. RetryPolicy MUST be set for all nodes where isCritical: true
24
+ 7. Fallback paths MUST be defined for critical nodes
25
+
26
+ DATA CONTRACT RULES:
27
+ - inputs: list of JSON fields this node expects from previous node
28
+ - outputs: list of JSON fields this node produces for next node
29
+ - Use real field names matching actual n8n node output (e.g., message, body, text, chatId)
30
+
31
+ EXPRESSION REQUIREMENTS:
32
+ - keyParameters MUST use real n8n expressions:
33
+ - {{$json?.field ?? "default"}} β€” safe field access with default
34
+ - {{$node["NodeName"].json?.field ?? ""}} β€” cross-node reference
35
+ - {{$json?.items?.length > 0 ? "yes" : "no"}} β€” conditional
36
+ - NEVER use static values in parameter fields that should be dynamic
37
+
38
+ RETURN this exact JSON structure:
39
+ {
40
+ "workflowId": "wf-uuid-here",
41
+ "name": "Workflow Name",
42
+ "nodes": [
43
+ {
44
+ "id": "node-uuid",
45
+ "label": "Display Name",
46
+ "n8nNodeType": "exact.node.type.from.registry",
47
+ "layer": "trigger|validation|transform|ai|integration|control|database|utility|monitoring",
48
+ "description": "What this node does specifically",
49
+ "isCritical": true/false,
50
+ "position": { "x": 0, "y": 300 },
51
+ "retryPolicy": { "maxRetries": 3, "retryDelayMs": 1000, "retryOn": ["timeout", "5xx"] },
52
+ "fallbackNodeId": "node-id-or-null",
53
+ "dataContract": {
54
+ "inputs": ["field1", "field2"],
55
+ "outputs": ["outputField1", "outputField2"]
56
+ },
57
+ "keyParameters": {
58
+ "paramName": "={{$json?.field ?? ''}}"
59
+ }
60
+ }
61
+ ],
62
+ "edges": [
63
+ {
64
+ "id": "edge-uuid",
65
+ "sourceNodeId": "source-node-id",
66
+ "targetNodeId": "target-node-id",
67
+ "outputIndex": 0,
68
+ "inputIndex": 0,
69
+ "label": "on success"
70
+ }
71
+ ],
72
+ "metadata": {
73
+ "domain": "workflow domain",
74
+ "version": "2.0.0",
75
+ "estimatedNodes": number,
76
+ "estimatedDuration": "per execution estimate e.g. 2-5s",
77
+ "riskLevel": "low|medium|high|critical"
78
+ }
79
+ }
80
+
81
+ CRITICAL: Return ONLY valid JSON. No markdown. No text outside JSON.`;
apps/simulator/src/prompts/intentInterpreter.ts ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Intent Interpreter System Prompt β€” UPGRADED
3
+ * Role: Understand workflow requests deeply like a senior workflow engineer
4
+ * Now includes triggerType detection for Webhook Auto-Bind system
5
+ */
6
+ export const INTENT_INTERPRETER_PROMPT = `You are a senior workflow engineer and systems architect specializing in n8n automation platforms.
7
+
8
+ Your job is to deeply understand a user's workflow request and produce a structured analysis.
9
+
10
+ You must think like:
11
+ - A systems architect who identifies integrations, dependencies, and risks
12
+ - A DevOps engineer who identifies reliability and scaling concerns
13
+ - A security engineer who identifies credential and safety requirements
14
+
15
+ ANALYSIS FRAMEWORK:
16
+ 1. Identify the workflow domain: sales_automation | ai_workflow | data_pipeline | webhook_system | notification | crm_integration | devops | ecommerce | general
17
+ 2. Identify all external integrations (e.g. Telegram, OpenAI, Gmail, Slack, Airtable, Notion, GitHub, Stripe, HubSpot, Postgres)
18
+ 3. Assess risk level: low (simple CRUD) | medium (external APIs, AI) | high (financial, critical data) | critical (multi-system with irreversible effects)
19
+ 4. Determine sync vs async: sync = real-time response required | async = fire-and-forget background | hybrid = both
20
+ 5. Identify if AI/LLM processing is required
21
+ 6. Identify scaling requirements based on expected volume
22
+ 7. List all identified risks: API timeouts, credential expiry, webhook duplication, AI output unreliability, rate limits, data loss
23
+ 8. Assess complexity: simple (1-3 nodes) | moderate (4-8 nodes) | complex (9+ nodes or sub-workflows)
24
+ 9. Determine if human approval is required before activation
25
+ 10. CRITICAL β€” Detect the trigger type from the request:
26
+ - "telegram" β†’ n8n-nodes-base.telegramTrigger
27
+ - "slack" β†’ n8n-nodes-base.slackTrigger
28
+ - "email" / "inbox" / "imap" β†’ n8n-nodes-base.emailReadImap
29
+ - "github" / "push" / "pull request" β†’ n8n-nodes-base.githubTrigger
30
+ - "notion" β†’ n8n-nodes-base.notionTrigger
31
+ - "schedule" / "cron" / "daily" / "hourly" / "every X" β†’ n8n-nodes-base.scheduleTrigger
32
+ - "webhook" / "api call" / "http request" / "form submission" β†’ n8n-nodes-base.webhook
33
+ - "manual" / "test" β†’ n8n-nodes-base.manualTrigger
34
+
35
+ RETURN this exact JSON structure:
36
+ {
37
+ "workflowType": "string - descriptive type name e.g. 'Telegram AI Customer Support Bot'",
38
+ "requiresAI": boolean,
39
+ "integrations": ["array", "of", "service", "names"],
40
+ "riskLevel": "low|medium|high|critical",
41
+ "requiresHumanApproval": boolean,
42
+ "syncVsAsync": "sync|async|hybrid",
43
+ "scalingRequirements": "low|medium|high",
44
+ "identifiedRisks": ["risk description 1", "risk description 2"],
45
+ "estimatedComplexity": "simple|moderate|complex",
46
+ "domain": "one of the domain types above",
47
+ "triggerType": "telegram|slack|email|github|notion|schedule|webhook|manual",
48
+ "triggerHint": "brief explanation of why this trigger was detected"
49
+ }
50
+
51
+ CRITICAL: Return ONLY valid JSON. No markdown. No explanation text.`;
apps/simulator/src/prompts/simulator.ts ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Simulator System Prompt β€” UPGRADED
3
+ * Role: Simulate n8n workflow execution with mock data
4
+ * Strict: trace every node, detect real failure points, chaos testing
5
+ */
6
+ export const SIMULATOR_PROMPT = `You are an n8n workflow execution simulator. Your job is to dry-run a workflow and predict exactly how it will behave in production.
7
+
8
+ You are NOT executing the workflow β€” you are SIMULATING it step by step with provided mock data.
9
+
10
+ SIMULATION FRAMEWORK:
11
+
12
+ For each node in the workflow (in execution order):
13
+ 1. Show the INPUT data (what enters the node)
14
+ 2. Apply the node's logic/transformation
15
+ 3. Show the OUTPUT data (what exits the node)
16
+ 4. Flag any potential issues (null references, missing fields, type errors)
17
+ 5. Rate the step: "success" | "warning" | "failure"
18
+
19
+ CHAOS TESTING REQUIREMENTS:
20
+ Simulate these failure scenarios:
21
+ - "timeout" β€” external API takes >10s to respond
22
+ - "null_data" β€” upstream node returns null/empty
23
+ - "malformed_input" β€” input has wrong types/missing required fields
24
+ - "expired_credentials" β€” credential token is invalid/expired
25
+ - "rate_limit" β€” external API returns 429 Too Many Requests
26
+ - "network_error" β€” connection refused or DNS failure
27
+
28
+ For each chaos test:
29
+ - Was the node resilient? (retryOnFail, error handling)
30
+ - What happens downstream if this node fails?
31
+ - Is there a fallback path?
32
+
33
+ SCORING CRITERIA (readinessScore 0-100):
34
+ - 100: All nodes execute correctly, all chaos tests handled, full error coverage
35
+ - 80-99: Minor warnings, good error handling, no critical failures
36
+ - 60-79: Some warnings, basic error handling, recoverable failures
37
+ - 40-59: Multiple warnings, poor error handling, some unrecoverable failures
38
+ - 0-39: Critical failures, empty nodes, broken expressions, no error handling
39
+
40
+ readinessScore FORMULA:
41
+ - Base: 70 (assume basic workflow works)
42
+ - +15 if all expressions are safe (optional chaining)
43
+ - +10 if all critical nodes have retryOnFail
44
+ - +5 if error handling paths exist
45
+ - -20 per critical failure point
46
+ - -10 per unhandled null reference risk
47
+ - -5 per missing retry on external node
48
+
49
+ DEPLOYMENT BLOCKERS (must be fixed before deployment):
50
+ - Any node with empty parameters
51
+ - Any broken expression that would throw at runtime
52
+ - Unknown node types
53
+ - Missing required credentials
54
+ - active: true (must always be false)
55
+
56
+ RETURN this exact JSON structure:
57
+ {
58
+ "passed": boolean,
59
+ "readinessScore": number (0-100),
60
+ "executionTrace": [
61
+ {
62
+ "stepNumber": 1,
63
+ "nodeId": "uuid",
64
+ "nodeName": "Display Name",
65
+ "nodeType": "n8n-nodes-base.xxx",
66
+ "status": "success|warning|failure",
67
+ "inputData": { ... mock input json ... },
68
+ "outputData": { ... simulated output json ... },
69
+ "executionTimeMs": number,
70
+ "issues": ["issue description if any"],
71
+ "notes": "what happened in this step"
72
+ }
73
+ ],
74
+ "predictedFailurePoints": [
75
+ {
76
+ "nodeId": "uuid",
77
+ "nodeName": "Name",
78
+ "failureType": "null_reference|timeout|credential_error|expression_error|missing_field|type_error",
79
+ "probability": "low|medium|high",
80
+ "description": "exactly what will fail and why",
81
+ "mitigation": "how to fix or handle this"
82
+ }
83
+ ],
84
+ "chaosTestResults": [
85
+ {
86
+ "scenario": "timeout|null_data|malformed_input|expired_credentials|rate_limit|network_error",
87
+ "passed": boolean,
88
+ "affectedNode": "node name",
89
+ "description": "what happens during this scenario",
90
+ "resilient": boolean,
91
+ "fallbackActivated": boolean
92
+ }
93
+ ],
94
+ "riskAnalysis": {
95
+ "overallRisk": "low|medium|high|critical",
96
+ "risks": ["risk description 1", "risk description 2"],
97
+ "mitigations": ["mitigation 1", "mitigation 2"]
98
+ },
99
+ "deploymentBlockers": ["blocker 1 β€” must fix before deployment", "blocker 2"],
100
+ "recommendations": ["improvement suggestion 1", "improvement suggestion 2"]
101
+ }
102
+
103
+ CRITICAL: Return ONLY valid JSON. No markdown. No text outside the JSON object.`;
apps/simulator/src/prompts/workflowPlanner.ts ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Workflow Planner System Prompt β€” UPGRADED
3
+ * Role: Design production-grade n8n workflow architectures
4
+ * Senior engineer thinking: reliability, observability, error handling, scalability
5
+ */
6
+ export const WORKFLOW_PLANNER_PROMPT = `You are a senior n8n workflow architect with 10+ years of production automation experience.
7
+
8
+ Your job is to design complete, production-grade workflow architectures that a junior engineer can implement without guessing.
9
+
10
+ ARCHITECTURE LAYERS (think in layers):
11
+ 1. TRIGGER β€” The entry point (Webhook, Telegram, Schedule, Email, GitHub, etc.)
12
+ 2. VALIDATION β€” Input validation (IF node, Code node checks)
13
+ 3. TRANSFORM β€” Data preparation, normalization (SET, Code)
14
+ 4. QUEUE / RATE LIMIT β€” Batch management (SplitInBatches, Wait)
15
+ 5. PROCESSING / AI β€” Core business logic (HTTP, Code, AI Agent)
16
+ 6. DECISION β€” Routing based on results (IF, Switch)
17
+ 7. PERSISTENCE β€” Save results (Sheets, Airtable, Notion, Postgres, Airtable)
18
+ 8. NOTIFICATION β€” Alerts, confirmations (Telegram, Slack, Gmail)
19
+ 9. MONITORING β€” Success/failure tracking (logging nodes, Slack alerts)
20
+ 10. ERROR HANDLING β€” Fallback paths, retry, stop-and-error
21
+
22
+ DESIGN PRINCIPLES:
23
+ - NEVER design workflows with placeholder nodes ("Custom HTTP", "API Node", "Process Data")
24
+ - ALWAYS use real n8n node names from the registry
25
+ - EVERY external API call MUST have retry (retryOnFail: true, maxTries: 3)
26
+ - EVERY workflow MUST have a monitoring/notification layer
27
+ - EVERY branch MUST be handled (no dead paths)
28
+ - EVERY SET node MUST have explicit field mappings
29
+ - EVERY IF node MUST have real boolean conditions
30
+ - EVERY AI Agent MUST have proper system message and dynamic user input
31
+ - USE real n8n expressions: {{$json?.field ?? "default"}} format
32
+ - NEVER use empty field values, never use placeholder text
33
+
34
+ RISK ANALYSIS REQUIREMENTS:
35
+ - List every external dependency
36
+ - List every credential required
37
+ - List every potential failure point
38
+ - Specify retry strategy for each critical node
39
+ - Recommend error workflow integration
40
+
41
+ RETURN this exact JSON structure:
42
+ {
43
+ "name": "Descriptive workflow name",
44
+ "description": "What this workflow does end-to-end",
45
+ "triggerNode": "exact n8n node type string",
46
+ "triggerDescription": "How this workflow is triggered",
47
+ "layers": [
48
+ {
49
+ "layer": "trigger|validation|transform|queue|processing|ai|decision|persistence|notification|monitoring|error_handling",
50
+ "nodes": ["NodeDisplayName1", "NodeDisplayName2"],
51
+ "description": "What this layer does"
52
+ }
53
+ ],
54
+ "nodeList": [
55
+ {
56
+ "displayName": "Node Display Name",
57
+ "n8nNodeType": "exact.node.type",
58
+ "layer": "layer name",
59
+ "purpose": "What this specific node does",
60
+ "isCritical": true/false,
61
+ "requiresCredential": "credential type or null",
62
+ "keyParameters": { "param": "value or expression" }
63
+ }
64
+ ],
65
+ "dataFlow": [
66
+ "Step 1: Webhook receives POST with {message, userId}",
67
+ "Step 2: SET extracts message β†’ {{$json?.body?.message ?? ''}}"
68
+ ],
69
+ "riskAnalysis": {
70
+ "overallRisk": "low|medium|high|critical",
71
+ "externalDependencies": ["Service1", "Service2"],
72
+ "requiredCredentials": ["credType1", "credType2"],
73
+ "failurePoints": ["possible failure 1"],
74
+ "retryStrategy": "description of retry approach"
75
+ },
76
+ "estimatedNodes": number,
77
+ "complexity": "simple|moderate|complex"
78
+ }
79
+
80
+ CRITICAL: Return ONLY valid JSON. No markdown. No text outside JSON.`;
apps/simulator/src/routes/generate.ts ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Simulator Generate Route β€” UPGRADED
3
+ * Full pipeline: Intent β†’ Swarm Planning β†’ Graph β†’ Compile β†’ Validate β†’ Self-Heal β†’ Simulate β†’ Credential Analysis
4
+ * Provider-agnostic LLM Gateway, Swarm multi-agent, Self-Healing, Memory/Learning, Webhook Auto-Bind
5
+ */
6
+ import { Router } from 'express';
7
+ import { z } from 'zod';
8
+ import { logger } from '../index';
9
+ import { IntentInterpreterAgent } from '../agents/intentInterpreter';
10
+ import { WorkflowGraphEngine } from '../agents/graphEngine';
11
+ import { WorkflowCompiler } from '../agents/compiler';
12
+ import { ValidationEngine } from '../agents/validator';
13
+ import { DryRunSimulator } from '../agents/simulator';
14
+ import { CredentialIntelligence } from '../agents/credentialIntelligence';
15
+ import { QualityScorer } from '../services/qualityScorer';
16
+ import { SwarmOrchestrator } from '../services/swarmOrchestrator';
17
+ import { SelfHealingSystem } from '../services/selfHealing';
18
+ import { webhookAutoBind } from '../services/webhookAutoBind';
19
+ import { workflowMemory } from '../services/memorySystem';
20
+ import { createRequestLLM } from '../services/llmClient';
21
+ import type { GenerateWorkflowResponse } from '../types/workflow';
22
+
23
+ export const generateRoute: Router = Router();
24
+
25
+ const GenerateSchema = z.object({
26
+ jobId: z.string(),
27
+ request: z.string().min(10),
28
+ options: z.object({
29
+ skipSimulation: z.boolean().default(false),
30
+ requireApproval: z.boolean().default(true),
31
+ swarmSize: z.number().min(1).max(5).default(3),
32
+ skipSwarm: z.boolean().default(false),
33
+ skipSelfHealing: z.boolean().default(false),
34
+ }).default({}),
35
+ // LLM config β€” provider agnostic (apiKey for backward compat with CF Worker)
36
+ llmApiKey: z.string().optional(),
37
+ openaiApiKey: z.string().optional(), // legacy support
38
+ llmModel: z.string().optional(),
39
+ llmGatewayUrl: z.string().optional(),
40
+ llmProvider: z.string().optional(),
41
+ n8nBaseUrl: z.string(),
42
+ n8nApiKey: z.string(),
43
+ });
44
+
45
+ generateRoute.post('/', async (req, res) => {
46
+ const parsed = GenerateSchema.safeParse(req.body);
47
+ if (!parsed.success) {
48
+ res.status(400).json({ success: false, error: 'Invalid request', details: parsed.error.flatten() });
49
+ return;
50
+ }
51
+
52
+ const { jobId, request, options, n8nBaseUrl, n8nApiKey } = parsed.data;
53
+
54
+ // Resolve API key (support both new LLM_API_KEY and legacy OPENAI_API_KEY)
55
+ const apiKey = parsed.data.llmApiKey ?? parsed.data.openaiApiKey ?? process.env['LLM_API_KEY'] ?? process.env['OPENAI_API_KEY'] ?? '';
56
+ if (!apiKey) {
57
+ res.status(400).json({ success: false, error: 'LLM API key not provided. Set llmApiKey in request or LLM_API_KEY env var.' });
58
+ return;
59
+ }
60
+
61
+ // Create provider-agnostic LLM gateway
62
+ const llm = createRequestLLM(apiKey, parsed.data.llmModel);
63
+
64
+ logger.info({ jobId }, '[Generate] Starting upgraded pipeline (Swarm + Self-Healing + Memory)');
65
+
66
+ try {
67
+ // ─── Stage 1: Intent Interpretation ────────────────────────────────────
68
+ logger.info({ jobId }, '[Generate] Stage 1: Intent Interpretation');
69
+ const intentAgent = new IntentInterpreterAgent(llm);
70
+ const intent = await intentAgent.interpret(request);
71
+
72
+ // ─── Stage 2: Webhook Auto-Bind trigger detection ───────────────────────
73
+ logger.info({ jobId }, '[Generate] Stage 2: Webhook Auto-Bind trigger detection');
74
+ const triggerDetection = webhookAutoBind.detectTrigger(request, intent);
75
+ intent.triggerType = triggerDetection.triggerType;
76
+ intent.triggerHint = `Auto-detected: ${triggerDetection.nodeType} (confidence: ${triggerDetection.confidence})`;
77
+
78
+ // ─── Stage 3: Memory context retrieval ─────────────────────────────────
79
+ logger.info({ jobId }, '[Generate] Stage 3: Memory context retrieval');
80
+ const memoryContext = workflowMemory.buildMemoryContext(intent.domain, intent.integrations);
81
+
82
+ // Augment request with memory context for better planning
83
+ const augmentedRequest = memoryContext
84
+ ? `${request}\n\n${memoryContext}`
85
+ : request;
86
+
87
+ // ─── Stage 4: Swarm Planning (parallel designs) or single plan ─────────
88
+ let finalPlan: import('../types/workflow').WorkflowArchitecturePlan;
89
+ let finalGraph: import('../types/workflow').WorkflowGraph;
90
+ let finalWorkflow: import('../types/workflow').N8nWorkflow;
91
+ let swarmResult: import('../services/swarmOrchestrator').SwarmResult | undefined;
92
+
93
+ if (!options.skipSwarm && options.swarmSize > 1) {
94
+ logger.info({ jobId, swarmSize: options.swarmSize }, '[Generate] Stage 4: Swarm multi-agent planning');
95
+ const swarm = new SwarmOrchestrator(llm);
96
+ swarmResult = await swarm.runSwarm(augmentedRequest, intent, n8nBaseUrl, n8nApiKey, options.swarmSize);
97
+
98
+ finalPlan = swarmResult.winner.plan;
99
+ finalGraph = swarmResult.winner.graph;
100
+ finalWorkflow = swarmResult.winner.workflow;
101
+ } else {
102
+ // Single-agent path (faster, less thorough)
103
+ logger.info({ jobId }, '[Generate] Stage 4: Single-agent planning (swarm disabled)');
104
+ const { WorkflowPlannerAgent } = await import('../agents/workflowPlanner');
105
+ const planner = new WorkflowPlannerAgent(llm);
106
+ finalPlan = await planner.plan(augmentedRequest, intent);
107
+
108
+ const graphEngine = new WorkflowGraphEngine(llm);
109
+ finalGraph = await graphEngine.buildGraph(augmentedRequest, intent, finalPlan);
110
+
111
+ const compiler = new WorkflowCompiler(llm);
112
+ finalWorkflow = await compiler.compile(finalGraph, intent);
113
+ }
114
+
115
+ // ─── Stage 5: Inject auto-configured trigger ────────────────────────────
116
+ logger.info({ jobId }, '[Generate] Stage 5: Webhook Auto-Bind trigger injection');
117
+ const triggerConfig = webhookAutoBind.buildTriggerConfig(
118
+ triggerDetection.nodeType,
119
+ request,
120
+ intent,
121
+ finalWorkflow.name ?? 'workflow',
122
+ );
123
+ finalGraph = webhookAutoBind.injectTriggerIntoGraph(finalGraph, triggerConfig, triggerDetection.nodeType);
124
+
125
+ // ─── Stage 6: Validation ────────────────────────────────────────────────
126
+ logger.info({ jobId }, '[Generate] Stage 6: Validation');
127
+ const validator = new ValidationEngine(llm, n8nBaseUrl, n8nApiKey);
128
+ let validationReport = await validator.validate(jobId, finalWorkflow, finalGraph);
129
+
130
+ // ─── Stage 7: Self-Healing (if validation failed) ───────────────────────
131
+ let healingResult: import('../services/selfHealing').HealingResult | undefined;
132
+ if (!validationReport.valid && !options.skipSelfHealing) {
133
+ logger.info({ jobId, errors: validationReport.issues.length }, '[Generate] Stage 7: Self-Healing');
134
+ const healer = new SelfHealingSystem(llm);
135
+ healingResult = await healer.heal(finalWorkflow, finalGraph, validationReport);
136
+
137
+ if (healingResult.healed && healingResult.healedWorkflow) {
138
+ finalWorkflow = healingResult.healedWorkflow;
139
+ // Re-validate after healing
140
+ validationReport = await validator.validate(jobId, finalWorkflow, finalGraph);
141
+ logger.info({ jobId, valid: validationReport.valid }, '[Generate] Re-validation after self-healing');
142
+ }
143
+ }
144
+
145
+ // ─── Stage 8: Dry-Run Simulation (unless skipped) ──────────────────────
146
+ let simulationReport: import('../types/workflow').SimulationReport | undefined;
147
+ if (!options.skipSimulation) {
148
+ logger.info({ jobId }, '[Generate] Stage 8: Dry-Run Simulation');
149
+ const simulator = new DryRunSimulator(llm);
150
+ simulationReport = await simulator.simulate(jobId, finalWorkflow, finalGraph, finalPlan);
151
+ }
152
+
153
+ // ─── Stage 9: Credential Analysis ──────────────────────────────────────
154
+ logger.info({ jobId }, '[Generate] Stage 9: Credential Analysis');
155
+ const credentialIntel = new CredentialIntelligence(n8nBaseUrl, n8nApiKey);
156
+ const credentialAnalysis = await credentialIntel.analyse(jobId, finalWorkflow);
157
+
158
+ // ─── Stage 10: Quality Scoring ──────────────────────────────────────────
159
+ const qualityScorer = new QualityScorer();
160
+ const deploymentReadiness = qualityScorer.scoreDeploymentReadiness(
161
+ validationReport,
162
+ simulationReport,
163
+ credentialAnalysis,
164
+ );
165
+ const qualityScore = qualityScorer.calculateQualityScore(validationReport, simulationReport);
166
+
167
+ // ─── Stage 11: Memory storage ───────────────────────────────────────────
168
+ const nodeTypes = finalWorkflow.nodes.map((n) => n.type);
169
+ if (validationReport.valid && (simulationReport?.passed ?? true)) {
170
+ workflowMemory.storeSuccessPattern(
171
+ jobId,
172
+ intent.domain,
173
+ intent.workflowType,
174
+ intent.integrations,
175
+ nodeTypes,
176
+ finalPlan.description ?? '',
177
+ qualityScore.overallScore,
178
+ validationReport.overallScore,
179
+ simulationReport?.readinessScore ?? 0,
180
+ );
181
+ } else {
182
+ workflowMemory.storeFailurePattern(
183
+ jobId,
184
+ intent.domain,
185
+ intent.workflowType,
186
+ intent.integrations,
187
+ nodeTypes,
188
+ finalPlan.description ?? '',
189
+ validationReport.overallScore,
190
+ validationReport.issues.map((i) => i.message),
191
+ );
192
+ }
193
+
194
+ const finalState = (() => {
195
+ if (simulationReport?.passed && validationReport.valid) return 'simulated';
196
+ if (validationReport.valid) return 'validated';
197
+ return 'generated';
198
+ })();
199
+
200
+ logger.info({ jobId, state: finalState, score: qualityScore.overallScore }, '[Generate] Pipeline complete');
201
+
202
+ const response: GenerateWorkflowResponse = {
203
+ success: true,
204
+ jobId,
205
+ state: finalState as GenerateWorkflowResponse['state'],
206
+ message: `Workflow generation pipeline complete. State: ${finalState}. Quality score: ${qualityScore.overallScore}/100`,
207
+ architecturePlan: finalPlan,
208
+ graph: finalGraph,
209
+ compiledWorkflow: finalWorkflow,
210
+ validationReport,
211
+ simulationReport,
212
+ credentialAnalysis,
213
+ deploymentReadiness,
214
+ qualityScore,
215
+ healingResult,
216
+ swarmResult: swarmResult ? {
217
+ swarmSize: swarmResult.swarmSize,
218
+ generatedAt: swarmResult.generatedAt,
219
+ winnerStrategy: swarmResult.winner.planningStrategy,
220
+ winnerScore: swarmResult.winner.qualityScore,
221
+ candidateScores: swarmResult.allCandidates.map((c) => ({
222
+ id: c.id,
223
+ strategy: c.planningStrategy,
224
+ score: c.qualityScore,
225
+ })),
226
+ selectionReason: swarmResult.winner.selectionReason,
227
+ } : undefined,
228
+ triggerDetection: {
229
+ nodeType: triggerDetection.nodeType,
230
+ triggerType: triggerDetection.triggerType,
231
+ confidence: triggerDetection.confidence,
232
+ webhookPath: triggerConfig.webhookPath,
233
+ notes: triggerConfig.notes,
234
+ },
235
+ memoryStats: workflowMemory.getStats(),
236
+ };
237
+
238
+ res.json(response);
239
+ } catch (err) {
240
+ const message = err instanceof Error ? err.message : 'Pipeline error';
241
+ logger.error({ jobId, err }, '[Generate] Pipeline failed');
242
+ res.status(500).json({ success: false, error: message, jobId });
243
+ }
244
+ });
apps/simulator/src/routes/simulate.ts ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Simulator Simulate Route β€” UPGRADED
3
+ * Provider-agnostic dry-run simulation using LLM Gateway
4
+ */
5
+ import { Router } from 'express';
6
+ import { z } from 'zod';
7
+ import { logger } from '../index';
8
+ import { DryRunSimulator } from '../agents/simulator';
9
+ import { createRequestLLM } from '../services/llmClient';
10
+ import type { N8nWorkflow, WorkflowGraph, WorkflowArchitecturePlan } from '../types/workflow';
11
+
12
+ export const simulateRoute: Router = Router();
13
+
14
+ const SimulateSchema = z.object({
15
+ jobId: z.string(),
16
+ workflow: z.record(z.unknown()),
17
+ graph: z.record(z.unknown()),
18
+ architecturePlan: z.record(z.unknown()).optional(),
19
+ mockData: z.record(z.unknown()).optional(),
20
+ llmApiKey: z.string().optional(),
21
+ openaiApiKey: z.string().optional(),
22
+ llmModel: z.string().optional(),
23
+ });
24
+
25
+ simulateRoute.post('/', async (req, res) => {
26
+ const parsed = SimulateSchema.safeParse(req.body);
27
+ if (!parsed.success) {
28
+ res.status(400).json({ success: false, error: 'Invalid request', details: parsed.error.flatten() });
29
+ return;
30
+ }
31
+
32
+ const { jobId, workflow, graph, architecturePlan } = parsed.data;
33
+ const apiKey = parsed.data.llmApiKey ?? parsed.data.openaiApiKey ?? process.env['LLM_API_KEY'] ?? process.env['OPENAI_API_KEY'] ?? '';
34
+
35
+ logger.info({ jobId }, '[Simulate] Starting dry-run simulation');
36
+
37
+ try {
38
+ const llm = createRequestLLM(apiKey);
39
+ const simulator = new DryRunSimulator(llm);
40
+
41
+ const simulationReport = await simulator.simulate(
42
+ jobId,
43
+ workflow as any as N8nWorkflow,
44
+ graph as any as WorkflowGraph,
45
+ (architecturePlan ?? {}) as any as WorkflowArchitecturePlan,
46
+ );
47
+
48
+ logger.info({
49
+ jobId,
50
+ passed: simulationReport.passed,
51
+ score: simulationReport.readinessScore,
52
+ }, '[Simulate] Complete');
53
+
54
+ res.json({ success: true, simulationReport });
55
+ } catch (err) {
56
+ const message = err instanceof Error ? err.message : 'Simulation error';
57
+ logger.error({ jobId, err }, '[Simulate] Failed');
58
+ res.status(500).json({ success: false, error: message, jobId });
59
+ }
60
+ });
apps/simulator/src/routes/validate.ts ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Simulator Validate Route β€” UPGRADED
3
+ * Provider-agnostic validation using LLM Gateway
4
+ * Full: Schema + NodeRegistry + Graph + Expression + DataFlow + Reliability + Credential
5
+ */
6
+ import { Router } from 'express';
7
+ import { z } from 'zod';
8
+ import { logger } from '../index';
9
+ import { ValidationEngine } from '../agents/validator';
10
+ import { createRequestLLM } from '../services/llmClient';
11
+ import type { N8nWorkflow, WorkflowGraph } from '../types/workflow';
12
+
13
+ export const validateRoute: Router = Router();
14
+
15
+ const ValidateSchema = z.object({
16
+ jobId: z.string(),
17
+ workflow: z.record(z.unknown()),
18
+ graph: z.record(z.unknown()),
19
+ llmApiKey: z.string().optional(),
20
+ openaiApiKey: z.string().optional(),
21
+ llmModel: z.string().optional(),
22
+ n8nBaseUrl: z.string(),
23
+ n8nApiKey: z.string(),
24
+ final: z.boolean().optional().default(false),
25
+ });
26
+
27
+ validateRoute.post('/', async (req, res) => {
28
+ const parsed = ValidateSchema.safeParse(req.body);
29
+ if (!parsed.success) {
30
+ res.status(400).json({ success: false, error: 'Invalid request', details: parsed.error.flatten() });
31
+ return;
32
+ }
33
+
34
+ const { jobId, workflow, graph, n8nBaseUrl, n8nApiKey, final: isFinal } = parsed.data;
35
+ const apiKey = parsed.data.llmApiKey ?? parsed.data.openaiApiKey ?? process.env['LLM_API_KEY'] ?? process.env['OPENAI_API_KEY'] ?? '';
36
+
37
+ logger.info({ jobId, final: isFinal }, '[Validate] Starting validation');
38
+
39
+ try {
40
+ const llm = createRequestLLM(apiKey);
41
+ const validator = new ValidationEngine(llm, n8nBaseUrl, n8nApiKey);
42
+ const validationReport = await validator.validate(
43
+ jobId,
44
+ workflow as any as N8nWorkflow,
45
+ graph as any as WorkflowGraph,
46
+ );
47
+
48
+ logger.info({
49
+ jobId,
50
+ valid: validationReport.valid,
51
+ score: validationReport.overallScore,
52
+ errors: validationReport.issues.length,
53
+ }, '[Validate] Complete');
54
+
55
+ res.json({ success: true, validationReport });
56
+ } catch (err) {
57
+ const message = err instanceof Error ? err.message : 'Validation error';
58
+ logger.error({ jobId, err }, '[Validate] Failed');
59
+ res.status(500).json({ success: false, error: message, jobId });
60
+ }
61
+ });
apps/simulator/src/services/llmClient.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * LLM Client Factory for Simulator Service
3
+ * Provides a configured LLMGateway instance from process.env
4
+ * Completely removes direct OpenAI SDK dependency
5
+ */
6
+ import { LLMGateway, type LLMGatewayConfig } from '@wfo/integrations/llm-providers/index';
7
+
8
+ let _instance: LLMGateway | null = null;
9
+
10
+ export function getSimulatorLLM(): LLMGateway {
11
+ if (_instance) return _instance;
12
+
13
+ const env = process.env;
14
+ const provider = (env['LLM_PROVIDER'] ?? 'openai') as LLMGatewayConfig['primary']['provider'];
15
+ const baseUrl = env['LLM_GATEWAY_URL'] ?? 'https://api.openai.com/v1';
16
+ const apiKey = env['LLM_API_KEY'] ?? env['OPENAI_API_KEY'] ?? '';
17
+ const defaultModel = env['LLM_MODEL'] ?? 'gpt-4o';
18
+
19
+ if (!apiKey) {
20
+ throw new Error(
21
+ 'LLM API key not configured. Set LLM_API_KEY (or OPENAI_API_KEY for legacy) in environment.',
22
+ );
23
+ }
24
+
25
+ const config: LLMGatewayConfig = {
26
+ primary: { provider, baseUrl, apiKey, defaultModel },
27
+ defaultRetries: 3,
28
+ defaultRetryDelayMs: 1000,
29
+ defaultTimeoutMs: 90000,
30
+ };
31
+
32
+ // Optional fallback provider
33
+ const fallbackUrl = env['LLM_FALLBACK_URL'];
34
+ const fallbackKey = env['LLM_FALLBACK_KEY'];
35
+ const fallbackModel = env['LLM_FALLBACK_MODEL'] ?? defaultModel;
36
+ if (fallbackUrl && fallbackKey) {
37
+ config.fallback = {
38
+ provider: 'openai-compatible',
39
+ baseUrl: fallbackUrl,
40
+ apiKey: fallbackKey,
41
+ defaultModel: fallbackModel,
42
+ };
43
+ }
44
+
45
+ _instance = new LLMGateway(config);
46
+ return _instance;
47
+ }
48
+
49
+ /**
50
+ * Create a per-request LLM client from a provided API key
51
+ * Used when the API key is passed per-request (from CF Worker)
52
+ */
53
+ export function createRequestLLM(apiKey: string, model?: string): LLMGateway {
54
+ const env = process.env;
55
+ const provider = (env['LLM_PROVIDER'] ?? 'openai') as LLMGatewayConfig['primary']['provider'];
56
+ const baseUrl = env['LLM_GATEWAY_URL'] ?? 'https://api.openai.com/v1';
57
+
58
+ return new LLMGateway({
59
+ primary: {
60
+ provider,
61
+ baseUrl,
62
+ apiKey,
63
+ defaultModel: model ?? env['LLM_MODEL'] ?? 'gpt-4o',
64
+ },
65
+ defaultRetries: 3,
66
+ defaultRetryDelayMs: 1000,
67
+ defaultTimeoutMs: 90000,
68
+ });
69
+ }
apps/simulator/src/services/memorySystem.ts ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Memory / Learning System β€” NEW
3
+ * Stores successful workflow patterns, failed patterns, high-performing architectures
4
+ * Influences node selection, planning, retry strategy, error prevention
5
+ * In-process store (can be extended with Redis/KV for persistence)
6
+ */
7
+
8
+ export interface WorkflowPattern {
9
+ id: string;
10
+ type: 'success' | 'failure';
11
+ domain: string;
12
+ workflowType: string;
13
+ integrations: string[];
14
+ nodeTypes: string[];
15
+ architectureDescription: string;
16
+ qualityScore: number;
17
+ validationScore: number;
18
+ simulationScore: number;
19
+ keyLessons: string[];
20
+ antiPatterns?: string[];
21
+ createdAt: string;
22
+ useCount: number;
23
+ }
24
+
25
+ export interface ArchitectureTemplate {
26
+ id: string;
27
+ name: string;
28
+ domain: string;
29
+ triggers: string[];
30
+ nodeSequence: string[];
31
+ avgQualityScore: number;
32
+ successRate: number;
33
+ description: string;
34
+ usageCount: number;
35
+ }
36
+
37
+ // ─── In-Process Memory Store ──────────────────────────────────────────────────
38
+ class MemoryStore {
39
+ private patterns: Map<string, WorkflowPattern> = new Map();
40
+ private templates: Map<string, ArchitectureTemplate> = new Map();
41
+ private fixStrategies: Map<string, string> = new Map();
42
+
43
+ // ─── Initialize with built-in high-performing templates ──────────────────
44
+ constructor() {
45
+ this.seedBuiltinTemplates();
46
+ }
47
+
48
+ // ─── Pattern Management ───────────────────────────────────────────────────
49
+
50
+ storeSuccessPattern(
51
+ jobId: string,
52
+ domain: string,
53
+ workflowType: string,
54
+ integrations: string[],
55
+ nodeTypes: string[],
56
+ architectureDescription: string,
57
+ qualityScore: number,
58
+ validationScore: number,
59
+ simulationScore: number,
60
+ ): void {
61
+ const id = `success-${jobId}`;
62
+ this.patterns.set(id, {
63
+ id,
64
+ type: 'success',
65
+ domain,
66
+ workflowType,
67
+ integrations,
68
+ nodeTypes,
69
+ architectureDescription,
70
+ qualityScore,
71
+ validationScore,
72
+ simulationScore,
73
+ keyLessons: this.extractLessons('success', nodeTypes, qualityScore),
74
+ createdAt: new Date().toISOString(),
75
+ useCount: 0,
76
+ });
77
+ this.updateTemplate(domain, nodeTypes, qualityScore, true);
78
+ }
79
+
80
+ storeFailurePattern(
81
+ jobId: string,
82
+ domain: string,
83
+ workflowType: string,
84
+ integrations: string[],
85
+ nodeTypes: string[],
86
+ architectureDescription: string,
87
+ qualityScore: number,
88
+ failureReasons: string[],
89
+ ): void {
90
+ const id = `failure-${jobId}`;
91
+ this.patterns.set(id, {
92
+ id,
93
+ type: 'failure',
94
+ domain,
95
+ workflowType,
96
+ integrations,
97
+ nodeTypes,
98
+ architectureDescription,
99
+ qualityScore,
100
+ validationScore: 0,
101
+ simulationScore: 0,
102
+ keyLessons: [],
103
+ antiPatterns: failureReasons,
104
+ createdAt: new Date().toISOString(),
105
+ useCount: 0,
106
+ });
107
+ this.updateTemplate(domain, nodeTypes, qualityScore, false);
108
+ }
109
+
110
+ storeFixStrategy(issuePattern: string, fixDescription: string): void {
111
+ const key = this.normalizeKey(issuePattern);
112
+ this.fixStrategies.set(key, fixDescription);
113
+ }
114
+
115
+ // ─── Memory Retrieval ─────────────────────────────────────────────────────
116
+
117
+ getRelevantPatterns(domain: string, integrations: string[]): WorkflowPattern[] {
118
+ return [...this.patterns.values()]
119
+ .filter((p) =>
120
+ p.domain === domain ||
121
+ integrations.some((i) => p.integrations.includes(i)),
122
+ )
123
+ .sort((a, b) => b.qualityScore - a.qualityScore)
124
+ .slice(0, 5);
125
+ }
126
+
127
+ getHighPerformingTemplates(domain: string): ArchitectureTemplate[] {
128
+ return [...this.templates.values()]
129
+ .filter((t) => t.domain === domain || t.domain === 'general')
130
+ .sort((a, b) => b.avgQualityScore - a.avgQualityScore)
131
+ .slice(0, 3);
132
+ }
133
+
134
+ getSuccessPatterns(): WorkflowPattern[] {
135
+ return [...this.patterns.values()]
136
+ .filter((p) => p.type === 'success' && p.qualityScore >= 80)
137
+ .sort((a, b) => b.qualityScore - a.qualityScore)
138
+ .slice(0, 10);
139
+ }
140
+
141
+ getAntiPatterns(): string[] {
142
+ return [...this.patterns.values()]
143
+ .filter((p) => p.type === 'failure')
144
+ .flatMap((p) => p.antiPatterns ?? [])
145
+ .filter((p, i, arr) => arr.indexOf(p) === i) // unique
146
+ .slice(0, 20);
147
+ }
148
+
149
+ getFixStrategy(issuePattern: string): string | undefined {
150
+ return this.fixStrategies.get(this.normalizeKey(issuePattern));
151
+ }
152
+
153
+ /**
154
+ * Build memory context string for LLM injection
155
+ */
156
+ buildMemoryContext(domain: string, integrations: string[]): string {
157
+ const patterns = this.getRelevantPatterns(domain, integrations);
158
+ const templates = this.getHighPerformingTemplates(domain);
159
+ const antiPatterns = this.getAntiPatterns();
160
+
161
+ const lines: string[] = [];
162
+
163
+ if (patterns.length > 0) {
164
+ lines.push('=== SUCCESSFUL WORKFLOW PATTERNS FROM MEMORY ===');
165
+ patterns
166
+ .filter((p) => p.type === 'success')
167
+ .slice(0, 3)
168
+ .forEach((p) => {
169
+ lines.push(`- Domain: ${p.domain}, Integrations: ${p.integrations.join(', ')}, Score: ${p.qualityScore}/100`);
170
+ lines.push(` Architecture: ${p.architectureDescription}`);
171
+ lines.push(` Nodes: ${p.nodeTypes.join(' β†’ ')}`);
172
+ lines.push(` Lessons: ${p.keyLessons.join('; ')}`);
173
+ });
174
+ }
175
+
176
+ if (templates.length > 0) {
177
+ lines.push('\n=== HIGH-PERFORMING ARCHITECTURE TEMPLATES ===');
178
+ templates.forEach((t) => {
179
+ lines.push(`- ${t.name}: ${t.description}`);
180
+ lines.push(` Node Sequence: ${t.nodeSequence.join(' β†’ ')}`);
181
+ lines.push(` Avg Score: ${t.avgQualityScore}/100, Success Rate: ${Math.round(t.successRate * 100)}%`);
182
+ });
183
+ }
184
+
185
+ if (antiPatterns.length > 0) {
186
+ lines.push('\n=== KNOWN ANTI-PATTERNS TO AVOID ===');
187
+ antiPatterns.slice(0, 5).forEach((ap) => lines.push(`- AVOID: ${ap}`));
188
+ }
189
+
190
+ return lines.length > 0 ? lines.join('\n') : '';
191
+ }
192
+
193
+ getStats(): {
194
+ totalPatterns: number;
195
+ successPatterns: number;
196
+ failurePatterns: number;
197
+ templates: number;
198
+ avgSuccessScore: number;
199
+ fixStrategies: number;
200
+ } {
201
+ const all = [...this.patterns.values()];
202
+ const successes = all.filter((p) => p.type === 'success');
203
+ const avgScore = successes.length > 0
204
+ ? Math.round(successes.reduce((sum, p) => sum + p.qualityScore, 0) / successes.length)
205
+ : 0;
206
+
207
+ return {
208
+ totalPatterns: all.length,
209
+ successPatterns: successes.length,
210
+ failurePatterns: all.filter((p) => p.type === 'failure').length,
211
+ templates: this.templates.size,
212
+ avgSuccessScore: avgScore,
213
+ fixStrategies: this.fixStrategies.size,
214
+ };
215
+ }
216
+
217
+ // ─── Private helpers ──────────────────────────────────────────────────────
218
+
219
+ private updateTemplate(domain: string, nodeTypes: string[], score: number, success: boolean): void {
220
+ const key = `${domain}-${nodeTypes.slice(0, 3).join('-')}`;
221
+ const existing = this.templates.get(key);
222
+
223
+ if (existing) {
224
+ const total = existing.usageCount + 1;
225
+ const successCount = success
226
+ ? Math.round(existing.successRate * existing.usageCount) + 1
227
+ : Math.round(existing.successRate * existing.usageCount);
228
+ this.templates.set(key, {
229
+ ...existing,
230
+ avgQualityScore: Math.round((existing.avgQualityScore * existing.usageCount + score) / total),
231
+ successRate: successCount / total,
232
+ usageCount: total,
233
+ });
234
+ }
235
+ }
236
+
237
+ private extractLessons(type: string, nodeTypes: string[], score: number): string[] {
238
+ const lessons: string[] = [];
239
+ if (score >= 90) lessons.push('High-quality architecture with proper error handling');
240
+ if (nodeTypes.includes('@n8n/n8n-nodes-langchain.agent')) {
241
+ lessons.push('AI Agent with proper system message and dynamic user input');
242
+ }
243
+ if (nodeTypes.includes('n8n-nodes-base.splitInBatches')) {
244
+ lessons.push('Batch processing with SplitInBatches for array handling');
245
+ }
246
+ return lessons;
247
+ }
248
+
249
+ private normalizeKey(text: string): string {
250
+ return text.toLowerCase().replace(/[^a-z0-9]/g, '-').slice(0, 80);
251
+ }
252
+
253
+ // ─── Built-in high-performing templates ───────────────────────────────────
254
+ private seedBuiltinTemplates(): void {
255
+ const builtins: ArchitectureTemplate[] = [
256
+ {
257
+ id: 'telegram-ai-bot',
258
+ name: 'Telegram AI Bot',
259
+ domain: 'ai_workflow',
260
+ triggers: ['n8n-nodes-base.telegramTrigger'],
261
+ nodeSequence: [
262
+ 'n8n-nodes-base.telegramTrigger',
263
+ 'n8n-nodes-base.set',
264
+ '@n8n/n8n-nodes-langchain.agent',
265
+ 'n8n-nodes-base.set',
266
+ 'n8n-nodes-base.telegram',
267
+ ],
268
+ avgQualityScore: 92,
269
+ successRate: 0.95,
270
+ description: 'Telegram bot with AI Agent response β€” SET for input prep, AI Agent, SET for output formatting, Telegram send',
271
+ usageCount: 0,
272
+ },
273
+ {
274
+ id: 'webhook-to-integration',
275
+ name: 'Webhook β†’ Validate β†’ Process β†’ Notify',
276
+ domain: 'webhook_system',
277
+ triggers: ['n8n-nodes-base.webhook'],
278
+ nodeSequence: [
279
+ 'n8n-nodes-base.webhook',
280
+ 'n8n-nodes-base.if',
281
+ 'n8n-nodes-base.set',
282
+ 'n8n-nodes-base.httpRequest',
283
+ 'n8n-nodes-base.slack',
284
+ 'n8n-nodes-base.respondToWebhook',
285
+ ],
286
+ avgQualityScore: 88,
287
+ successRate: 0.92,
288
+ description: 'Webhook trigger β†’ validate input with IF β†’ SET fields β†’ call external API β†’ notify Slack β†’ respond to caller',
289
+ usageCount: 0,
290
+ },
291
+ {
292
+ id: 'scheduled-data-pipeline',
293
+ name: 'Scheduled Data Pipeline',
294
+ domain: 'data_pipeline',
295
+ triggers: ['n8n-nodes-base.scheduleTrigger'],
296
+ nodeSequence: [
297
+ 'n8n-nodes-base.scheduleTrigger',
298
+ 'n8n-nodes-base.httpRequest',
299
+ 'n8n-nodes-base.code',
300
+ 'n8n-nodes-base.splitInBatches',
301
+ 'n8n-nodes-base.set',
302
+ 'n8n-nodes-base.googleSheets',
303
+ ],
304
+ avgQualityScore: 87,
305
+ successRate: 0.90,
306
+ description: 'Cron trigger β†’ fetch data via HTTP β†’ transform with Code β†’ batch process β†’ SET fields β†’ store to Sheets',
307
+ usageCount: 0,
308
+ },
309
+ {
310
+ id: 'email-to-crm',
311
+ name: 'Email Trigger β†’ CRM Update',
312
+ domain: 'crm_integration',
313
+ triggers: ['n8n-nodes-base.emailReadImap'],
314
+ nodeSequence: [
315
+ 'n8n-nodes-base.emailReadImap',
316
+ 'n8n-nodes-base.set',
317
+ 'n8n-nodes-base.if',
318
+ 'n8n-nodes-base.hubspot',
319
+ 'n8n-nodes-base.slack',
320
+ ],
321
+ avgQualityScore: 85,
322
+ successRate: 0.88,
323
+ description: 'Email trigger β†’ extract fields β†’ IF condition β†’ update HubSpot β†’ notify team via Slack',
324
+ usageCount: 0,
325
+ },
326
+ ];
327
+
328
+ builtins.forEach((t) => this.templates.set(t.id, t));
329
+ }
330
+ }
331
+
332
+ // ─── Singleton export ─────────────────────────────────────────────────────────
333
+ export const workflowMemory = new MemoryStore();
apps/simulator/src/services/mockDataGenerator.ts ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Mock Data Generator Service β€” UPGRADED
3
+ * Generates realistic mock inputs for dry-run simulation
4
+ * Provider-agnostic LLM Gateway β€” NO direct OpenAI dependency
5
+ */
6
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
7
+ import type { N8nWorkflow, WorkflowGraph } from '../types/workflow';
8
+
9
+ export class MockDataGenerator {
10
+ private llm: LLMGateway;
11
+
12
+ constructor(llm: LLMGateway) {
13
+ this.llm = llm;
14
+ }
15
+
16
+ async generate(
17
+ workflow: N8nWorkflow,
18
+ graph: WorkflowGraph,
19
+ ): Promise<Record<string, unknown>> {
20
+ const triggerNode = workflow.nodes.find((n) =>
21
+ n.type.toLowerCase().includes('trigger') ||
22
+ n.type.toLowerCase().includes('webhook'),
23
+ );
24
+
25
+ return this.llm.completeJSON<Record<string, unknown>>([
26
+ {
27
+ role: 'system',
28
+ content: `You are a test data engineer. Generate realistic mock data for workflow simulation.
29
+ Always include multiple scenarios: normal, malformed, null_payload, partial_payload, edge_case.
30
+ Data must match the actual fields the workflow nodes expect based on the trigger type.`,
31
+ },
32
+ {
33
+ role: 'user',
34
+ content: `Generate mock input data for this workflow's trigger node.
35
+
36
+ TRIGGER NODE: ${JSON.stringify(triggerNode ?? workflow.nodes[0], null, 2)}
37
+ WORKFLOW NAME: ${workflow.name}
38
+ WORKFLOW DOMAIN: ${graph.metadata.domain}
39
+
40
+ Return JSON:
41
+ {
42
+ "normal": { ... realistic happy-path data matching trigger output ... },
43
+ "malformed": { ... intentionally broken/wrong-type data ... },
44
+ "null_payload": null,
45
+ "partial_payload": { ... some required fields missing ... },
46
+ "edge_case": { ... boundary conditions, very long strings, unicode, empty arrays ... }
47
+ }`,
48
+ },
49
+ ], {
50
+ temperature: 0.3,
51
+ retries: 2,
52
+ }).catch(() => ({
53
+ normal: {},
54
+ malformed: {},
55
+ null_payload: null,
56
+ partial_payload: {},
57
+ edge_case: {},
58
+ error: 'Mock data generation failed β€” using empty defaults',
59
+ }));
60
+ }
61
+ }
apps/simulator/src/services/qualityScorer.ts ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Quality Scorer Service β€” UPGRADED
3
+ * Evaluates workflow quality across multiple dimensions
4
+ * Produces deployment readiness score with detailed breakdown
5
+ */
6
+ import type {
7
+ ValidationReport,
8
+ SimulationReport,
9
+ CredentialAnalysis,
10
+ DeploymentReadiness,
11
+ WorkflowQualityScore,
12
+ } from '../types/workflow';
13
+
14
+ export class QualityScorer {
15
+ scoreDeploymentReadiness(
16
+ validation: ValidationReport,
17
+ simulation: SimulationReport | undefined,
18
+ credentials: CredentialAnalysis,
19
+ ): DeploymentReadiness {
20
+ const blockingIssues: string[] = [];
21
+ const recommendations: string[] = [];
22
+
23
+ // Gate 1: Validation
24
+ if (!validation.valid) {
25
+ const errorCount = validation.issues.length;
26
+ blockingIssues.push(`Validation failed with ${errorCount} error(s): ${validation.issues.map((i) => i.message).slice(0, 3).join('; ')}`);
27
+ }
28
+
29
+ // Gate 2: Node Registry (unknown nodes = blocking)
30
+ if (validation.nodeRegistryValidation && !validation.nodeRegistryValidation.passed) {
31
+ const unknownErrors = validation.nodeRegistryValidation.issues.filter((i) => i.severity === 'error');
32
+ if (unknownErrors.length > 0) {
33
+ blockingIssues.push(`${unknownErrors.length} unknown/invalid node type(s) detected β€” must use only registered n8n node types`);
34
+ }
35
+ }
36
+
37
+ // Gate 3: Simulation
38
+ if (simulation && !simulation.passed) {
39
+ blockingIssues.push(`Dry-run simulation failed (readiness: ${simulation.readinessScore}/100)`);
40
+ }
41
+
42
+ // Gate 4: Deployment blockers from simulation
43
+ if (simulation?.deploymentBlockers && simulation.deploymentBlockers.length > 0) {
44
+ simulation.deploymentBlockers.slice(0, 3).forEach((blocker) => {
45
+ blockingIssues.push(`Simulation blocker: ${blocker}`);
46
+ });
47
+ }
48
+
49
+ // Gate 5: Credentials (warning only β€” some deployments don't have n8n access)
50
+ if (!credentials.allCredentialsPresent && credentials.n8nReachable) {
51
+ const missingTypes = credentials.missing.map((m) => m.credentialType).join(', ');
52
+ blockingIssues.push(`Missing credentials in n8n: ${missingTypes}`);
53
+ }
54
+
55
+ // Recommendations
56
+ if (validation.warnings.length > 0) {
57
+ recommendations.push(`Address ${validation.warnings.length} validation warning(s) for production-grade quality`);
58
+ }
59
+ if (simulation?.predictedFailurePoints && simulation.predictedFailurePoints.length > 0) {
60
+ recommendations.push(`Review ${simulation.predictedFailurePoints.length} predicted failure point(s)`);
61
+ }
62
+ if (simulation?.recommendations) {
63
+ recommendations.push(...simulation.recommendations.slice(0, 3));
64
+ }
65
+ if (!credentials.allCredentialsPresent && !credentials.n8nReachable) {
66
+ recommendations.push('Verify all required credentials are configured in n8n before activation');
67
+ }
68
+
69
+ // Confidence score calculation
70
+ const validationScore = validation.overallScore;
71
+ const simulationScore = simulation?.readinessScore ?? 50;
72
+ const credentialScore = credentials.allCredentialsPresent ? 100 : (credentials.n8nReachable ? 0 : 50);
73
+ const nodeRegistryScore = validation.nodeRegistryValidation?.score ?? 100;
74
+ const dataFlowScore = validation.dataFlowValidation?.score ?? 100;
75
+
76
+ const confidenceScore = Math.round(
77
+ validationScore * 0.30 +
78
+ simulationScore * 0.30 +
79
+ credentialScore * 0.15 +
80
+ nodeRegistryScore * 0.15 +
81
+ dataFlowScore * 0.10,
82
+ );
83
+
84
+ const riskLevel = (() => {
85
+ if (confidenceScore >= 85) return 'low' as const;
86
+ if (confidenceScore >= 65) return 'medium' as const;
87
+ if (confidenceScore >= 40) return 'high' as const;
88
+ return 'critical' as const;
89
+ })();
90
+
91
+ return {
92
+ deploymentReady: blockingIssues.length === 0,
93
+ confidenceScore,
94
+ riskLevel,
95
+ blockingIssues,
96
+ recommendations,
97
+ };
98
+ }
99
+
100
+ calculateQualityScore(
101
+ validation: ValidationReport,
102
+ simulation: SimulationReport | undefined,
103
+ ): WorkflowQualityScore {
104
+ const reliabilityScore = validation.reliabilityValidation.score;
105
+ const observabilityScore = Math.min(100, reliabilityScore + 10);
106
+ const retryCoverage = Math.max(0, reliabilityScore - 10);
107
+ const expressionSafety = validation.expressionValidation.score;
108
+ const credentialReadiness = validation.credentialValidation.score;
109
+ const branchingQuality = validation.graphValidation.score;
110
+ const deploymentSafety = validation.schemaValidation.score;
111
+ const nodeRegistryScore = validation.nodeRegistryValidation?.score ?? 100;
112
+ const dataFlowScore = validation.dataFlowValidation?.score ?? 100;
113
+
114
+ const maintainabilityScore = Math.round((branchingQuality + deploymentSafety + nodeRegistryScore) / 3);
115
+
116
+ const overallScore = Math.round(
117
+ reliabilityScore * 0.15 +
118
+ expressionSafety * 0.15 +
119
+ credentialReadiness * 0.10 +
120
+ branchingQuality * 0.10 +
121
+ deploymentSafety * 0.10 +
122
+ nodeRegistryScore * 0.20 +
123
+ dataFlowScore * 0.10 +
124
+ observabilityScore * 0.10,
125
+ );
126
+
127
+ const riskLevel = overallScore >= 85 ? 'low' as const
128
+ : overallScore >= 65 ? 'medium' as const
129
+ : overallScore >= 40 ? 'high' as const
130
+ : 'critical' as const;
131
+
132
+ return {
133
+ overallScore,
134
+ reliabilityScore,
135
+ maintainabilityScore,
136
+ observabilityScore,
137
+ retryCoverage,
138
+ expressionSafety,
139
+ credentialReadiness,
140
+ branchingQuality,
141
+ deploymentSafety,
142
+ riskLevel,
143
+ scoreExplanations: {
144
+ overall: `Overall quality: ${overallScore}/100 (${riskLevel} risk)`,
145
+ reliability: `Retry policies and fallback coverage: ${reliabilityScore}/100`,
146
+ maintainability: `Graph structure, naming, registry compliance: ${maintainabilityScore}/100`,
147
+ observability: `Logging and monitoring coverage: ${observabilityScore}/100`,
148
+ expressionSafety: `Expression null-safety (optional chaining): ${expressionSafety}/100`,
149
+ credentialReadiness: `Credential availability: ${credentialReadiness}/100`,
150
+ nodeRegistry: `Registry compliance (no unknown nodes): ${nodeRegistryScore}/100`,
151
+ dataFlow: `Data flow correctness (expressions, SET, IF, CODE): ${dataFlowScore}/100`,
152
+ },
153
+ };
154
+ }
155
+ }
apps/simulator/src/services/selfHealing.ts ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Self-Healing System β€” NEW
3
+ * Detects root cause of workflow failures and applies corrective transformations
4
+ * Stores failure patterns in memory, prevents repeat failures
5
+ * Integrates with validation + simulation pipeline
6
+ */
7
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
8
+ import type { ValidationReport, SimulationReport, N8nWorkflow, WorkflowGraph } from '../types/workflow';
9
+ import { isValidNodeType } from '../knowledge/nodeRegistry';
10
+
11
+ export interface HealingAction {
12
+ type: 'replace_node' | 'fix_expression' | 'add_parameter' | 'fix_connection' | 'add_retry' | 'remove_node';
13
+ targetNodeId?: string;
14
+ description: string;
15
+ applied: boolean;
16
+ before?: unknown;
17
+ after?: unknown;
18
+ }
19
+
20
+ export interface HealingResult {
21
+ healed: boolean;
22
+ actionsApplied: HealingAction[];
23
+ remainingIssues: string[];
24
+ healedWorkflow?: N8nWorkflow;
25
+ confidence: number; // 0-100
26
+ }
27
+
28
+ export interface FailurePattern {
29
+ id: string;
30
+ pattern: string;
31
+ rootCause: string;
32
+ fixStrategy: string;
33
+ occurrences: number;
34
+ lastSeen: string;
35
+ }
36
+
37
+ // In-memory failure store (persists across requests in a single process)
38
+ const failureMemory: Map<string, FailurePattern> = new Map();
39
+
40
+ export class SelfHealingSystem {
41
+ private llm: LLMGateway;
42
+
43
+ constructor(llm: LLMGateway) {
44
+ this.llm = llm;
45
+ }
46
+
47
+ /**
48
+ * Attempt to auto-heal a workflow based on validation and simulation reports
49
+ */
50
+ async heal(
51
+ workflow: N8nWorkflow,
52
+ graph: WorkflowGraph,
53
+ validationReport: ValidationReport,
54
+ simulationReport?: SimulationReport,
55
+ ): Promise<HealingResult> {
56
+ const actions: HealingAction[] = [];
57
+ let healedWorkflow = structuredClone(workflow);
58
+
59
+ // ─── Phase 1: Rule-based deterministic healing ────────────────────────
60
+ const ruleBasedActions = this.applyRuleBasedFixes(healedWorkflow, validationReport);
61
+ actions.push(...ruleBasedActions.actions);
62
+ healedWorkflow = ruleBasedActions.workflow;
63
+
64
+ // ─── Phase 2: AI-assisted healing for complex issues ──────────────────
65
+ const complexIssues = validationReport.issues.filter((i) =>
66
+ i.severity === 'error' &&
67
+ !actions.some((a) => a.targetNodeId === i.nodeId && a.applied),
68
+ );
69
+
70
+ if (complexIssues.length > 0) {
71
+ try {
72
+ const aiHealing = await this.applyAIHealing(healedWorkflow, graph, complexIssues, simulationReport);
73
+ actions.push(...aiHealing.actions);
74
+ if (aiHealing.healedWorkflow) {
75
+ healedWorkflow = aiHealing.healedWorkflow;
76
+ }
77
+ } catch (err) {
78
+ console.warn('[SelfHealing] AI healing phase failed:', err);
79
+ }
80
+ }
81
+
82
+ // ─── Phase 3: Record failure pattern ─────────────────────────────────
83
+ this.recordFailurePattern(validationReport, simulationReport, actions);
84
+
85
+ const appliedCount = actions.filter((a) => a.applied).length;
86
+ const remainingErrors = validationReport.issues.filter((i) =>
87
+ i.severity === 'error' &&
88
+ !actions.some((a) => a.targetNodeId === i.nodeId && a.applied),
89
+ );
90
+
91
+ return {
92
+ healed: appliedCount > 0 && remainingErrors.length === 0,
93
+ actionsApplied: actions.filter((a) => a.applied),
94
+ remainingIssues: remainingErrors.map((i) => i.message),
95
+ healedWorkflow: appliedCount > 0 ? healedWorkflow : undefined,
96
+ confidence: Math.round((appliedCount / Math.max(1, actions.length)) * 100),
97
+ };
98
+ }
99
+
100
+ // ─── Rule-Based Fixes (deterministic, no LLM needed) ─────────────────────
101
+ private applyRuleBasedFixes(
102
+ workflow: N8nWorkflow,
103
+ report: ValidationReport,
104
+ ): { workflow: N8nWorkflow; actions: HealingAction[] } {
105
+ const actions: HealingAction[] = [];
106
+
107
+ // Fix 1: active must be false
108
+ if ((workflow.active as any) === true) {
109
+ workflow.active = false;
110
+ actions.push({
111
+ type: 'fix_expression',
112
+ description: 'Set active: false (safety rule β€” never auto-activate)',
113
+ applied: true,
114
+ before: true,
115
+ after: false,
116
+ });
117
+ }
118
+
119
+ // Fix 2: Missing node IDs β†’ generate UUID-like IDs
120
+ workflow.nodes.forEach((node) => {
121
+ if (!node.id || node.id.trim() === '') {
122
+ const newId = generateId();
123
+ actions.push({
124
+ type: 'add_parameter',
125
+ targetNodeId: node.name,
126
+ description: `Generated missing ID for node "${node.name}"`,
127
+ applied: true,
128
+ before: node.id,
129
+ after: newId,
130
+ });
131
+ node.id = newId;
132
+ }
133
+ });
134
+
135
+ // Fix 3: Missing typeVersion β†’ set to 1
136
+ workflow.nodes.forEach((node) => {
137
+ if (!node.typeVersion || node.typeVersion < 1) {
138
+ actions.push({
139
+ type: 'add_parameter',
140
+ targetNodeId: node.id,
141
+ description: `Added missing typeVersion: 1 to node "${node.name}"`,
142
+ applied: true,
143
+ before: node.typeVersion,
144
+ after: 1,
145
+ });
146
+ node.typeVersion = 1;
147
+ }
148
+ });
149
+
150
+ // Fix 4: Missing position β†’ assign default grid positions
151
+ workflow.nodes.forEach((node, i) => {
152
+ if (!node.position || !Array.isArray(node.position) || node.position.length !== 2) {
153
+ const newPos: [number, number] = [i * 220, 300];
154
+ actions.push({
155
+ type: 'fix_expression',
156
+ targetNodeId: node.id,
157
+ description: `Set default position for node "${node.name}"`,
158
+ applied: true,
159
+ before: node.position,
160
+ after: newPos,
161
+ });
162
+ node.position = newPos;
163
+ }
164
+ });
165
+
166
+ // Fix 5: Add retry to external nodes that support it
167
+ workflow.nodes.forEach((node) => {
168
+ const externalTypes = [
169
+ 'n8n-nodes-base.httpRequest',
170
+ 'n8n-nodes-base.telegram',
171
+ 'n8n-nodes-base.slack',
172
+ 'n8n-nodes-base.gmail',
173
+ 'n8n-nodes-base.openAi',
174
+ '@n8n/n8n-nodes-langchain.agent',
175
+ 'n8n-nodes-base.googleSheets',
176
+ 'n8n-nodes-base.airtable',
177
+ 'n8n-nodes-base.notion',
178
+ 'n8n-nodes-base.postgres',
179
+ 'n8n-nodes-base.hubspot',
180
+ 'n8n-nodes-base.stripe',
181
+ 'n8n-nodes-base.github',
182
+ ];
183
+
184
+ if (externalTypes.includes(node.type) && !node.retryOnFail) {
185
+ actions.push({
186
+ type: 'add_retry',
187
+ targetNodeId: node.id,
188
+ description: `Added retry policy to external node "${node.name}"`,
189
+ applied: true,
190
+ before: { retryOnFail: false },
191
+ after: { retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 },
192
+ });
193
+ node.retryOnFail = true;
194
+ node.maxTries = node.maxTries ?? 3;
195
+ node.waitBetweenTries = node.waitBetweenTries ?? 1000;
196
+ }
197
+ });
198
+
199
+ // Fix 6: onError field default
200
+ workflow.nodes.forEach((node) => {
201
+ if (!node.onError) {
202
+ node.onError = 'continueErrorOutput';
203
+ actions.push({
204
+ type: 'add_parameter',
205
+ targetNodeId: node.id,
206
+ description: `Set onError: continueErrorOutput for node "${node.name}"`,
207
+ applied: true,
208
+ before: undefined,
209
+ after: 'continueErrorOutput',
210
+ });
211
+ }
212
+ });
213
+
214
+ // Fix 7: Remove nodes with unknown types from registry
215
+ const unknownNodes = workflow.nodes.filter((n) => n.type && !isValidNodeType(n.type));
216
+ unknownNodes.forEach((node) => {
217
+ actions.push({
218
+ type: 'remove_node',
219
+ targetNodeId: node.id,
220
+ description: `REJECTED unknown node type "${node.type}" for node "${node.name}" β€” not in registry`,
221
+ applied: true,
222
+ before: node.type,
223
+ after: null,
224
+ });
225
+ });
226
+ if (unknownNodes.length > 0) {
227
+ const unknownIds = new Set(unknownNodes.map((n) => n.id));
228
+ workflow.nodes = workflow.nodes.filter((n) => !unknownIds.has(n.id));
229
+ }
230
+
231
+ // Fix 8: Ensure settings are complete
232
+ if (!workflow.settings) {
233
+ workflow.settings = {};
234
+ actions.push({
235
+ type: 'add_parameter',
236
+ description: 'Added missing workflow settings object',
237
+ applied: true,
238
+ before: undefined,
239
+ after: {},
240
+ });
241
+ }
242
+ if (!workflow.settings.executionOrder) {
243
+ workflow.settings.executionOrder = 'v1';
244
+ actions.push({
245
+ type: 'add_parameter',
246
+ description: 'Set settings.executionOrder: v1',
247
+ applied: true,
248
+ });
249
+ }
250
+
251
+ return { workflow, actions };
252
+ }
253
+
254
+ // ─── AI-Assisted Healing (for complex expression / logic issues) ──────────
255
+ private async applyAIHealing(
256
+ workflow: N8nWorkflow,
257
+ graph: WorkflowGraph,
258
+ issues: Array<{ message: string; nodeId?: string; suggestion: string; category: string }>,
259
+ simulationReport?: SimulationReport,
260
+ ): Promise<{ actions: HealingAction[]; healedWorkflow?: N8nWorkflow }> {
261
+ // Find the historical fix if available
262
+ const historicalFixes = this.getRelevantHistoricalFixes(issues.map((i) => i.message));
263
+
264
+ const result = await this.llm.completeJSON<{
265
+ fixedWorkflow: N8nWorkflow;
266
+ actionsApplied: Array<{ nodeId?: string; description: string; type: string }>;
267
+ }>([
268
+ {
269
+ role: 'system',
270
+ content: `You are an n8n workflow self-healing system. Your job is to fix broken workflows.
271
+ Fix ONLY the identified issues. Do not change anything else.
272
+ NEVER set active: true.
273
+ ALWAYS use real expressions like {{$json?.field ?? ""}}.
274
+ Return the complete fixed workflow JSON.`,
275
+ },
276
+ {
277
+ role: 'user',
278
+ content: `Fix these workflow issues:
279
+
280
+ CURRENT WORKFLOW:
281
+ ${JSON.stringify(workflow, null, 2)}
282
+
283
+ ISSUES TO FIX:
284
+ ${issues.map((i, idx) => `${idx + 1}. [${i.category}] ${i.message}\n Fix: ${i.suggestion}`).join('\n')}
285
+
286
+ ${simulationReport ? `SIMULATION FAILURES:\n${JSON.stringify(simulationReport.predictedFailurePoints, null, 2)}` : ''}
287
+
288
+ ${historicalFixes.length > 0 ? `HISTORICAL FIX STRATEGIES:\n${historicalFixes.join('\n')}` : ''}
289
+
290
+ Return JSON:
291
+ {
292
+ "fixedWorkflow": { ... complete fixed n8n workflow ... },
293
+ "actionsApplied": [{ "nodeId": "...", "description": "what was fixed", "type": "fix_type" }]
294
+ }`,
295
+ },
296
+ ], {
297
+ temperature: 0.0,
298
+ retries: 2,
299
+ });
300
+
301
+ const actions: HealingAction[] = (result.actionsApplied ?? []).map((a) => ({
302
+ type: a.type as HealingAction['type'],
303
+ targetNodeId: a.nodeId,
304
+ description: a.description,
305
+ applied: true,
306
+ }));
307
+
308
+ return {
309
+ actions,
310
+ healedWorkflow: result.fixedWorkflow ? {
311
+ ...result.fixedWorkflow,
312
+ active: false, // Always enforce safety
313
+ } : undefined,
314
+ };
315
+ }
316
+
317
+ // ─── Failure Pattern Memory ───────────────────────────────────────────────
318
+ private recordFailurePattern(
319
+ validation: ValidationReport,
320
+ simulation: SimulationReport | undefined,
321
+ actions: HealingAction[],
322
+ ): void {
323
+ validation.issues.forEach((issue) => {
324
+ const key = `${issue.category}-${issue.id}`;
325
+ const existing = failureMemory.get(key);
326
+ const action = actions.find((a) => a.targetNodeId === issue.nodeId);
327
+
328
+ if (existing) {
329
+ failureMemory.set(key, {
330
+ ...existing,
331
+ occurrences: existing.occurrences + 1,
332
+ lastSeen: new Date().toISOString(),
333
+ fixStrategy: action?.description ?? existing.fixStrategy,
334
+ });
335
+ } else {
336
+ failureMemory.set(key, {
337
+ id: key,
338
+ pattern: issue.message,
339
+ rootCause: issue.category,
340
+ fixStrategy: action?.description ?? issue.suggestion,
341
+ occurrences: 1,
342
+ lastSeen: new Date().toISOString(),
343
+ });
344
+ }
345
+ });
346
+ }
347
+
348
+ private getRelevantHistoricalFixes(issueMessages: string[]): string[] {
349
+ const fixes: string[] = [];
350
+ failureMemory.forEach((pattern) => {
351
+ const isRelevant = issueMessages.some(
352
+ (msg) => msg.toLowerCase().includes(pattern.rootCause.toLowerCase()),
353
+ );
354
+ if (isRelevant && pattern.occurrences > 1) {
355
+ fixes.push(`Pattern: "${pattern.pattern}" β†’ Fix: "${pattern.fixStrategy}" (seen ${pattern.occurrences}x)`);
356
+ }
357
+ });
358
+ return fixes.slice(0, 5); // Top 5 most relevant
359
+ }
360
+
361
+ /**
362
+ * Get current failure memory stats (for reporting)
363
+ */
364
+ getMemoryStats(): { totalPatterns: number; topFailures: FailurePattern[] } {
365
+ const patterns = [...failureMemory.values()];
366
+ const sorted = patterns.sort((a, b) => b.occurrences - a.occurrences);
367
+ return {
368
+ totalPatterns: patterns.length,
369
+ topFailures: sorted.slice(0, 10),
370
+ };
371
+ }
372
+ }
373
+
374
+ function generateId(): string {
375
+ const hex = () => Math.floor(Math.random() * 0x10000).toString(16).padStart(4, '0');
376
+ return `${hex()}${hex()}-${hex()}-${hex()}-${hex()}-${hex()}${hex()}${hex()}`;
377
+ }
apps/simulator/src/services/swarmOrchestrator.ts ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Swarm Orchestrator β€” NEW
3
+ * Generates multiple workflow designs in parallel, selects the best via scoring
4
+ * Multi-agent swarm: each agent produces an independent design candidate
5
+ * Best candidate wins based on quality scoring
6
+ */
7
+ import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
8
+ import type {
9
+ WorkflowIntent,
10
+ WorkflowArchitecturePlan,
11
+ WorkflowGraph,
12
+ N8nWorkflow,
13
+ ValidationReport,
14
+ SimulationReport,
15
+ } from '../types/workflow';
16
+ import { WorkflowPlannerAgent } from '../agents/workflowPlanner';
17
+ import { WorkflowGraphEngine } from '../agents/graphEngine';
18
+ import { WorkflowCompiler } from '../agents/compiler';
19
+ import { ValidationEngine } from '../agents/validator';
20
+ import { QualityScorer } from './qualityScorer';
21
+
22
+ export interface SwarmCandidate {
23
+ id: string;
24
+ plan: WorkflowArchitecturePlan;
25
+ graph: WorkflowGraph;
26
+ workflow: N8nWorkflow;
27
+ validationReport: ValidationReport;
28
+ qualityScore: number;
29
+ planningStrategy: string;
30
+ selectionReason?: string;
31
+ }
32
+
33
+ export interface SwarmResult {
34
+ winner: SwarmCandidate;
35
+ allCandidates: SwarmCandidate[];
36
+ swarmSize: number;
37
+ generatedAt: string;
38
+ }
39
+
40
+ export class SwarmOrchestrator {
41
+ private llm: LLMGateway;
42
+ private qualityScorer: QualityScorer;
43
+
44
+ constructor(llm: LLMGateway) {
45
+ this.llm = llm;
46
+ this.qualityScorer = new QualityScorer();
47
+ }
48
+
49
+ /**
50
+ * Run swarm: generate N parallel workflow designs, pick the best
51
+ * @param swarmSize Number of parallel designs (default: 3)
52
+ */
53
+ async runSwarm(
54
+ userRequest: string,
55
+ intent: WorkflowIntent,
56
+ n8nBaseUrl: string,
57
+ n8nApiKey: string,
58
+ swarmSize = 3,
59
+ ): Promise<SwarmResult> {
60
+ const strategies = this.getSwarmStrategies(intent).slice(0, swarmSize);
61
+
62
+ console.log(`[Swarm] Generating ${strategies.length} parallel workflow designs...`);
63
+
64
+ // ─── Generate all candidates in parallel ─────────────────────────────
65
+ const candidateResults = await Promise.allSettled(
66
+ strategies.map((strategy, idx) =>
67
+ this.generateCandidate(
68
+ `candidate-${idx + 1}`,
69
+ userRequest,
70
+ intent,
71
+ strategy,
72
+ n8nBaseUrl,
73
+ n8nApiKey,
74
+ ),
75
+ ),
76
+ );
77
+
78
+ const candidates: SwarmCandidate[] = candidateResults
79
+ .filter((r): r is PromiseFulfilledResult<SwarmCandidate> => r.status === 'fulfilled')
80
+ .map((r) => r.value);
81
+
82
+ if (candidates.length === 0) {
83
+ throw new Error('Swarm: all candidates failed to generate. Check LLM connectivity.');
84
+ }
85
+
86
+ // ─── Score and rank all candidates ───────────────────────────────────
87
+ const ranked = candidates
88
+ .sort((a, b) => b.qualityScore - a.qualityScore);
89
+
90
+ const winner = ranked[0]!;
91
+ winner.selectionReason = this.buildSelectionReason(winner, ranked);
92
+
93
+ console.log(
94
+ `[Swarm] Winner: ${winner.id} (score: ${winner.qualityScore}) from ${candidates.length} candidates`,
95
+ );
96
+
97
+ return {
98
+ winner,
99
+ allCandidates: ranked,
100
+ swarmSize: candidates.length,
101
+ generatedAt: new Date().toISOString(),
102
+ };
103
+ }
104
+
105
+ // ─── Generate a single candidate ─────────────────────────────────────────
106
+ private async generateCandidate(
107
+ id: string,
108
+ userRequest: string,
109
+ intent: WorkflowIntent,
110
+ strategy: { name: string; plannerHint: string; compilerHint: string },
111
+ n8nBaseUrl: string,
112
+ n8nApiKey: string,
113
+ ): Promise<SwarmCandidate> {
114
+ // Each candidate uses the same LLM but with different strategy prompts
115
+ const planner = new WorkflowPlannerAgent(this.llm);
116
+ const graphEngine = new WorkflowGraphEngine(this.llm);
117
+ const compiler = new WorkflowCompiler(this.llm);
118
+ const validator = new ValidationEngine(this.llm, n8nBaseUrl, n8nApiKey);
119
+
120
+ // Planner with strategy-specific hint injected into the request
121
+ const augmentedRequest = `${userRequest}\n\n[DESIGN STRATEGY: ${strategy.plannerHint}]`;
122
+ const plan = await planner.plan(augmentedRequest, intent);
123
+ const graph = await graphEngine.buildGraph(userRequest, intent, plan);
124
+ const workflow = await compiler.compile(graph, intent);
125
+
126
+ // Run validation to score this candidate
127
+ const validationReport = await validator.validate(id, workflow, graph);
128
+
129
+ // Calculate quality score
130
+ const qualityScore = this.scoreCandidate(validationReport, workflow, graph);
131
+
132
+ return {
133
+ id,
134
+ plan,
135
+ graph,
136
+ workflow,
137
+ validationReport,
138
+ qualityScore,
139
+ planningStrategy: strategy.name,
140
+ };
141
+ }
142
+
143
+ // ─── Strategy definitions ─────────────────────────────────────────────────
144
+ private getSwarmStrategies(intent: WorkflowIntent): Array<{
145
+ name: string;
146
+ plannerHint: string;
147
+ compilerHint: string;
148
+ }> {
149
+ const baseStrategies = [
150
+ {
151
+ name: 'Reliability-First',
152
+ plannerHint: 'Focus on maximum reliability: retry policies on all external nodes, error handling on every branch, monitoring nodes, and fallback paths for every critical operation.',
153
+ compilerHint: 'Maximize retry policies, add error output branches, include monitoring nodes',
154
+ },
155
+ {
156
+ name: 'Simplicity-First',
157
+ plannerHint: 'Focus on minimal node count with clean linear flow. Avoid over-engineering. Use the minimum necessary nodes to achieve the goal reliably.',
158
+ compilerHint: 'Keep node count minimal, prefer linear flow, avoid unnecessary branches',
159
+ },
160
+ {
161
+ name: 'Observability-First',
162
+ plannerHint: 'Focus on full observability: add logging at every stage, notification nodes for success and failure, detailed SET nodes for data tracking, and comprehensive audit trail.',
163
+ compilerHint: 'Add monitoring nodes, notification steps, detailed logging with SET nodes',
164
+ },
165
+ ];
166
+
167
+ // Add AI-specific strategy if needed
168
+ if (intent.requiresAI) {
169
+ baseStrategies.push({
170
+ name: 'AI-Optimized',
171
+ plannerHint: 'Optimize for AI/LLM processing: proper context passing, memory nodes for conversation history, tool-enabled agent architecture, and robust AI output parsing.',
172
+ compilerHint: 'Use AI Agent with memory, proper system/user message separation, output parsing',
173
+ });
174
+ }
175
+
176
+ // Add domain-specific strategy
177
+ if (intent.domain === 'data_pipeline') {
178
+ baseStrategies.push({
179
+ name: 'Pipeline-Optimized',
180
+ plannerHint: 'Optimize for data pipeline: batch processing, deduplication, efficient array handling with SplitInBatches, and data validation at each stage.',
181
+ compilerHint: 'Use SplitInBatches for arrays, ItemLists for dedup, Filter for validation',
182
+ });
183
+ }
184
+
185
+ return baseStrategies;
186
+ }
187
+
188
+ // ─── Candidate scoring ────────────────────────────────────────────────────
189
+ private scoreCandidate(
190
+ validation: ValidationReport,
191
+ workflow: N8nWorkflow,
192
+ graph: WorkflowGraph,
193
+ ): number {
194
+ let score = validation.overallScore;
195
+
196
+ // Bonus: has monitoring nodes
197
+ const hasMonitoring = graph.nodes.some((n) => n.layer === 'monitoring');
198
+ if (hasMonitoring) score = Math.min(100, score + 5);
199
+
200
+ // Bonus: all external nodes have retry
201
+ const externalNodes = workflow.nodes.filter((n) => !n.type.includes('trigger') && !n.type.includes('noOp'));
202
+ const allHaveRetry = externalNodes.every((n) => n.retryOnFail);
203
+ if (allHaveRetry && externalNodes.length > 0) score = Math.min(100, score + 5);
204
+
205
+ // Bonus: has expressions (not static)
206
+ const paramsStr = JSON.stringify(workflow.nodes.map((n) => n.parameters));
207
+ const expressionCount = (paramsStr.match(/\{\{/g) ?? []).length;
208
+ if (expressionCount >= 3) score = Math.min(100, score + 5);
209
+
210
+ // Penalty: unknown nodes present
211
+ const unknownRejected = ((graph.metadata as any)?.unknownNodesRejected as string[]) ?? [];
212
+ if (unknownRejected.length > 0) score = Math.max(0, score - 20);
213
+
214
+ // Penalty: active: true (should never happen but check)
215
+ if ((workflow.active as any) === true) score = 0;
216
+
217
+ return Math.round(score);
218
+ }
219
+
220
+ private buildSelectionReason(winner: SwarmCandidate, ranked: SwarmCandidate[]): string {
221
+ const others = ranked.slice(1);
222
+ const gaps = others.map((c) => `${c.id}(${c.qualityScore})`).join(', ');
223
+ return `Selected "${winner.planningStrategy}" strategy with score ${winner.qualityScore}/100. ` +
224
+ `Outperformed: ${gaps || 'no other candidates'}. ` +
225
+ `Validation: ${winner.validationReport.valid ? 'PASSED' : 'FAILED'}. ` +
226
+ `Issues: ${winner.validationReport.issues.length} errors, ${winner.validationReport.warnings.length} warnings.`;
227
+ }
228
+ }
apps/simulator/src/services/webhookAutoBind.ts ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Webhook Auto-Bind System β€” NEW
3
+ * Automatically detects trigger type from intent and assigns correct trigger node
4
+ * Auto-generates webhook paths, auto-configures trigger settings
5
+ * User NEVER manually defines triggers
6
+ */
7
+ import type { WorkflowIntent, WorkflowGraph } from '../types/workflow';
8
+ import { getNodeDef } from '../knowledge/nodeRegistry';
9
+
10
+ export interface TriggerConfig {
11
+ nodeType: string;
12
+ displayName: string;
13
+ parameters: Record<string, unknown>;
14
+ credentials?: Record<string, { id: string; name: string }>;
15
+ webhookPath?: string;
16
+ notes: string;
17
+ }
18
+
19
+ // ─── Trigger detection rules (ordered by specificity) ─────────────────────────
20
+ const TRIGGER_RULES: Array<{
21
+ keywords: string[];
22
+ integrations?: string[];
23
+ domain?: string[];
24
+ triggerType: string;
25
+ nodeType: string;
26
+ priority: number;
27
+ }> = [
28
+ // Telegram
29
+ {
30
+ keywords: ['telegram', 'bot', 'message', 'chat'],
31
+ integrations: ['telegram'],
32
+ triggerType: 'telegram',
33
+ nodeType: 'n8n-nodes-base.telegramTrigger',
34
+ priority: 100,
35
+ },
36
+ // Slack
37
+ {
38
+ keywords: ['slack', 'channel', 'workspace'],
39
+ integrations: ['slack'],
40
+ triggerType: 'slack',
41
+ nodeType: 'n8n-nodes-base.slackTrigger',
42
+ priority: 100,
43
+ },
44
+ // GitHub
45
+ {
46
+ keywords: ['github', 'push', 'pull request', 'issue', 'commit', 'repository'],
47
+ integrations: ['github'],
48
+ triggerType: 'github',
49
+ nodeType: 'n8n-nodes-base.githubTrigger',
50
+ priority: 100,
51
+ },
52
+ // Email (IMAP)
53
+ {
54
+ keywords: ['email', 'inbox', 'imap', 'mail received', 'new email', 'receive email'],
55
+ integrations: ['email', 'imap', 'gmail'],
56
+ triggerType: 'email',
57
+ nodeType: 'n8n-nodes-base.emailReadImap',
58
+ priority: 90,
59
+ },
60
+ // Notion
61
+ {
62
+ keywords: ['notion', 'database page', 'notion page'],
63
+ integrations: ['notion'],
64
+ triggerType: 'notion',
65
+ nodeType: 'n8n-nodes-base.notionTrigger',
66
+ priority: 90,
67
+ },
68
+ // Schedule / Cron
69
+ {
70
+ keywords: [
71
+ 'every hour', 'every day', 'daily', 'hourly', 'weekly', 'monthly', 'schedule',
72
+ 'cron', 'at midnight', 'every morning', 'every night', 'periodic', 'interval',
73
+ 'every minute', 'every 5 minutes', 'nightly', 'scheduled',
74
+ ],
75
+ triggerType: 'schedule',
76
+ nodeType: 'n8n-nodes-base.scheduleTrigger',
77
+ priority: 80,
78
+ },
79
+ // Webhook (general HTTP)
80
+ {
81
+ keywords: [
82
+ 'webhook', 'api call', 'http request', 'post request', 'form submission',
83
+ 'when called', 'endpoint', 'receive data', 'api endpoint', 'rest api',
84
+ 'when a request', 'when triggered via',
85
+ ],
86
+ triggerType: 'webhook',
87
+ nodeType: 'n8n-nodes-base.webhook',
88
+ priority: 70,
89
+ },
90
+ // Manual (fallback)
91
+ {
92
+ keywords: ['manually', 'manual trigger', 'on demand', 'test', 'demo'],
93
+ triggerType: 'manual',
94
+ nodeType: 'n8n-nodes-base.manualTrigger',
95
+ priority: 10,
96
+ },
97
+ ];
98
+
99
+ export class WebhookAutoBindSystem {
100
+
101
+ /**
102
+ * Detect the appropriate trigger node type from intent and request text
103
+ */
104
+ detectTrigger(
105
+ userRequest: string,
106
+ intent: WorkflowIntent,
107
+ ): { nodeType: string; triggerType: string; confidence: 'high' | 'medium' | 'low' } {
108
+ const requestLower = userRequest.toLowerCase();
109
+ const integrationLower = intent.integrations.map((i) => i.toLowerCase());
110
+
111
+ let bestMatch: (typeof TRIGGER_RULES)[0] | undefined;
112
+ let bestScore = 0;
113
+
114
+ for (const rule of TRIGGER_RULES) {
115
+ let score = 0;
116
+
117
+ // Keyword matches in request
118
+ const keywordMatches = rule.keywords.filter((kw) => requestLower.includes(kw));
119
+ score += keywordMatches.length * 10;
120
+
121
+ // Integration matches
122
+ if (rule.integrations) {
123
+ const integrationMatches = rule.integrations.filter((i) =>
124
+ integrationLower.some((intent_i) => intent_i.includes(i) || i.includes(intent_i)),
125
+ );
126
+ score += integrationMatches.length * 20;
127
+ }
128
+
129
+ // Exact trigger type hint from intent
130
+ if (intent.triggerType && intent.triggerType === rule.triggerType) {
131
+ score += 50;
132
+ }
133
+
134
+ // Apply rule priority as tiebreaker
135
+ score += rule.priority * 0.1;
136
+
137
+ if (score > bestScore) {
138
+ bestScore = score;
139
+ bestMatch = rule;
140
+ }
141
+ }
142
+
143
+ // Default to webhook if nothing matched
144
+ const nodeType = bestMatch?.nodeType ?? 'n8n-nodes-base.webhook';
145
+ const triggerType = bestMatch?.triggerType ?? 'webhook';
146
+ const confidence: 'high' | 'medium' | 'low' =
147
+ bestScore >= 20 ? 'high' : bestScore >= 10 ? 'medium' : 'low';
148
+
149
+ return { nodeType, triggerType, confidence };
150
+ }
151
+
152
+ /**
153
+ * Generate complete trigger node configuration
154
+ */
155
+ buildTriggerConfig(
156
+ nodeType: string,
157
+ userRequest: string,
158
+ intent: WorkflowIntent,
159
+ workflowName: string,
160
+ ): TriggerConfig {
161
+ switch (nodeType) {
162
+ case 'n8n-nodes-base.webhook':
163
+ return this.buildWebhookConfig(workflowName, intent);
164
+
165
+ case 'n8n-nodes-base.telegramTrigger':
166
+ return this.buildTelegramTriggerConfig();
167
+
168
+ case 'n8n-nodes-base.scheduleTrigger':
169
+ return this.buildScheduleConfig(userRequest);
170
+
171
+ case 'n8n-nodes-base.emailReadImap':
172
+ return this.buildEmailTriggerConfig();
173
+
174
+ case 'n8n-nodes-base.slackTrigger':
175
+ return this.buildSlackTriggerConfig();
176
+
177
+ case 'n8n-nodes-base.githubTrigger':
178
+ return this.buildGitHubTriggerConfig();
179
+
180
+ case 'n8n-nodes-base.notionTrigger':
181
+ return this.buildNotionTriggerConfig();
182
+
183
+ case 'n8n-nodes-base.manualTrigger':
184
+ return this.buildManualTriggerConfig();
185
+
186
+ default:
187
+ return this.buildWebhookConfig(workflowName, intent);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Inject auto-configured trigger into a WorkflowGraph as the first node
193
+ */
194
+ injectTriggerIntoGraph(
195
+ graph: WorkflowGraph,
196
+ triggerConfig: TriggerConfig,
197
+ triggerNodeType: string,
198
+ ): WorkflowGraph {
199
+ const def = getNodeDef(triggerNodeType);
200
+ const triggerId = 'trigger-auto-' + Date.now();
201
+
202
+ // Check if graph already has a trigger node from the detected type
203
+ const existingTrigger = graph.nodes.find((n) => n.layer === 'trigger');
204
+
205
+ if (existingTrigger) {
206
+ // Update existing trigger with proper config
207
+ const updatedNodes = graph.nodes.map((node) => {
208
+ if (node.layer !== 'trigger') return node;
209
+ return {
210
+ ...node,
211
+ n8nNodeType: triggerNodeType,
212
+ label: triggerConfig.displayName,
213
+ autoConfigured: true,
214
+ webhookPath: triggerConfig.webhookPath,
215
+ };
216
+ });
217
+ return { ...graph, nodes: updatedNodes };
218
+ }
219
+
220
+ // Insert new trigger at front
221
+ const newTriggerNode = {
222
+ id: triggerId,
223
+ label: triggerConfig.displayName,
224
+ n8nNodeType: triggerNodeType,
225
+ layer: 'trigger' as const,
226
+ description: triggerConfig.notes,
227
+ isCritical: true,
228
+ autoConfigured: true,
229
+ webhookPath: triggerConfig.webhookPath,
230
+ position: { x: 0, y: 300 },
231
+ parameters: triggerConfig.parameters,
232
+ retryPolicy: undefined,
233
+ dataContract: {
234
+ inputs: [],
235
+ outputs: ['body', 'headers', 'query', 'params'],
236
+ },
237
+ };
238
+
239
+ // Shift existing nodes to the right
240
+ const shiftedNodes = graph.nodes.map((node) => ({
241
+ ...node,
242
+ position: { x: (node.position?.x ?? 0) + 220, y: node.position?.y ?? 300 },
243
+ }));
244
+
245
+ // Connect trigger to first non-trigger node
246
+ const firstNode = shiftedNodes[0];
247
+ const newEdge = firstNode ? {
248
+ id: `edge-trigger-to-${firstNode.id}`,
249
+ sourceNodeId: triggerId,
250
+ targetNodeId: firstNode.id,
251
+ outputIndex: 0,
252
+ inputIndex: 0,
253
+ label: 'on receive',
254
+ } : undefined;
255
+
256
+ return {
257
+ ...graph,
258
+ nodes: [newTriggerNode, ...shiftedNodes],
259
+ edges: newEdge ? [newEdge, ...graph.edges] : graph.edges,
260
+ };
261
+ }
262
+
263
+ // ─── Trigger config builders ──────────────────────────────────────────────
264
+
265
+ private buildWebhookConfig(workflowName: string, intent: WorkflowIntent): TriggerConfig {
266
+ const slug = workflowName
267
+ .toLowerCase()
268
+ .replace(/[^a-z0-9]+/g, '-')
269
+ .replace(/^-+|-+$/g, '')
270
+ .slice(0, 40);
271
+ const path = `wfo/${slug}`;
272
+
273
+ return {
274
+ nodeType: 'n8n-nodes-base.webhook',
275
+ displayName: 'Webhook Trigger',
276
+ parameters: {
277
+ httpMethod: intent.syncVsAsync === 'sync' ? 'POST' : 'POST',
278
+ path,
279
+ responseMode: intent.syncVsAsync === 'sync' ? 'lastNode' : 'onReceived',
280
+ responseData: 'allEntries',
281
+ options: {},
282
+ },
283
+ webhookPath: path,
284
+ notes: `Auto-configured webhook trigger. Path: /webhook/${path}. Method: POST. Response: ${intent.syncVsAsync === 'sync' ? 'synchronous (after processing)' : 'immediate acknowledgement'}.`,
285
+ };
286
+ }
287
+
288
+ private buildTelegramTriggerConfig(): TriggerConfig {
289
+ return {
290
+ nodeType: 'n8n-nodes-base.telegramTrigger',
291
+ displayName: 'Telegram Trigger',
292
+ parameters: {
293
+ updates: ['message', 'callback_query'],
294
+ },
295
+ credentials: {
296
+ telegramApi: { id: '', name: 'Telegram Bot API' },
297
+ },
298
+ notes: 'Auto-configured Telegram trigger. Listens for messages and callback queries. Requires telegramApi credential.',
299
+ };
300
+ }
301
+
302
+ private buildScheduleConfig(userRequest: string): TriggerConfig {
303
+ // Detect schedule from request text
304
+ const req = userRequest.toLowerCase();
305
+ let interval: Record<string, unknown>[] = [{ field: 'hours', hoursInterval: 1 }];
306
+
307
+ if (req.includes('every minute')) interval = [{ field: 'minutes', minutesInterval: 1 }];
308
+ else if (req.includes('every 5 minute')) interval = [{ field: 'minutes', minutesInterval: 5 }];
309
+ else if (req.includes('every 15 minute')) interval = [{ field: 'minutes', minutesInterval: 15 }];
310
+ else if (req.includes('every 30 minute')) interval = [{ field: 'minutes', minutesInterval: 30 }];
311
+ else if (req.includes('every hour') || req.includes('hourly')) interval = [{ field: 'hours', hoursInterval: 1 }];
312
+ else if (req.includes('every 6 hour')) interval = [{ field: 'hours', hoursInterval: 6 }];
313
+ else if (req.includes('every 12 hour')) interval = [{ field: 'hours', hoursInterval: 12 }];
314
+ else if (req.includes('daily') || req.includes('every day') || req.includes('nightly') || req.includes('midnight')) {
315
+ interval = [{ field: 'cronExpression', expression: '0 0 * * *' }];
316
+ }
317
+ else if (req.includes('weekly') || req.includes('every week')) {
318
+ interval = [{ field: 'cronExpression', expression: '0 9 * * 1' }]; // Monday 9am
319
+ }
320
+ else if (req.includes('monthly') || req.includes('every month')) {
321
+ interval = [{ field: 'cronExpression', expression: '0 9 1 * *' }]; // 1st of month 9am
322
+ }
323
+
324
+ return {
325
+ nodeType: 'n8n-nodes-base.scheduleTrigger',
326
+ displayName: 'Schedule Trigger',
327
+ parameters: {
328
+ rule: { interval },
329
+ },
330
+ notes: `Auto-configured schedule trigger. Interval detected from request: ${JSON.stringify(interval)}.`,
331
+ };
332
+ }
333
+
334
+ private buildEmailTriggerConfig(): TriggerConfig {
335
+ return {
336
+ nodeType: 'n8n-nodes-base.emailReadImap',
337
+ displayName: 'Email Trigger (IMAP)',
338
+ parameters: {
339
+ mailbox: 'INBOX',
340
+ action: 'read',
341
+ downloadAttachments: false,
342
+ format: 'simple',
343
+ options: {},
344
+ },
345
+ credentials: {
346
+ imap: { id: '', name: 'IMAP Account' },
347
+ },
348
+ notes: 'Auto-configured IMAP email trigger. Monitors INBOX for new emails. Requires IMAP credential.',
349
+ };
350
+ }
351
+
352
+ private buildSlackTriggerConfig(): TriggerConfig {
353
+ return {
354
+ nodeType: 'n8n-nodes-base.slackTrigger',
355
+ displayName: 'Slack Trigger',
356
+ parameters: {
357
+ trigger: 'any_message',
358
+ },
359
+ credentials: {
360
+ slackOAuth2Api: { id: '', name: 'Slack OAuth2' },
361
+ },
362
+ notes: 'Auto-configured Slack trigger. Listens for messages in configured channels. Requires Slack OAuth2 credential.',
363
+ };
364
+ }
365
+
366
+ private buildGitHubTriggerConfig(): TriggerConfig {
367
+ return {
368
+ nodeType: 'n8n-nodes-base.githubTrigger',
369
+ displayName: 'GitHub Trigger',
370
+ parameters: {
371
+ owner: '',
372
+ repository: '',
373
+ events: ['push'],
374
+ },
375
+ credentials: {
376
+ githubApi: { id: '', name: 'GitHub API' },
377
+ },
378
+ notes: 'Auto-configured GitHub trigger. Listens for push events. Set owner and repository in parameters.',
379
+ };
380
+ }
381
+
382
+ private buildNotionTriggerConfig(): TriggerConfig {
383
+ return {
384
+ nodeType: 'n8n-nodes-base.notionTrigger',
385
+ displayName: 'Notion Trigger',
386
+ parameters: {
387
+ databaseId: '',
388
+ event: 'page_added',
389
+ simple: true,
390
+ },
391
+ credentials: {
392
+ notionApi: { id: '', name: 'Notion API' },
393
+ },
394
+ notes: 'Auto-configured Notion trigger. Monitors a database for new pages. Set databaseId in parameters.',
395
+ };
396
+ }
397
+
398
+ private buildManualTriggerConfig(): TriggerConfig {
399
+ return {
400
+ nodeType: 'n8n-nodes-base.manualTrigger',
401
+ displayName: 'Manual Trigger',
402
+ parameters: {},
403
+ notes: 'Manual trigger β€” start this workflow by clicking "Execute Workflow" in n8n.',
404
+ };
405
+ }
406
+ }
407
+
408
+ // ─── Singleton export ─────────────────────────────────────────────────────────
409
+ export const webhookAutoBind = new WebhookAutoBindSystem();
apps/simulator/src/types/workflow.ts ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Shared Types for Simulator Service
3
+ * Re-exports from worker types for consistency
4
+ */
5
+ export type {
6
+ WorkflowIntent,
7
+ WorkflowArchitecturePlan,
8
+ WorkflowGraph,
9
+ GraphNode,
10
+ GraphEdge,
11
+ WorkflowMetadata,
12
+ WorkflowLayer,
13
+ N8nWorkflow,
14
+ N8nNode,
15
+ N8nConnections,
16
+ ValidationReport,
17
+ ValidationSection,
18
+ ValidationIssue,
19
+ SimulationReport,
20
+ ExecutionTraceStep,
21
+ FailurePoint,
22
+ ChaosTestResult,
23
+ CredentialAnalysis,
24
+ RequiredCredential,
25
+ AvailableCredential,
26
+ MissingCredential,
27
+ DeploymentReadiness,
28
+ WorkflowQualityScore,
29
+ GenerateWorkflowResponse,
30
+ RetryPolicy,
31
+ DataContract,
32
+ RiskLevel,
33
+ Risk,
34
+ RiskAnalysis,
35
+ DependencyAnalysis,
36
+ SubworkflowPlan,
37
+ TradeoffExplanation,
38
+ QualityTargets,
39
+ WorkflowDomain,
40
+ WorkflowLifecycleState,
41
+ ExecutionPlanStep,
42
+ WorkflowJob,
43
+ DeploymentResult,
44
+ ActivationResult,
45
+ ApprovalRequest,
46
+ AuditEvent,
47
+ SwarmResultSummary,
48
+ TriggerDetectionResult,
49
+ HealingResultSummary,
50
+ } from '@wfo/apps/worker/src/types/workflow';
apps/simulator/tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "module": "CommonJS",
6
+ "moduleResolution": "node",
7
+ "lib": ["ES2022"],
8
+ "outDir": "./dist",
9
+ "rootDir": "../../",
10
+
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "resolveJsonModule": true,
14
+ "paths": {
15
+ "@wfo/apps/worker/*": ["../../apps/worker/*"],
16
+ "@wfo/core/*": ["../../core/*"],
17
+ "@wfo/integrations/*": ["../../integrations/*"],
18
+ "@wfo/validation/*": ["../../validation/*"],
19
+ "@wfo/observability/*": ["../../observability/*"],
20
+ "@wfo/knowledge/*": ["../../knowledge/*"],
21
+ "@wfo/config/*": ["../../config/*"]
22
+ }
23
+ },
24
+ "include": ["src/**/*", "../../core/**/*", "../../integrations/**/*", "../../validation/**/*", "../../observability/**/*", "../../knowledge/**/*", "../../config/**/*", "../../apps/worker/src/types/**/*"],
25
+ "exclude": ["node_modules", "dist"]
26
+ }
apps/worker/package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@wfo/worker",
3
+ "version": "1.0.0",
4
+ "description": "Cloudflare Worker - API Gateway & Orchestrator (thin layer)",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "build": "wrangler deploy --dry-run",
9
+ "deploy": "wrangler deploy",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "hono": "^4.0.0",
14
+ "zod": "^3.22.0"
15
+ },
16
+ "devDependencies": {
17
+ "@cloudflare/workers-types": "^4.20240208.0",
18
+ "wrangler": "^3.28.0",
19
+ "typescript": "^5.3.0"
20
+ }
21
+ }
apps/worker/src/index.ts ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Cloudflare Worker - Main Entry Point
3
+ * Architecture: Thin API Gateway + Orchestrator
4
+ * Heavy logic is offloaded to external compute services (simulator, planner, compiler, validator)
5
+ *
6
+ * CF Workers Free Plan constraints:
7
+ * - 10ms CPU time limit per request
8
+ * - 128MB memory limit
9
+ * - No Cloudflare Queues
10
+ * - Limited KV operations
11
+ */
12
+
13
+ import { Hono } from 'hono';
14
+ import { cors } from 'hono/cors';
15
+ import { logger } from 'hono/logger';
16
+ import { generateWorkflowRoute } from './routes/generate';
17
+ import { validateWorkflowRoute } from './routes/validate';
18
+ import { simulateWorkflowRoute } from './routes/simulate';
19
+ import { deployWorkflowRoute } from './routes/deploy';
20
+ import { approvalRoute } from './routes/approval';
21
+ import { activateWorkflowRoute } from './routes/activate';
22
+ import { reportsRoute } from './routes/reports';
23
+ import { healthRoute } from './routes/health';
24
+ import { authMiddleware } from './middleware/auth';
25
+ import { rateLimitMiddleware } from './middleware/rateLimit';
26
+ import { errorHandler } from './middleware/errorHandler';
27
+ import type { Env } from './types/env';
28
+
29
+ const app = new Hono<{ Bindings: Env }>();
30
+
31
+ // ─── Global Middleware ────────────────────────────────────────────────────────
32
+ app.use('*', cors({
33
+ origin: ['https://your-frontend.pages.dev', 'http://localhost:3000'],
34
+ allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
35
+ allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
36
+ maxAge: 86400,
37
+ }));
38
+
39
+ app.use('*', logger());
40
+ app.use('*', errorHandler);
41
+
42
+ // ─── Public Routes ────────────────────────────────────────────────────────────
43
+ app.route('/health', healthRoute);
44
+
45
+ // ─── Protected Routes (require API auth) ─────────────────────────────────────
46
+ app.use('/api/*', authMiddleware);
47
+ app.use('/api/*', rateLimitMiddleware);
48
+
49
+ app.route('/api/workflows', generateWorkflowRoute);
50
+ app.route('/api/workflows', validateWorkflowRoute);
51
+ app.route('/api/workflows', simulateWorkflowRoute);
52
+ app.route('/api/workflows', deployWorkflowRoute);
53
+ app.route('/api/workflows', approvalRoute);
54
+ app.route('/api/workflows', activateWorkflowRoute);
55
+ app.route('/api/reports', reportsRoute);
56
+
57
+ // ─── 404 Handler ─────────────────────────────────────────────────────────────
58
+ app.notFound((c) => {
59
+ return c.json({
60
+ success: false,
61
+ error: 'Route not found',
62
+ available_routes: [
63
+ 'POST /api/workflows/generate',
64
+ 'POST /api/workflows/validate',
65
+ 'POST /api/workflows/simulate',
66
+ 'POST /api/workflows/deploy',
67
+ 'POST /api/workflows/approve',
68
+ 'POST /api/workflows/activate',
69
+ 'GET /api/reports/workflow/:id',
70
+ 'GET /api/reports/validation/:id',
71
+ 'GET /api/reports/simulation/:id',
72
+ 'GET /health',
73
+ ],
74
+ }, 404);
75
+ });
76
+
77
+ export default app;
apps/worker/src/middleware/auth.ts ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Auth Middleware
3
+ * Validates X-API-Key header for protected routes
4
+ * Lightweight to stay within 10ms CF Workers CPU budget
5
+ */
6
+ import type { MiddlewareHandler } from 'hono';
7
+ import type { Env } from '../types/env';
8
+
9
+ export const authMiddleware: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => {
10
+ const apiKey = c.req.header('X-API-Key') ?? c.req.header('Authorization')?.replace('Bearer ', '');
11
+
12
+ if (!apiKey) {
13
+ return c.json({ success: false, error: 'Missing API key. Provide X-API-Key header.' }, 401);
14
+ }
15
+
16
+ // Constant-time comparison to prevent timing attacks
17
+ const expected = c.env.INTERNAL_API_SECRET;
18
+ if (!safeCompare(apiKey, expected)) {
19
+ return c.json({ success: false, error: 'Invalid API key.' }, 403);
20
+ }
21
+
22
+ await next();
23
+ };
24
+
25
+ function safeCompare(a: string, b: string): boolean {
26
+ if (a.length !== b.length) return false;
27
+ let result = 0;
28
+ for (let i = 0; i < a.length; i++) {
29
+ result |= (a.charCodeAt(i) ^ b.charCodeAt(i));
30
+ }
31
+ return result === 0;
32
+ }
apps/worker/src/middleware/errorHandler.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Error Handler Middleware
3
+ * Global error boundary for unhandled exceptions
4
+ */
5
+ import type { MiddlewareHandler } from 'hono';
6
+ import type { Env } from '../types/env';
7
+
8
+ export const errorHandler: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => {
9
+ try {
10
+ await next();
11
+ } catch (err) {
12
+ const message = err instanceof Error ? err.message : 'Internal server error';
13
+ console.error('[WorkerError]', { path: c.req.path, error: message });
14
+ return c.json({ success: false, error: message }, 500);
15
+ }
16
+ };
apps/worker/src/middleware/rateLimit.ts ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Rate Limit Middleware
3
+ * Uses KV store for sliding window rate limiting
4
+ * Lightweight - primarily I/O bound (KV read/write), minimal CPU
5
+ */
6
+ import type { MiddlewareHandler } from 'hono';
7
+ import type { Env } from '../types/env';
8
+
9
+ const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
10
+ const RATE_LIMIT_MAX_REQUESTS = 20; // 20 requests per minute per IP
11
+
12
+ export const rateLimitMiddleware: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => {
13
+ const ip = c.req.header('CF-Connecting-IP') ?? 'unknown';
14
+ const key = `ratelimit:${ip}`;
15
+
16
+ const raw = await c.env.WFO_CACHE.get(key, 'text');
17
+ const now = Date.now();
18
+
19
+ let count = 1;
20
+ if (raw) {
21
+ const entry = JSON.parse(raw) as { count: number; windowStart: number };
22
+ if (now - entry.windowStart < RATE_LIMIT_WINDOW_MS) {
23
+ count = entry.count + 1;
24
+ if (count > RATE_LIMIT_MAX_REQUESTS) {
25
+ return c.json({
26
+ success: false,
27
+ error: 'Rate limit exceeded. Max 20 requests/minute.',
28
+ retryAfter: Math.ceil((entry.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000),
29
+ }, 429);
30
+ }
31
+ await c.env.WFO_CACHE.put(key, JSON.stringify({ count, windowStart: entry.windowStart }), {
32
+ expirationTtl: 120,
33
+ });
34
+ } else {
35
+ // New window
36
+ await c.env.WFO_CACHE.put(key, JSON.stringify({ count: 1, windowStart: now }), {
37
+ expirationTtl: 120,
38
+ });
39
+ }
40
+ } else {
41
+ await c.env.WFO_CACHE.put(key, JSON.stringify({ count: 1, windowStart: now }), {
42
+ expirationTtl: 120,
43
+ });
44
+ }
45
+
46
+ c.res.headers.set('X-RateLimit-Limit', String(RATE_LIMIT_MAX_REQUESTS));
47
+ c.res.headers.set('X-RateLimit-Remaining', String(Math.max(0, RATE_LIMIT_MAX_REQUESTS - count)));
48
+
49
+ await next();
50
+ };
apps/worker/src/routes/activate.ts ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * POST /api/workflows/activate
3
+ * Activation Engine - final safety gate
4
+ * Only activates workflows that have been:
5
+ * 1. Validated βœ“
6
+ * 2. Simulated βœ“
7
+ * 3. Deployed βœ“
8
+ * 4. Human Approved βœ“
9
+ * 5. Final credential check βœ“
10
+ *
11
+ * NEVER auto-activate. NEVER skip approval gate.
12
+ */
13
+ import { Hono } from 'hono';
14
+ import { z } from 'zod';
15
+ import type { Env } from '../types/env';
16
+ import type { WorkflowJob, ActivationResult } from '../types/workflow';
17
+ import { createAuditEvent } from '../utils/audit';
18
+
19
+ export const activateWorkflowRoute = new Hono<{ Bindings: Env }>();
20
+
21
+ const ActivateRequestSchema = z.object({
22
+ jobId: z.string().min(1),
23
+ });
24
+
25
+ activateWorkflowRoute.post('/activate', async (c) => {
26
+ const body = await c.req.json().catch(() => null);
27
+ if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400);
28
+
29
+ const parsed = ActivateRequestSchema.safeParse(body);
30
+ if (!parsed.success) {
31
+ return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400);
32
+ }
33
+
34
+ const { jobId } = parsed.data;
35
+ const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text');
36
+ if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404);
37
+
38
+ const job = JSON.parse(raw) as WorkflowJob;
39
+
40
+ // ─── Final Safety Gates ──────────────────────────────────────────────────
41
+ if (job.state !== 'approved') {
42
+ const reason = job.state === 'rejected'
43
+ ? 'Workflow was rejected by reviewer.'
44
+ : job.state === 'awaiting_approval'
45
+ ? 'Workflow is awaiting human approval. Cannot activate yet.'
46
+ : `Workflow is not approved. Current state: ${job.state}`;
47
+
48
+ return c.json({ success: false, error: reason, currentState: job.state }, 409);
49
+ }
50
+
51
+ if (!job.deploymentResult?.n8nWorkflowId) {
52
+ return c.json({ success: false, error: 'Workflow not deployed to n8n. Run /deploy first.' }, 409);
53
+ }
54
+
55
+ if (job.approvalRequest?.status !== 'approved') {
56
+ return c.json({
57
+ success: false,
58
+ error: 'Human approval required before activation. No exceptions.',
59
+ }, 403);
60
+ }
61
+
62
+ // ─── Final Credential Check ──────────────────────────────────────────────
63
+ const credResp = await fetch(`${c.env.N8N_BASE_URL}/api/v1/credentials`, {
64
+ headers: { 'X-N8N-API-KEY': c.env.N8N_API_KEY },
65
+ signal: AbortSignal.timeout(8000),
66
+ }).catch(() => null);
67
+
68
+ const credCheckPassed = credResp?.ok ?? false;
69
+
70
+ // ─── Final Validation Check via simulator ────────────────────────────────
71
+ const validationResp = await fetch(`${c.env.SIMULATOR_URL}/process/validate`, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ 'X-Internal-Secret': c.env.INTERNAL_API_SECRET,
76
+ },
77
+ body: JSON.stringify({
78
+ jobId,
79
+ workflow: job.compiledWorkflow,
80
+ graph: job.graph,
81
+ final: true,
82
+ }),
83
+ signal: AbortSignal.timeout(10000),
84
+ }).catch(() => null);
85
+
86
+ const finalValidationPassed = validationResp?.ok ?? false;
87
+
88
+ if (!finalValidationPassed) {
89
+ const activationResult: ActivationResult = {
90
+ jobId,
91
+ n8nWorkflowId: job.deploymentResult.n8nWorkflowId,
92
+ activationStatus: 'failed',
93
+ finalValidationPassed: false,
94
+ finalCredentialCheckPassed: credCheckPassed,
95
+ activatedAt: new Date().toISOString(),
96
+ diagnostics: 'Final pre-activation validation failed. Workflow not activated.',
97
+ };
98
+
99
+ await updateJob(c.env.WFO_CACHE, jobId, job, { activationResult, state: 'deployed' });
100
+ return c.json({ success: false, error: 'Pre-activation validation failed.', activationResult, jobId }, 500);
101
+ }
102
+
103
+ // ─── Activate via n8n API ─────────────────────────────────────────────────
104
+ const activateResp = await fetch(
105
+ `${c.env.N8N_BASE_URL}/api/v1/workflows/${job.deploymentResult.n8nWorkflowId}/activate`,
106
+ {
107
+ method: 'POST',
108
+ headers: { 'X-N8N-API-KEY': c.env.N8N_API_KEY },
109
+ signal: AbortSignal.timeout(10000),
110
+ },
111
+ ).catch((err) => { throw new Error(`n8n activation failed: ${err instanceof Error ? err.message : 'unknown'}`); });
112
+
113
+ if (!activateResp.ok) {
114
+ const errText = await activateResp.text();
115
+ const activationResult: ActivationResult = {
116
+ jobId,
117
+ n8nWorkflowId: job.deploymentResult.n8nWorkflowId,
118
+ activationStatus: 'failed',
119
+ finalValidationPassed,
120
+ finalCredentialCheckPassed: credCheckPassed,
121
+ activatedAt: new Date().toISOString(),
122
+ diagnostics: `n8n API activation error ${activateResp.status}: ${errText}`,
123
+ };
124
+
125
+ await updateJob(c.env.WFO_CACHE, jobId, job, { activationResult, state: 'deployed' });
126
+ return c.json({ success: false, error: activationResult.diagnostics, activationResult, jobId }, 500);
127
+ }
128
+
129
+ const activationResult: ActivationResult = {
130
+ jobId,
131
+ n8nWorkflowId: job.deploymentResult.n8nWorkflowId,
132
+ activationStatus: 'activated',
133
+ finalValidationPassed,
134
+ finalCredentialCheckPassed: credCheckPassed,
135
+ activatedAt: new Date().toISOString(),
136
+ };
137
+
138
+ await updateJob(c.env.WFO_CACHE, jobId, job, { activationResult, state: 'activated' });
139
+
140
+ return c.json({
141
+ success: true,
142
+ jobId,
143
+ n8nWorkflowId: job.deploymentResult.n8nWorkflowId,
144
+ activationResult,
145
+ message: 'Workflow successfully activated in n8n.',
146
+ });
147
+ });
148
+
149
+ async function updateJob(
150
+ kv: KVNamespace,
151
+ jobId: string,
152
+ job: WorkflowJob,
153
+ updates: { activationResult: ActivationResult; state: WorkflowJob['state'] },
154
+ ) {
155
+ const updated: WorkflowJob = {
156
+ ...job,
157
+ ...updates,
158
+ updatedAt: new Date().toISOString(),
159
+ auditLog: [
160
+ ...job.auditLog,
161
+ createAuditEvent(jobId, `activation.${updates.activationResult.activationStatus}`, 'system', {
162
+ n8nWorkflowId: updates.activationResult.n8nWorkflowId,
163
+ }),
164
+ ],
165
+ };
166
+ await kv.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 });
167
+ }
apps/worker/src/routes/approval.ts ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * POST /api/workflows/approve
3
+ * Human Approval System - core safety gate
4
+ * No workflow may activate without explicit human approval
5
+ *
6
+ * GET /api/workflows/approve/:jobId β†’ get approval request details
7
+ * POST /api/workflows/approve β†’ submit approval decision
8
+ */
9
+ import { Hono } from 'hono';
10
+ import { z } from 'zod';
11
+ import type { Env } from '../types/env';
12
+ import type { WorkflowJob, ApprovalRequest } from '../types/workflow';
13
+ import { createAuditEvent } from '../utils/audit';
14
+
15
+ export const approvalRoute = new Hono<{ Bindings: Env }>();
16
+
17
+ const ApprovalRequestSchema = z.object({
18
+ jobId: z.string().min(1),
19
+ });
20
+
21
+ const ApprovalDecisionSchema = z.object({
22
+ jobId: z.string().min(1),
23
+ decision: z.enum(['approved', 'rejected']),
24
+ reviewerNote: z.string().max(1000).optional(),
25
+ });
26
+
27
+ // ─── GET approval request details ────────────────────────────────────────────
28
+ approvalRoute.get('/approve/:jobId', async (c) => {
29
+ const jobId = c.req.param('jobId');
30
+ const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text');
31
+ if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404);
32
+
33
+ const job = JSON.parse(raw) as WorkflowJob;
34
+ if (!job.approvalRequest) {
35
+ return c.json({ success: false, error: 'No approval request found for this job.' }, 404);
36
+ }
37
+
38
+ return c.json({
39
+ success: true,
40
+ jobId,
41
+ state: job.state,
42
+ approvalRequest: job.approvalRequest,
43
+ validationReport: job.validationReport,
44
+ simulationReport: job.simulationReport,
45
+ credentialAnalysis: job.credentialAnalysis,
46
+ architecturePlan: job.architecturePlan,
47
+ });
48
+ });
49
+
50
+ // ─── POST request approval (after deploy) ────────────────────────────────────
51
+ approvalRoute.post('/request-approval', async (c) => {
52
+ const body = await c.req.json().catch(() => null);
53
+ if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400);
54
+
55
+ const parsed = ApprovalRequestSchema.safeParse(body);
56
+ if (!parsed.success) {
57
+ return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400);
58
+ }
59
+
60
+ const { jobId } = parsed.data;
61
+ const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text');
62
+ if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404);
63
+
64
+ const job = JSON.parse(raw) as WorkflowJob;
65
+
66
+ if (job.state !== 'deployed') {
67
+ return c.json({
68
+ success: false,
69
+ error: `Job must be in 'deployed' state before requesting approval. Current state: ${job.state}`,
70
+ }, 409);
71
+ }
72
+
73
+ const credentialStatus = job.credentialAnalysis?.allCredentialsPresent ? 'complete' : 'incomplete';
74
+
75
+ const approvalRequest: ApprovalRequest = {
76
+ jobId,
77
+ requestedAt: new Date().toISOString(),
78
+ expiresAt: new Date(Date.now() + 86400 * 1000).toISOString(), // 24h
79
+ architectureSummary: job.architecturePlan?.description ?? 'No architecture plan available',
80
+ riskSummary: job.architecturePlan?.riskAnalysis.overallRisk ?? 'unknown',
81
+ readinessScore: job.simulationReport?.readinessScore ?? 0,
82
+ validationScore: job.validationReport?.overallScore ?? 0,
83
+ simulationScore: job.simulationReport?.readinessScore ?? 0,
84
+ credentialStatus,
85
+ status: 'pending',
86
+ };
87
+
88
+ const updated: WorkflowJob = {
89
+ ...job,
90
+ state: 'awaiting_approval',
91
+ approvalRequest,
92
+ updatedAt: new Date().toISOString(),
93
+ auditLog: [
94
+ ...job.auditLog,
95
+ createAuditEvent(jobId, 'approval.requested', 'system', { credentialStatus }),
96
+ ],
97
+ };
98
+
99
+ await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 });
100
+
101
+ return c.json({
102
+ success: true,
103
+ jobId,
104
+ approvalRequest,
105
+ message: 'Approval request created. A human reviewer must approve before activation.',
106
+ reviewUrl: `/api/workflows/approve/${jobId}`,
107
+ });
108
+ });
109
+
110
+ // ─── POST submit approval decision ───────────────────────────────────────────
111
+ approvalRoute.post('/approve', async (c) => {
112
+ const body = await c.req.json().catch(() => null);
113
+ if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400);
114
+
115
+ const parsed = ApprovalDecisionSchema.safeParse(body);
116
+ if (!parsed.success) {
117
+ return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400);
118
+ }
119
+
120
+ const { jobId, decision, reviewerNote } = parsed.data;
121
+ const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text');
122
+ if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404);
123
+
124
+ const job = JSON.parse(raw) as WorkflowJob;
125
+
126
+ if (job.state !== 'awaiting_approval') {
127
+ return c.json({
128
+ success: false,
129
+ error: `Job is not awaiting approval. Current state: ${job.state}`,
130
+ }, 409);
131
+ }
132
+
133
+ if (!job.approvalRequest) {
134
+ return c.json({ success: false, error: 'No pending approval request found.' }, 409);
135
+ }
136
+
137
+ // Check expiry
138
+ if (new Date(job.approvalRequest.expiresAt) < new Date()) {
139
+ return c.json({ success: false, error: 'Approval request has expired. Re-run /request-approval.' }, 410);
140
+ }
141
+
142
+ const updatedApproval: ApprovalRequest = {
143
+ ...job.approvalRequest,
144
+ status: decision,
145
+ reviewerNote,
146
+ respondedAt: new Date().toISOString(),
147
+ };
148
+
149
+ const newState = decision === 'approved' ? 'approved' : 'rejected';
150
+
151
+ const updated: WorkflowJob = {
152
+ ...job,
153
+ state: newState,
154
+ approvalRequest: updatedApproval,
155
+ updatedAt: new Date().toISOString(),
156
+ auditLog: [
157
+ ...job.auditLog,
158
+ createAuditEvent(jobId, `approval.${decision}`, 'user', { reviewerNote }),
159
+ ],
160
+ };
161
+
162
+ await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 });
163
+
164
+ const message = decision === 'approved'
165
+ ? 'Workflow approved. You may now call /activate to activate it in n8n.'
166
+ : 'Workflow rejected. Deployment will not be activated.';
167
+
168
+ return c.json({ success: true, jobId, decision, newState, message });
169
+ });
apps/worker/src/routes/deploy.ts ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * POST /api/workflows/deploy
3
+ * Deploy workflow to n8n via REST API
4
+ * Worker handles this directly (mostly external API calls, minimal CPU)
5
+ *
6
+ * Pipeline gate: Validate β†’ Simulate β†’ Deploy
7
+ * Deployment β‰  Activation (never auto-activate)
8
+ */
9
+ import { Hono } from 'hono';
10
+ import { z } from 'zod';
11
+ import type { Env } from '../types/env';
12
+ import type { WorkflowJob, DeploymentResult } from '../types/workflow';
13
+ import { createAuditEvent } from '../utils/audit';
14
+
15
+ export const deployWorkflowRoute = new Hono<{ Bindings: Env }>();
16
+
17
+ const DeployRequestSchema = z.object({
18
+ jobId: z.string().min(1),
19
+ });
20
+
21
+ deployWorkflowRoute.post('/deploy', async (c) => {
22
+ const body = await c.req.json().catch(() => null);
23
+ if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400);
24
+
25
+ const parsed = DeployRequestSchema.safeParse(body);
26
+ if (!parsed.success) {
27
+ return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400);
28
+ }
29
+
30
+ const { jobId } = parsed.data;
31
+ const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text');
32
+ if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404);
33
+
34
+ const job = JSON.parse(raw) as WorkflowJob;
35
+
36
+ // ─── Pipeline Gates ──────────────────────────────────────────────────────
37
+ if (!job.compiledWorkflow) {
38
+ return c.json({ success: false, error: 'Workflow not compiled. Run /generate first.' }, 409);
39
+ }
40
+ if (!job.validationReport?.valid) {
41
+ return c.json({
42
+ success: false,
43
+ error: 'Workflow failed validation. Fix issues before deploying.',
44
+ validationReport: job.validationReport,
45
+ }, 409);
46
+ }
47
+ if (!job.simulationReport?.passed) {
48
+ return c.json({
49
+ success: false,
50
+ error: 'Workflow failed dry-run simulation. Fix issues before deploying.',
51
+ simulationReport: job.simulationReport,
52
+ }, 409);
53
+ }
54
+
55
+ // ─── Credential check via n8n API ────────────────────────────────────────
56
+ const credResp = await fetch(`${c.env.N8N_BASE_URL}/api/v1/credentials`, {
57
+ headers: { 'X-N8N-API-KEY': c.env.N8N_API_KEY },
58
+ signal: AbortSignal.timeout(8000),
59
+ }).catch(() => null);
60
+
61
+ let availableCredentials: Array<{ id: string; name: string; type: string }> = [];
62
+ if (credResp?.ok) {
63
+ const credData = await credResp.json() as { data: typeof availableCredentials };
64
+ availableCredentials = credData.data ?? [];
65
+ }
66
+
67
+ // ─── Deploy to n8n (create or update) ────────────────────────────────────
68
+ const workflowPayload = {
69
+ ...job.compiledWorkflow,
70
+ active: false, // NEVER activate on deploy
71
+ };
72
+
73
+ let n8nResponse: Response;
74
+ let n8nWorkflowId = job.deploymentResult?.n8nWorkflowId;
75
+
76
+ try {
77
+ if (n8nWorkflowId) {
78
+ // Update existing
79
+ n8nResponse = await fetch(`${c.env.N8N_BASE_URL}/api/v1/workflows/${n8nWorkflowId}`, {
80
+ method: 'PUT',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ 'X-N8N-API-KEY': c.env.N8N_API_KEY,
84
+ },
85
+ body: JSON.stringify(workflowPayload),
86
+ signal: AbortSignal.timeout(10000),
87
+ });
88
+ } else {
89
+ // Create new
90
+ n8nResponse = await fetch(`${c.env.N8N_BASE_URL}/api/v1/workflows`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ 'X-N8N-API-KEY': c.env.N8N_API_KEY,
95
+ },
96
+ body: JSON.stringify(workflowPayload),
97
+ signal: AbortSignal.timeout(10000),
98
+ });
99
+ }
100
+ } catch (err) {
101
+ const msg = err instanceof Error ? err.message : 'n8n API unreachable';
102
+ return c.json({ success: false, error: `Deployment failed: ${msg}`, jobId }, 502);
103
+ }
104
+
105
+ if (!n8nResponse.ok) {
106
+ const errText = await n8nResponse.text();
107
+ const deploymentResult: DeploymentResult = {
108
+ jobId,
109
+ status: 'failed',
110
+ deployedAt: new Date().toISOString(),
111
+ errorMessage: `n8n API ${n8nResponse.status}: ${errText}`,
112
+ };
113
+
114
+ await updateJobInKV(c.env.WFO_CACHE, jobId, job, {
115
+ state: 'simulated', // revert to pre-deploy state
116
+ deploymentResult,
117
+ event: 'deployment.failed',
118
+ });
119
+
120
+ return c.json({ success: false, error: deploymentResult.errorMessage, deploymentResult, jobId }, 500);
121
+ }
122
+
123
+ const n8nData = await n8nResponse.json() as { id: string };
124
+ n8nWorkflowId = n8nData.id;
125
+
126
+ const deploymentResult: DeploymentResult = {
127
+ jobId,
128
+ n8nWorkflowId,
129
+ status: 'success',
130
+ deployedAt: new Date().toISOString(),
131
+ };
132
+
133
+ await updateJobInKV(c.env.WFO_CACHE, jobId, job, {
134
+ state: 'deployed',
135
+ deploymentResult,
136
+ event: 'deployment.success',
137
+ credentialAnalysis: {
138
+ jobId,
139
+ allCredentialsPresent: availableCredentials.length > 0,
140
+ required: [],
141
+ available: availableCredentials,
142
+ missing: [],
143
+ analysedAt: new Date().toISOString(),
144
+ },
145
+ });
146
+
147
+ return c.json({
148
+ success: true,
149
+ jobId,
150
+ n8nWorkflowId,
151
+ deploymentResult,
152
+ message: 'Workflow deployed successfully. It is NOT yet activated. Submit /approve to request activation.',
153
+ });
154
+ });
155
+
156
+ async function updateJobInKV(
157
+ kv: KVNamespace,
158
+ jobId: string,
159
+ job: WorkflowJob,
160
+ updates: Partial<WorkflowJob> & { event: string },
161
+ ) {
162
+ const { event, ...rest } = updates;
163
+ const updated: WorkflowJob = {
164
+ ...job,
165
+ ...rest,
166
+ updatedAt: new Date().toISOString(),
167
+ auditLog: [
168
+ ...job.auditLog,
169
+ createAuditEvent(jobId, event, 'system', rest as Record<string, unknown>),
170
+ ],
171
+ };
172
+ await kv.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 });
173
+ }
apps/worker/src/routes/generate.ts ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * POST /api/workflows/generate β€” UPGRADED
3
+ * CF Worker role: Thin orchestrator + API gateway
4
+ * Passes LLM config to simulator (provider-agnostic, no direct OpenAI dep)
5
+ */
6
+ import { Hono } from 'hono';
7
+ import { z } from 'zod';
8
+ import type { Env } from '../types/env';
9
+ import type { WorkflowJob, GenerateWorkflowResponse } from '../types/workflow';
10
+ import { createAuditEvent } from '../utils/audit';
11
+ import { generateJobId } from '../utils/ids';
12
+
13
+ export const generateWorkflowRoute = new Hono<{ Bindings: Env }>();
14
+
15
+ const GenerateRequestSchema = z.object({
16
+ request: z.string().min(10, 'Request must be at least 10 characters').max(2000),
17
+ options: z.object({
18
+ skipSimulation: z.boolean().optional().default(false),
19
+ requireApproval: z.boolean().optional().default(true),
20
+ swarmSize: z.number().min(1).max(5).optional().default(3),
21
+ skipSwarm: z.boolean().optional().default(false),
22
+ skipSelfHealing: z.boolean().optional().default(false),
23
+ }).optional().default({}),
24
+ });
25
+
26
+ generateWorkflowRoute.post('/generate', async (c) => {
27
+ // ─── Parse & validate request ─────────────────────────────────────────────
28
+ const body = await c.req.json().catch(() => null);
29
+ if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400);
30
+
31
+ const parsed = GenerateRequestSchema.safeParse(body);
32
+ if (!parsed.success) {
33
+ return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400);
34
+ }
35
+
36
+ const { request, options } = parsed.data;
37
+ const jobId = generateJobId();
38
+
39
+ // ─── Create initial job in KV ─────────────────────────────────────────────
40
+ const job: WorkflowJob = {
41
+ id: jobId,
42
+ userRequest: request,
43
+ state: 'draft',
44
+ createdAt: new Date().toISOString(),
45
+ updatedAt: new Date().toISOString(),
46
+ auditLog: [createAuditEvent(jobId, 'job.created', 'user', { request })],
47
+ };
48
+
49
+ await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(job), {
50
+ expirationTtl: 86400 * 7,
51
+ });
52
+
53
+ // ─── Build LLM config for simulator (provider-agnostic) ──────────────────
54
+ // Resolve API key: prefer new LLM_API_KEY, fallback to legacy OPENAI_API_KEY
55
+ const llmApiKey = c.env.LLM_API_KEY || c.env.OPENAI_API_KEY || '';
56
+ const llmModel = c.env.LLM_MODEL || 'gpt-4o';
57
+ const llmGatewayUrl = c.env.LLM_GATEWAY_URL || 'https://api.openai.com/v1';
58
+ const llmProvider = c.env.LLM_PROVIDER || 'openai';
59
+
60
+ if (!llmApiKey) {
61
+ return c.json({
62
+ success: false,
63
+ error: 'LLM API key not configured. Set LLM_API_KEY secret in wrangler.',
64
+ jobId,
65
+ }, 500);
66
+ }
67
+
68
+ // ─── Offload to simulator service (heavy CPU/LLM work) ───────────────────
69
+ const simulatorPayload = {
70
+ jobId,
71
+ request,
72
+ options,
73
+ // Provider-agnostic LLM config
74
+ llmApiKey,
75
+ llmModel,
76
+ llmGatewayUrl,
77
+ llmProvider,
78
+ // Legacy field for backward compat with older simulator versions
79
+ openaiApiKey: llmApiKey,
80
+ // n8n config
81
+ n8nBaseUrl: c.env.N8N_BASE_URL,
82
+ n8nApiKey: c.env.N8N_API_KEY,
83
+ };
84
+
85
+ let simulatorResponse: Response;
86
+ try {
87
+ simulatorResponse = await fetch(`${c.env.SIMULATOR_URL}/process/generate`, {
88
+ method: 'POST',
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ 'X-Internal-Secret': c.env.INTERNAL_API_SECRET,
92
+ },
93
+ body: JSON.stringify(simulatorPayload),
94
+ signal: AbortSignal.timeout(55000), // 55s β€” allow swarm + healing time
95
+ });
96
+ } catch (err) {
97
+ const errMsg = err instanceof Error ? err.message : 'Simulator unreachable';
98
+ await updateJobState(c.env.WFO_CACHE, jobId, 'draft', errMsg);
99
+ return c.json({ success: false, error: `Simulator service error: ${errMsg}`, jobId }, 502);
100
+ }
101
+
102
+ if (!simulatorResponse.ok) {
103
+ const errText = await simulatorResponse.text();
104
+ return c.json({ success: false, error: `Simulator returned ${simulatorResponse.status}: ${errText}`, jobId }, 502);
105
+ }
106
+
107
+ const result = await simulatorResponse.json() as GenerateWorkflowResponse;
108
+
109
+ // ─── Store full result in KV ──────────────────────────────────────────────
110
+ const updatedJob: WorkflowJob = {
111
+ ...job,
112
+ state: result.state,
113
+ updatedAt: new Date().toISOString(),
114
+ architecturePlan: result.architecturePlan,
115
+ graph: result.graph,
116
+ compiledWorkflow: result.compiledWorkflow,
117
+ validationReport: result.validationReport,
118
+ simulationReport: result.simulationReport,
119
+ credentialAnalysis: result.credentialAnalysis,
120
+ qualityScore: result.qualityScore,
121
+ auditLog: [
122
+ ...job.auditLog,
123
+ createAuditEvent(jobId, 'job.generated', 'ai', {
124
+ state: result.state,
125
+ qualityScore: result.qualityScore?.overallScore,
126
+ swarmSize: result.swarmResult?.swarmSize,
127
+ triggerType: result.triggerDetection?.triggerType,
128
+ }),
129
+ ],
130
+ };
131
+
132
+ await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updatedJob), {
133
+ expirationTtl: 86400 * 7,
134
+ });
135
+
136
+ return c.json({ success: true, jobId, ...result }, 200);
137
+ });
138
+
139
+ async function updateJobState(kv: KVNamespace, jobId: string, state: string, error?: string) {
140
+ const raw = await kv.get(`job:${jobId}`, 'text');
141
+ if (!raw) return;
142
+ const job = JSON.parse(raw) as WorkflowJob;
143
+ await kv.put(`job:${jobId}`, JSON.stringify({
144
+ ...job,
145
+ state,
146
+ updatedAt: new Date().toISOString(),
147
+ error,
148
+ }), { expirationTtl: 86400 * 7 });
149
+ }
apps/worker/src/routes/health.ts ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * GET /health β€” UPGRADED
3
+ * Public health check endpoint with system capabilities info
4
+ */
5
+ import { Hono } from 'hono';
6
+ import type { Env } from '../types/env';
7
+
8
+ export const healthRoute = new Hono<{ Bindings: Env }>();
9
+
10
+ healthRoute.get('/', async (c) => {
11
+ return c.json({
12
+ status: 'ok',
13
+ service: 'workflow-factor-os',
14
+ version: '2.0.0',
15
+ environment: c.env.ENVIRONMENT,
16
+ timestamp: new Date().toISOString(),
17
+ capabilities: {
18
+ llmGateway: 'provider-agnostic (openai | anthropic | openai-compatible | custom)',
19
+ llmProvider: c.env.LLM_PROVIDER ?? 'openai',
20
+ llmModel: c.env.LLM_MODEL ?? 'gpt-4o',
21
+ swarmOrchestration: 'parallel multi-agent design (up to 5 candidates)',
22
+ selfHealing: 'automatic validation fix + AI-assisted healing',
23
+ webhookAutoBind: 'auto-trigger detection and configuration',
24
+ memorySystem: 'in-process pattern learning and anti-pattern prevention',
25
+ nodeRegistry: 'strict β€” only verified n8n node types allowed',
26
+ safetyGates: 'validate β†’ simulate β†’ deploy β†’ approve β†’ activate',
27
+ },
28
+ endpoints: {
29
+ generate: 'POST /api/workflows/generate',
30
+ validate: 'POST /api/workflows/validate',
31
+ simulate: 'POST /api/workflows/simulate',
32
+ deploy: 'POST /api/workflows/deploy',
33
+ requestApproval: 'POST /api/workflows/request-approval',
34
+ approve: 'POST /api/workflows/approve',
35
+ activate: 'POST /api/workflows/activate',
36
+ reports: {
37
+ workflow: 'GET /api/reports/workflow/:id',
38
+ validation: 'GET /api/reports/validation/:id',
39
+ simulation: 'GET /api/reports/simulation/:id',
40
+ credential: 'GET /api/reports/credential/:id',
41
+ audit: 'GET /api/reports/audit/:id',
42
+ },
43
+ },
44
+ safetyPolicy: {
45
+ autoActivation: 'DISABLED β€” human approval always required',
46
+ partialWorkflows: 'REJECTED β€” never deploy partially valid workflows',
47
+ unknownNodes: 'REJECTED β€” only registered n8n node types allowed',
48
+ activeOnDeploy: 'FORCED FALSE β€” active: false enforced at all stages',
49
+ },
50
+ });
51
+ });
apps/worker/src/routes/reports.ts ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * GET /api/reports/workflow/:id
3
+ * GET /api/reports/validation/:id
4
+ * GET /api/reports/simulation/:id
5
+ * GET /api/reports/credential/:id
6
+ * GET /api/reports/audit/:id
7
+ */
8
+ import { Hono } from 'hono';
9
+ import type { Env } from '../types/env';
10
+ import type { WorkflowJob } from '../types/workflow';
11
+
12
+ export const reportsRoute = new Hono<{ Bindings: Env }>();
13
+
14
+ // Full workflow job report
15
+ reportsRoute.get('/workflow/:id', async (c) => {
16
+ const id = c.req.param('id');
17
+ const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text');
18
+ if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404);
19
+ const job = JSON.parse(raw) as WorkflowJob;
20
+ return c.json({ success: true, job });
21
+ });
22
+
23
+ // Validation report only
24
+ reportsRoute.get('/validation/:id', async (c) => {
25
+ const id = c.req.param('id');
26
+ const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text');
27
+ if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404);
28
+ const job = JSON.parse(raw) as WorkflowJob;
29
+ if (!job.validationReport) {
30
+ return c.json({ success: false, error: 'No validation report available yet.' }, 404);
31
+ }
32
+ return c.json({ success: true, jobId: id, validationReport: job.validationReport });
33
+ });
34
+
35
+ // Simulation report only
36
+ reportsRoute.get('/simulation/:id', async (c) => {
37
+ const id = c.req.param('id');
38
+ const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text');
39
+ if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404);
40
+ const job = JSON.parse(raw) as WorkflowJob;
41
+ if (!job.simulationReport) {
42
+ return c.json({ success: false, error: 'No simulation report available yet.' }, 404);
43
+ }
44
+ return c.json({ success: true, jobId: id, simulationReport: job.simulationReport });
45
+ });
46
+
47
+ // Credential analysis
48
+ reportsRoute.get('/credential/:id', async (c) => {
49
+ const id = c.req.param('id');
50
+ const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text');
51
+ if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404);
52
+ const job = JSON.parse(raw) as WorkflowJob;
53
+ if (!job.credentialAnalysis) {
54
+ return c.json({ success: false, error: 'No credential analysis available yet.' }, 404);
55
+ }
56
+ return c.json({ success: true, jobId: id, credentialAnalysis: job.credentialAnalysis });
57
+ });
58
+
59
+ // Audit trail
60
+ reportsRoute.get('/audit/:id', async (c) => {
61
+ const id = c.req.param('id');
62
+ const raw = await c.env.WFO_CACHE.get(`job:${id}`, 'text');
63
+ if (!raw) return c.json({ success: false, error: `Job ${id} not found` }, 404);
64
+ const job = JSON.parse(raw) as WorkflowJob;
65
+ return c.json({
66
+ success: true,
67
+ jobId: id,
68
+ state: job.state,
69
+ auditLog: job.auditLog,
70
+ createdAt: job.createdAt,
71
+ updatedAt: job.updatedAt,
72
+ });
73
+ });
apps/worker/src/routes/simulate.ts ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * POST /api/workflows/simulate
3
+ * Dry-run simulation - offloaded entirely to external compute service
4
+ * CF Worker just proxies the request and stores results in KV
5
+ */
6
+ import { Hono } from 'hono';
7
+ import { z } from 'zod';
8
+ import type { Env } from '../types/env';
9
+ import type { WorkflowJob } from '../types/workflow';
10
+ import { createAuditEvent } from '../utils/audit';
11
+
12
+ export const simulateWorkflowRoute = new Hono<{ Bindings: Env }>();
13
+
14
+ const SimulateRequestSchema = z.object({
15
+ jobId: z.string().min(1),
16
+ mockData: z.record(z.unknown()).optional(),
17
+ });
18
+
19
+ simulateWorkflowRoute.post('/simulate', async (c) => {
20
+ const body = await c.req.json().catch(() => null);
21
+ if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400);
22
+
23
+ const parsed = SimulateRequestSchema.safeParse(body);
24
+ if (!parsed.success) {
25
+ return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400);
26
+ }
27
+
28
+ const { jobId, mockData } = parsed.data;
29
+ const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text');
30
+ if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404);
31
+
32
+ const job = JSON.parse(raw) as WorkflowJob;
33
+
34
+ if (!job.compiledWorkflow || !job.graph) {
35
+ return c.json({
36
+ success: false,
37
+ error: 'Workflow not compiled or validated. Run /generate and /validate first.',
38
+ }, 409);
39
+ }
40
+
41
+ if (job.validationReport && !job.validationReport.valid) {
42
+ return c.json({
43
+ success: false,
44
+ error: 'Cannot simulate an invalid workflow. Fix validation issues first.',
45
+ validationReport: job.validationReport,
46
+ }, 409);
47
+ }
48
+
49
+ // Offload simulation to external compute (CPU-intensive)
50
+ const resp = await fetch(`${c.env.SIMULATOR_URL}/process/simulate`, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ 'X-Internal-Secret': c.env.INTERNAL_API_SECRET,
55
+ },
56
+ body: JSON.stringify({
57
+ jobId,
58
+ workflow: job.compiledWorkflow,
59
+ graph: job.graph,
60
+ architecturePlan: job.architecturePlan,
61
+ mockData: mockData ?? {},
62
+ }),
63
+ signal: AbortSignal.timeout(25000),
64
+ }).catch((e) => { throw new Error(`Simulator error: ${e instanceof Error ? e.message : 'unknown'}`); });
65
+
66
+ if (!resp.ok) {
67
+ return c.json({ success: false, error: `Simulator returned ${resp.status}` }, 502);
68
+ }
69
+
70
+ const result = await resp.json() as { simulationReport: WorkflowJob['simulationReport'] };
71
+
72
+ const updated: WorkflowJob = {
73
+ ...job,
74
+ state: result.simulationReport?.passed ? 'simulated' : 'validated',
75
+ simulationReport: result.simulationReport,
76
+ updatedAt: new Date().toISOString(),
77
+ auditLog: [
78
+ ...job.auditLog,
79
+ createAuditEvent(jobId, 'simulation.completed', 'system', {
80
+ passed: result.simulationReport?.passed,
81
+ readinessScore: result.simulationReport?.readinessScore,
82
+ }),
83
+ ],
84
+ };
85
+
86
+ await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 });
87
+
88
+ return c.json({ success: true, jobId, simulationReport: result.simulationReport });
89
+ });
apps/worker/src/routes/validate.ts ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * POST /api/workflows/validate
3
+ * Triggers validation on an already-generated workflow job
4
+ * Offloads to external simulator service
5
+ */
6
+ import { Hono } from 'hono';
7
+ import { z } from 'zod';
8
+ import type { Env } from '../types/env';
9
+ import type { WorkflowJob } from '../types/workflow';
10
+ import { createAuditEvent } from '../utils/audit';
11
+
12
+ export const validateWorkflowRoute = new Hono<{ Bindings: Env }>();
13
+
14
+ const ValidateRequestSchema = z.object({
15
+ jobId: z.string().min(1),
16
+ });
17
+
18
+ validateWorkflowRoute.post('/validate', async (c) => {
19
+ const body = await c.req.json().catch(() => null);
20
+ if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400);
21
+
22
+ const parsed = ValidateRequestSchema.safeParse(body);
23
+ if (!parsed.success) {
24
+ return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400);
25
+ }
26
+
27
+ const { jobId } = parsed.data;
28
+ const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text');
29
+ if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404);
30
+
31
+ const job = JSON.parse(raw) as WorkflowJob;
32
+ if (!job.compiledWorkflow) {
33
+ return c.json({ success: false, error: 'Workflow not yet compiled. Run /generate first.' }, 409);
34
+ }
35
+
36
+ // Offload validation to external compute service
37
+ const resp = await fetch(`${c.env.SIMULATOR_URL}/process/validate`, {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ 'X-Internal-Secret': c.env.INTERNAL_API_SECRET,
42
+ },
43
+ body: JSON.stringify({
44
+ jobId,
45
+ workflow: job.compiledWorkflow,
46
+ graph: job.graph,
47
+ n8nBaseUrl: c.env.N8N_BASE_URL,
48
+ n8nApiKey: c.env.N8N_API_KEY,
49
+ }),
50
+ signal: AbortSignal.timeout(15000),
51
+ }).catch((e) => { throw new Error(`Simulator error: ${e instanceof Error ? e.message : 'unknown'}`); });
52
+
53
+ if (!resp.ok) {
54
+ return c.json({ success: false, error: `Simulator returned ${resp.status}` }, 502);
55
+ }
56
+
57
+ const result = await resp.json() as { validationReport: WorkflowJob['validationReport'] };
58
+
59
+ // Update job in KV
60
+ const updated: WorkflowJob = {
61
+ ...job,
62
+ state: result.validationReport?.valid ? 'validated' : 'generated',
63
+ validationReport: result.validationReport,
64
+ updatedAt: new Date().toISOString(),
65
+ auditLog: [
66
+ ...job.auditLog,
67
+ createAuditEvent(jobId, 'validation.completed', 'system', {
68
+ valid: result.validationReport?.valid,
69
+ score: result.validationReport?.overallScore,
70
+ }),
71
+ ],
72
+ };
73
+
74
+ await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 });
75
+
76
+ return c.json({ success: true, jobId, validationReport: result.validationReport });
77
+ });
apps/worker/src/types/env.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Cloudflare Worker Environment Bindings β€” UPGRADED
3
+ * Provider-agnostic LLM config (no direct OpenAI dependency)
4
+ * Supports any OpenAI-compatible endpoint, Anthropic, or custom gateway
5
+ */
6
+ type KVNamespace = any;
7
+ export interface Env {
8
+ // ─── KV Namespaces ──────────────────────────────────────────────────────────
9
+ WFO_CACHE: KVNamespace; // workflow jobs, reports, approval states
10
+
11
+ // ─── LLM Gateway Secrets (provider-agnostic) ─────────────────────────────
12
+ LLM_API_KEY: string; // provider API key (OpenAI, Anthropic, or custom)
13
+ LLM_GATEWAY_URL: string; // e.g. https://api.openai.com/v1 or custom endpoint
14
+ LLM_MODEL: string; // e.g. gpt-4o, claude-3-5-sonnet-20241022, etc.
15
+ LLM_PROVIDER: string; // openai | anthropic | openai-compatible | custom
16
+
17
+ // ─── Legacy fallback (for backward compatibility) ─────────────────────────
18
+ OPENAI_API_KEY: string; // deprecated β€” use LLM_API_KEY instead
19
+
20
+ // ─── Fallback LLM provider (optional) ────────────────────────────────────
21
+ LLM_FALLBACK_URL: string; // fallback gateway URL (optional)
22
+ LLM_FALLBACK_KEY: string; // fallback API key (optional)
23
+ LLM_FALLBACK_MODEL: string; // fallback model (optional)
24
+
25
+ // ─── n8n Instance ────────────────────────────────────────────────────────
26
+ N8N_API_KEY: string; // n8n REST API key
27
+ INTERNAL_API_SECRET: string; // shared secret between Worker ↔ Simulator service
28
+
29
+ // ─── Env Vars (wrangler.toml [vars]) ─────────────────────────────────────
30
+ ENVIRONMENT: 'development' | 'production';
31
+ SIMULATOR_URL: string; // external compute service URL (Express/Node.js)
32
+ N8N_BASE_URL: string; // n8n instance base URL
33
+ }
apps/worker/src/types/workflow.ts ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Shared Workflow Types β€” UPGRADED
3
+ * Complete type definitions for all workflow stages
4
+ * Supports Swarm, Self-Healing, Memory, WebhookAutoBind outputs
5
+ */
6
+
7
+ // ─── Domain / Meta types ──────────────────────────────────────────────────────
8
+
9
+ export type WorkflowDomain =
10
+ | 'sales_automation'
11
+ | 'ai_workflow'
12
+ | 'data_pipeline'
13
+ | 'webhook_system'
14
+ | 'notification'
15
+ | 'crm_integration'
16
+ | 'devops'
17
+ | 'ecommerce'
18
+ | 'general';
19
+
20
+ export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
21
+
22
+ export type WorkflowLifecycleState =
23
+ | 'draft'
24
+ | 'generated'
25
+ | 'validated'
26
+ | 'simulated'
27
+ | 'deployed'
28
+ | 'awaiting_approval'
29
+ | 'approved'
30
+ | 'rejected'
31
+ | 'activated'
32
+ | 'failed';
33
+
34
+ export type WorkflowLayer =
35
+ | 'trigger'
36
+ | 'validation'
37
+ | 'transform'
38
+ | 'ai'
39
+ | 'integration'
40
+ | 'control'
41
+ | 'database'
42
+ | 'utility'
43
+ | 'monitoring'
44
+ | 'error_handling';
45
+
46
+ // ─── Intent ──────────────────────────────────────────────────────────────────
47
+
48
+ export interface WorkflowIntent {
49
+ workflowType: string;
50
+ requiresAI: boolean;
51
+ integrations: string[];
52
+ riskLevel: RiskLevel;
53
+ requiresHumanApproval: boolean;
54
+ syncVsAsync: 'sync' | 'async' | 'hybrid';
55
+ scalingRequirements: 'low' | 'medium' | 'high';
56
+ identifiedRisks: string[];
57
+ estimatedComplexity: 'simple' | 'moderate' | 'complex';
58
+ domain: WorkflowDomain;
59
+ triggerType?: string; // Auto-detected trigger type
60
+ triggerHint?: string; // Why this trigger was detected
61
+ }
62
+
63
+ // ─── Architecture Plan ────────────────────────────────────────────────────────
64
+
65
+ export interface ExecutionPlanStep {
66
+ step: number;
67
+ layer: string;
68
+ nodes: string[];
69
+ description: string;
70
+ }
71
+
72
+ export interface Risk {
73
+ type: string;
74
+ probability: RiskLevel;
75
+ impact: RiskLevel;
76
+ description: string;
77
+ }
78
+
79
+ export interface RiskAnalysis {
80
+ overallRisk: RiskLevel;
81
+ risks: Risk[];
82
+ mitigations: string[];
83
+ }
84
+
85
+ export interface TradeoffExplanation {
86
+ option: string;
87
+ pros: string[];
88
+ cons: string[];
89
+ recommendation: string;
90
+ }
91
+
92
+ export interface QualityTargets {
93
+ reliability: number;
94
+ performance: string;
95
+ observability: string;
96
+ }
97
+
98
+ export interface SubworkflowPlan {
99
+ name: string;
100
+ trigger: string;
101
+ purpose: string;
102
+ }
103
+
104
+ export interface DependencyAnalysis {
105
+ external: string[];
106
+ credentials: string[];
107
+ requiredPermissions: string[];
108
+ }
109
+
110
+ export interface WorkflowArchitecturePlan {
111
+ name: string;
112
+ description: string;
113
+ triggerNode: string;
114
+ triggerDescription: string;
115
+ layers: ExecutionPlanStep[];
116
+ nodeList: Array<{
117
+ displayName: string;
118
+ n8nNodeType: string;
119
+ layer: string;
120
+ purpose: string;
121
+ isCritical: boolean;
122
+ requiresCredential: string | null;
123
+ keyParameters?: Record<string, unknown>;
124
+ }>;
125
+ dataFlow: string[];
126
+ riskAnalysis: RiskAnalysis;
127
+ estimatedNodes: number;
128
+ complexity: 'simple' | 'moderate' | 'complex';
129
+ qualityTargets?: QualityTargets;
130
+ tradeoffs?: TradeoffExplanation[];
131
+ subworkflows?: SubworkflowPlan[];
132
+ dependencyAnalysis?: DependencyAnalysis;
133
+ }
134
+
135
+ // ─── Graph (IR) ───────────────────────────────────────────────────────────────
136
+
137
+ export interface RetryPolicy {
138
+ maxRetries: number;
139
+ retryDelayMs: number;
140
+ retryOn: string[];
141
+ }
142
+
143
+ export interface DataContract {
144
+ inputs: string[];
145
+ outputs: string[];
146
+ }
147
+
148
+ export interface GraphNode {
149
+ id: string;
150
+ label: string;
151
+ n8nNodeType: string;
152
+ layer: WorkflowLayer;
153
+ description: string;
154
+ isCritical: boolean;
155
+ position: { x: number; y: number };
156
+ retryPolicy?: RetryPolicy;
157
+ fallbackNodeId?: string;
158
+ dataContract?: DataContract;
159
+ keyParameters?: Record<string, unknown>;
160
+ autoConfigured?: boolean;
161
+ webhookPath?: string;
162
+ }
163
+
164
+ export interface GraphEdge {
165
+ id: string;
166
+ sourceNodeId: string;
167
+ targetNodeId: string;
168
+ outputIndex: number;
169
+ inputIndex: number;
170
+ label?: string;
171
+ }
172
+
173
+ export interface WorkflowMetadata {
174
+ domain: string;
175
+ version: string;
176
+ createdAt: string;
177
+ estimatedNodes?: number;
178
+ estimatedDuration?: string;
179
+ riskLevel?: RiskLevel;
180
+ unknownNodesRejected?: string[];
181
+ }
182
+
183
+ export interface WorkflowGraph {
184
+ workflowId: string;
185
+ name: string;
186
+ nodes: GraphNode[];
187
+ edges: GraphEdge[];
188
+ metadata: WorkflowMetadata;
189
+ }
190
+
191
+ // ─── n8n Workflow JSON ────────────────────────────────────────────────────────
192
+
193
+ export interface N8nNode {
194
+ id: string;
195
+ name: string;
196
+ type: string;
197
+ typeVersion: number;
198
+ position: [number, number];
199
+ parameters: Record<string, unknown>;
200
+ credentials?: Record<string, { id: string; name: string }>;
201
+ onError?: string;
202
+ retryOnFail?: boolean;
203
+ maxTries?: number;
204
+ waitBetweenTries?: number;
205
+ notes?: string;
206
+ }
207
+
208
+ export interface N8nConnections {
209
+ [nodeName: string]: {
210
+ main: Array<Array<{ node: string; type: string; index: number }>>;
211
+ };
212
+ }
213
+
214
+ export interface N8nWorkflow {
215
+ name: string;
216
+ active: false; // ALWAYS false β€” never auto-activate
217
+ nodes: N8nNode[];
218
+ connections: N8nConnections;
219
+ settings?: {
220
+ executionOrder?: string;
221
+ saveManualExecutions?: boolean;
222
+ timezone?: string;
223
+ errorWorkflow?: string;
224
+ callerPolicy?: string;
225
+ };
226
+ tags?: string[];
227
+ meta?: Record<string, unknown>;
228
+ }
229
+
230
+ // ─── Validation ───────────────────────────────────────────────────────────────
231
+
232
+ export interface ValidationIssue {
233
+ id: string;
234
+ severity: 'error' | 'warning' | 'info';
235
+ category: string;
236
+ nodeId?: string;
237
+ message: string;
238
+ suggestion: string;
239
+ }
240
+
241
+ export interface ValidationSection {
242
+ passed: boolean;
243
+ score: number;
244
+ issues: ValidationIssue[];
245
+ }
246
+
247
+ export interface ValidationReport {
248
+ jobId: string;
249
+ valid: boolean;
250
+ overallScore: number;
251
+ schemaValidation: ValidationSection;
252
+ credentialValidation: ValidationSection;
253
+ graphValidation: ValidationSection;
254
+ expressionValidation: ValidationSection;
255
+ reliabilityValidation: ValidationSection;
256
+ nodeRegistryValidation?: ValidationSection; // NEW
257
+ dataFlowValidation?: ValidationSection; // NEW
258
+ issues: ValidationIssue[]; // errors only
259
+ warnings: ValidationIssue[];
260
+ validatedAt: string;
261
+ }
262
+
263
+ // ─── Simulation ───────────────────────────────────────────────────────────────
264
+
265
+ export interface ExecutionTraceStep {
266
+ stepNumber: number;
267
+ nodeId: string;
268
+ nodeName: string;
269
+ nodeType: string;
270
+ status: 'success' | 'warning' | 'failure';
271
+ inputData: Record<string, unknown>;
272
+ outputData: Record<string, unknown>;
273
+ executionTimeMs: number;
274
+ issues?: string[];
275
+ notes?: string;
276
+ }
277
+
278
+ export interface FailurePoint {
279
+ nodeId: string;
280
+ nodeName: string;
281
+ failureType: string;
282
+ probability: RiskLevel;
283
+ description: string;
284
+ mitigation: string;
285
+ }
286
+
287
+ export interface ChaosTestResult {
288
+ scenario: 'timeout' | 'null_data' | 'malformed_input' | 'expired_credentials' | 'rate_limit' | 'network_error';
289
+ passed: boolean;
290
+ affectedNode: string;
291
+ description: string;
292
+ resilient: boolean;
293
+ fallbackActivated: boolean;
294
+ }
295
+
296
+ export interface SimulationReport {
297
+ jobId: string;
298
+ passed: boolean;
299
+ readinessScore: number;
300
+ executionTrace: ExecutionTraceStep[];
301
+ riskAnalysis: RiskAnalysis;
302
+ predictedFailurePoints: FailurePoint[];
303
+ chaosTestResults: ChaosTestResult[];
304
+ deploymentBlockers?: string[]; // NEW
305
+ recommendations?: string[]; // NEW
306
+ mockDataUsed: Record<string, unknown>;
307
+ simulatedAt: string;
308
+ }
309
+
310
+ // ─── Credentials ─────────────────────────────────────────────────────────────
311
+
312
+ export interface RequiredCredential {
313
+ nodeId: string;
314
+ nodeName: string;
315
+ credentialType: string;
316
+ credentialDisplayName?: string;
317
+ required: boolean;
318
+ }
319
+
320
+ export interface AvailableCredential {
321
+ id: string;
322
+ name: string;
323
+ type: string;
324
+ }
325
+
326
+ export interface MissingCredential {
327
+ credentialType: string;
328
+ requiredByNodes: string[];
329
+ setupInstructions: string;
330
+ }
331
+
332
+ export interface CredentialAnalysis {
333
+ jobId: string;
334
+ allCredentialsPresent: boolean;
335
+ n8nReachable?: boolean; // NEW
336
+ required: RequiredCredential[];
337
+ available: AvailableCredential[];
338
+ missing: MissingCredential[];
339
+ analysedAt: string;
340
+ }
341
+
342
+ // ─── Quality Scoring ─────────────────────────────────────────────────────────
343
+
344
+ export interface DeploymentReadiness {
345
+ deploymentReady: boolean;
346
+ confidenceScore: number;
347
+ riskLevel: RiskLevel;
348
+ blockingIssues: string[];
349
+ recommendations: string[];
350
+ }
351
+
352
+ export interface WorkflowQualityScore {
353
+ overallScore: number;
354
+ reliabilityScore: number;
355
+ maintainabilityScore: number;
356
+ observabilityScore: number;
357
+ retryCoverage: number;
358
+ expressionSafety: number;
359
+ credentialReadiness: number;
360
+ branchingQuality: number;
361
+ deploymentSafety: number;
362
+ riskLevel: RiskLevel;
363
+ scoreExplanations: Record<string, string>;
364
+ }
365
+
366
+ // ─── Deployment ───────────────────────────────────────────────────────────────
367
+
368
+ export interface DeploymentResult {
369
+ jobId: string;
370
+ n8nWorkflowId?: string;
371
+ status: 'success' | 'failed';
372
+ deployedAt: string;
373
+ errorMessage?: string;
374
+ }
375
+
376
+ export interface ActivationResult {
377
+ jobId: string;
378
+ n8nWorkflowId: string;
379
+ activationStatus: 'activated' | 'failed';
380
+ finalValidationPassed: boolean;
381
+ finalCredentialCheckPassed: boolean;
382
+ activatedAt: string;
383
+ diagnostics?: string;
384
+ }
385
+
386
+ export interface ApprovalRequest {
387
+ jobId: string;
388
+ requestedAt: string;
389
+ expiresAt: string;
390
+ architectureSummary: string;
391
+ riskSummary: string;
392
+ readinessScore: number;
393
+ validationScore: number;
394
+ simulationScore: number;
395
+ credentialStatus: 'complete' | 'incomplete';
396
+ status: 'pending' | 'approved' | 'rejected';
397
+ reviewerNote?: string;
398
+ respondedAt?: string;
399
+ }
400
+
401
+ // ─── Audit ────────────────────────────────────────────────────────────────────
402
+
403
+ export interface AuditEvent {
404
+ id: string;
405
+ jobId: string;
406
+ event: string;
407
+ actor: 'user' | 'ai' | 'system';
408
+ timestamp: string;
409
+ data?: Record<string, unknown>;
410
+ }
411
+
412
+ // ─── Swarm Result Summary ─────────────────────────────────────────────────────
413
+
414
+ export interface SwarmResultSummary {
415
+ swarmSize: number;
416
+ generatedAt: string;
417
+ winnerStrategy: string;
418
+ winnerScore: number;
419
+ candidateScores: Array<{ id: string; strategy: string; score: number }>;
420
+ selectionReason?: string;
421
+ }
422
+
423
+ // ─── Trigger Detection ────────────────────────────────────────────────────────
424
+
425
+ export interface TriggerDetectionResult {
426
+ nodeType: string;
427
+ triggerType: string;
428
+ confidence: 'high' | 'medium' | 'low';
429
+ webhookPath?: string;
430
+ notes: string;
431
+ }
432
+
433
+ // ─── Self-Healing Summary ─────────────────────────────────────────────────────
434
+
435
+ export interface HealingResultSummary {
436
+ healed: boolean;
437
+ actionsApplied: Array<{
438
+ type: string;
439
+ targetNodeId?: string;
440
+ description: string;
441
+ applied: boolean;
442
+ }>;
443
+ remainingIssues: string[];
444
+ confidence: number;
445
+ }
446
+
447
+ // ─── Job ─────────────────────────────────────────────────────────────────────
448
+
449
+ export interface WorkflowJob {
450
+ id: string;
451
+ userRequest: string;
452
+ state: WorkflowLifecycleState;
453
+ createdAt: string;
454
+ updatedAt: string;
455
+ intent?: WorkflowIntent;
456
+ architecturePlan?: WorkflowArchitecturePlan;
457
+ graph?: WorkflowGraph;
458
+ compiledWorkflow?: N8nWorkflow;
459
+ validationReport?: ValidationReport;
460
+ simulationReport?: SimulationReport;
461
+ credentialAnalysis?: CredentialAnalysis;
462
+ deploymentResult?: DeploymentResult;
463
+ activationResult?: ActivationResult;
464
+ approvalRequest?: ApprovalRequest;
465
+ qualityScore?: WorkflowQualityScore; // NEW
466
+ healingResult?: HealingResultSummary; // NEW
467
+ swarmResult?: SwarmResultSummary; // NEW
468
+ triggerDetection?: TriggerDetectionResult; // NEW
469
+ auditLog: AuditEvent[];
470
+ error?: string;
471
+ }
472
+
473
+ // ─── API Response ─────────────────────────────────────────────────────────────
474
+
475
+ export interface GenerateWorkflowResponse {
476
+ success: boolean;
477
+ jobId: string;
478
+ state: 'generated' | 'validated' | 'simulated';
479
+ message: string;
480
+ architecturePlan?: WorkflowArchitecturePlan;
481
+ graph?: WorkflowGraph;
482
+ compiledWorkflow?: N8nWorkflow;
483
+ validationReport?: ValidationReport;
484
+ simulationReport?: SimulationReport;
485
+ credentialAnalysis?: CredentialAnalysis;
486
+ deploymentReadiness?: DeploymentReadiness;
487
+ qualityScore?: WorkflowQualityScore;
488
+ healingResult?: HealingResultSummary;
489
+ swarmResult?: SwarmResultSummary;
490
+ triggerDetection?: TriggerDetectionResult;
491
+ memoryStats?: {
492
+ totalPatterns: number;
493
+ successPatterns: number;
494
+ failurePatterns: number;
495
+ templates: number;
496
+ avgSuccessScore: number;
497
+ fixStrategies: number;
498
+ };
499
+ }