@kairos-sdk/core 0.4.0 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/{chunk-N6LRD2FN.js → chunk-4TS6GW6O.js} +60 -372
- package/dist/chunk-4TS6GW6O.js.map +1 -0
- package/dist/chunk-6CLI43FI.js +315 -0
- package/dist/chunk-6CLI43FI.js.map +1 -0
- package/dist/chunk-6FOFWVMG.js +1 -0
- package/dist/chunk-6FOFWVMG.js.map +1 -0
- package/dist/{chunk-NJ6QZBIC.js → chunk-6IXW3WCC.js} +477 -534
- package/dist/chunk-6IXW3WCC.js.map +1 -0
- package/dist/chunk-CR2NHLOH.js +523 -0
- package/dist/chunk-CR2NHLOH.js.map +1 -0
- package/dist/cli.cjs +632 -154
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +56 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +577 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -540
- package/dist/index.d.ts +3 -540
- package/dist/index.js +8 -4
- package/dist/mcp-server.cjs +651 -122
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/mcp-server.js +91 -8
- package/dist/mcp-server.js.map +1 -1
- package/dist/reader-CpUcHhKW.d.cts +566 -0
- package/dist/reader-CpUcHhKW.d.ts +566 -0
- package/dist/standalone.cjs +2460 -0
- package/dist/standalone.cjs.map +1 -0
- package/dist/standalone.d.cts +105 -0
- package/dist/standalone.d.ts +105 -0
- package/dist/standalone.js +58 -0
- package/dist/standalone.js.map +1 -0
- package/package.json +6 -1
- package/dist/chunk-N6LRD2FN.js.map +0 -1
- package/dist/chunk-NJ6QZBIC.js.map +0 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
Kairos turns plain-English workflow descriptions into validated, deployable n8n workflow JSON. Use it as an **MCP server** (connect to Claude Code, Claude Desktop, or any MCP host — your LLM generates, Kairos validates and deploys, no Anthropic API key needed) or as a **TypeScript SDK** for programmatic control (calls Claude internally with a specialized prompt). Either way, workflows pass through a **
|
|
11
|
+
Kairos turns plain-English workflow descriptions into validated, deployable n8n workflow JSON. Use it as an **MCP server** (connect to Claude Code, Claude Desktop, or any MCP host — your LLM generates, Kairos validates and deploys, no Anthropic API key needed) or as a **TypeScript SDK** for programmatic control (calls Claude internally with a specialized prompt). Either way, workflows pass through a **26-rule structural validator** with automatic correction, and a local workflow library with **hybrid retrieval** (TF-IDF + node fingerprinting + outcome history + cluster reranking) injects past failure patterns into future generations. With a seeded template library, Kairos achieves **100% first-try structural validation pass rate** across 20 benchmark prompts (meaning the generated JSON is structurally valid on the first attempt — runtime behavior depends on your credentials and node configuration).
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import { Kairos } from '@kairos-sdk/core'
|
|
@@ -95,7 +95,7 @@ The MCP server does **not** call an LLM internally. Instead, it gives your host
|
|
|
95
95
|
|
|
96
96
|
1. **Host LLM calls `kairos_prompt`** — gets the n8n system prompt, node catalog, library matches, and failure patterns
|
|
97
97
|
2. **Host LLM generates the workflow JSON** using that context (no separate API call)
|
|
98
|
-
3. **Host LLM calls `kairos_validate`** — checks the JSON against
|
|
98
|
+
3. **Host LLM calls `kairos_validate`** — checks the JSON against 26 structural rules
|
|
99
99
|
4. If invalid, the host LLM fixes the issues and validates again
|
|
100
100
|
5. **Host LLM calls `kairos_deploy`** — sends the validated workflow to n8n
|
|
101
101
|
|
|
@@ -108,7 +108,7 @@ This means Kairos works with **any LLM** — Claude, GPT, Gemini, Llama, or anyt
|
|
|
108
108
|
| Tool | Description |
|
|
109
109
|
|------|-------------|
|
|
110
110
|
| `kairos_prompt` | Returns the specialized system prompt, node catalog, library matches, and failure patterns for a given description |
|
|
111
|
-
| `kairos_validate` | Validates workflow JSON against
|
|
111
|
+
| `kairos_validate` | Validates workflow JSON against 26 structural rules — returns errors and warnings |
|
|
112
112
|
| `kairos_search` | Searches the local workflow library for similar past builds |
|
|
113
113
|
| `kairos_sync` | Manually refresh the node catalog from your n8n instance (auto-runs on first `kairos_prompt` call) |
|
|
114
114
|
|
|
@@ -180,7 +180,7 @@ console.log(deployed.workflowId) // now live in n8n
|
|
|
180
180
|
|
|
181
181
|
## Benchmark Results
|
|
182
182
|
|
|
183
|
-
Tested against 20 workflow prompts of varying complexity (simple triggers, multi-step conditional logic, AI agents with memory). Results measure **structural validation pass rate** — whether the generated workflow passes all
|
|
183
|
+
Tested against 20 workflow prompts of varying complexity (simple triggers, multi-step conditional logic, AI agents with memory). Results measure **structural validation pass rate** — whether the generated workflow passes all 26 validator rules, not end-to-end execution correctness.
|
|
184
184
|
|
|
185
185
|
### Before vs After: Template-Seeded Library
|
|
186
186
|
|
|
@@ -192,7 +192,7 @@ Tested against 20 workflow prompts of varying complexity (simple triggers, multi
|
|
|
192
192
|
| Avg generation time | 30.6s | **20.7s** | -32% |
|
|
193
193
|
| Failures | 0 | 0 | — |
|
|
194
194
|
|
|
195
|
-
The baseline run used Claude with the
|
|
195
|
+
The baseline run used Claude with the 26-rule validator and correction loop but no library. The seeded run used the same validator plus a library of 105 workflows (16 organic + 89 ingested from the n8n community). The broader local development library now contains 286+ generated/ingested workflows. Template seeding eliminated the correction loop entirely and cut generation time by a third.
|
|
196
196
|
|
|
197
197
|
> **Note:** These results confirm that generated workflows are structurally valid and deployable to n8n. They do not verify runtime execution correctness, credential configuration, or whether the workflow output matches user intent.
|
|
198
198
|
|
|
@@ -205,7 +205,7 @@ The baseline run used Claude with the 23-rule validator and correction loop but
|
|
|
205
205
|
1. **Search** — Kairos searches its local workflow library for similar past builds. Matching workflows and their failure patterns are pulled into context.
|
|
206
206
|
2. **Warn** — Known failure patterns (from library matches and global telemetry rates) are injected into the system prompt so Claude avoids repeating known mistakes.
|
|
207
207
|
3. **Generate** — Your description is sent to Claude with a detailed system prompt, forcing a `generate_workflow` tool call that produces structured n8n workflow JSON.
|
|
208
|
-
4. **Validate** — The workflow is checked against **
|
|
208
|
+
4. **Validate** — The workflow is checked against **26 structural rules** covering node IDs, types, versions, names, positions, connections, forbidden fields, trigger presence, AI connection direction, cycle detection, webhook pairing, and required parameters.
|
|
209
209
|
5. **Correct** — If validation fails, the specific rule violations are sent back to Claude for correction (up to 3 attempts, with tighter temperature on the final try).
|
|
210
210
|
6. **Strip** — Forbidden server-assigned fields (`id`, `createdAt`, `updatedAt`, etc.) are stripped before deployment.
|
|
211
211
|
7. **Deploy** — The validated workflow is posted to your n8n instance via REST API.
|
|
@@ -215,7 +215,7 @@ The baseline run used Claude with the 23-rule validator and correction loop but
|
|
|
215
215
|
|
|
216
216
|
1. **Prompt** — Your LLM calls `kairos_prompt`, which searches the library and returns the specialized system prompt, node catalog, library matches, and failure patterns.
|
|
217
217
|
2. **Generate** — Your LLM generates the workflow JSON itself using that context. No separate API call.
|
|
218
|
-
3. **Validate** — Your LLM calls `kairos_validate`, which checks the JSON against the same
|
|
218
|
+
3. **Validate** — Your LLM calls `kairos_validate`, which checks the JSON against the same 26 structural rules.
|
|
219
219
|
4. **Correct** — If validation fails, your LLM fixes the issues and calls `kairos_validate` again.
|
|
220
220
|
5. **Deploy** — Your LLM calls `kairos_deploy`, which strips forbidden fields and posts the workflow to n8n.
|
|
221
221
|
6. **Record** — The deployed workflow is saved to the local library for future retrieval.
|
|
@@ -224,7 +224,7 @@ The baseline run used Claude with the 23-rule validator and correction loop but
|
|
|
224
224
|
|
|
225
225
|
## Validator Rules
|
|
226
226
|
|
|
227
|
-
The
|
|
227
|
+
The 26-rule validator is the core of what makes Kairos reliable. In baseline testing (no library), Claude needed the correction loop 45% of the time. Each rule targets a specific class of error:
|
|
228
228
|
|
|
229
229
|
| Rule | Severity | What it checks |
|
|
230
230
|
|------|----------|----------------|
|
|
@@ -251,6 +251,9 @@ The 23-rule validator is the core of what makes Kairos reliable. In baseline tes
|
|
|
251
251
|
| 21 | warn | Webhook with responseMode="responseNode" has respondToWebhook |
|
|
252
252
|
| 22 | warn | Required parameters present for known node types |
|
|
253
253
|
| 23 | warn | Node type is recognized in the registry (unknown types may not exist in n8n) |
|
|
254
|
+
| 24 | warn | No deprecated `$node["..."]` accessor syntax in expressions |
|
|
255
|
+
| 25 | warn | No `$json.items[n]` array access (n8n flattens items automatically) |
|
|
256
|
+
| 26 | warn | Node references use `.first()` or `.all()` (bare `$('Node').json` throws at runtime) |
|
|
254
257
|
|
|
255
258
|
Errors block deployment. Warnings are recorded and fed back into the prompt for future builds.
|
|
256
259
|
|
|
@@ -389,7 +392,7 @@ try {
|
|
|
389
392
|
|---|---|
|
|
390
393
|
| `GenerationError` | Anthropic API call failed |
|
|
391
394
|
| `ResponseParseError` | Claude responded but produced no usable tool call |
|
|
392
|
-
| `ValidationError` | Workflow failed
|
|
395
|
+
| `ValidationError` | Workflow failed 26-rule validation after max retries (carries `.attemptMetadata` and `.warnedRules`) |
|
|
393
396
|
| `ProviderError` | Network/auth failure talking to n8n |
|
|
394
397
|
| `ApiError` | n8n returned a 4xx or 5xx (carries `.statusCode`) |
|
|
395
398
|
| `GuardError` | Input validation failed (empty description) or `delete()` called without `{ confirm: true }` |
|
|
@@ -1,175 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
PromptBuilder,
|
|
3
|
+
inferWorkflowType
|
|
4
|
+
} from "./chunk-CR2NHLOH.js";
|
|
5
|
+
import {
|
|
6
|
+
GenerationError,
|
|
7
|
+
GuardError,
|
|
8
|
+
N8nProvider,
|
|
9
|
+
NullLibrary,
|
|
10
|
+
ResponseParseError,
|
|
11
|
+
ValidationError
|
|
12
|
+
} from "./chunk-6CLI43FI.js";
|
|
13
|
+
import {
|
|
3
14
|
N8nApiClient,
|
|
4
15
|
N8nFieldStripper,
|
|
5
16
|
N8nValidator,
|
|
6
17
|
PatternAnalyzer,
|
|
7
|
-
|
|
18
|
+
TelemetryCollector,
|
|
8
19
|
TelemetryReader,
|
|
9
20
|
generateUUID,
|
|
10
21
|
nullLogger,
|
|
11
22
|
scoreToMode
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
|
|
14
|
-
// src/library/null-library.ts
|
|
15
|
-
var NullLibrary = class {
|
|
16
|
-
async initialize() {
|
|
17
|
-
}
|
|
18
|
-
async search(_description, _options) {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
async save(_workflow, _metadata) {
|
|
22
|
-
return generateUUID();
|
|
23
|
-
}
|
|
24
|
-
async recordDeployment(_id) {
|
|
25
|
-
}
|
|
26
|
-
async recordOutcome(_id, _outcome) {
|
|
27
|
-
}
|
|
28
|
-
async get(_id) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
async list(_filters) {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// src/errors/guard-error.ts
|
|
37
|
-
var GuardError = class extends KairosError {
|
|
38
|
-
constructor(message) {
|
|
39
|
-
super(message);
|
|
40
|
-
this.name = "GuardError";
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// src/providers/n8n/provider.ts
|
|
45
|
-
var N8nProvider = class {
|
|
46
|
-
constructor(client, stripper) {
|
|
47
|
-
this.client = client;
|
|
48
|
-
this.stripper = stripper;
|
|
49
|
-
}
|
|
50
|
-
client;
|
|
51
|
-
stripper;
|
|
52
|
-
platform = "n8n";
|
|
53
|
-
async deploy(workflow) {
|
|
54
|
-
const stripped = this.stripper.stripForCreate(workflow);
|
|
55
|
-
const response = await this.client.createWorkflow(stripped);
|
|
56
|
-
return { workflowId: response.id, name: response.name };
|
|
57
|
-
}
|
|
58
|
-
async update(id, workflow) {
|
|
59
|
-
const stripped = this.stripper.stripForUpdate(workflow);
|
|
60
|
-
const response = await this.client.updateWorkflow(id, stripped);
|
|
61
|
-
return { workflowId: response.id, name: response.name };
|
|
62
|
-
}
|
|
63
|
-
async get(id) {
|
|
64
|
-
const response = await this.client.getWorkflow(id);
|
|
65
|
-
return {
|
|
66
|
-
name: response.name,
|
|
67
|
-
nodes: response.nodes,
|
|
68
|
-
connections: response.connections,
|
|
69
|
-
...response.settings !== void 0 ? { settings: response.settings } : {},
|
|
70
|
-
...response.tags !== void 0 ? { tags: response.tags } : {}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
async list() {
|
|
74
|
-
return this.client.listWorkflows();
|
|
75
|
-
}
|
|
76
|
-
async activate(id) {
|
|
77
|
-
await this.client.activateWorkflow(id);
|
|
78
|
-
}
|
|
79
|
-
async deactivate(id) {
|
|
80
|
-
await this.client.deactivateWorkflow(id);
|
|
81
|
-
}
|
|
82
|
-
async delete(id, options) {
|
|
83
|
-
if (options.confirm !== true) {
|
|
84
|
-
throw new GuardError("delete() requires { confirm: true } to prevent accidental deletion");
|
|
85
|
-
}
|
|
86
|
-
await this.client.deleteWorkflow(id);
|
|
87
|
-
}
|
|
88
|
-
async executions(workflowId, filter) {
|
|
89
|
-
return this.client.getExecutions(workflowId, filter);
|
|
90
|
-
}
|
|
91
|
-
async execution(id) {
|
|
92
|
-
return this.client.getExecution(id);
|
|
93
|
-
}
|
|
94
|
-
async listTags() {
|
|
95
|
-
return this.client.listTags();
|
|
96
|
-
}
|
|
97
|
-
async createTag(name) {
|
|
98
|
-
return this.client.createTag(name);
|
|
99
|
-
}
|
|
100
|
-
async tag(workflowId, tagIds) {
|
|
101
|
-
await this.client.tagWorkflow(workflowId, tagIds);
|
|
102
|
-
}
|
|
103
|
-
async untag(workflowId, tagIds) {
|
|
104
|
-
await this.client.untagWorkflow(workflowId, tagIds);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// src/errors/generation-error.ts
|
|
109
|
-
var GenerationError = class extends KairosError {
|
|
110
|
-
constructor(message, cause) {
|
|
111
|
-
super(message, cause);
|
|
112
|
-
this.name = "GenerationError";
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// src/errors/response-parse-error.ts
|
|
117
|
-
var ResponseParseError = class extends KairosError {
|
|
118
|
-
constructor(message, cause) {
|
|
119
|
-
super(message, cause);
|
|
120
|
-
this.name = "ResponseParseError";
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// src/errors/validation-error.ts
|
|
125
|
-
var ValidationError = class extends KairosError {
|
|
126
|
-
constructor(message, issues, attemptMetadata, warnedRules) {
|
|
127
|
-
super(message);
|
|
128
|
-
this.issues = issues;
|
|
129
|
-
this.attemptMetadata = attemptMetadata;
|
|
130
|
-
this.warnedRules = warnedRules;
|
|
131
|
-
this.name = "ValidationError";
|
|
132
|
-
}
|
|
133
|
-
issues;
|
|
134
|
-
attemptMetadata;
|
|
135
|
-
warnedRules;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// src/telemetry/collector.ts
|
|
139
|
-
import { appendFile, mkdir } from "fs/promises";
|
|
140
|
-
import { join } from "path";
|
|
141
|
-
import { homedir } from "os";
|
|
142
|
-
|
|
143
|
-
// src/telemetry/types.ts
|
|
144
|
-
var TELEMETRY_SCHEMA_VERSION = 2;
|
|
145
|
-
|
|
146
|
-
// src/telemetry/collector.ts
|
|
147
|
-
var TelemetryCollector = class {
|
|
148
|
-
dir;
|
|
149
|
-
sessionId;
|
|
150
|
-
dirReady = null;
|
|
151
|
-
constructor(dir) {
|
|
152
|
-
this.dir = dir ?? join(homedir(), ".kairos", "telemetry");
|
|
153
|
-
this.sessionId = generateUUID();
|
|
154
|
-
}
|
|
155
|
-
async emit(eventType, data) {
|
|
156
|
-
const event = {
|
|
157
|
-
schemaVersion: TELEMETRY_SCHEMA_VERSION,
|
|
158
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
159
|
-
sessionId: this.sessionId,
|
|
160
|
-
eventType,
|
|
161
|
-
data
|
|
162
|
-
};
|
|
163
|
-
if (!this.dirReady) {
|
|
164
|
-
this.dirReady = mkdir(this.dir, { recursive: true }).then(() => {
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
await this.dirReady;
|
|
168
|
-
const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
|
|
169
|
-
const filepath = join(this.dir, filename);
|
|
170
|
-
await appendFile(filepath, JSON.stringify(event) + "\n", "utf-8");
|
|
171
|
-
}
|
|
172
|
-
};
|
|
23
|
+
} from "./chunk-6IXW3WCC.js";
|
|
173
24
|
|
|
174
25
|
// src/client.ts
|
|
175
26
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -217,12 +68,12 @@ var GENERATE_WORKFLOW_TOOL = {
|
|
|
217
68
|
}
|
|
218
69
|
};
|
|
219
70
|
var WorkflowDesigner = class {
|
|
220
|
-
constructor(anthropic, model, logger) {
|
|
71
|
+
constructor(anthropic, model, logger, patternsPath) {
|
|
221
72
|
this.anthropic = anthropic;
|
|
222
73
|
this.model = model;
|
|
223
74
|
this.logger = logger;
|
|
224
75
|
this.validator = new N8nValidator();
|
|
225
|
-
this.promptBuilder = new PromptBuilder();
|
|
76
|
+
this.promptBuilder = new PromptBuilder(patternsPath);
|
|
226
77
|
}
|
|
227
78
|
anthropic;
|
|
228
79
|
model;
|
|
@@ -306,6 +157,11 @@ var WorkflowDesigner = class {
|
|
|
306
157
|
}
|
|
307
158
|
}
|
|
308
159
|
extractToolUse(message) {
|
|
160
|
+
if (message.stop_reason === "max_tokens") {
|
|
161
|
+
throw new GenerationError(
|
|
162
|
+
"Claude response was truncated (max_tokens reached) \u2014 the workflow may be too large. Try a simpler description or break it into smaller workflows."
|
|
163
|
+
);
|
|
164
|
+
}
|
|
309
165
|
const toolUseBlock = message.content.find(
|
|
310
166
|
(block) => block.type === "tool_use"
|
|
311
167
|
);
|
|
@@ -332,6 +188,8 @@ var WorkflowDesigner = class {
|
|
|
332
188
|
};
|
|
333
189
|
|
|
334
190
|
// src/client.ts
|
|
191
|
+
import { homedir } from "os";
|
|
192
|
+
import { join } from "path";
|
|
335
193
|
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
336
194
|
var Kairos = class {
|
|
337
195
|
provider;
|
|
@@ -360,7 +218,8 @@ var Kairos = class {
|
|
|
360
218
|
this.provider = null;
|
|
361
219
|
}
|
|
362
220
|
const anthropic = new Anthropic({ apiKey: options.anthropicApiKey });
|
|
363
|
-
|
|
221
|
+
const patternsPath = typeof options.telemetry === "string" ? join(options.telemetry, "..", "patterns.json") : join(homedir(), ".kairos", "patterns.json");
|
|
222
|
+
this.designer = new WorkflowDesigner(anthropic, this.model, logger, patternsPath);
|
|
364
223
|
this.validator = new N8nValidator();
|
|
365
224
|
this.library = options.library ?? new NullLibrary();
|
|
366
225
|
this.logger = logger;
|
|
@@ -393,11 +252,13 @@ var Kairos = class {
|
|
|
393
252
|
this.validateDescription(description);
|
|
394
253
|
this.logger.info("Kairos.build", { description, dryRun: options?.dryRun });
|
|
395
254
|
const buildStart = Date.now();
|
|
255
|
+
const runId = generateUUID();
|
|
256
|
+
const workflowType = inferWorkflowType(description);
|
|
396
257
|
await this.telemetry?.emit("build_start", {
|
|
397
258
|
description,
|
|
398
259
|
model: this.model,
|
|
399
260
|
dryRun: options?.dryRun ?? false
|
|
400
|
-
});
|
|
261
|
+
}, runId);
|
|
401
262
|
await this.library.initialize();
|
|
402
263
|
const matches = await this.library.search(description);
|
|
403
264
|
if (matches.length > 0) {
|
|
@@ -432,8 +293,9 @@ var Kairos = class {
|
|
|
432
293
|
tokensOutput: meta.tokensOutput,
|
|
433
294
|
validationPassed: meta.validationPassed,
|
|
434
295
|
issueCount: meta.issues.length,
|
|
435
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
436
|
-
|
|
296
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, severity: i.severity, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null })),
|
|
297
|
+
workflowType
|
|
298
|
+
}, runId);
|
|
437
299
|
}
|
|
438
300
|
await this.telemetry?.emit("build_complete", {
|
|
439
301
|
description,
|
|
@@ -446,13 +308,14 @@ var Kairos = class {
|
|
|
446
308
|
workflowId: null,
|
|
447
309
|
dryRun: options?.dryRun ?? false,
|
|
448
310
|
credentialsNeeded: 0,
|
|
449
|
-
warnedRules: err.warnedRules ?? []
|
|
450
|
-
|
|
311
|
+
warnedRules: err.warnedRules ?? [],
|
|
312
|
+
workflowType
|
|
313
|
+
}, runId);
|
|
451
314
|
this.updatePatterns();
|
|
452
315
|
}
|
|
453
316
|
throw err;
|
|
454
317
|
}
|
|
455
|
-
await this.emitAttemptTelemetry(description, designResult);
|
|
318
|
+
await this.emitAttemptTelemetry(description, designResult, workflowType, runId);
|
|
456
319
|
const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
|
|
457
320
|
this.saveToLibrary(workflow, description, designResult, matches);
|
|
458
321
|
if (options?.dryRun) {
|
|
@@ -469,8 +332,9 @@ var Kairos = class {
|
|
|
469
332
|
workflowId: null,
|
|
470
333
|
dryRun: true,
|
|
471
334
|
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
472
|
-
warnedRules: designResult.warnedRules
|
|
473
|
-
|
|
335
|
+
warnedRules: designResult.warnedRules,
|
|
336
|
+
workflowType
|
|
337
|
+
}, runId);
|
|
474
338
|
this.updatePatterns();
|
|
475
339
|
return {
|
|
476
340
|
workflowId: null,
|
|
@@ -501,8 +365,9 @@ var Kairos = class {
|
|
|
501
365
|
workflowId: deployed.workflowId,
|
|
502
366
|
dryRun: false,
|
|
503
367
|
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
504
|
-
warnedRules: designResult.warnedRules
|
|
505
|
-
|
|
368
|
+
warnedRules: designResult.warnedRules,
|
|
369
|
+
workflowType
|
|
370
|
+
}, runId);
|
|
506
371
|
this.updatePatterns();
|
|
507
372
|
return {
|
|
508
373
|
workflowId: deployed.workflowId,
|
|
@@ -518,11 +383,13 @@ var Kairos = class {
|
|
|
518
383
|
this.validateDescription(description);
|
|
519
384
|
this.logger.info("Kairos.update", { id, description });
|
|
520
385
|
const buildStart = Date.now();
|
|
386
|
+
const runId = generateUUID();
|
|
387
|
+
const workflowType = inferWorkflowType(description);
|
|
521
388
|
await this.telemetry?.emit("build_start", {
|
|
522
389
|
description,
|
|
523
390
|
model: this.model,
|
|
524
391
|
dryRun: false
|
|
525
|
-
});
|
|
392
|
+
}, runId);
|
|
526
393
|
await this.library.initialize();
|
|
527
394
|
const matches = await this.library.search(description);
|
|
528
395
|
const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
|
|
@@ -541,8 +408,9 @@ var Kairos = class {
|
|
|
541
408
|
tokensOutput: meta.tokensOutput,
|
|
542
409
|
validationPassed: meta.validationPassed,
|
|
543
410
|
issueCount: meta.issues.length,
|
|
544
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
545
|
-
|
|
411
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, severity: i.severity, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null })),
|
|
412
|
+
workflowType
|
|
413
|
+
}, runId);
|
|
546
414
|
}
|
|
547
415
|
await this.telemetry?.emit("build_complete", {
|
|
548
416
|
description,
|
|
@@ -555,13 +423,14 @@ var Kairos = class {
|
|
|
555
423
|
workflowId: null,
|
|
556
424
|
dryRun: false,
|
|
557
425
|
credentialsNeeded: 0,
|
|
558
|
-
warnedRules: err.warnedRules ?? []
|
|
559
|
-
|
|
426
|
+
warnedRules: err.warnedRules ?? [],
|
|
427
|
+
workflowType
|
|
428
|
+
}, runId);
|
|
560
429
|
this.updatePatterns();
|
|
561
430
|
}
|
|
562
431
|
throw err;
|
|
563
432
|
}
|
|
564
|
-
await this.emitAttemptTelemetry(description, designResult);
|
|
433
|
+
await this.emitAttemptTelemetry(description, designResult, workflowType, runId);
|
|
565
434
|
const provider = this.requireProvider();
|
|
566
435
|
const deployed = await provider.update(id, designResult.workflow);
|
|
567
436
|
this.saveToLibrary(designResult.workflow, description, designResult, matches);
|
|
@@ -579,8 +448,9 @@ var Kairos = class {
|
|
|
579
448
|
workflowId: deployed.workflowId,
|
|
580
449
|
dryRun: false,
|
|
581
450
|
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
582
|
-
warnedRules: designResult.warnedRules
|
|
583
|
-
|
|
451
|
+
warnedRules: designResult.warnedRules,
|
|
452
|
+
workflowType
|
|
453
|
+
}, runId);
|
|
584
454
|
this.updatePatterns();
|
|
585
455
|
return {
|
|
586
456
|
workflowId: deployed.workflowId,
|
|
@@ -603,7 +473,7 @@ var Kairos = class {
|
|
|
603
473
|
return null;
|
|
604
474
|
});
|
|
605
475
|
}
|
|
606
|
-
async emitAttemptTelemetry(description, designResult) {
|
|
476
|
+
async emitAttemptTelemetry(description, designResult, workflowType, runId) {
|
|
607
477
|
for (const meta of designResult.attemptMetadata) {
|
|
608
478
|
await this.telemetry?.emit("generation_attempt", {
|
|
609
479
|
description,
|
|
@@ -614,8 +484,9 @@ var Kairos = class {
|
|
|
614
484
|
tokensOutput: meta.tokensOutput,
|
|
615
485
|
validationPassed: meta.validationPassed,
|
|
616
486
|
issueCount: meta.issues.length,
|
|
617
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
618
|
-
|
|
487
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, severity: i.severity, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null })),
|
|
488
|
+
workflowType
|
|
489
|
+
}, runId);
|
|
619
490
|
}
|
|
620
491
|
}
|
|
621
492
|
recordDeploy() {
|
|
@@ -712,190 +583,7 @@ var Kairos = class {
|
|
|
712
583
|
}
|
|
713
584
|
};
|
|
714
585
|
|
|
715
|
-
// src/templates/safety.ts
|
|
716
|
-
var BLOCKED_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
717
|
-
"n8n-nodes-base.code",
|
|
718
|
-
"n8n-nodes-base.executeCommand",
|
|
719
|
-
"n8n-nodes-base.ssh"
|
|
720
|
-
]);
|
|
721
|
-
var REVIEW_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
722
|
-
"n8n-nodes-base.httpRequest"
|
|
723
|
-
]);
|
|
724
|
-
var SECRET_PATTERNS = [
|
|
725
|
-
/sk-[a-zA-Z0-9]{20,}/,
|
|
726
|
-
/ghp_[a-zA-Z0-9]{36}/,
|
|
727
|
-
/xoxb-[0-9]+-[0-9]+-[a-zA-Z0-9]+/,
|
|
728
|
-
/AIza[a-zA-Z0-9_-]{35}/,
|
|
729
|
-
/AKIA[A-Z0-9]{16}/
|
|
730
|
-
];
|
|
731
|
-
function assessTemplateSafety(workflow) {
|
|
732
|
-
const reasons = [];
|
|
733
|
-
let worst = "safe";
|
|
734
|
-
const escalate = (level, reason) => {
|
|
735
|
-
reasons.push(reason);
|
|
736
|
-
if (level === "blocked") worst = "blocked";
|
|
737
|
-
else if (level === "review" && worst === "safe") worst = "review";
|
|
738
|
-
};
|
|
739
|
-
for (const node of workflow.nodes) {
|
|
740
|
-
if (BLOCKED_NODE_TYPES.has(node.type)) {
|
|
741
|
-
escalate("blocked", `Contains ${node.type} node "${node.name}"`);
|
|
742
|
-
}
|
|
743
|
-
if (REVIEW_NODE_TYPES.has(node.type)) {
|
|
744
|
-
escalate("review", `Contains ${node.type} node "${node.name}"`);
|
|
745
|
-
}
|
|
746
|
-
const paramStr = JSON.stringify(node.parameters);
|
|
747
|
-
for (const pattern of SECRET_PATTERNS) {
|
|
748
|
-
if (pattern.test(paramStr)) {
|
|
749
|
-
escalate("blocked", `Node "${node.name}" parameters contain a hardcoded secret`);
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
return { trustLevel: worst, reasons };
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// src/templates/syncer.ts
|
|
758
|
-
var N8N_TEMPLATE_API = "https://api.n8n.io/api/templates";
|
|
759
|
-
var PAGE_SIZE = 50;
|
|
760
|
-
var DELAY_BETWEEN_FETCHES_MS = 200;
|
|
761
|
-
var DEFAULT_SETTINGS = {
|
|
762
|
-
executionOrder: "v1",
|
|
763
|
-
saveManualExecutions: true,
|
|
764
|
-
timezone: "UTC"
|
|
765
|
-
};
|
|
766
|
-
var TemplateSyncer = class {
|
|
767
|
-
constructor(library, logger) {
|
|
768
|
-
this.library = library;
|
|
769
|
-
this.validator = new N8nValidator();
|
|
770
|
-
this.logger = logger;
|
|
771
|
-
}
|
|
772
|
-
library;
|
|
773
|
-
validator;
|
|
774
|
-
logger;
|
|
775
|
-
async sync(options) {
|
|
776
|
-
const maxTemplates = options?.maxTemplates ?? 500;
|
|
777
|
-
await this.library.initialize();
|
|
778
|
-
const existing = await this.library.list();
|
|
779
|
-
const existingSourceIds = new Set(
|
|
780
|
-
existing.filter((w) => w.sourceKind === "n8n-template" && w.sourceId).map((w) => w.sourceId)
|
|
781
|
-
);
|
|
782
|
-
const progress = {
|
|
783
|
-
total: 0,
|
|
784
|
-
processed: 0,
|
|
785
|
-
saved: 0,
|
|
786
|
-
skippedPaid: 0,
|
|
787
|
-
skippedDuplicate: 0,
|
|
788
|
-
blocked: 0,
|
|
789
|
-
reviewed: 0
|
|
790
|
-
};
|
|
791
|
-
const templateIds = await this.fetchTemplateIds(maxTemplates, progress);
|
|
792
|
-
for (const id of templateIds) {
|
|
793
|
-
if (existingSourceIds.has(String(id))) {
|
|
794
|
-
progress.skippedDuplicate++;
|
|
795
|
-
progress.processed++;
|
|
796
|
-
options?.onProgress?.(progress);
|
|
797
|
-
continue;
|
|
798
|
-
}
|
|
799
|
-
try {
|
|
800
|
-
await this.processTemplate(id, progress);
|
|
801
|
-
} catch (err) {
|
|
802
|
-
this.logger.warn(`Failed to process template ${id}`, { err: String(err) });
|
|
803
|
-
}
|
|
804
|
-
progress.processed++;
|
|
805
|
-
options?.onProgress?.(progress);
|
|
806
|
-
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_FETCHES_MS));
|
|
807
|
-
}
|
|
808
|
-
return progress;
|
|
809
|
-
}
|
|
810
|
-
async fetchTemplateIds(max, progress) {
|
|
811
|
-
const ids = [];
|
|
812
|
-
let page = 1;
|
|
813
|
-
while (ids.length < max) {
|
|
814
|
-
const url = `${N8N_TEMPLATE_API}/search?page=${page}&rows=${PAGE_SIZE}`;
|
|
815
|
-
const response = await fetch(url);
|
|
816
|
-
if (!response.ok) break;
|
|
817
|
-
const data = await response.json();
|
|
818
|
-
progress.total = Math.min(data.totalWorkflows, max);
|
|
819
|
-
for (const template of data.workflows) {
|
|
820
|
-
if (ids.length >= max) break;
|
|
821
|
-
if (template.price && template.price > 0) {
|
|
822
|
-
progress.skippedPaid++;
|
|
823
|
-
continue;
|
|
824
|
-
}
|
|
825
|
-
ids.push(template.id);
|
|
826
|
-
}
|
|
827
|
-
if (data.workflows.length < PAGE_SIZE) break;
|
|
828
|
-
page++;
|
|
829
|
-
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_FETCHES_MS));
|
|
830
|
-
}
|
|
831
|
-
return ids;
|
|
832
|
-
}
|
|
833
|
-
async processTemplate(id, progress) {
|
|
834
|
-
const url = `${N8N_TEMPLATE_API}/workflows/${id}`;
|
|
835
|
-
const response = await fetch(url);
|
|
836
|
-
if (!response.ok) return;
|
|
837
|
-
const data = await response.json();
|
|
838
|
-
const templateMeta = data.workflow;
|
|
839
|
-
const rawWorkflow = templateMeta.workflow;
|
|
840
|
-
if (!rawWorkflow?.nodes?.length) return;
|
|
841
|
-
const workflow = {
|
|
842
|
-
name: templateMeta.name,
|
|
843
|
-
nodes: rawWorkflow.nodes.filter((n) => n.type && n.name),
|
|
844
|
-
connections: rawWorkflow.connections,
|
|
845
|
-
settings: rawWorkflow.settings ? { executionOrder: "v1", ...rawWorkflow.settings } : { ...DEFAULT_SETTINGS }
|
|
846
|
-
};
|
|
847
|
-
const validation = this.validator.validate(workflow);
|
|
848
|
-
const validationErrors = validation.issues.filter((i) => i.severity === "error");
|
|
849
|
-
if (validationErrors.length > 0) {
|
|
850
|
-
progress.blocked++;
|
|
851
|
-
this.logger.debug(`Template ${id} blocked: ${validationErrors.length} validation errors`);
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
const safety = assessTemplateSafety(workflow);
|
|
855
|
-
if (safety.trustLevel === "blocked") {
|
|
856
|
-
progress.blocked++;
|
|
857
|
-
this.logger.debug(`Template ${id} blocked: ${safety.reasons.join(", ")}`);
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
if (safety.trustLevel === "review") {
|
|
861
|
-
progress.reviewed++;
|
|
862
|
-
}
|
|
863
|
-
const description = this.cleanDescription(templateMeta.description);
|
|
864
|
-
const autoTags = Array.from(new Set(
|
|
865
|
-
workflow.nodes.flatMap((n) => {
|
|
866
|
-
const bare = n.type.split(".").pop() ?? "";
|
|
867
|
-
const tags = [bare];
|
|
868
|
-
if (n.type.includes("Trigger") || n.type.includes("trigger")) tags.push(`trigger:${bare}`);
|
|
869
|
-
if (n.type.includes("langchain")) tags.push("ai");
|
|
870
|
-
return tags;
|
|
871
|
-
})
|
|
872
|
-
));
|
|
873
|
-
const metadata = {
|
|
874
|
-
description,
|
|
875
|
-
tags: autoTags,
|
|
876
|
-
sourceKind: "n8n-template",
|
|
877
|
-
sourceId: String(id),
|
|
878
|
-
sourceUrl: `https://n8n.io/workflows/${id}`,
|
|
879
|
-
trustLevel: safety.trustLevel
|
|
880
|
-
};
|
|
881
|
-
await this.library.save(workflow, metadata);
|
|
882
|
-
progress.saved++;
|
|
883
|
-
this.logger.debug(`Template ${id} saved: "${templateMeta.name}" (${safety.trustLevel})`);
|
|
884
|
-
}
|
|
885
|
-
cleanDescription(raw) {
|
|
886
|
-
return raw.replace(/#{1,6}\s*/g, "").replace(/\*{1,2}([^*]+)\*{1,2}/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\n{3,}/g, "\n\n").trim().slice(0, 500);
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
|
-
|
|
890
586
|
export {
|
|
891
|
-
|
|
892
|
-
GuardError,
|
|
893
|
-
N8nProvider,
|
|
894
|
-
GenerationError,
|
|
895
|
-
ResponseParseError,
|
|
896
|
-
ValidationError,
|
|
897
|
-
TelemetryCollector,
|
|
898
|
-
Kairos,
|
|
899
|
-
TemplateSyncer
|
|
587
|
+
Kairos
|
|
900
588
|
};
|
|
901
|
-
//# sourceMappingURL=chunk-
|
|
589
|
+
//# sourceMappingURL=chunk-4TS6GW6O.js.map
|