@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.
@@ -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: SYSTEM_PROMPT_V1,
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/generation/designer.ts
1336
- var MAX_ATTEMPTS = 3;
1337
- var BASE_TEMPERATURE = 0.2;
1338
- var FINAL_TEMPERATURE = 0.1;
1339
- var GENERATE_WORKFLOW_TOOL = {
1340
- name: "generate_workflow",
1341
- description: "Generate a valid n8n workflow JSON object",
1342
- input_schema: {
1343
- type: "object",
1344
- properties: {
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
- anthropic;
1386
- model;
1387
- logger;
1388
- validator;
1389
- promptBuilder;
1390
- async design(request, matches, globalFailureRates = []) {
1391
- const attemptMetadata = [];
1392
- let lastErrors = [];
1393
- let attempts = 0;
1394
- const built = this.promptBuilder.build(request, matches, globalFailureRates);
1395
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
1396
- attempts = attempt;
1397
- const temperature = attempt === MAX_ATTEMPTS ? FINAL_TEMPERATURE : BASE_TEMPERATURE;
1398
- let userMessage;
1399
- if (attempt === 1) {
1400
- userMessage = built.userMessage;
1401
- this.logger.debug("WorkflowDesigner: attempt 1", { description: request.description });
1402
- } else {
1403
- const issueLines = lastErrors.map(
1404
- (i) => `- [Rule ${i.rule}] ${i.message}${i.nodeId ? ` (node: ${i.nodeId})` : ""}`
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
- const validation = this.validator.validate(parsed.workflow);
1417
- const errors = validation.issues.filter((i) => i.severity === "error");
1418
- attemptMetadata.push({
1419
- attempt,
1420
- temperature,
1421
- durationMs,
1422
- tokensInput: message.usage.input_tokens,
1423
- tokensOutput: message.usage.output_tokens,
1424
- validationPassed: validation.valid,
1425
- issues: validation.issues
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
- lastErrors = errors;
1431
- this.logger.warn(`WorkflowDesigner: validation failed on attempt ${attempt}`, {
1432
- errorCount: errors.length
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
- const finalIssues = attemptMetadata.at(-1)?.issues ?? lastErrors;
1436
- throw new ValidationError(
1437
- `Workflow failed validation after ${MAX_ATTEMPTS} attempts`,
1438
- finalIssues
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 callClaude(system, userMessage, temperature) {
1442
- const controller = new AbortController();
1443
- const timer = setTimeout(() => controller.abort(), 12e4);
1144
+ async readRecentEvents(days) {
1145
+ let files;
1444
1146
  try {
1445
- return await this.anthropic.messages.create(
1446
- {
1447
- model: this.model,
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 workflow = input["workflow"];
1485
- const credentialsNeeded = input["credentialsNeeded"] ?? [];
1486
- return { workflow, credentialsNeeded };
1487
- }
1488
- };
1489
-
1490
- // src/client.ts
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
- new URL(options.n8nBaseUrl);
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
- 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);
1170
+ return events;
1780
1171
  }
1781
- async untag(workflowId, tagIds) {
1782
- await this.requireProvider().untag(workflowId, tagIds);
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 as mkdir2 } from "fs/promises";
2003
- import { join as join3 } from "path";
2004
- import { homedir as homedir3 } from "os";
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 ?? join3(homedir3(), ".kairos", "library");
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 mkdir2(this.dir, { recursive: true });
2033
- const indexPath = join3(this.dir, "index.json");
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 = join3(this.dir, "index.json");
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
- NullLibrary,
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
- GenerationError,
2362
- ResponseParseError,
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-QQJDLS5A.js.map
1602
+ //# sourceMappingURL=chunk-RYGYNOR6.js.map