@kairos-sdk/core 0.2.1 → 0.3.1
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 +112 -5
- package/dist/chunk-KQSNT3HZ.js +809 -0
- package/dist/chunk-KQSNT3HZ.js.map +1 -0
- package/dist/{chunk-QQJDLS5A.js → chunk-RYGYNOR6.js} +121 -895
- package/dist/chunk-RYGYNOR6.js.map +1 -0
- package/dist/cli.cjs +19 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +19 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -10
- package/dist/mcp-server.cjs +2080 -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 +509 -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) {
|
|
@@ -220,6 +193,14 @@ var N8nApiClient = class {
|
|
|
220
193
|
const remaining = (current.tags ?? []).filter((t) => !tagIds.includes(t.id)).map((t) => ({ id: t.id }));
|
|
221
194
|
await this.request("PUT", `/workflows/${workflowId}/tags`, remaining);
|
|
222
195
|
}
|
|
196
|
+
async getNodeTypes() {
|
|
197
|
+
try {
|
|
198
|
+
const response = await this.request("GET", "/node-types");
|
|
199
|
+
return response.data ?? response;
|
|
200
|
+
} catch {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
223
204
|
mapExecution(e) {
|
|
224
205
|
return {
|
|
225
206
|
id: e.id,
|
|
@@ -267,78 +248,6 @@ var N8nFieldStripper = class {
|
|
|
267
248
|
}
|
|
268
249
|
};
|
|
269
250
|
|
|
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
251
|
// src/validation/registry.ts
|
|
343
252
|
var DEFAULT_REGISTRY = [
|
|
344
253
|
// Trigger nodes
|
|
@@ -843,170 +752,6 @@ var N8nValidator = class {
|
|
|
843
752
|
}
|
|
844
753
|
};
|
|
845
754
|
|
|
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
755
|
// src/generation/prompts/v1.ts
|
|
1011
756
|
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
757
|
|
|
@@ -1223,9 +968,9 @@ var RULE_REMEDIES = {
|
|
|
1223
968
|
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)"
|
|
1224
969
|
};
|
|
1225
970
|
var PromptBuilder = class {
|
|
1226
|
-
build(request, matches, globalFailureRates = []) {
|
|
971
|
+
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
1227
972
|
const mode = this.resolveMode(matches);
|
|
1228
|
-
const system = this.buildSystem(matches, mode, globalFailureRates);
|
|
973
|
+
const system = this.buildSystem(matches, mode, globalFailureRates, dynamicCatalog);
|
|
1229
974
|
const userMessage = this.buildUserMessage(request, matches, mode);
|
|
1230
975
|
return { system, userMessage, mode, matches };
|
|
1231
976
|
}
|
|
@@ -1244,11 +989,18 @@ Fix ALL of the above issues in your new response. Do not repeat any of these mis
|
|
|
1244
989
|
if (!top) return "scratch";
|
|
1245
990
|
return scoreToMode(top.score);
|
|
1246
991
|
}
|
|
1247
|
-
buildSystem(matches, mode, globalFailureRates = []) {
|
|
992
|
+
buildSystem(matches, mode, globalFailureRates = [], dynamicCatalog) {
|
|
993
|
+
let basePrompt = SYSTEM_PROMPT_V1;
|
|
994
|
+
if (dynamicCatalog) {
|
|
995
|
+
basePrompt = basePrompt.replace(
|
|
996
|
+
/## NODE CATALOG — exact type strings and safe typeVersions[\s\S]*?(?=## PRE-DELIVERY SELF-CHECK)/,
|
|
997
|
+
dynamicCatalog + "\n\n"
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1248
1000
|
const blocks = [
|
|
1249
1001
|
{
|
|
1250
1002
|
type: "text",
|
|
1251
|
-
text:
|
|
1003
|
+
text: basePrompt,
|
|
1252
1004
|
cache_control: { type: "ephemeral" }
|
|
1253
1005
|
}
|
|
1254
1006
|
];
|
|
@@ -1332,454 +1084,102 @@ Workflow name: "${request.name}"` : "";
|
|
|
1332
1084
|
}
|
|
1333
1085
|
};
|
|
1334
1086
|
|
|
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();
|
|
1087
|
+
// src/telemetry/reader.ts
|
|
1088
|
+
import { readFile, readdir } from "fs/promises";
|
|
1089
|
+
import { join } from "path";
|
|
1090
|
+
import { homedir } from "os";
|
|
1091
|
+
var TelemetryReader = class {
|
|
1092
|
+
dir;
|
|
1093
|
+
cache = null;
|
|
1094
|
+
cacheTime = 0;
|
|
1095
|
+
constructor(dir) {
|
|
1096
|
+
this.dir = dir ?? join(homedir(), ".kairos", "telemetry");
|
|
1384
1097
|
}
|
|
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}`);
|
|
1098
|
+
async getFailureRates(days = 30) {
|
|
1099
|
+
const now = Date.now();
|
|
1100
|
+
if (this.cache && now - this.cacheTime < 5 * 60 * 1e3) {
|
|
1101
|
+
return this.cache;
|
|
1102
|
+
}
|
|
1103
|
+
const events = await this.readRecentEvents(days);
|
|
1104
|
+
const buildSessions = new Set(
|
|
1105
|
+
events.filter((e) => e.eventType === "build_complete" && !e.data.dryRun).map((e) => e.sessionId)
|
|
1106
|
+
);
|
|
1107
|
+
if (buildSessions.size === 0) return [];
|
|
1108
|
+
const ruleSessions = /* @__PURE__ */ new Map();
|
|
1109
|
+
for (const event of events) {
|
|
1110
|
+
if (event.eventType !== "generation_attempt") continue;
|
|
1111
|
+
if (!buildSessions.has(event.sessionId)) continue;
|
|
1112
|
+
const data = event.data;
|
|
1113
|
+
if (data.validationPassed || !data.issues) continue;
|
|
1114
|
+
for (const issue of data.issues) {
|
|
1115
|
+
const entry = ruleSessions.get(issue.rule) ?? { sessions: /* @__PURE__ */ new Set(), messages: /* @__PURE__ */ new Map() };
|
|
1116
|
+
entry.sessions.add(event.sessionId);
|
|
1117
|
+
entry.messages.set(issue.message, (entry.messages.get(issue.message) ?? 0) + 1);
|
|
1118
|
+
ruleSessions.set(issue.rule, entry);
|
|
1415
1119
|
}
|
|
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 };
|
|
1120
|
+
}
|
|
1121
|
+
const rates = [];
|
|
1122
|
+
for (const [rule, entry] of ruleSessions) {
|
|
1123
|
+
let topMessage = "";
|
|
1124
|
+
let topCount = 0;
|
|
1125
|
+
for (const [msg, count] of entry.messages) {
|
|
1126
|
+
if (count > topCount) {
|
|
1127
|
+
topMessage = msg;
|
|
1128
|
+
topCount = count;
|
|
1129
|
+
}
|
|
1429
1130
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1131
|
+
rates.push({
|
|
1132
|
+
rule,
|
|
1133
|
+
failureCount: entry.sessions.size,
|
|
1134
|
+
totalBuilds: buildSessions.size,
|
|
1135
|
+
rate: entry.sessions.size / buildSessions.size,
|
|
1136
|
+
commonMessage: topMessage
|
|
1433
1137
|
});
|
|
1434
1138
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
);
|
|
1139
|
+
rates.sort((a, b) => b.rate - a.rate);
|
|
1140
|
+
this.cache = rates;
|
|
1141
|
+
this.cacheTime = now;
|
|
1142
|
+
return rates;
|
|
1440
1143
|
}
|
|
1441
|
-
async
|
|
1442
|
-
|
|
1443
|
-
const timer = setTimeout(() => controller.abort(), 12e4);
|
|
1144
|
+
async readRecentEvents(days) {
|
|
1145
|
+
let files;
|
|
1444
1146
|
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");
|
|
1147
|
+
files = await readdir(this.dir);
|
|
1148
|
+
} catch {
|
|
1149
|
+
return [];
|
|
1483
1150
|
}
|
|
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) {
|
|
1151
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1152
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
1153
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
1154
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
1155
|
+
const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr).sort();
|
|
1156
|
+
const events = [];
|
|
1157
|
+
for (const file of recentFiles) {
|
|
1506
1158
|
try {
|
|
1507
|
-
|
|
1159
|
+
const content = await readFile(join(this.dir, file), "utf-8");
|
|
1160
|
+
for (const line of content.split("\n")) {
|
|
1161
|
+
if (!line.trim()) continue;
|
|
1162
|
+
try {
|
|
1163
|
+
events.push(JSON.parse(line));
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1508
1167
|
} catch {
|
|
1509
|
-
throw new GuardError(`Invalid n8nBaseUrl: "${options.n8nBaseUrl}" \u2014 must be a valid URL`);
|
|
1510
|
-
}
|
|
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
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
requireProvider() {
|
|
1534
|
-
if (!this.provider) {
|
|
1535
|
-
throw new GuardError("n8nBaseUrl and n8nApiKey are required for this operation \u2014 set them in the Kairos constructor, or use { dryRun: true } for generation-only mode");
|
|
1536
|
-
}
|
|
1537
|
-
return this.provider;
|
|
1538
|
-
}
|
|
1539
|
-
validateDescription(description) {
|
|
1540
|
-
if (!description || description.trim().length === 0) {
|
|
1541
|
-
throw new GuardError("Description is required and must be non-empty");
|
|
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
1168
|
}
|
|
1567
1169
|
}
|
|
1568
|
-
|
|
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);
|
|
1170
|
+
return events;
|
|
1780
1171
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// src/utils/logger.ts
|
|
1175
|
+
var nullLogger = {
|
|
1176
|
+
debug() {
|
|
1177
|
+
},
|
|
1178
|
+
info() {
|
|
1179
|
+
},
|
|
1180
|
+
warn() {
|
|
1181
|
+
},
|
|
1182
|
+
error() {
|
|
1783
1183
|
}
|
|
1784
1184
|
};
|
|
1785
1185
|
|
|
@@ -1999,9 +1399,16 @@ function rerank(candidates, clusters) {
|
|
|
1999
1399
|
}
|
|
2000
1400
|
|
|
2001
1401
|
// src/library/file-library.ts
|
|
2002
|
-
import { readFile as readFile2, writeFile, rename, mkdir
|
|
2003
|
-
import { join as
|
|
2004
|
-
import { homedir as
|
|
1402
|
+
import { readFile as readFile2, writeFile, rename, mkdir } from "fs/promises";
|
|
1403
|
+
import { join as join2 } from "path";
|
|
1404
|
+
import { homedir as homedir2 } from "os";
|
|
1405
|
+
|
|
1406
|
+
// src/utils/uuid.ts
|
|
1407
|
+
function generateUUID() {
|
|
1408
|
+
return crypto.randomUUID();
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/library/file-library.ts
|
|
2005
1412
|
function tokenize(text) {
|
|
2006
1413
|
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 2);
|
|
2007
1414
|
}
|
|
@@ -2020,7 +1427,7 @@ var FileLibrary = class {
|
|
|
2020
1427
|
initPromise = null;
|
|
2021
1428
|
writeQueue = Promise.resolve();
|
|
2022
1429
|
constructor(dir) {
|
|
2023
|
-
this.dir = dir ??
|
|
1430
|
+
this.dir = dir ?? join2(homedir2(), ".kairos", "library");
|
|
2024
1431
|
}
|
|
2025
1432
|
async initialize() {
|
|
2026
1433
|
if (!this.initPromise) {
|
|
@@ -2029,8 +1436,8 @@ var FileLibrary = class {
|
|
|
2029
1436
|
return this.initPromise;
|
|
2030
1437
|
}
|
|
2031
1438
|
async doInitialize() {
|
|
2032
|
-
await
|
|
2033
|
-
const indexPath =
|
|
1439
|
+
await mkdir(this.dir, { recursive: true });
|
|
1440
|
+
const indexPath = join2(this.dir, "index.json");
|
|
2034
1441
|
try {
|
|
2035
1442
|
const raw = await readFile2(indexPath, "utf-8");
|
|
2036
1443
|
const parsed = JSON.parse(raw);
|
|
@@ -2162,7 +1569,7 @@ var FileLibrary = class {
|
|
|
2162
1569
|
}
|
|
2163
1570
|
persist() {
|
|
2164
1571
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
2165
|
-
const indexPath =
|
|
1572
|
+
const indexPath = join2(this.dir, "index.json");
|
|
2166
1573
|
const tmpPath = `${indexPath}.tmp`;
|
|
2167
1574
|
await writeFile(tmpPath, JSON.stringify(this.workflows, null, 2), "utf-8");
|
|
2168
1575
|
await rename(tmpPath, indexPath);
|
|
@@ -2171,206 +1578,25 @@ var FileLibrary = class {
|
|
|
2171
1578
|
}
|
|
2172
1579
|
};
|
|
2173
1580
|
|
|
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
1581
|
export {
|
|
2350
|
-
|
|
1582
|
+
generateUUID,
|
|
2351
1583
|
KairosError,
|
|
2352
1584
|
ApiError,
|
|
2353
1585
|
ProviderError,
|
|
2354
1586
|
N8nApiClient,
|
|
2355
1587
|
N8nFieldStripper,
|
|
2356
|
-
GuardError,
|
|
2357
|
-
N8nProvider,
|
|
2358
1588
|
DEFAULT_REGISTRY,
|
|
2359
1589
|
NodeRegistry,
|
|
2360
1590
|
N8nValidator,
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
ValidationError,
|
|
2364
|
-
TelemetryCollector,
|
|
1591
|
+
scoreToMode,
|
|
1592
|
+
PromptBuilder,
|
|
2365
1593
|
TelemetryReader,
|
|
2366
1594
|
nullLogger,
|
|
2367
|
-
Kairos,
|
|
2368
1595
|
hybridScore,
|
|
2369
1596
|
clusterWorkflows,
|
|
2370
1597
|
rerank,
|
|
2371
1598
|
tokenize,
|
|
2372
1599
|
buildSearchCorpus,
|
|
2373
|
-
FileLibrary
|
|
2374
|
-
TemplateSyncer
|
|
1600
|
+
FileLibrary
|
|
2375
1601
|
};
|
|
2376
|
-
//# sourceMappingURL=chunk-
|
|
1602
|
+
//# sourceMappingURL=chunk-RYGYNOR6.js.map
|