@kairos-sdk/core 0.2.1 → 0.3.0
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 +111 -5
- package/dist/{chunk-QQJDLS5A.js → chunk-DDV7ZART.js} +102 -891
- package/dist/chunk-DDV7ZART.js.map +1 -0
- package/dist/chunk-Q77XA7UC.js +809 -0
- package/dist/chunk-Q77XA7UC.js.map +1 -0
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +12 -10
- package/dist/mcp-server.cjs +1936 -0
- package/dist/mcp-server.cjs.map +1 -0
- package/dist/mcp-server.d.cts +1 -0
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +376 -0
- package/dist/mcp-server.js.map +1 -0
- package/package.json +16 -5
- package/dist/chunk-QQJDLS5A.js.map +0 -1
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
// src/utils/uuid.ts
|
|
2
|
-
function generateUUID() {
|
|
3
|
-
return crypto.randomUUID();
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
// src/library/null-library.ts
|
|
7
|
-
var NullLibrary = class {
|
|
8
|
-
async initialize() {
|
|
9
|
-
}
|
|
10
|
-
async search(_description, _options) {
|
|
11
|
-
return [];
|
|
12
|
-
}
|
|
13
|
-
async save(_workflow, _metadata) {
|
|
14
|
-
return generateUUID();
|
|
15
|
-
}
|
|
16
|
-
async recordDeployment(_id) {
|
|
17
|
-
}
|
|
18
|
-
async recordOutcome(_id, _outcome) {
|
|
19
|
-
}
|
|
20
|
-
async get(_id) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
async list(_filters) {
|
|
24
|
-
return [];
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
1
|
// src/errors/base.ts
|
|
29
2
|
var KairosError = class extends Error {
|
|
30
3
|
constructor(message, cause) {
|
|
@@ -267,78 +240,6 @@ var N8nFieldStripper = class {
|
|
|
267
240
|
}
|
|
268
241
|
};
|
|
269
242
|
|
|
270
|
-
// src/errors/guard-error.ts
|
|
271
|
-
var GuardError = class extends KairosError {
|
|
272
|
-
constructor(message) {
|
|
273
|
-
super(message);
|
|
274
|
-
this.name = "GuardError";
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
// src/providers/n8n/provider.ts
|
|
279
|
-
var N8nProvider = class {
|
|
280
|
-
constructor(client, stripper) {
|
|
281
|
-
this.client = client;
|
|
282
|
-
this.stripper = stripper;
|
|
283
|
-
}
|
|
284
|
-
client;
|
|
285
|
-
stripper;
|
|
286
|
-
platform = "n8n";
|
|
287
|
-
async deploy(workflow) {
|
|
288
|
-
const stripped = this.stripper.stripForCreate(workflow);
|
|
289
|
-
const response = await this.client.createWorkflow(stripped);
|
|
290
|
-
return { workflowId: response.id, name: response.name };
|
|
291
|
-
}
|
|
292
|
-
async update(id, workflow) {
|
|
293
|
-
const stripped = this.stripper.stripForUpdate(workflow);
|
|
294
|
-
const response = await this.client.updateWorkflow(id, stripped);
|
|
295
|
-
return { workflowId: response.id, name: response.name };
|
|
296
|
-
}
|
|
297
|
-
async get(id) {
|
|
298
|
-
const response = await this.client.getWorkflow(id);
|
|
299
|
-
return {
|
|
300
|
-
name: response.name,
|
|
301
|
-
nodes: response.nodes,
|
|
302
|
-
connections: response.connections,
|
|
303
|
-
...response.settings !== void 0 ? { settings: response.settings } : {},
|
|
304
|
-
...response.tags !== void 0 ? { tags: response.tags } : {}
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
async list() {
|
|
308
|
-
return this.client.listWorkflows();
|
|
309
|
-
}
|
|
310
|
-
async activate(id) {
|
|
311
|
-
await this.client.activateWorkflow(id);
|
|
312
|
-
}
|
|
313
|
-
async deactivate(id) {
|
|
314
|
-
await this.client.deactivateWorkflow(id);
|
|
315
|
-
}
|
|
316
|
-
async delete(id, options) {
|
|
317
|
-
if (options.confirm !== true) {
|
|
318
|
-
throw new GuardError("delete() requires { confirm: true } to prevent accidental deletion");
|
|
319
|
-
}
|
|
320
|
-
await this.client.deleteWorkflow(id);
|
|
321
|
-
}
|
|
322
|
-
async executions(workflowId, filter) {
|
|
323
|
-
return this.client.getExecutions(workflowId, filter);
|
|
324
|
-
}
|
|
325
|
-
async execution(id) {
|
|
326
|
-
return this.client.getExecution(id);
|
|
327
|
-
}
|
|
328
|
-
async listTags() {
|
|
329
|
-
return this.client.listTags();
|
|
330
|
-
}
|
|
331
|
-
async createTag(name) {
|
|
332
|
-
return this.client.createTag(name);
|
|
333
|
-
}
|
|
334
|
-
async tag(workflowId, tagIds) {
|
|
335
|
-
await this.client.tagWorkflow(workflowId, tagIds);
|
|
336
|
-
}
|
|
337
|
-
async untag(workflowId, tagIds) {
|
|
338
|
-
await this.client.untagWorkflow(workflowId, tagIds);
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
|
|
342
243
|
// src/validation/registry.ts
|
|
343
244
|
var DEFAULT_REGISTRY = [
|
|
344
245
|
// Trigger nodes
|
|
@@ -843,170 +744,6 @@ var N8nValidator = class {
|
|
|
843
744
|
}
|
|
844
745
|
};
|
|
845
746
|
|
|
846
|
-
// src/errors/generation-error.ts
|
|
847
|
-
var GenerationError = class extends KairosError {
|
|
848
|
-
constructor(message, cause) {
|
|
849
|
-
super(message, cause);
|
|
850
|
-
this.name = "GenerationError";
|
|
851
|
-
}
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
// src/errors/response-parse-error.ts
|
|
855
|
-
var ResponseParseError = class extends KairosError {
|
|
856
|
-
constructor(message, cause) {
|
|
857
|
-
super(message, cause);
|
|
858
|
-
this.name = "ResponseParseError";
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
// src/errors/validation-error.ts
|
|
863
|
-
var ValidationError = class extends KairosError {
|
|
864
|
-
constructor(message, issues) {
|
|
865
|
-
super(message);
|
|
866
|
-
this.issues = issues;
|
|
867
|
-
this.name = "ValidationError";
|
|
868
|
-
}
|
|
869
|
-
issues;
|
|
870
|
-
};
|
|
871
|
-
|
|
872
|
-
// src/telemetry/collector.ts
|
|
873
|
-
import { appendFile, mkdir } from "fs/promises";
|
|
874
|
-
import { join } from "path";
|
|
875
|
-
import { homedir } from "os";
|
|
876
|
-
|
|
877
|
-
// src/telemetry/types.ts
|
|
878
|
-
var TELEMETRY_SCHEMA_VERSION = 2;
|
|
879
|
-
|
|
880
|
-
// src/telemetry/collector.ts
|
|
881
|
-
var TelemetryCollector = class {
|
|
882
|
-
dir;
|
|
883
|
-
sessionId;
|
|
884
|
-
dirReady = null;
|
|
885
|
-
constructor(dir) {
|
|
886
|
-
this.dir = dir ?? join(homedir(), ".kairos", "telemetry");
|
|
887
|
-
this.sessionId = generateUUID();
|
|
888
|
-
}
|
|
889
|
-
async emit(eventType, data) {
|
|
890
|
-
const event = {
|
|
891
|
-
schemaVersion: TELEMETRY_SCHEMA_VERSION,
|
|
892
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
893
|
-
sessionId: this.sessionId,
|
|
894
|
-
eventType,
|
|
895
|
-
data
|
|
896
|
-
};
|
|
897
|
-
if (!this.dirReady) {
|
|
898
|
-
this.dirReady = mkdir(this.dir, { recursive: true }).then(() => {
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
await this.dirReady;
|
|
902
|
-
const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
|
|
903
|
-
const filepath = join(this.dir, filename);
|
|
904
|
-
await appendFile(filepath, JSON.stringify(event) + "\n", "utf-8");
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
|
-
|
|
908
|
-
// src/telemetry/reader.ts
|
|
909
|
-
import { readFile, readdir } from "fs/promises";
|
|
910
|
-
import { join as join2 } from "path";
|
|
911
|
-
import { homedir as homedir2 } from "os";
|
|
912
|
-
var TelemetryReader = class {
|
|
913
|
-
dir;
|
|
914
|
-
cache = null;
|
|
915
|
-
cacheTime = 0;
|
|
916
|
-
constructor(dir) {
|
|
917
|
-
this.dir = dir ?? join2(homedir2(), ".kairos", "telemetry");
|
|
918
|
-
}
|
|
919
|
-
async getFailureRates(days = 30) {
|
|
920
|
-
const now = Date.now();
|
|
921
|
-
if (this.cache && now - this.cacheTime < 5 * 60 * 1e3) {
|
|
922
|
-
return this.cache;
|
|
923
|
-
}
|
|
924
|
-
const events = await this.readRecentEvents(days);
|
|
925
|
-
const buildSessions = new Set(
|
|
926
|
-
events.filter((e) => e.eventType === "build_complete" && !e.data.dryRun).map((e) => e.sessionId)
|
|
927
|
-
);
|
|
928
|
-
if (buildSessions.size === 0) return [];
|
|
929
|
-
const ruleSessions = /* @__PURE__ */ new Map();
|
|
930
|
-
for (const event of events) {
|
|
931
|
-
if (event.eventType !== "generation_attempt") continue;
|
|
932
|
-
if (!buildSessions.has(event.sessionId)) continue;
|
|
933
|
-
const data = event.data;
|
|
934
|
-
if (data.validationPassed || !data.issues) continue;
|
|
935
|
-
for (const issue of data.issues) {
|
|
936
|
-
const entry = ruleSessions.get(issue.rule) ?? { sessions: /* @__PURE__ */ new Set(), messages: /* @__PURE__ */ new Map() };
|
|
937
|
-
entry.sessions.add(event.sessionId);
|
|
938
|
-
entry.messages.set(issue.message, (entry.messages.get(issue.message) ?? 0) + 1);
|
|
939
|
-
ruleSessions.set(issue.rule, entry);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
const rates = [];
|
|
943
|
-
for (const [rule, entry] of ruleSessions) {
|
|
944
|
-
let topMessage = "";
|
|
945
|
-
let topCount = 0;
|
|
946
|
-
for (const [msg, count] of entry.messages) {
|
|
947
|
-
if (count > topCount) {
|
|
948
|
-
topMessage = msg;
|
|
949
|
-
topCount = count;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
rates.push({
|
|
953
|
-
rule,
|
|
954
|
-
failureCount: entry.sessions.size,
|
|
955
|
-
totalBuilds: buildSessions.size,
|
|
956
|
-
rate: entry.sessions.size / buildSessions.size,
|
|
957
|
-
commonMessage: topMessage
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
rates.sort((a, b) => b.rate - a.rate);
|
|
961
|
-
this.cache = rates;
|
|
962
|
-
this.cacheTime = now;
|
|
963
|
-
return rates;
|
|
964
|
-
}
|
|
965
|
-
async readRecentEvents(days) {
|
|
966
|
-
let files;
|
|
967
|
-
try {
|
|
968
|
-
files = await readdir(this.dir);
|
|
969
|
-
} catch {
|
|
970
|
-
return [];
|
|
971
|
-
}
|
|
972
|
-
const cutoff = /* @__PURE__ */ new Date();
|
|
973
|
-
cutoff.setDate(cutoff.getDate() - days);
|
|
974
|
-
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
975
|
-
const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
976
|
-
const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr).sort();
|
|
977
|
-
const events = [];
|
|
978
|
-
for (const file of recentFiles) {
|
|
979
|
-
try {
|
|
980
|
-
const content = await readFile(join2(this.dir, file), "utf-8");
|
|
981
|
-
for (const line of content.split("\n")) {
|
|
982
|
-
if (!line.trim()) continue;
|
|
983
|
-
try {
|
|
984
|
-
events.push(JSON.parse(line));
|
|
985
|
-
} catch {
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
} catch {
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return events;
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
// src/utils/logger.ts
|
|
996
|
-
var nullLogger = {
|
|
997
|
-
debug() {
|
|
998
|
-
},
|
|
999
|
-
info() {
|
|
1000
|
-
},
|
|
1001
|
-
warn() {
|
|
1002
|
-
},
|
|
1003
|
-
error() {
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
// src/client.ts
|
|
1008
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
1009
|
-
|
|
1010
747
|
// src/generation/prompts/v1.ts
|
|
1011
748
|
var SYSTEM_PROMPT_V1 = `You are a workflow generation engine for n8n. Your only output is a generate_workflow tool call containing valid n8n workflow JSON. You never respond with prose, explanations, or markdown. If you cannot fulfill the request, set the error field in the tool call.
|
|
1012
749
|
|
|
@@ -1332,454 +1069,102 @@ Workflow name: "${request.name}"` : "";
|
|
|
1332
1069
|
}
|
|
1333
1070
|
};
|
|
1334
1071
|
|
|
1335
|
-
// src/
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
var
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
workflow: {
|
|
1346
|
-
type: "object",
|
|
1347
|
-
description: "The complete n8n workflow object",
|
|
1348
|
-
properties: {
|
|
1349
|
-
name: { type: "string" },
|
|
1350
|
-
nodes: { type: "array" },
|
|
1351
|
-
connections: { type: "object" },
|
|
1352
|
-
settings: { type: "object" }
|
|
1353
|
-
},
|
|
1354
|
-
required: ["name", "nodes", "connections"]
|
|
1355
|
-
},
|
|
1356
|
-
credentialsNeeded: {
|
|
1357
|
-
type: "array",
|
|
1358
|
-
description: "List of credentials the user must configure before activating",
|
|
1359
|
-
items: {
|
|
1360
|
-
type: "object",
|
|
1361
|
-
properties: {
|
|
1362
|
-
service: { type: "string" },
|
|
1363
|
-
credentialType: { type: "string" },
|
|
1364
|
-
description: { type: "string" }
|
|
1365
|
-
},
|
|
1366
|
-
required: ["service", "credentialType", "description"]
|
|
1367
|
-
}
|
|
1368
|
-
},
|
|
1369
|
-
error: {
|
|
1370
|
-
type: "string",
|
|
1371
|
-
description: "Set this if the request cannot be fulfilled \u2014 explain why"
|
|
1372
|
-
}
|
|
1373
|
-
},
|
|
1374
|
-
required: []
|
|
1375
|
-
}
|
|
1376
|
-
};
|
|
1377
|
-
var WorkflowDesigner = class {
|
|
1378
|
-
constructor(anthropic, model, logger) {
|
|
1379
|
-
this.anthropic = anthropic;
|
|
1380
|
-
this.model = model;
|
|
1381
|
-
this.logger = logger;
|
|
1382
|
-
this.validator = new N8nValidator();
|
|
1383
|
-
this.promptBuilder = new PromptBuilder();
|
|
1072
|
+
// src/telemetry/reader.ts
|
|
1073
|
+
import { readFile, readdir } from "fs/promises";
|
|
1074
|
+
import { join } from "path";
|
|
1075
|
+
import { homedir } from "os";
|
|
1076
|
+
var TelemetryReader = class {
|
|
1077
|
+
dir;
|
|
1078
|
+
cache = null;
|
|
1079
|
+
cacheTime = 0;
|
|
1080
|
+
constructor(dir) {
|
|
1081
|
+
this.dir = dir ?? join(homedir(), ".kairos", "telemetry");
|
|
1384
1082
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
);
|
|
1406
|
-
userMessage = this.promptBuilder.buildCorrectionMessage(request, matches, issueLines, attempt - 1);
|
|
1407
|
-
this.logger.debug(`WorkflowDesigner: correction attempt ${attempt}`, { issueCount: lastErrors.length });
|
|
1408
|
-
}
|
|
1409
|
-
const start = Date.now();
|
|
1410
|
-
const message = await this.callClaude(built.system, userMessage, temperature);
|
|
1411
|
-
const durationMs = Date.now() - start;
|
|
1412
|
-
const parsed = this.extractToolUse(message);
|
|
1413
|
-
if (parsed.error) {
|
|
1414
|
-
throw new GenerationError(`Claude declined to generate workflow: ${parsed.error}`);
|
|
1083
|
+
async getFailureRates(days = 30) {
|
|
1084
|
+
const now = Date.now();
|
|
1085
|
+
if (this.cache && now - this.cacheTime < 5 * 60 * 1e3) {
|
|
1086
|
+
return this.cache;
|
|
1087
|
+
}
|
|
1088
|
+
const events = await this.readRecentEvents(days);
|
|
1089
|
+
const buildSessions = new Set(
|
|
1090
|
+
events.filter((e) => e.eventType === "build_complete" && !e.data.dryRun).map((e) => e.sessionId)
|
|
1091
|
+
);
|
|
1092
|
+
if (buildSessions.size === 0) return [];
|
|
1093
|
+
const ruleSessions = /* @__PURE__ */ new Map();
|
|
1094
|
+
for (const event of events) {
|
|
1095
|
+
if (event.eventType !== "generation_attempt") continue;
|
|
1096
|
+
if (!buildSessions.has(event.sessionId)) continue;
|
|
1097
|
+
const data = event.data;
|
|
1098
|
+
if (data.validationPassed || !data.issues) continue;
|
|
1099
|
+
for (const issue of data.issues) {
|
|
1100
|
+
const entry = ruleSessions.get(issue.rule) ?? { sessions: /* @__PURE__ */ new Set(), messages: /* @__PURE__ */ new Map() };
|
|
1101
|
+
entry.sessions.add(event.sessionId);
|
|
1102
|
+
entry.messages.set(issue.message, (entry.messages.get(issue.message) ?? 0) + 1);
|
|
1103
|
+
ruleSessions.set(issue.rule, entry);
|
|
1415
1104
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
});
|
|
1427
|
-
if (validation.valid) {
|
|
1428
|
-
return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata };
|
|
1105
|
+
}
|
|
1106
|
+
const rates = [];
|
|
1107
|
+
for (const [rule, entry] of ruleSessions) {
|
|
1108
|
+
let topMessage = "";
|
|
1109
|
+
let topCount = 0;
|
|
1110
|
+
for (const [msg, count] of entry.messages) {
|
|
1111
|
+
if (count > topCount) {
|
|
1112
|
+
topMessage = msg;
|
|
1113
|
+
topCount = count;
|
|
1114
|
+
}
|
|
1429
1115
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1116
|
+
rates.push({
|
|
1117
|
+
rule,
|
|
1118
|
+
failureCount: entry.sessions.size,
|
|
1119
|
+
totalBuilds: buildSessions.size,
|
|
1120
|
+
rate: entry.sessions.size / buildSessions.size,
|
|
1121
|
+
commonMessage: topMessage
|
|
1433
1122
|
});
|
|
1434
1123
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
);
|
|
1124
|
+
rates.sort((a, b) => b.rate - a.rate);
|
|
1125
|
+
this.cache = rates;
|
|
1126
|
+
this.cacheTime = now;
|
|
1127
|
+
return rates;
|
|
1440
1128
|
}
|
|
1441
|
-
async
|
|
1442
|
-
|
|
1443
|
-
const timer = setTimeout(() => controller.abort(), 12e4);
|
|
1129
|
+
async readRecentEvents(days) {
|
|
1130
|
+
let files;
|
|
1444
1131
|
try {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
max_tokens: 8192,
|
|
1449
|
-
temperature,
|
|
1450
|
-
system: system.map((b) => ({ type: b.type, text: b.text, ...b.cache_control ? { cache_control: b.cache_control } : {} })),
|
|
1451
|
-
messages: [{ role: "user", content: userMessage }],
|
|
1452
|
-
tools: [GENERATE_WORKFLOW_TOOL],
|
|
1453
|
-
tool_choice: { type: "tool", name: "generate_workflow" }
|
|
1454
|
-
},
|
|
1455
|
-
{ signal: controller.signal }
|
|
1456
|
-
);
|
|
1457
|
-
} catch (err) {
|
|
1458
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1459
|
-
throw new GenerationError(`Anthropic API call failed: ${detail}`, err);
|
|
1460
|
-
} finally {
|
|
1461
|
-
clearTimeout(timer);
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
extractToolUse(message) {
|
|
1465
|
-
const toolUseBlock = message.content.find(
|
|
1466
|
-
(block) => block.type === "tool_use"
|
|
1467
|
-
);
|
|
1468
|
-
if (!toolUseBlock) {
|
|
1469
|
-
throw new ResponseParseError(
|
|
1470
|
-
"Claude response contained no tool_use block \u2014 forced tool_choice failed unexpectedly"
|
|
1471
|
-
);
|
|
1472
|
-
}
|
|
1473
|
-
const input = toolUseBlock.input;
|
|
1474
|
-
if (typeof input["error"] === "string") {
|
|
1475
|
-
return {
|
|
1476
|
-
workflow: { name: "", nodes: [], connections: {} },
|
|
1477
|
-
credentialsNeeded: [],
|
|
1478
|
-
error: input["error"]
|
|
1479
|
-
};
|
|
1480
|
-
}
|
|
1481
|
-
if (!input["workflow"] || typeof input["workflow"] !== "object") {
|
|
1482
|
-
throw new ResponseParseError("generate_workflow tool call missing workflow field");
|
|
1132
|
+
files = await readdir(this.dir);
|
|
1133
|
+
} catch {
|
|
1134
|
+
return [];
|
|
1483
1135
|
}
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
1492
|
-
var Kairos = class {
|
|
1493
|
-
provider;
|
|
1494
|
-
designer;
|
|
1495
|
-
validator;
|
|
1496
|
-
library;
|
|
1497
|
-
logger;
|
|
1498
|
-
telemetry;
|
|
1499
|
-
telemetryReader;
|
|
1500
|
-
model;
|
|
1501
|
-
saveQueue = Promise.resolve(null);
|
|
1502
|
-
constructor(options) {
|
|
1503
|
-
const logger = options.logger ?? nullLogger;
|
|
1504
|
-
this.model = options.model ?? DEFAULT_MODEL;
|
|
1505
|
-
if (options.n8nBaseUrl && options.n8nApiKey) {
|
|
1136
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1137
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
1138
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
1139
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
1140
|
+
const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr).sort();
|
|
1141
|
+
const events = [];
|
|
1142
|
+
for (const file of recentFiles) {
|
|
1506
1143
|
try {
|
|
1507
|
-
|
|
1144
|
+
const content = await readFile(join(this.dir, file), "utf-8");
|
|
1145
|
+
for (const line of content.split("\n")) {
|
|
1146
|
+
if (!line.trim()) continue;
|
|
1147
|
+
try {
|
|
1148
|
+
events.push(JSON.parse(line));
|
|
1149
|
+
} catch {
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1508
1152
|
} catch {
|
|
1509
|
-
throw new GuardError(`Invalid n8nBaseUrl: "${options.n8nBaseUrl}" \u2014 must be a valid URL`);
|
|
1510
1153
|
}
|
|
1511
|
-
const apiClient = new N8nApiClient(options.n8nBaseUrl, options.n8nApiKey, logger);
|
|
1512
|
-
const stripper = new N8nFieldStripper();
|
|
1513
|
-
this.provider = new N8nProvider(apiClient, stripper);
|
|
1514
|
-
} else {
|
|
1515
|
-
this.provider = null;
|
|
1516
|
-
}
|
|
1517
|
-
const anthropic = new Anthropic({ apiKey: options.anthropicApiKey });
|
|
1518
|
-
this.designer = new WorkflowDesigner(anthropic, this.model, logger);
|
|
1519
|
-
this.validator = new N8nValidator();
|
|
1520
|
-
this.library = options.library ?? new NullLibrary();
|
|
1521
|
-
this.logger = logger;
|
|
1522
|
-
if (options.telemetry === true) {
|
|
1523
|
-
this.telemetry = new TelemetryCollector();
|
|
1524
|
-
this.telemetryReader = new TelemetryReader();
|
|
1525
|
-
} else if (typeof options.telemetry === "string") {
|
|
1526
|
-
this.telemetry = new TelemetryCollector(options.telemetry);
|
|
1527
|
-
this.telemetryReader = new TelemetryReader(options.telemetry);
|
|
1528
|
-
} else {
|
|
1529
|
-
this.telemetry = null;
|
|
1530
|
-
this.telemetryReader = null;
|
|
1531
1154
|
}
|
|
1155
|
+
return events;
|
|
1532
1156
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
async build(description, options) {
|
|
1545
|
-
this.validateDescription(description);
|
|
1546
|
-
this.logger.info("Kairos.build", { description, dryRun: options?.dryRun });
|
|
1547
|
-
const buildStart = Date.now();
|
|
1548
|
-
await this.telemetry?.emit("build_start", {
|
|
1549
|
-
description,
|
|
1550
|
-
model: this.model,
|
|
1551
|
-
dryRun: options?.dryRun ?? false
|
|
1552
|
-
});
|
|
1553
|
-
await this.library.initialize();
|
|
1554
|
-
const matches = await this.library.search(description);
|
|
1555
|
-
if (matches.length > 0) {
|
|
1556
|
-
const top = matches[0];
|
|
1557
|
-
this.logger.info(`Library: ${matches.length} match(es), top="${top.workflow.description.slice(0, 50)}" score=${top.score.toFixed(2)} mode=${top.mode}`);
|
|
1558
|
-
} else {
|
|
1559
|
-
this.logger.info("Library: no matches (scratch mode)");
|
|
1560
|
-
}
|
|
1561
|
-
const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
|
|
1562
|
-
if (globalFailureRates.length > 0) {
|
|
1563
|
-
const highFreq = globalFailureRates.filter((r) => r.rate >= 0.15);
|
|
1564
|
-
if (highFreq.length > 0) {
|
|
1565
|
-
this.logger.info(`Telemetry: ${highFreq.length} high-frequency failure rule(s) will be warned about`);
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
const designResult = await this.designer.design(
|
|
1569
|
-
{ description, ...options?.name ? { name: options.name } : {} },
|
|
1570
|
-
matches,
|
|
1571
|
-
globalFailureRates
|
|
1572
|
-
);
|
|
1573
|
-
await this.emitAttemptTelemetry(description, designResult);
|
|
1574
|
-
const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
|
|
1575
|
-
this.saveToLibrary(workflow, description, designResult, matches);
|
|
1576
|
-
if (options?.dryRun) {
|
|
1577
|
-
const totalTokensInput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
|
|
1578
|
-
const totalTokensOutput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
|
|
1579
|
-
await this.telemetry?.emit("build_complete", {
|
|
1580
|
-
description,
|
|
1581
|
-
success: true,
|
|
1582
|
-
totalAttempts: designResult.attempts,
|
|
1583
|
-
totalDurationMs: Date.now() - buildStart,
|
|
1584
|
-
totalTokensInput: totalTokensInput2,
|
|
1585
|
-
totalTokensOutput: totalTokensOutput2,
|
|
1586
|
-
workflowName: workflow.name,
|
|
1587
|
-
workflowId: null,
|
|
1588
|
-
dryRun: true,
|
|
1589
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
1590
|
-
});
|
|
1591
|
-
return {
|
|
1592
|
-
workflowId: null,
|
|
1593
|
-
name: workflow.name,
|
|
1594
|
-
workflow,
|
|
1595
|
-
credentialsNeeded: designResult.credentialsNeeded,
|
|
1596
|
-
activationRequired: true,
|
|
1597
|
-
generationAttempts: designResult.attempts,
|
|
1598
|
-
dryRun: true
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
const provider = this.requireProvider();
|
|
1602
|
-
const deployed = await provider.deploy(workflow);
|
|
1603
|
-
this.recordDeploy();
|
|
1604
|
-
if (options?.activate) {
|
|
1605
|
-
await provider.activate(deployed.workflowId);
|
|
1606
|
-
}
|
|
1607
|
-
const totalTokensInput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
|
|
1608
|
-
const totalTokensOutput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
|
|
1609
|
-
await this.telemetry?.emit("build_complete", {
|
|
1610
|
-
description,
|
|
1611
|
-
success: true,
|
|
1612
|
-
totalAttempts: designResult.attempts,
|
|
1613
|
-
totalDurationMs: Date.now() - buildStart,
|
|
1614
|
-
totalTokensInput,
|
|
1615
|
-
totalTokensOutput,
|
|
1616
|
-
workflowName: deployed.name,
|
|
1617
|
-
workflowId: deployed.workflowId,
|
|
1618
|
-
dryRun: false,
|
|
1619
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
1620
|
-
});
|
|
1621
|
-
return {
|
|
1622
|
-
workflowId: deployed.workflowId,
|
|
1623
|
-
name: deployed.name,
|
|
1624
|
-
workflow,
|
|
1625
|
-
credentialsNeeded: designResult.credentialsNeeded,
|
|
1626
|
-
activationRequired: !options?.activate,
|
|
1627
|
-
generationAttempts: designResult.attempts,
|
|
1628
|
-
dryRun: false
|
|
1629
|
-
};
|
|
1630
|
-
}
|
|
1631
|
-
async replace(id, description) {
|
|
1632
|
-
this.validateDescription(description);
|
|
1633
|
-
this.logger.info("Kairos.update", { id, description });
|
|
1634
|
-
const buildStart = Date.now();
|
|
1635
|
-
await this.telemetry?.emit("build_start", {
|
|
1636
|
-
description,
|
|
1637
|
-
model: this.model,
|
|
1638
|
-
dryRun: false
|
|
1639
|
-
});
|
|
1640
|
-
await this.library.initialize();
|
|
1641
|
-
const matches = await this.library.search(description);
|
|
1642
|
-
const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
|
|
1643
|
-
const designResult = await this.designer.design({ description }, matches, globalFailureRates);
|
|
1644
|
-
await this.emitAttemptTelemetry(description, designResult);
|
|
1645
|
-
const provider = this.requireProvider();
|
|
1646
|
-
const deployed = await provider.update(id, designResult.workflow);
|
|
1647
|
-
this.saveToLibrary(designResult.workflow, description, designResult, matches);
|
|
1648
|
-
this.recordDeploy();
|
|
1649
|
-
const totalTokensInput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
|
|
1650
|
-
const totalTokensOutput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
|
|
1651
|
-
await this.telemetry?.emit("build_complete", {
|
|
1652
|
-
description,
|
|
1653
|
-
success: true,
|
|
1654
|
-
totalAttempts: designResult.attempts,
|
|
1655
|
-
totalDurationMs: Date.now() - buildStart,
|
|
1656
|
-
totalTokensInput,
|
|
1657
|
-
totalTokensOutput,
|
|
1658
|
-
workflowName: deployed.name,
|
|
1659
|
-
workflowId: deployed.workflowId,
|
|
1660
|
-
dryRun: false,
|
|
1661
|
-
credentialsNeeded: designResult.credentialsNeeded.length
|
|
1662
|
-
});
|
|
1663
|
-
return {
|
|
1664
|
-
workflowId: deployed.workflowId,
|
|
1665
|
-
name: deployed.name,
|
|
1666
|
-
workflow: designResult.workflow,
|
|
1667
|
-
credentialsNeeded: designResult.credentialsNeeded,
|
|
1668
|
-
activationRequired: true,
|
|
1669
|
-
generationAttempts: designResult.attempts,
|
|
1670
|
-
dryRun: false
|
|
1671
|
-
};
|
|
1672
|
-
}
|
|
1673
|
-
async drain() {
|
|
1674
|
-
await this.saveQueue.catch(() => {
|
|
1675
|
-
});
|
|
1676
|
-
}
|
|
1677
|
-
async emitAttemptTelemetry(description, designResult) {
|
|
1678
|
-
for (const meta of designResult.attemptMetadata) {
|
|
1679
|
-
await this.telemetry?.emit("generation_attempt", {
|
|
1680
|
-
description,
|
|
1681
|
-
attempt: meta.attempt,
|
|
1682
|
-
temperature: meta.temperature,
|
|
1683
|
-
durationMs: meta.durationMs,
|
|
1684
|
-
tokensInput: meta.tokensInput,
|
|
1685
|
-
tokensOutput: meta.tokensOutput,
|
|
1686
|
-
validationPassed: meta.validationPassed,
|
|
1687
|
-
issueCount: meta.issues.length,
|
|
1688
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message }))
|
|
1689
|
-
});
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
recordDeploy() {
|
|
1693
|
-
this.saveQueue = this.saveQueue.then(async (savedId) => {
|
|
1694
|
-
if (savedId) {
|
|
1695
|
-
await this.library.recordDeployment(savedId);
|
|
1696
|
-
}
|
|
1697
|
-
return savedId;
|
|
1698
|
-
}).catch((err) => {
|
|
1699
|
-
this.logger.warn("Failed to record deployment (non-fatal)", { err: String(err) });
|
|
1700
|
-
return null;
|
|
1701
|
-
});
|
|
1702
|
-
}
|
|
1703
|
-
saveToLibrary(workflow, description, designResult, matches) {
|
|
1704
|
-
const failedAttempts = designResult.attemptMetadata.filter((m) => !m.validationPassed);
|
|
1705
|
-
const failurePatterns = failedAttempts.flatMap(
|
|
1706
|
-
(m) => m.issues.map((i) => ({ rule: i.rule, message: i.message }))
|
|
1707
|
-
);
|
|
1708
|
-
const topMatch = matches[0];
|
|
1709
|
-
const generationMode = topMatch ? scoreToMode(topMatch.score) : "scratch";
|
|
1710
|
-
const autoTags = Array.from(new Set(
|
|
1711
|
-
workflow.nodes.flatMap((n) => {
|
|
1712
|
-
const bare = n.type.split(".").pop() ?? "";
|
|
1713
|
-
const tags = [bare];
|
|
1714
|
-
if (n.type.includes("Trigger") || n.type.includes("trigger")) tags.push(`trigger:${bare}`);
|
|
1715
|
-
if (n.type.includes("langchain")) tags.push("ai");
|
|
1716
|
-
return tags;
|
|
1717
|
-
})
|
|
1718
|
-
));
|
|
1719
|
-
const metadata = {
|
|
1720
|
-
description,
|
|
1721
|
-
generationMode,
|
|
1722
|
-
generationAttempts: designResult.attempts
|
|
1723
|
-
};
|
|
1724
|
-
if (autoTags.length > 0) metadata.tags = autoTags;
|
|
1725
|
-
if (failurePatterns.length > 0) metadata.failurePatterns = failurePatterns;
|
|
1726
|
-
if (matches.length > 0) metadata.sourceWorkflowIds = matches.map((m) => m.workflow.id);
|
|
1727
|
-
if (topMatch) metadata.topMatchScore = topMatch.score;
|
|
1728
|
-
if (designResult.credentialsNeeded.length > 0) metadata.credentialsNeeded = designResult.credentialsNeeded;
|
|
1729
|
-
const firstTryPass = designResult.attemptMetadata.length > 0 && designResult.attemptMetadata[0].validationPassed;
|
|
1730
|
-
const failedRules = Array.from(new Set(
|
|
1731
|
-
designResult.attemptMetadata.filter((m) => !m.validationPassed).flatMap((m) => m.issues.map((i) => i.rule))
|
|
1732
|
-
));
|
|
1733
|
-
this.saveQueue = this.saveQueue.then(async () => {
|
|
1734
|
-
const savedId = await this.library.save(workflow, metadata);
|
|
1735
|
-
for (const match of matches) {
|
|
1736
|
-
if (match.mode === "direct" || match.mode === "reference") {
|
|
1737
|
-
await this.library.recordOutcome(match.workflow.id, {
|
|
1738
|
-
attempts: designResult.attempts,
|
|
1739
|
-
firstTryPass,
|
|
1740
|
-
failedRules,
|
|
1741
|
-
mode: match.mode
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
return savedId;
|
|
1746
|
-
}).catch((err) => {
|
|
1747
|
-
this.logger.warn("Failed to save workflow to library (non-fatal)", { err: String(err) });
|
|
1748
|
-
return null;
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
async get(id) {
|
|
1752
|
-
return this.requireProvider().get(id);
|
|
1753
|
-
}
|
|
1754
|
-
async list() {
|
|
1755
|
-
return this.requireProvider().list();
|
|
1756
|
-
}
|
|
1757
|
-
async activate(id) {
|
|
1758
|
-
await this.requireProvider().activate(id);
|
|
1759
|
-
}
|
|
1760
|
-
async deactivate(id) {
|
|
1761
|
-
await this.requireProvider().deactivate(id);
|
|
1762
|
-
}
|
|
1763
|
-
async delete(id, options) {
|
|
1764
|
-
await this.requireProvider().delete(id, options);
|
|
1765
|
-
}
|
|
1766
|
-
async executions(workflowId, filter) {
|
|
1767
|
-
return this.requireProvider().executions(workflowId, filter);
|
|
1768
|
-
}
|
|
1769
|
-
async execution(id) {
|
|
1770
|
-
return this.requireProvider().execution(id);
|
|
1771
|
-
}
|
|
1772
|
-
async listTags() {
|
|
1773
|
-
return this.requireProvider().listTags();
|
|
1774
|
-
}
|
|
1775
|
-
async createTag(name) {
|
|
1776
|
-
return this.requireProvider().createTag(name);
|
|
1777
|
-
}
|
|
1778
|
-
async tag(workflowId, tagIds) {
|
|
1779
|
-
await this.requireProvider().tag(workflowId, tagIds);
|
|
1780
|
-
}
|
|
1781
|
-
async untag(workflowId, tagIds) {
|
|
1782
|
-
await this.requireProvider().untag(workflowId, tagIds);
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/utils/logger.ts
|
|
1160
|
+
var nullLogger = {
|
|
1161
|
+
debug() {
|
|
1162
|
+
},
|
|
1163
|
+
info() {
|
|
1164
|
+
},
|
|
1165
|
+
warn() {
|
|
1166
|
+
},
|
|
1167
|
+
error() {
|
|
1783
1168
|
}
|
|
1784
1169
|
};
|
|
1785
1170
|
|
|
@@ -1999,9 +1384,16 @@ function rerank(candidates, clusters) {
|
|
|
1999
1384
|
}
|
|
2000
1385
|
|
|
2001
1386
|
// src/library/file-library.ts
|
|
2002
|
-
import { readFile as readFile2, writeFile, rename, mkdir
|
|
2003
|
-
import { join as
|
|
2004
|
-
import { homedir as
|
|
1387
|
+
import { readFile as readFile2, writeFile, rename, mkdir } from "fs/promises";
|
|
1388
|
+
import { join as join2 } from "path";
|
|
1389
|
+
import { homedir as homedir2 } from "os";
|
|
1390
|
+
|
|
1391
|
+
// src/utils/uuid.ts
|
|
1392
|
+
function generateUUID() {
|
|
1393
|
+
return crypto.randomUUID();
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/library/file-library.ts
|
|
2005
1397
|
function tokenize(text) {
|
|
2006
1398
|
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 2);
|
|
2007
1399
|
}
|
|
@@ -2020,7 +1412,7 @@ var FileLibrary = class {
|
|
|
2020
1412
|
initPromise = null;
|
|
2021
1413
|
writeQueue = Promise.resolve();
|
|
2022
1414
|
constructor(dir) {
|
|
2023
|
-
this.dir = dir ??
|
|
1415
|
+
this.dir = dir ?? join2(homedir2(), ".kairos", "library");
|
|
2024
1416
|
}
|
|
2025
1417
|
async initialize() {
|
|
2026
1418
|
if (!this.initPromise) {
|
|
@@ -2029,8 +1421,8 @@ var FileLibrary = class {
|
|
|
2029
1421
|
return this.initPromise;
|
|
2030
1422
|
}
|
|
2031
1423
|
async doInitialize() {
|
|
2032
|
-
await
|
|
2033
|
-
const indexPath =
|
|
1424
|
+
await mkdir(this.dir, { recursive: true });
|
|
1425
|
+
const indexPath = join2(this.dir, "index.json");
|
|
2034
1426
|
try {
|
|
2035
1427
|
const raw = await readFile2(indexPath, "utf-8");
|
|
2036
1428
|
const parsed = JSON.parse(raw);
|
|
@@ -2162,7 +1554,7 @@ var FileLibrary = class {
|
|
|
2162
1554
|
}
|
|
2163
1555
|
persist() {
|
|
2164
1556
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
2165
|
-
const indexPath =
|
|
1557
|
+
const indexPath = join2(this.dir, "index.json");
|
|
2166
1558
|
const tmpPath = `${indexPath}.tmp`;
|
|
2167
1559
|
await writeFile(tmpPath, JSON.stringify(this.workflows, null, 2), "utf-8");
|
|
2168
1560
|
await rename(tmpPath, indexPath);
|
|
@@ -2171,206 +1563,25 @@ var FileLibrary = class {
|
|
|
2171
1563
|
}
|
|
2172
1564
|
};
|
|
2173
1565
|
|
|
2174
|
-
// src/templates/safety.ts
|
|
2175
|
-
var BLOCKED_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
2176
|
-
"n8n-nodes-base.code",
|
|
2177
|
-
"n8n-nodes-base.executeCommand",
|
|
2178
|
-
"n8n-nodes-base.ssh"
|
|
2179
|
-
]);
|
|
2180
|
-
var REVIEW_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
2181
|
-
"n8n-nodes-base.httpRequest"
|
|
2182
|
-
]);
|
|
2183
|
-
var SECRET_PATTERNS = [
|
|
2184
|
-
/sk-[a-zA-Z0-9]{20,}/,
|
|
2185
|
-
/ghp_[a-zA-Z0-9]{36}/,
|
|
2186
|
-
/xoxb-[0-9]+-[0-9]+-[a-zA-Z0-9]+/,
|
|
2187
|
-
/AIza[a-zA-Z0-9_-]{35}/,
|
|
2188
|
-
/AKIA[A-Z0-9]{16}/
|
|
2189
|
-
];
|
|
2190
|
-
function assessTemplateSafety(workflow) {
|
|
2191
|
-
const reasons = [];
|
|
2192
|
-
let worst = "safe";
|
|
2193
|
-
const escalate = (level, reason) => {
|
|
2194
|
-
reasons.push(reason);
|
|
2195
|
-
if (level === "blocked") worst = "blocked";
|
|
2196
|
-
else if (level === "review" && worst === "safe") worst = "review";
|
|
2197
|
-
};
|
|
2198
|
-
for (const node of workflow.nodes) {
|
|
2199
|
-
if (BLOCKED_NODE_TYPES.has(node.type)) {
|
|
2200
|
-
escalate("blocked", `Contains ${node.type} node "${node.name}"`);
|
|
2201
|
-
}
|
|
2202
|
-
if (REVIEW_NODE_TYPES.has(node.type)) {
|
|
2203
|
-
escalate("review", `Contains ${node.type} node "${node.name}"`);
|
|
2204
|
-
}
|
|
2205
|
-
const paramStr = JSON.stringify(node.parameters);
|
|
2206
|
-
for (const pattern of SECRET_PATTERNS) {
|
|
2207
|
-
if (pattern.test(paramStr)) {
|
|
2208
|
-
escalate("blocked", `Node "${node.name}" parameters contain a hardcoded secret`);
|
|
2209
|
-
break;
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
return { trustLevel: worst, reasons };
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
// src/templates/syncer.ts
|
|
2217
|
-
var N8N_TEMPLATE_API = "https://api.n8n.io/api/templates";
|
|
2218
|
-
var PAGE_SIZE = 50;
|
|
2219
|
-
var DELAY_BETWEEN_FETCHES_MS = 200;
|
|
2220
|
-
var DEFAULT_SETTINGS = {
|
|
2221
|
-
executionOrder: "v1",
|
|
2222
|
-
saveManualExecutions: true,
|
|
2223
|
-
timezone: "UTC"
|
|
2224
|
-
};
|
|
2225
|
-
var TemplateSyncer = class {
|
|
2226
|
-
constructor(library, logger) {
|
|
2227
|
-
this.library = library;
|
|
2228
|
-
this.validator = new N8nValidator();
|
|
2229
|
-
this.logger = logger;
|
|
2230
|
-
}
|
|
2231
|
-
library;
|
|
2232
|
-
validator;
|
|
2233
|
-
logger;
|
|
2234
|
-
async sync(options) {
|
|
2235
|
-
const maxTemplates = options?.maxTemplates ?? 500;
|
|
2236
|
-
await this.library.initialize();
|
|
2237
|
-
const existing = await this.library.list();
|
|
2238
|
-
const existingSourceIds = new Set(
|
|
2239
|
-
existing.filter((w) => w.sourceKind === "n8n-template" && w.sourceId).map((w) => w.sourceId)
|
|
2240
|
-
);
|
|
2241
|
-
const progress = {
|
|
2242
|
-
total: 0,
|
|
2243
|
-
processed: 0,
|
|
2244
|
-
saved: 0,
|
|
2245
|
-
skippedPaid: 0,
|
|
2246
|
-
skippedDuplicate: 0,
|
|
2247
|
-
blocked: 0,
|
|
2248
|
-
reviewed: 0
|
|
2249
|
-
};
|
|
2250
|
-
const templateIds = await this.fetchTemplateIds(maxTemplates, progress);
|
|
2251
|
-
for (const id of templateIds) {
|
|
2252
|
-
if (existingSourceIds.has(String(id))) {
|
|
2253
|
-
progress.skippedDuplicate++;
|
|
2254
|
-
progress.processed++;
|
|
2255
|
-
options?.onProgress?.(progress);
|
|
2256
|
-
continue;
|
|
2257
|
-
}
|
|
2258
|
-
try {
|
|
2259
|
-
await this.processTemplate(id, progress);
|
|
2260
|
-
} catch (err) {
|
|
2261
|
-
this.logger.warn(`Failed to process template ${id}`, { err: String(err) });
|
|
2262
|
-
}
|
|
2263
|
-
progress.processed++;
|
|
2264
|
-
options?.onProgress?.(progress);
|
|
2265
|
-
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_FETCHES_MS));
|
|
2266
|
-
}
|
|
2267
|
-
return progress;
|
|
2268
|
-
}
|
|
2269
|
-
async fetchTemplateIds(max, progress) {
|
|
2270
|
-
const ids = [];
|
|
2271
|
-
let page = 1;
|
|
2272
|
-
while (ids.length < max) {
|
|
2273
|
-
const url = `${N8N_TEMPLATE_API}/search?page=${page}&rows=${PAGE_SIZE}`;
|
|
2274
|
-
const response = await fetch(url);
|
|
2275
|
-
if (!response.ok) break;
|
|
2276
|
-
const data = await response.json();
|
|
2277
|
-
progress.total = Math.min(data.totalWorkflows, max);
|
|
2278
|
-
for (const template of data.workflows) {
|
|
2279
|
-
if (ids.length >= max) break;
|
|
2280
|
-
if (template.price && template.price > 0) {
|
|
2281
|
-
progress.skippedPaid++;
|
|
2282
|
-
continue;
|
|
2283
|
-
}
|
|
2284
|
-
ids.push(template.id);
|
|
2285
|
-
}
|
|
2286
|
-
if (data.workflows.length < PAGE_SIZE) break;
|
|
2287
|
-
page++;
|
|
2288
|
-
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_FETCHES_MS));
|
|
2289
|
-
}
|
|
2290
|
-
return ids;
|
|
2291
|
-
}
|
|
2292
|
-
async processTemplate(id, progress) {
|
|
2293
|
-
const url = `${N8N_TEMPLATE_API}/workflows/${id}`;
|
|
2294
|
-
const response = await fetch(url);
|
|
2295
|
-
if (!response.ok) return;
|
|
2296
|
-
const data = await response.json();
|
|
2297
|
-
const templateMeta = data.workflow;
|
|
2298
|
-
const rawWorkflow = templateMeta.workflow;
|
|
2299
|
-
if (!rawWorkflow?.nodes?.length) return;
|
|
2300
|
-
const workflow = {
|
|
2301
|
-
name: templateMeta.name,
|
|
2302
|
-
nodes: rawWorkflow.nodes.filter((n) => n.type && n.name),
|
|
2303
|
-
connections: rawWorkflow.connections,
|
|
2304
|
-
settings: rawWorkflow.settings ? { executionOrder: "v1", ...rawWorkflow.settings } : { ...DEFAULT_SETTINGS }
|
|
2305
|
-
};
|
|
2306
|
-
const validation = this.validator.validate(workflow);
|
|
2307
|
-
const validationErrors = validation.issues.filter((i) => i.severity === "error");
|
|
2308
|
-
if (validationErrors.length > 0) {
|
|
2309
|
-
progress.blocked++;
|
|
2310
|
-
this.logger.debug(`Template ${id} blocked: ${validationErrors.length} validation errors`);
|
|
2311
|
-
return;
|
|
2312
|
-
}
|
|
2313
|
-
const safety = assessTemplateSafety(workflow);
|
|
2314
|
-
if (safety.trustLevel === "blocked") {
|
|
2315
|
-
progress.blocked++;
|
|
2316
|
-
this.logger.debug(`Template ${id} blocked: ${safety.reasons.join(", ")}`);
|
|
2317
|
-
return;
|
|
2318
|
-
}
|
|
2319
|
-
if (safety.trustLevel === "review") {
|
|
2320
|
-
progress.reviewed++;
|
|
2321
|
-
}
|
|
2322
|
-
const description = this.cleanDescription(templateMeta.description);
|
|
2323
|
-
const autoTags = Array.from(new Set(
|
|
2324
|
-
workflow.nodes.flatMap((n) => {
|
|
2325
|
-
const bare = n.type.split(".").pop() ?? "";
|
|
2326
|
-
const tags = [bare];
|
|
2327
|
-
if (n.type.includes("Trigger") || n.type.includes("trigger")) tags.push(`trigger:${bare}`);
|
|
2328
|
-
if (n.type.includes("langchain")) tags.push("ai");
|
|
2329
|
-
return tags;
|
|
2330
|
-
})
|
|
2331
|
-
));
|
|
2332
|
-
const metadata = {
|
|
2333
|
-
description,
|
|
2334
|
-
tags: autoTags,
|
|
2335
|
-
sourceKind: "n8n-template",
|
|
2336
|
-
sourceId: String(id),
|
|
2337
|
-
sourceUrl: `https://n8n.io/workflows/${id}`,
|
|
2338
|
-
trustLevel: safety.trustLevel
|
|
2339
|
-
};
|
|
2340
|
-
await this.library.save(workflow, metadata);
|
|
2341
|
-
progress.saved++;
|
|
2342
|
-
this.logger.debug(`Template ${id} saved: "${templateMeta.name}" (${safety.trustLevel})`);
|
|
2343
|
-
}
|
|
2344
|
-
cleanDescription(raw) {
|
|
2345
|
-
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);
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
|
|
2349
1566
|
export {
|
|
2350
|
-
|
|
1567
|
+
generateUUID,
|
|
2351
1568
|
KairosError,
|
|
2352
1569
|
ApiError,
|
|
2353
1570
|
ProviderError,
|
|
2354
1571
|
N8nApiClient,
|
|
2355
1572
|
N8nFieldStripper,
|
|
2356
|
-
GuardError,
|
|
2357
|
-
N8nProvider,
|
|
2358
1573
|
DEFAULT_REGISTRY,
|
|
2359
1574
|
NodeRegistry,
|
|
2360
1575
|
N8nValidator,
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
ValidationError,
|
|
2364
|
-
TelemetryCollector,
|
|
1576
|
+
scoreToMode,
|
|
1577
|
+
PromptBuilder,
|
|
2365
1578
|
TelemetryReader,
|
|
2366
1579
|
nullLogger,
|
|
2367
|
-
Kairos,
|
|
2368
1580
|
hybridScore,
|
|
2369
1581
|
clusterWorkflows,
|
|
2370
1582
|
rerank,
|
|
2371
1583
|
tokenize,
|
|
2372
1584
|
buildSearchCorpus,
|
|
2373
|
-
FileLibrary
|
|
2374
|
-
TemplateSyncer
|
|
1585
|
+
FileLibrary
|
|
2375
1586
|
};
|
|
2376
|
-
//# sourceMappingURL=chunk-
|
|
1587
|
+
//# sourceMappingURL=chunk-DDV7ZART.js.map
|