@kairos-sdk/core 0.4.0 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/{chunk-N6LRD2FN.js → chunk-4TS6GW6O.js} +60 -372
- package/dist/chunk-4TS6GW6O.js.map +1 -0
- package/dist/chunk-6CLI43FI.js +315 -0
- package/dist/chunk-6CLI43FI.js.map +1 -0
- package/dist/chunk-6FOFWVMG.js +1 -0
- package/dist/chunk-6FOFWVMG.js.map +1 -0
- package/dist/{chunk-NJ6QZBIC.js → chunk-6IXW3WCC.js} +477 -534
- package/dist/chunk-6IXW3WCC.js.map +1 -0
- package/dist/chunk-CR2NHLOH.js +523 -0
- package/dist/chunk-CR2NHLOH.js.map +1 -0
- package/dist/cli.cjs +632 -154
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +56 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +577 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -540
- package/dist/index.d.ts +3 -540
- package/dist/index.js +8 -4
- package/dist/mcp-server.cjs +651 -122
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/mcp-server.js +91 -8
- package/dist/mcp-server.js.map +1 -1
- package/dist/reader-CpUcHhKW.d.cts +566 -0
- package/dist/reader-CpUcHhKW.d.ts +566 -0
- package/dist/standalone.cjs +2460 -0
- package/dist/standalone.cjs.map +1 -0
- package/dist/standalone.d.cts +105 -0
- package/dist/standalone.d.ts +105 -0
- package/dist/standalone.js +58 -0
- package/dist/standalone.js.map +1 -0
- package/package.json +6 -1
- package/dist/chunk-N6LRD2FN.js.map +0 -1
- package/dist/chunk-NJ6QZBIC.js.map +0 -1
package/dist/mcp-server.cjs
CHANGED
|
@@ -253,14 +253,28 @@ function buildSearchCorpus(w) {
|
|
|
253
253
|
return `${w.description} ${w.workflow.name} ${w.tags.join(" ")} ${nodeTokens.join(" ")}`;
|
|
254
254
|
}
|
|
255
255
|
var MAX_LIBRARY_SIZE = 500;
|
|
256
|
+
function isValidMeta(item) {
|
|
257
|
+
return typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.description === "string" && typeof item.workflowName === "string" && Array.isArray(item.cachedNodeTypes);
|
|
258
|
+
}
|
|
259
|
+
function isValidOldEntry(item) {
|
|
260
|
+
return typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.description === "string" && typeof item.workflow === "object" && item.workflow !== null && Array.isArray(
|
|
261
|
+
item.workflow.nodes
|
|
262
|
+
);
|
|
263
|
+
}
|
|
256
264
|
var FileLibrary = class {
|
|
257
265
|
dir;
|
|
258
|
-
|
|
266
|
+
meta = [];
|
|
259
267
|
initPromise = null;
|
|
260
268
|
writeQueue = Promise.resolve();
|
|
261
269
|
constructor(dir) {
|
|
262
270
|
this.dir = dir ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".kairos", "library");
|
|
263
271
|
}
|
|
272
|
+
get workflowsDir() {
|
|
273
|
+
return (0, import_node_path.join)(this.dir, "workflows");
|
|
274
|
+
}
|
|
275
|
+
workflowFilePath(id) {
|
|
276
|
+
return (0, import_node_path.join)(this.workflowsDir, `${id}.json`);
|
|
277
|
+
}
|
|
264
278
|
async initialize() {
|
|
265
279
|
if (!this.initPromise) {
|
|
266
280
|
this.initPromise = this.doInitialize();
|
|
@@ -270,60 +284,147 @@ var FileLibrary = class {
|
|
|
270
284
|
async doInitialize() {
|
|
271
285
|
await (0, import_promises.mkdir)(this.dir, { recursive: true });
|
|
272
286
|
const indexPath = (0, import_node_path.join)(this.dir, "index.json");
|
|
287
|
+
let workflowsDirExists = false;
|
|
273
288
|
try {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
);
|
|
289
|
+
await (0, import_promises.stat)(this.workflowsDir);
|
|
290
|
+
workflowsDirExists = true;
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
if (workflowsDirExists) {
|
|
294
|
+
try {
|
|
295
|
+
const raw = await (0, import_promises.readFile)(indexPath, "utf-8");
|
|
296
|
+
const parsed = JSON.parse(raw);
|
|
297
|
+
if (Array.isArray(parsed)) {
|
|
298
|
+
this.meta = parsed.filter(isValidMeta);
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
this.meta = [];
|
|
282
302
|
}
|
|
303
|
+
} else {
|
|
304
|
+
try {
|
|
305
|
+
const raw = await (0, import_promises.readFile)(indexPath, "utf-8");
|
|
306
|
+
const parsed = JSON.parse(raw);
|
|
307
|
+
if (Array.isArray(parsed) && parsed.length > 0 && isValidOldEntry(parsed[0])) {
|
|
308
|
+
await this.migrateFromMonolithic(parsed.filter(isValidOldEntry));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
this.meta = [];
|
|
314
|
+
await (0, import_promises.mkdir)(this.workflowsDir, { recursive: true });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* One-time transparent migration from v0.4.x monolithic index.json.
|
|
319
|
+
* Splits each stored workflow into a per-file workflow JSON and a lightweight
|
|
320
|
+
* meta entry. Rewrites index.json in the new format.
|
|
321
|
+
*/
|
|
322
|
+
async migrateFromMonolithic(oldEntries) {
|
|
323
|
+
await (0, import_promises.mkdir)(this.workflowsDir, { recursive: true });
|
|
324
|
+
const newMeta = [];
|
|
325
|
+
for (const entry of oldEntries) {
|
|
326
|
+
const wfPath = this.workflowFilePath(entry.id);
|
|
327
|
+
const tmpPath = `${wfPath}.tmp`;
|
|
328
|
+
await (0, import_promises.writeFile)(tmpPath, JSON.stringify(entry.workflow), "utf-8");
|
|
329
|
+
await (0, import_promises.rename)(tmpPath, wfPath);
|
|
330
|
+
const { workflow, ...metaFields } = entry;
|
|
331
|
+
newMeta.push({
|
|
332
|
+
...metaFields,
|
|
333
|
+
workflowName: workflow.name,
|
|
334
|
+
cachedNodeTypes: workflow.nodes.map((n) => n.type)
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
this.meta = newMeta;
|
|
338
|
+
await this.persistNow();
|
|
339
|
+
}
|
|
340
|
+
async loadWorkflowFile(id) {
|
|
341
|
+
try {
|
|
342
|
+
const raw = await (0, import_promises.readFile)(this.workflowFilePath(id), "utf-8");
|
|
343
|
+
return JSON.parse(raw);
|
|
283
344
|
} catch {
|
|
284
|
-
|
|
345
|
+
return null;
|
|
285
346
|
}
|
|
286
347
|
}
|
|
348
|
+
async writeWorkflowFile(id, workflow) {
|
|
349
|
+
const wfPath = this.workflowFilePath(id);
|
|
350
|
+
const tmpPath = `${wfPath}.tmp`;
|
|
351
|
+
await (0, import_promises.writeFile)(tmpPath, JSON.stringify(workflow), "utf-8");
|
|
352
|
+
await (0, import_promises.rename)(tmpPath, wfPath);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Build a lightweight StoredWorkflow shell from a meta entry for use in
|
|
356
|
+
* scoring / clustering. Only node.type is populated in each node — no other
|
|
357
|
+
* node fields are used by hybridScore or clusterWorkflows.
|
|
358
|
+
*/
|
|
359
|
+
makeSearchShell(m) {
|
|
360
|
+
return {
|
|
361
|
+
...m,
|
|
362
|
+
workflow: {
|
|
363
|
+
name: m.workflowName,
|
|
364
|
+
nodes: m.cachedNodeTypes.map((type) => ({
|
|
365
|
+
id: "",
|
|
366
|
+
name: "",
|
|
367
|
+
type,
|
|
368
|
+
typeVersion: 1,
|
|
369
|
+
position: [0, 0],
|
|
370
|
+
parameters: {}
|
|
371
|
+
})),
|
|
372
|
+
connections: {}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
287
376
|
async search(description, options) {
|
|
288
|
-
const
|
|
289
|
-
if (
|
|
377
|
+
const filteredMeta = this.meta.filter((m) => m.trustLevel !== "blocked");
|
|
378
|
+
if (filteredMeta.length === 0) return [];
|
|
290
379
|
const limit = options?.limit ?? 3;
|
|
291
380
|
const queryTokens = tokenize(description);
|
|
292
381
|
if (queryTokens.length === 0) return [];
|
|
293
|
-
const
|
|
382
|
+
const shells = filteredMeta.map((m) => this.makeSearchShell(m));
|
|
383
|
+
const docTokenArrays = shells.map((w) => tokenize(buildSearchCorpus(w)));
|
|
294
384
|
const docTokenSets = docTokenArrays.map((tokens) => new Set(tokens));
|
|
295
|
-
const docCount =
|
|
385
|
+
const docCount = shells.length;
|
|
296
386
|
const idf = /* @__PURE__ */ new Map();
|
|
297
387
|
const allTokens = new Set(queryTokens);
|
|
298
388
|
for (const token of allTokens) {
|
|
299
389
|
const docsWithToken = docTokenSets.filter((d) => d.has(token)).length;
|
|
300
390
|
idf.set(token, Math.log((docCount + 1) / (docsWithToken + 1)) + 1);
|
|
301
391
|
}
|
|
302
|
-
const scored = hybridScore(queryTokens, description,
|
|
303
|
-
const clusters = clusterWorkflows(
|
|
392
|
+
const scored = hybridScore(queryTokens, description, shells, docTokenArrays, idf).filter((m) => m.score > 0).sort((a, b) => b.score - a.score);
|
|
393
|
+
const clusters = clusterWorkflows(shells);
|
|
304
394
|
const reranked = rerank(scored, clusters).slice(0, limit);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
395
|
+
if (reranked.length === 0) return [];
|
|
396
|
+
for (const r of reranked) {
|
|
397
|
+
const m = this.meta.find((m2) => m2.id === r.workflow.id);
|
|
398
|
+
if (m) m.timesRetrieved = (m.timesRetrieved ?? 0) + 1;
|
|
399
|
+
}
|
|
400
|
+
this.persist();
|
|
401
|
+
const results = await Promise.all(
|
|
402
|
+
reranked.map(async (r) => {
|
|
403
|
+
const m = this.meta.find((meta) => meta.id === r.workflow.id);
|
|
404
|
+
const workflow = await this.loadWorkflowFile(r.workflow.id);
|
|
405
|
+
if (!workflow) return null;
|
|
406
|
+
return {
|
|
407
|
+
workflow: { ...m, workflow },
|
|
408
|
+
score: r.score,
|
|
409
|
+
mode: scoreToMode(r.score)
|
|
410
|
+
};
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
return results.filter((r) => r !== null);
|
|
315
414
|
}
|
|
316
415
|
async save(workflow, metadata) {
|
|
317
416
|
const id = generateUUID();
|
|
417
|
+
await this.writeWorkflowFile(id, workflow);
|
|
318
418
|
const failurePatterns = this.deduplicateFailurePatterns(metadata.failurePatterns);
|
|
319
|
-
const
|
|
419
|
+
const meta = {
|
|
320
420
|
id,
|
|
321
|
-
workflow,
|
|
322
421
|
description: metadata.description,
|
|
323
422
|
tags: metadata.tags ?? [],
|
|
324
423
|
platform: metadata.platform ?? "n8n",
|
|
325
424
|
deployCount: 0,
|
|
326
425
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
426
|
+
workflowName: workflow.name,
|
|
427
|
+
cachedNodeTypes: workflow.nodes.map((n) => n.type),
|
|
327
428
|
...failurePatterns?.length ? { failurePatterns } : {},
|
|
328
429
|
...metadata.sourceWorkflowIds?.length ? { sourceWorkflowIds: metadata.sourceWorkflowIds } : {},
|
|
329
430
|
...metadata.generationMode ? { generationMode: metadata.generationMode } : {},
|
|
@@ -335,31 +436,35 @@ var FileLibrary = class {
|
|
|
335
436
|
...metadata.sourceUrl ? { sourceUrl: metadata.sourceUrl } : {},
|
|
336
437
|
...metadata.trustLevel ? { trustLevel: metadata.trustLevel } : {}
|
|
337
438
|
};
|
|
338
|
-
this.
|
|
339
|
-
if (this.
|
|
340
|
-
this.
|
|
341
|
-
|
|
439
|
+
this.meta.push(meta);
|
|
440
|
+
if (this.meta.length > MAX_LIBRARY_SIZE) {
|
|
441
|
+
this.meta.sort((a, b) => {
|
|
442
|
+
if (a.id === id) return -1;
|
|
443
|
+
if (b.id === id) return 1;
|
|
444
|
+
return (b.deployCount ?? 0) - (a.deployCount ?? 0);
|
|
445
|
+
});
|
|
446
|
+
this.meta = this.meta.slice(0, MAX_LIBRARY_SIZE);
|
|
342
447
|
}
|
|
343
448
|
await this.persist();
|
|
344
449
|
return id;
|
|
345
450
|
}
|
|
346
451
|
async recordDeployment(id) {
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
452
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
453
|
+
if (m) {
|
|
454
|
+
m.deployCount++;
|
|
455
|
+
m.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
351
456
|
await this.persist();
|
|
352
457
|
}
|
|
353
458
|
}
|
|
354
459
|
async recordOutcome(id, outcome) {
|
|
355
|
-
const
|
|
356
|
-
if (!
|
|
460
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
461
|
+
if (!m) return;
|
|
357
462
|
if (outcome.mode === "direct") {
|
|
358
|
-
|
|
463
|
+
m.timesUsedAsDirect = (m.timesUsedAsDirect ?? 0) + 1;
|
|
359
464
|
} else {
|
|
360
|
-
|
|
465
|
+
m.timesUsedAsReference = (m.timesUsedAsReference ?? 0) + 1;
|
|
361
466
|
}
|
|
362
|
-
const stats =
|
|
467
|
+
const stats = m.outcomeStats ?? { totalUses: 0, totalAttempts: 0, firstTryPasses: 0, failedRules: {} };
|
|
363
468
|
stats.totalUses++;
|
|
364
469
|
stats.totalAttempts += outcome.attempts;
|
|
365
470
|
if (outcome.firstTryPass) stats.firstTryPasses++;
|
|
@@ -367,24 +472,35 @@ var FileLibrary = class {
|
|
|
367
472
|
const key = String(rule);
|
|
368
473
|
stats.failedRules[key] = (stats.failedRules[key] ?? 0) + 1;
|
|
369
474
|
}
|
|
370
|
-
|
|
475
|
+
m.outcomeStats = stats;
|
|
371
476
|
await this.persist();
|
|
372
477
|
}
|
|
373
478
|
async drain() {
|
|
374
479
|
await this.writeQueue;
|
|
375
480
|
}
|
|
376
481
|
async get(id) {
|
|
377
|
-
|
|
482
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
483
|
+
if (!m) return null;
|
|
484
|
+
const workflow = await this.loadWorkflowFile(id);
|
|
485
|
+
if (!workflow) return null;
|
|
486
|
+
return { ...m, workflow };
|
|
378
487
|
}
|
|
379
488
|
async list(filters) {
|
|
380
|
-
let
|
|
489
|
+
let filtered = this.meta;
|
|
381
490
|
if (filters?.platform) {
|
|
382
|
-
|
|
491
|
+
filtered = filtered.filter((m) => m.platform === filters.platform);
|
|
383
492
|
}
|
|
384
493
|
if (filters?.tags && filters.tags.length > 0) {
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
494
|
+
filtered = filtered.filter((m) => filters.tags.some((t) => m.tags.includes(t)));
|
|
495
|
+
}
|
|
496
|
+
const results = await Promise.all(
|
|
497
|
+
filtered.map(async (m) => {
|
|
498
|
+
const workflow = await this.loadWorkflowFile(m.id);
|
|
499
|
+
if (!workflow) return null;
|
|
500
|
+
return { ...m, workflow };
|
|
501
|
+
})
|
|
502
|
+
);
|
|
503
|
+
return results.filter((r) => r !== null);
|
|
388
504
|
}
|
|
389
505
|
deduplicateFailurePatterns(patterns) {
|
|
390
506
|
if (!patterns?.length) return void 0;
|
|
@@ -399,11 +515,36 @@ var FileLibrary = class {
|
|
|
399
515
|
}
|
|
400
516
|
return [...map.values()];
|
|
401
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Direct write used only during migration (before writeQueue is needed).
|
|
520
|
+
*/
|
|
521
|
+
async persistNow() {
|
|
522
|
+
const indexPath = (0, import_node_path.join)(this.dir, "index.json");
|
|
523
|
+
const tmpPath = `${indexPath}.tmp`;
|
|
524
|
+
await (0, import_promises.writeFile)(tmpPath, JSON.stringify(this.meta, null, 2), "utf-8");
|
|
525
|
+
await (0, import_promises.rename)(tmpPath, indexPath);
|
|
526
|
+
}
|
|
402
527
|
persist() {
|
|
403
528
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
404
529
|
const indexPath = (0, import_node_path.join)(this.dir, "index.json");
|
|
530
|
+
let onDisk = [];
|
|
531
|
+
try {
|
|
532
|
+
const raw = await (0, import_promises.readFile)(indexPath, "utf-8");
|
|
533
|
+
const parsed = JSON.parse(raw);
|
|
534
|
+
if (Array.isArray(parsed)) {
|
|
535
|
+
onDisk = parsed.filter(isValidMeta);
|
|
536
|
+
}
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
const ourIds = new Set(this.meta.map((m) => m.id));
|
|
540
|
+
const external = onDisk.filter((m) => !ourIds.has(m.id));
|
|
541
|
+
let merged = [...this.meta, ...external];
|
|
542
|
+
if (merged.length > MAX_LIBRARY_SIZE) {
|
|
543
|
+
merged.sort((a, b) => (b.deployCount ?? 0) - (a.deployCount ?? 0));
|
|
544
|
+
merged = merged.slice(0, MAX_LIBRARY_SIZE);
|
|
545
|
+
}
|
|
405
546
|
const tmpPath = `${indexPath}.tmp`;
|
|
406
|
-
await (0, import_promises.writeFile)(tmpPath, JSON.stringify(
|
|
547
|
+
await (0, import_promises.writeFile)(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
407
548
|
await (0, import_promises.rename)(tmpPath, indexPath);
|
|
408
549
|
});
|
|
409
550
|
return this.writeQueue;
|
|
@@ -577,6 +718,9 @@ var N8nValidator = class {
|
|
|
577
718
|
this.checkRule21(workflow, issues);
|
|
578
719
|
this.checkRule22(workflow, issues);
|
|
579
720
|
this.checkRule23(workflow, issues);
|
|
721
|
+
this.checkRule24(workflow, issues);
|
|
722
|
+
this.checkRule25(workflow, issues);
|
|
723
|
+
this.checkRule26(workflow, issues);
|
|
580
724
|
if (Array.isArray(workflow.nodes)) {
|
|
581
725
|
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
582
726
|
for (const issue of issues) {
|
|
@@ -709,10 +853,14 @@ var N8nValidator = class {
|
|
|
709
853
|
checkRule11(w, issues) {
|
|
710
854
|
if (!Array.isArray(w.nodes) || typeof w.connections !== "object" || w.connections === null) return;
|
|
711
855
|
const reachable = /* @__PURE__ */ new Set();
|
|
712
|
-
|
|
856
|
+
const aiSubNodeSources = /* @__PURE__ */ new Set();
|
|
857
|
+
for (const [sourceName, outputs] of Object.entries(w.connections)) {
|
|
713
858
|
if (typeof outputs !== "object" || outputs === null) continue;
|
|
714
|
-
|
|
859
|
+
let hasAiPort = false;
|
|
860
|
+
for (const [portName, portGroup] of Object.entries(outputs)) {
|
|
715
861
|
if (!Array.isArray(portGroup)) continue;
|
|
862
|
+
const isAiPort = portName.startsWith("ai_");
|
|
863
|
+
if (isAiPort) hasAiPort = true;
|
|
716
864
|
for (const targets of portGroup) {
|
|
717
865
|
if (!Array.isArray(targets)) continue;
|
|
718
866
|
for (const target of targets) {
|
|
@@ -721,10 +869,13 @@ var N8nValidator = class {
|
|
|
721
869
|
}
|
|
722
870
|
}
|
|
723
871
|
}
|
|
872
|
+
if (hasAiPort) aiSubNodeSources.add(sourceName);
|
|
724
873
|
}
|
|
725
874
|
for (const node of w.nodes) {
|
|
726
875
|
if (node.type.includes("stickyNote")) continue;
|
|
727
|
-
if (
|
|
876
|
+
if (this.isTriggerNode(node)) continue;
|
|
877
|
+
if (aiSubNodeSources.has(node.name)) continue;
|
|
878
|
+
if (!reachable.has(node.name)) {
|
|
728
879
|
this.warn(issues, 11, `Node "${node.name}" has no incoming connections and may never execute`, node.id);
|
|
729
880
|
}
|
|
730
881
|
}
|
|
@@ -920,6 +1071,76 @@ var N8nValidator = class {
|
|
|
920
1071
|
}
|
|
921
1072
|
}
|
|
922
1073
|
}
|
|
1074
|
+
// Rule 24 (WARN): deprecated accessor syntax in expressions
|
|
1075
|
+
checkRule24(w, issues) {
|
|
1076
|
+
if (!Array.isArray(w.nodes)) return;
|
|
1077
|
+
const deprecated = /\$node\s*\[/;
|
|
1078
|
+
for (const node of w.nodes) {
|
|
1079
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
1080
|
+
if (deprecated.test(expr)) {
|
|
1081
|
+
this.warn(
|
|
1082
|
+
issues,
|
|
1083
|
+
24,
|
|
1084
|
+
`Node "${node.name}" uses deprecated accessor $node["..."] \u2014 use $('NodeName').item.json.field instead`,
|
|
1085
|
+
node.id
|
|
1086
|
+
);
|
|
1087
|
+
break;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
// Rule 25 (WARN): wrong item index assumptions in expressions
|
|
1093
|
+
checkRule25(w, issues) {
|
|
1094
|
+
if (!Array.isArray(w.nodes)) return;
|
|
1095
|
+
const itemIndex = /\$json\s*\.\s*items\s*\[/;
|
|
1096
|
+
for (const node of w.nodes) {
|
|
1097
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
1098
|
+
if (itemIndex.test(expr)) {
|
|
1099
|
+
this.warn(
|
|
1100
|
+
issues,
|
|
1101
|
+
25,
|
|
1102
|
+
`Node "${node.name}" accesses $json.items[n] \u2014 n8n flattens items automatically, use $json.field directly`,
|
|
1103
|
+
node.id
|
|
1104
|
+
);
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
// Rule 26 (WARN): missing .first() or .all() on node references
|
|
1111
|
+
checkRule26(w, issues) {
|
|
1112
|
+
if (!Array.isArray(w.nodes)) return;
|
|
1113
|
+
const bareRef = /\$\(\s*'[^']+'\s*\)\s*\.json/;
|
|
1114
|
+
for (const node of w.nodes) {
|
|
1115
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
1116
|
+
if (bareRef.test(expr)) {
|
|
1117
|
+
this.warn(
|
|
1118
|
+
issues,
|
|
1119
|
+
26,
|
|
1120
|
+
`Node "${node.name}" references $('NodeName').json without .first() or .all() \u2014 use $('NodeName').first().json.field`,
|
|
1121
|
+
node.id
|
|
1122
|
+
);
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
extractExpressions(params) {
|
|
1129
|
+
const expressions = [];
|
|
1130
|
+
const walk = (val) => {
|
|
1131
|
+
if (typeof val === "string") {
|
|
1132
|
+
if (val.includes("={{") || val.includes("$node") || val.includes("$('")) {
|
|
1133
|
+
expressions.push(val);
|
|
1134
|
+
}
|
|
1135
|
+
} else if (Array.isArray(val)) {
|
|
1136
|
+
for (const item of val) walk(item);
|
|
1137
|
+
} else if (val !== null && typeof val === "object") {
|
|
1138
|
+
for (const v of Object.values(val)) walk(v);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
walk(params);
|
|
1142
|
+
return expressions;
|
|
1143
|
+
}
|
|
923
1144
|
// Rule 21 (WARN): webhook with responseMode="responseNode" must have respondToWebhook node
|
|
924
1145
|
checkRule21(w, issues) {
|
|
925
1146
|
if (!Array.isArray(w.nodes)) return;
|
|
@@ -1209,9 +1430,11 @@ id, active, createdAt, updatedAt, versionId, meta, isArchived, activeVersionId,
|
|
|
1209
1430
|
- Never reuse IDs, never use sequential fake IDs like "node-1"
|
|
1210
1431
|
|
|
1211
1432
|
### Credentials:
|
|
1212
|
-
-
|
|
1213
|
-
|
|
1214
|
-
-
|
|
1433
|
+
- Each credential is keyed by its type string, with an object value containing id and name:
|
|
1434
|
+
"credentials": { "slackOAuth2Api": { "id": "placeholder-id", "name": "My Slack Credential" } }
|
|
1435
|
+
- Use "placeholder-id" as the id \u2014 users replace this with their real credential ID from n8n after deployment
|
|
1436
|
+
- The credentialsNeeded field in your response declares what credentials the user must configure
|
|
1437
|
+
- Never put API keys or tokens directly in node parameters when a credential type exists
|
|
1215
1438
|
|
|
1216
1439
|
### Node names:
|
|
1217
1440
|
- All node names must be unique within the workflow
|
|
@@ -1258,6 +1481,23 @@ Node parameters like conditions, assignments, and rule intervals MUST include al
|
|
|
1258
1481
|
|
|
1259
1482
|
---
|
|
1260
1483
|
|
|
1484
|
+
## EXPRESSION SYNTAX \u2014 how to reference upstream node data
|
|
1485
|
+
|
|
1486
|
+
### Accessing a field from an upstream node:
|
|
1487
|
+
- CORRECT: $('NodeName').item.json.field
|
|
1488
|
+
- WRONG: $node["NodeName"].json.field \u2190 deprecated accessor, fails at runtime (Rule 24)
|
|
1489
|
+
|
|
1490
|
+
### Accessing array items from $json:
|
|
1491
|
+
- CORRECT: $json.field \u2190 n8n auto-flattens items; each item is already a flat object
|
|
1492
|
+
- WRONG: $json.items[0].field \u2190 do not index into items[] (Rule 25)
|
|
1493
|
+
|
|
1494
|
+
### Calling node data \u2014 always qualify with .first() or .all():
|
|
1495
|
+
- CORRECT: $('NodeName').first().json.field \u2190 single item
|
|
1496
|
+
- CORRECT: $('NodeName').all() \u2190 array of all items
|
|
1497
|
+
- WRONG: $('NodeName').json \u2190 throws at runtime without .first() or .all() (Rule 26)
|
|
1498
|
+
|
|
1499
|
+
---
|
|
1500
|
+
|
|
1261
1501
|
## NODE CATALOG \u2014 exact type strings and safe typeVersions
|
|
1262
1502
|
|
|
1263
1503
|
### Triggers (always at least one required):
|
|
@@ -1357,6 +1597,9 @@ Cron: { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 *
|
|
|
1357
1597
|
5. At least one trigger node present
|
|
1358
1598
|
6. Every AI Agent has an ai_languageModel sub-node
|
|
1359
1599
|
7. settings block is complete with executionOrder: "v1"
|
|
1600
|
+
8. No deprecated $node["NodeName"].json \u2014 use $('NodeName').item.json.field
|
|
1601
|
+
9. No $json.items[0] array indexing \u2014 access fields directly as $json.field
|
|
1602
|
+
10. No bare $('NodeName').json \u2014 always use .first().json.field or .all()
|
|
1360
1603
|
|
|
1361
1604
|
---
|
|
1362
1605
|
|
|
@@ -1364,7 +1607,7 @@ Respond ONLY with a generate_workflow tool call. No prose. No markdown outside t
|
|
|
1364
1607
|
If the request is impossible or unclear, set the error field instead of generating a workflow.`;
|
|
1365
1608
|
|
|
1366
1609
|
// src/validation/rule-metadata.ts
|
|
1367
|
-
var VALIDATOR_RULE_IDS = Array.from({ length:
|
|
1610
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 26 }, (_, i) => i + 1);
|
|
1368
1611
|
var RULE_PIPELINE_STAGES = {
|
|
1369
1612
|
1: "node_generation",
|
|
1370
1613
|
2: "node_generation",
|
|
@@ -1388,7 +1631,28 @@ var RULE_PIPELINE_STAGES = {
|
|
|
1388
1631
|
20: "connection_wiring",
|
|
1389
1632
|
21: "workflow_structure",
|
|
1390
1633
|
22: "workflow_structure",
|
|
1391
|
-
23: "node_generation"
|
|
1634
|
+
23: "node_generation",
|
|
1635
|
+
24: "expression_syntax",
|
|
1636
|
+
25: "expression_syntax",
|
|
1637
|
+
26: "expression_syntax"
|
|
1638
|
+
};
|
|
1639
|
+
var RULE_EXAMPLES = {
|
|
1640
|
+
17: {
|
|
1641
|
+
bad: '"credentials": { "slackOAuth2Api": "my-token" }',
|
|
1642
|
+
good: '"credentials": { "slackOAuth2Api": { "id": "placeholder-id", "name": "My Slack OAuth" } }'
|
|
1643
|
+
},
|
|
1644
|
+
24: {
|
|
1645
|
+
bad: '$node["Fetch Data"].json.email',
|
|
1646
|
+
good: "$('Fetch Data').item.json.email"
|
|
1647
|
+
},
|
|
1648
|
+
25: {
|
|
1649
|
+
bad: "$json.items[0].email",
|
|
1650
|
+
good: "$json.email"
|
|
1651
|
+
},
|
|
1652
|
+
26: {
|
|
1653
|
+
bad: "$('Fetch Data').json.email",
|
|
1654
|
+
good: "$('Fetch Data').first().json.email"
|
|
1655
|
+
}
|
|
1392
1656
|
};
|
|
1393
1657
|
var RULE_MITIGATIONS = {
|
|
1394
1658
|
1: "Provide a non-empty workflow name string",
|
|
@@ -1407,21 +1671,44 @@ var RULE_MITIGATIONS = {
|
|
|
1407
1671
|
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
1408
1672
|
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
1409
1673
|
16: "All node names must be unique within the workflow",
|
|
1410
|
-
17: '
|
|
1674
|
+
17: 'Each credential entry must be keyed by credential type with an object value: { "slackOAuth2Api": { "id": "placeholder-id", "name": "My Credential" } } \u2014 the key is the credential type, the value has id and name strings',
|
|
1411
1675
|
18: "AI sub-nodes (languageModel, memory, tool) must be the CONNECTION SOURCE pointing TO the agent \u2014 not the reverse",
|
|
1412
1676
|
19: "Use known safe typeVersion values for each node type",
|
|
1413
1677
|
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
1414
1678
|
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
1415
1679
|
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1416
|
-
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync"
|
|
1680
|
+
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync",
|
|
1681
|
+
24: 'Use modern accessor syntax: $("NodeName").item.json.field instead of deprecated $node["NodeName"].json.field',
|
|
1682
|
+
25: "Access item fields directly with $json.field \u2014 n8n flattens items automatically, do not use $json.items[0]",
|
|
1683
|
+
26: 'Use $("NodeName").first().json.field or $("NodeName").all() \u2014 bare $("NodeName").json without .first() or .all() throws at runtime'
|
|
1417
1684
|
};
|
|
1418
1685
|
|
|
1419
1686
|
// src/generation/prompt-builder.ts
|
|
1420
1687
|
var CRITICAL_SCORE_THRESHOLD = 0.15;
|
|
1688
|
+
function resolveProfile() {
|
|
1689
|
+
const env = process.env["KAIROS_PROMPT_PROFILE"];
|
|
1690
|
+
if (env === "minimal" || env === "standard" || env === "rich") return env;
|
|
1691
|
+
return "standard";
|
|
1692
|
+
}
|
|
1693
|
+
var PROACTIVE_EXPRESSION_GUIDANCE = `## Expression Syntax Quick Reference
|
|
1694
|
+
|
|
1695
|
+
Always use these patterns in expressions:
|
|
1696
|
+
- Access node data: $('NodeName').item.json.field (not $node["NodeName"].json)
|
|
1697
|
+
- Access JSON field: $json.field (not $json.items[0].field)
|
|
1698
|
+
- Single item: $('NodeName').first().json.field
|
|
1699
|
+
- All items: $('NodeName').all()`;
|
|
1421
1700
|
var PromptBuilder = class {
|
|
1422
1701
|
patternsPath;
|
|
1423
|
-
|
|
1702
|
+
profile;
|
|
1703
|
+
_lastActivePatterns = null;
|
|
1704
|
+
constructor(patternsPath, profile) {
|
|
1424
1705
|
this.patternsPath = patternsPath ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".kairos", "patterns.json");
|
|
1706
|
+
this.profile = profile ?? resolveProfile();
|
|
1707
|
+
}
|
|
1708
|
+
resolveMaxPatterns() {
|
|
1709
|
+
if (this.profile === "minimal") return 3;
|
|
1710
|
+
if (this.profile === "rich") return 15;
|
|
1711
|
+
return 10;
|
|
1425
1712
|
}
|
|
1426
1713
|
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
1427
1714
|
const mode = this.resolveMode(matches);
|
|
@@ -1459,53 +1746,62 @@ Fix ALL of the above issues in your new response. Do not repeat any of these mis
|
|
|
1459
1746
|
cache_control: { type: "ephemeral" }
|
|
1460
1747
|
}
|
|
1461
1748
|
];
|
|
1462
|
-
if (
|
|
1463
|
-
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1749
|
+
if (this.profile !== "minimal") {
|
|
1750
|
+
if (mode === "reference" && matches.length > 0) {
|
|
1751
|
+
const refText = matches.slice(0, 3).map((m) => {
|
|
1752
|
+
const nodes = m.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1753
|
+
return `Reference workflow: "${m.workflow.description}" (similarity: ${m.score.toFixed(2)})
|
|
1466
1754
|
Nodes:
|
|
1467
1755
|
${nodes}`;
|
|
1468
|
-
|
|
1469
|
-
blocks.push({
|
|
1470
|
-
type: "text",
|
|
1471
|
-
text: `## Similar Workflows From Library (for reference only \u2014 adapt, do not copy verbatim)
|
|
1472
|
-
|
|
1473
|
-
${refText}`
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
if (mode === "direct" && matches[0]) {
|
|
1477
|
-
const match = matches[0];
|
|
1478
|
-
const json = JSON.stringify(match.workflow.workflow, null, 2);
|
|
1479
|
-
if (json.length > 3e4) {
|
|
1480
|
-
const nodes = match.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1756
|
+
}).join("\n\n");
|
|
1481
1757
|
blocks.push({
|
|
1482
1758
|
type: "text",
|
|
1483
|
-
text: `##
|
|
1759
|
+
text: `## Similar Workflows From Library (for reference only \u2014 adapt, do not copy verbatim)
|
|
1760
|
+
|
|
1761
|
+
${refText}`
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
if (mode === "direct" && matches[0]) {
|
|
1765
|
+
const match = matches[0];
|
|
1766
|
+
const json = JSON.stringify(match.workflow.workflow, null, 2);
|
|
1767
|
+
if (json.length > 3e4) {
|
|
1768
|
+
const nodes = match.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1769
|
+
blocks.push({
|
|
1770
|
+
type: "text",
|
|
1771
|
+
text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 too large for full JSON, using reference:
|
|
1484
1772
|
Nodes:
|
|
1485
1773
|
${nodes}`
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1774
|
+
});
|
|
1775
|
+
} else {
|
|
1776
|
+
blocks.push({
|
|
1777
|
+
type: "text",
|
|
1778
|
+
text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 adapt this structure:
|
|
1491
1779
|
|
|
1492
1780
|
${json}`
|
|
1493
|
-
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1494
1783
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
text: `## Weak Structural Hint
|
|
1784
|
+
if (mode === "scratch" && matches.length > 0 && matches[0].score >= 0.4) {
|
|
1785
|
+
const hint = matches[0];
|
|
1786
|
+
const nodeTypes = hint.workflow.workflow.nodes.map((n) => n.type.split(".").pop()).join(", ");
|
|
1787
|
+
blocks.push({
|
|
1788
|
+
type: "text",
|
|
1789
|
+
text: `## Weak Structural Hint
|
|
1502
1790
|
A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node types: ${nodeTypes}`
|
|
1503
|
-
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1504
1793
|
}
|
|
1505
1794
|
const warnings = this.buildFailureWarnings(matches, globalFailureRates);
|
|
1506
1795
|
if (warnings) {
|
|
1507
1796
|
blocks.push({ type: "text", text: warnings });
|
|
1508
1797
|
}
|
|
1798
|
+
if (this.profile === "rich") {
|
|
1799
|
+
const expressionRules = /* @__PURE__ */ new Set([24, 25, 26]);
|
|
1800
|
+
const expressionAlreadyCovered = (this._lastActivePatterns ?? []).some((p) => expressionRules.has(p.rule));
|
|
1801
|
+
if (!expressionAlreadyCovered) {
|
|
1802
|
+
blocks.push({ type: "text", text: PROACTIVE_EXPRESSION_GUIDANCE });
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1509
1805
|
return blocks;
|
|
1510
1806
|
}
|
|
1511
1807
|
loadPatterns() {
|
|
@@ -1519,18 +1815,19 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1519
1815
|
}
|
|
1520
1816
|
}
|
|
1521
1817
|
getWarnedRules() {
|
|
1522
|
-
|
|
1818
|
+
const patterns = this._lastActivePatterns ?? this.getActivePatterns(this.resolveMaxPatterns());
|
|
1819
|
+
return patterns.map((p) => p.rule);
|
|
1523
1820
|
}
|
|
1524
|
-
getActivePatterns() {
|
|
1525
|
-
const MAX_WARNED = 10;
|
|
1821
|
+
getActivePatterns(maxCount = 10) {
|
|
1526
1822
|
const all = this.loadPatterns().filter((p) => p.state !== "resolved" && p.confidence > 0);
|
|
1527
1823
|
const regressed = all.filter((p) => p.regressed).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1528
1824
|
const confirmed = all.filter((p) => !p.regressed && p.state === "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1529
1825
|
const drafts = all.filter((p) => !p.regressed && p.state !== "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1530
|
-
return [...regressed, ...confirmed, ...drafts].slice(0,
|
|
1826
|
+
return [...regressed, ...confirmed, ...drafts].slice(0, maxCount);
|
|
1531
1827
|
}
|
|
1532
1828
|
buildFailureWarnings(matches, globalFailureRates) {
|
|
1533
|
-
const richPatterns = this.getActivePatterns();
|
|
1829
|
+
const richPatterns = this.getActivePatterns(this.resolveMaxPatterns());
|
|
1830
|
+
this._lastActivePatterns = richPatterns;
|
|
1534
1831
|
if (richPatterns.length > 0) {
|
|
1535
1832
|
return this.buildStageGroupedWarnings(richPatterns, matches);
|
|
1536
1833
|
}
|
|
@@ -1541,7 +1838,8 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1541
1838
|
credential_injection: "CREDENTIAL FORMATTING",
|
|
1542
1839
|
connection_wiring: "CONNECTION WIRING",
|
|
1543
1840
|
node_generation: "NODE GENERATION",
|
|
1544
|
-
workflow_structure: "WORKFLOW STRUCTURE"
|
|
1841
|
+
workflow_structure: "WORKFLOW STRUCTURE",
|
|
1842
|
+
expression_syntax: "EXPRESSION SYNTAX"
|
|
1545
1843
|
};
|
|
1546
1844
|
const byStage = /* @__PURE__ */ new Map();
|
|
1547
1845
|
for (const p of patterns) {
|
|
@@ -1569,7 +1867,11 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1569
1867
|
const remedy = p.mitigation ?? RULE_MITIGATIONS[p.rule];
|
|
1570
1868
|
const remedyStr = remedy ? `
|
|
1571
1869
|
Fix: ${remedy}` : "";
|
|
1572
|
-
|
|
1870
|
+
const ex = RULE_EXAMPLES[p.rule];
|
|
1871
|
+
const exampleStr = ex ? `
|
|
1872
|
+
Bad: ${ex.bad}
|
|
1873
|
+
Good: ${ex.good}` : "";
|
|
1874
|
+
lines.push(`- ${urgency}${statePrefix}Rule ${p.rule}${trendSuffix}: ${p.exampleMessages[0] ?? "No example"}${remedyStr}${exampleStr}`);
|
|
1573
1875
|
} else {
|
|
1574
1876
|
const ruleNums = group.map((p) => p.rule).join(", ");
|
|
1575
1877
|
const totalFailures = group.reduce((s, p) => s + p.failureCount, 0);
|
|
@@ -1744,6 +2046,7 @@ var PATTERN_SCHEMA_VERSION = 2;
|
|
|
1744
2046
|
var PatternAnalyzer = class _PatternAnalyzer {
|
|
1745
2047
|
telemetryDir;
|
|
1746
2048
|
outputDir;
|
|
2049
|
+
_cachedEvents = null;
|
|
1747
2050
|
constructor(telemetryDir) {
|
|
1748
2051
|
const defaultDir = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos", "telemetry");
|
|
1749
2052
|
this.telemetryDir = telemetryDir ?? defaultDir;
|
|
@@ -1772,19 +2075,23 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1772
2075
|
}));
|
|
1773
2076
|
}
|
|
1774
2077
|
if (fromVersion < 2) {
|
|
1775
|
-
migrated = migrated.map((p) =>
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
...p
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
2078
|
+
migrated = migrated.map((p) => {
|
|
2079
|
+
const sf = p.scoringFactors ?? { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 };
|
|
2080
|
+
return {
|
|
2081
|
+
...p,
|
|
2082
|
+
scoringFactors: {
|
|
2083
|
+
...sf,
|
|
2084
|
+
stickinessBoost: sf.stickinessBoost ?? sf["validationBoost"] ?? 0
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
});
|
|
1782
2088
|
}
|
|
1783
2089
|
return migrated;
|
|
1784
2090
|
}
|
|
1785
2091
|
async analyze(days = 30) {
|
|
1786
2092
|
const previousPatterns = await this.loadPreviousPatterns();
|
|
1787
2093
|
const events = await this.readAllEvents(days);
|
|
2094
|
+
this._cachedEvents = events;
|
|
1788
2095
|
const starts = events.filter((e) => e.eventType === "build_start");
|
|
1789
2096
|
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
1790
2097
|
const passed = attempts.filter(
|
|
@@ -1797,13 +2104,18 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1797
2104
|
const credentialFailures = /* @__PURE__ */ new Map();
|
|
1798
2105
|
for (const a of failed) {
|
|
1799
2106
|
const weight = this.recencyWeight(a.fileDate);
|
|
2107
|
+
const buildId = a.runId ?? a.sessionId;
|
|
1800
2108
|
const data = a.data;
|
|
1801
2109
|
for (const issue of data.issues ?? []) {
|
|
1802
|
-
|
|
2110
|
+
if (issue.severity === "warn") continue;
|
|
2111
|
+
const entry = ruleFailures.get(issue.rule) ?? { count: 0, sessions: /* @__PURE__ */ new Set(), recencyWeights: [], allMessages: [], workflowTypes: /* @__PURE__ */ new Map() };
|
|
1803
2112
|
entry.count++;
|
|
1804
|
-
entry.sessions.add(
|
|
2113
|
+
entry.sessions.add(buildId);
|
|
1805
2114
|
entry.recencyWeights.push(weight);
|
|
1806
2115
|
entry.allMessages.push(issue.message);
|
|
2116
|
+
if (data.workflowType) {
|
|
2117
|
+
entry.workflowTypes.set(data.workflowType, (entry.workflowTypes.get(data.workflowType) ?? 0) + 1);
|
|
2118
|
+
}
|
|
1807
2119
|
ruleFailures.set(issue.rule, entry);
|
|
1808
2120
|
if (issue.rule === 17) {
|
|
1809
2121
|
const credPatterns = [
|
|
@@ -1856,9 +2168,10 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1856
2168
|
}
|
|
1857
2169
|
const sessions = /* @__PURE__ */ new Map();
|
|
1858
2170
|
for (const a of attempts) {
|
|
1859
|
-
const
|
|
2171
|
+
const buildId = a.runId ?? a.sessionId;
|
|
2172
|
+
const list = sessions.get(buildId) ?? [];
|
|
1860
2173
|
list.push(a);
|
|
1861
|
-
sessions.set(
|
|
2174
|
+
sessions.set(buildId, list);
|
|
1862
2175
|
}
|
|
1863
2176
|
let firstTryPass = 0;
|
|
1864
2177
|
let correctionNeeded = 0;
|
|
@@ -1905,7 +2218,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1905
2218
|
const avgRecency = entry.recencyWeights.length > 0 ? entry.recencyWeights.reduce((s, w) => s + w, 0) / entry.recencyWeights.length : 1;
|
|
1906
2219
|
const stickiness = stickinessCount.get(rule) ?? 0;
|
|
1907
2220
|
const { compositeScore, factors } = this.computeCompositeScore(rawConfidence, entry.count, state, avgRecency, stickiness);
|
|
1908
|
-
|
|
2221
|
+
const pattern = {
|
|
1909
2222
|
rule,
|
|
1910
2223
|
failureCount: entry.count,
|
|
1911
2224
|
confidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
@@ -1917,6 +2230,10 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1917
2230
|
exampleMessages: this.deduplicateMessages(entry.allMessages),
|
|
1918
2231
|
mitigation: RULE_MITIGATIONS[rule] ?? null
|
|
1919
2232
|
};
|
|
2233
|
+
if (entry.workflowTypes.size > 0) {
|
|
2234
|
+
pattern.workflowTypeBreakdown = Object.fromEntries(entry.workflowTypes);
|
|
2235
|
+
}
|
|
2236
|
+
return pattern;
|
|
1920
2237
|
}).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1921
2238
|
const activeRules = new Set(activePatterns.map((p) => p.rule));
|
|
1922
2239
|
for (const p of activePatterns) {
|
|
@@ -1973,7 +2290,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1973
2290
|
const warned = bcData.warnedRules ?? [];
|
|
1974
2291
|
if (warned.length === 0) continue;
|
|
1975
2292
|
const sessionFailedRules = /* @__PURE__ */ new Set();
|
|
1976
|
-
const sessionAttempts = sessions.get(bc.sessionId) ?? [];
|
|
2293
|
+
const sessionAttempts = sessions.get(bc.runId ?? bc.sessionId) ?? [];
|
|
1977
2294
|
for (const a of sessionAttempts) {
|
|
1978
2295
|
const ad = a.data;
|
|
1979
2296
|
if (ad.validationPassed === false) {
|
|
@@ -2056,8 +2373,55 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
2056
2373
|
};
|
|
2057
2374
|
const historyPath = (0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl");
|
|
2058
2375
|
await (0, import_promises3.appendFile)(historyPath, JSON.stringify(historySummary) + "\n", "utf-8");
|
|
2376
|
+
const sessions = await this.buildSessionSummaries(days);
|
|
2377
|
+
const sessionHistoryPath = (0, import_node_path5.join)(this.outputDir, "session-history.json");
|
|
2378
|
+
const sessionHistoryTmp = `${sessionHistoryPath}.tmp`;
|
|
2379
|
+
await (0, import_promises3.writeFile)(sessionHistoryTmp, JSON.stringify(sessions, null, 2), "utf-8");
|
|
2380
|
+
await (0, import_promises3.rename)(sessionHistoryTmp, sessionHistoryPath);
|
|
2059
2381
|
return analysis;
|
|
2060
2382
|
}
|
|
2383
|
+
async getSessions(limit = 20) {
|
|
2384
|
+
try {
|
|
2385
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "session-history.json"), "utf-8");
|
|
2386
|
+
const all = JSON.parse(raw);
|
|
2387
|
+
return all.slice(-limit);
|
|
2388
|
+
} catch {
|
|
2389
|
+
return [];
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
async buildSessionSummaries(days = 30) {
|
|
2393
|
+
const events = this._cachedEvents ?? await this.readAllEvents(days);
|
|
2394
|
+
const buildCompletes = events.filter((e) => e.eventType === "build_complete");
|
|
2395
|
+
const attemptsByBuild = /* @__PURE__ */ new Map();
|
|
2396
|
+
for (const e of events.filter((e2) => e2.eventType === "generation_attempt")) {
|
|
2397
|
+
const buildId = e.runId ?? e.sessionId;
|
|
2398
|
+
const list = attemptsByBuild.get(buildId) ?? [];
|
|
2399
|
+
list.push(e);
|
|
2400
|
+
attemptsByBuild.set(buildId, list);
|
|
2401
|
+
}
|
|
2402
|
+
const summaries = buildCompletes.map((bc) => {
|
|
2403
|
+
const data = bc.data;
|
|
2404
|
+
const sessionAttempts = attemptsByBuild.get(bc.runId ?? bc.sessionId) ?? [];
|
|
2405
|
+
const failedRules = Array.from(new Set(
|
|
2406
|
+
sessionAttempts.flatMap((a) => {
|
|
2407
|
+
const ad = a.data;
|
|
2408
|
+
if (ad.validationPassed !== false) return [];
|
|
2409
|
+
return (ad.issues ?? []).map((i) => i.rule);
|
|
2410
|
+
})
|
|
2411
|
+
));
|
|
2412
|
+
return {
|
|
2413
|
+
sessionId: bc.sessionId,
|
|
2414
|
+
date: bc.fileDate,
|
|
2415
|
+
description: data.description ?? "",
|
|
2416
|
+
workflowType: data.workflowType ?? null,
|
|
2417
|
+
attempts: data.totalAttempts ?? 1,
|
|
2418
|
+
success: data.success ?? false,
|
|
2419
|
+
failedRules,
|
|
2420
|
+
workflowName: data.workflowName ?? null
|
|
2421
|
+
};
|
|
2422
|
+
});
|
|
2423
|
+
return summaries.sort((a, b) => a.date.localeCompare(b.date));
|
|
2424
|
+
}
|
|
2061
2425
|
async getHistory(limit = 20) {
|
|
2062
2426
|
try {
|
|
2063
2427
|
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl"), "utf-8");
|
|
@@ -2079,7 +2443,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
2079
2443
|
alerts.push({
|
|
2080
2444
|
type: "stale_pattern",
|
|
2081
2445
|
rule: p.rule,
|
|
2082
|
-
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-
|
|
2446
|
+
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-26)`
|
|
2083
2447
|
});
|
|
2084
2448
|
}
|
|
2085
2449
|
}
|
|
@@ -2211,6 +2575,43 @@ ${regularLines}`;
|
|
|
2211
2575
|
}
|
|
2212
2576
|
};
|
|
2213
2577
|
|
|
2578
|
+
// src/telemetry/collector.ts
|
|
2579
|
+
var import_promises4 = require("fs/promises");
|
|
2580
|
+
var import_node_path6 = require("path");
|
|
2581
|
+
var import_node_os5 = require("os");
|
|
2582
|
+
|
|
2583
|
+
// src/telemetry/types.ts
|
|
2584
|
+
var TELEMETRY_SCHEMA_VERSION = 2;
|
|
2585
|
+
|
|
2586
|
+
// src/telemetry/collector.ts
|
|
2587
|
+
var TelemetryCollector = class {
|
|
2588
|
+
dir;
|
|
2589
|
+
sessionId;
|
|
2590
|
+
dirReady = null;
|
|
2591
|
+
constructor(dir) {
|
|
2592
|
+
this.dir = dir ?? (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".kairos", "telemetry");
|
|
2593
|
+
this.sessionId = generateUUID();
|
|
2594
|
+
}
|
|
2595
|
+
async emit(eventType, data, runId) {
|
|
2596
|
+
const event = {
|
|
2597
|
+
schemaVersion: TELEMETRY_SCHEMA_VERSION,
|
|
2598
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2599
|
+
sessionId: this.sessionId,
|
|
2600
|
+
...runId ? { runId } : {},
|
|
2601
|
+
eventType,
|
|
2602
|
+
data
|
|
2603
|
+
};
|
|
2604
|
+
if (!this.dirReady) {
|
|
2605
|
+
this.dirReady = (0, import_promises4.mkdir)(this.dir, { recursive: true }).then(() => {
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
await this.dirReady;
|
|
2609
|
+
const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
|
|
2610
|
+
const filepath = (0, import_node_path6.join)(this.dir, filename);
|
|
2611
|
+
await (0, import_promises4.appendFile)(filepath, JSON.stringify(event) + "\n", "utf-8");
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
|
|
2214
2615
|
// src/utils/logger.ts
|
|
2215
2616
|
var nullLogger = {
|
|
2216
2617
|
debug() {
|
|
@@ -2223,19 +2624,91 @@ var nullLogger = {
|
|
|
2223
2624
|
}
|
|
2224
2625
|
};
|
|
2225
2626
|
|
|
2627
|
+
// src/utils/workflow-type.ts
|
|
2628
|
+
var TYPE_KEYWORDS = [
|
|
2629
|
+
["gmail", "email"],
|
|
2630
|
+
["imap", "email"],
|
|
2631
|
+
["smtp", "email"],
|
|
2632
|
+
[" email", "email"],
|
|
2633
|
+
["slack", "slack"],
|
|
2634
|
+
["telegram", "messaging"],
|
|
2635
|
+
["discord", "messaging"],
|
|
2636
|
+
[" sms", "messaging"],
|
|
2637
|
+
["twilio", "messaging"],
|
|
2638
|
+
["webhook", "webhook"],
|
|
2639
|
+
["google sheets", "data"],
|
|
2640
|
+
["spreadsheet", "data"],
|
|
2641
|
+
["airtable", "data"],
|
|
2642
|
+
["notion", "data"],
|
|
2643
|
+
["github", "devops"],
|
|
2644
|
+
["gitlab", "devops"],
|
|
2645
|
+
["schedule", "schedule"],
|
|
2646
|
+
[" cron", "schedule"],
|
|
2647
|
+
["daily", "schedule"],
|
|
2648
|
+
["weekly", "schedule"],
|
|
2649
|
+
["hourly", "schedule"],
|
|
2650
|
+
["every day", "schedule"],
|
|
2651
|
+
["every hour", "schedule"],
|
|
2652
|
+
["every morning", "schedule"],
|
|
2653
|
+
["postgres", "database"],
|
|
2654
|
+
["mysql", "database"],
|
|
2655
|
+
["supabase", "database"],
|
|
2656
|
+
["redis", "database"],
|
|
2657
|
+
[" database", "database"],
|
|
2658
|
+
[" llm", "ai"],
|
|
2659
|
+
[" gpt", "ai"],
|
|
2660
|
+
["claude", "ai"],
|
|
2661
|
+
[" agent", "ai"],
|
|
2662
|
+
["langchain", "ai"],
|
|
2663
|
+
[" ai ", "ai"],
|
|
2664
|
+
[" ai", "ai"],
|
|
2665
|
+
["http request", "api"],
|
|
2666
|
+
["rest api", "api"],
|
|
2667
|
+
[" api", "api"]
|
|
2668
|
+
];
|
|
2669
|
+
function inferWorkflowType(description) {
|
|
2670
|
+
const lower = " " + description.toLowerCase();
|
|
2671
|
+
for (const [keyword, type] of TYPE_KEYWORDS) {
|
|
2672
|
+
if (lower.includes(keyword)) return type;
|
|
2673
|
+
}
|
|
2674
|
+
return null;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2226
2677
|
// src/mcp-server.ts
|
|
2227
2678
|
var import_node_fs3 = require("fs");
|
|
2228
|
-
var
|
|
2679
|
+
var import_node_path7 = require("path");
|
|
2680
|
+
var import_node_os6 = require("os");
|
|
2229
2681
|
var import_node_url = require("url");
|
|
2230
2682
|
var import_meta = {};
|
|
2231
|
-
var __dirname = (0,
|
|
2232
|
-
var pkg = JSON.parse((0, import_node_fs3.readFileSync)((0,
|
|
2683
|
+
var __dirname = (0, import_node_path7.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
2684
|
+
var pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path7.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
2233
2685
|
var library = new FileLibrary();
|
|
2234
2686
|
var validator = new N8nValidator();
|
|
2235
2687
|
var nodeSyncer = new NodeSyncer();
|
|
2236
2688
|
var lastSync = null;
|
|
2237
2689
|
var stripper = new N8nFieldStripper();
|
|
2238
|
-
var promptBuilder = new PromptBuilder();
|
|
2690
|
+
var promptBuilder = new PromptBuilder(getMcpPatternsPath());
|
|
2691
|
+
function getMcpTelemetry() {
|
|
2692
|
+
const val = process.env["KAIROS_TELEMETRY"];
|
|
2693
|
+
if (!val || val === "false") return null;
|
|
2694
|
+
return val === "true" ? new TelemetryCollector() : new TelemetryCollector(val);
|
|
2695
|
+
}
|
|
2696
|
+
function getMcpPatternsPath() {
|
|
2697
|
+
const val = process.env["KAIROS_TELEMETRY"];
|
|
2698
|
+
if (val && val !== "false" && val !== "true") {
|
|
2699
|
+
return (0, import_node_path7.join)(val, "..", "patterns.json");
|
|
2700
|
+
}
|
|
2701
|
+
return (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".kairos", "patterns.json");
|
|
2702
|
+
}
|
|
2703
|
+
var mcpTelemetry = getMcpTelemetry();
|
|
2704
|
+
var mcpSessions = /* @__PURE__ */ new Map();
|
|
2705
|
+
var SESSION_TTL_MS = 60 * 60 * 1e3;
|
|
2706
|
+
function evictStaleSessions() {
|
|
2707
|
+
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
2708
|
+
for (const [id, session] of mcpSessions) {
|
|
2709
|
+
if (session.startTime < cutoff) mcpSessions.delete(id);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2239
2712
|
function getTelemetryReader() {
|
|
2240
2713
|
try {
|
|
2241
2714
|
return new TelemetryReader();
|
|
@@ -2283,6 +2756,7 @@ server.tool(
|
|
|
2283
2756
|
name: import_zod.z.string().optional().describe("Optional workflow name override")
|
|
2284
2757
|
},
|
|
2285
2758
|
async ({ description, name }) => {
|
|
2759
|
+
evictStaleSessions();
|
|
2286
2760
|
const baseUrl = process.env["N8N_BASE_URL"];
|
|
2287
2761
|
const apiKey = process.env["N8N_API_KEY"];
|
|
2288
2762
|
if (!baseUrl || !apiKey) {
|
|
@@ -2294,6 +2768,8 @@ server.tool(
|
|
|
2294
2768
|
isError: true
|
|
2295
2769
|
};
|
|
2296
2770
|
}
|
|
2771
|
+
const runId = generateUUID();
|
|
2772
|
+
const workflowType = inferWorkflowType(description);
|
|
2297
2773
|
await library.initialize();
|
|
2298
2774
|
const syncResult = await autoSync();
|
|
2299
2775
|
const matches = await library.search(description);
|
|
@@ -2301,11 +2777,22 @@ server.tool(
|
|
|
2301
2777
|
const failureRates = await telemetryReader?.getFailureRates() ?? [];
|
|
2302
2778
|
const request = { description, ...name ? { name } : {} };
|
|
2303
2779
|
const built = promptBuilder.build(request, matches, failureRates, syncResult?.catalogText);
|
|
2780
|
+
if (mcpTelemetry) {
|
|
2781
|
+
mcpSessions.set(runId, {
|
|
2782
|
+
description,
|
|
2783
|
+
startTime: Date.now(),
|
|
2784
|
+
validateAttempts: 0,
|
|
2785
|
+
warnedRules: promptBuilder.getWarnedRules(),
|
|
2786
|
+
workflowType
|
|
2787
|
+
});
|
|
2788
|
+
await mcpTelemetry.emit("build_start", { description, model: "mcp-decomposed", dryRun: false }, runId);
|
|
2789
|
+
}
|
|
2304
2790
|
const systemText = built.system.map((block) => block.text).join("\n\n---\n\n");
|
|
2305
2791
|
return {
|
|
2306
2792
|
content: [{
|
|
2307
2793
|
type: "text",
|
|
2308
2794
|
text: JSON.stringify({
|
|
2795
|
+
kairos_run_id: runId,
|
|
2309
2796
|
mode: built.mode,
|
|
2310
2797
|
matchCount: matches.length,
|
|
2311
2798
|
topMatchScore: matches[0]?.score ?? null,
|
|
@@ -2337,11 +2824,12 @@ server.tool(
|
|
|
2337
2824
|
);
|
|
2338
2825
|
server.tool(
|
|
2339
2826
|
"kairos_validate",
|
|
2340
|
-
"Validate n8n workflow JSON against
|
|
2827
|
+
"Validate n8n workflow JSON against 26 structural rules. Returns pass/fail with specific issues. If validation fails, fix the issues and call this again. Errors block deployment; warnings are advisory.",
|
|
2341
2828
|
{
|
|
2342
|
-
workflow: import_zod.z.string().describe("The workflow JSON string to validate")
|
|
2829
|
+
workflow: import_zod.z.string().describe("The workflow JSON string to validate"),
|
|
2830
|
+
kairos_run_id: import_zod.z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation")
|
|
2343
2831
|
},
|
|
2344
|
-
async ({ workflow: workflowStr }) => {
|
|
2832
|
+
async ({ workflow: workflowStr, kairos_run_id }) => {
|
|
2345
2833
|
let parsed;
|
|
2346
2834
|
try {
|
|
2347
2835
|
parsed = JSON.parse(workflowStr);
|
|
@@ -2359,6 +2847,24 @@ server.tool(
|
|
|
2359
2847
|
const result = validator.validate(parsed);
|
|
2360
2848
|
const errors = result.issues.filter((i) => i.severity === "error");
|
|
2361
2849
|
const warnings = result.issues.filter((i) => i.severity === "warn");
|
|
2850
|
+
if (mcpTelemetry && kairos_run_id) {
|
|
2851
|
+
const session = mcpSessions.get(kairos_run_id);
|
|
2852
|
+
if (session) {
|
|
2853
|
+
session.validateAttempts++;
|
|
2854
|
+
await mcpTelemetry.emit("generation_attempt", {
|
|
2855
|
+
description: session.description,
|
|
2856
|
+
attempt: session.validateAttempts,
|
|
2857
|
+
temperature: 0,
|
|
2858
|
+
durationMs: 0,
|
|
2859
|
+
tokensInput: 0,
|
|
2860
|
+
tokensOutput: 0,
|
|
2861
|
+
validationPassed: result.valid,
|
|
2862
|
+
issueCount: result.issues.length,
|
|
2863
|
+
issues: result.issues.map((i) => ({ rule: i.rule, severity: i.severity, message: i.message, nodeId: i.nodeId ?? null })),
|
|
2864
|
+
workflowType: session.workflowType
|
|
2865
|
+
}, kairos_run_id);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2362
2868
|
return {
|
|
2363
2869
|
content: [{
|
|
2364
2870
|
type: "text",
|
|
@@ -2387,9 +2893,10 @@ server.tool(
|
|
|
2387
2893
|
"Deploy a validated workflow to n8n. Pass the workflow JSON that passed kairos_validate. Strips server-assigned fields automatically. Requires N8N_BASE_URL and N8N_API_KEY.",
|
|
2388
2894
|
{
|
|
2389
2895
|
workflow: import_zod.z.string().describe("The validated workflow JSON string to deploy"),
|
|
2390
|
-
activate: import_zod.z.boolean().default(false).describe("Activate the workflow immediately after deployment")
|
|
2896
|
+
activate: import_zod.z.boolean().default(false).describe("Activate the workflow immediately after deployment"),
|
|
2897
|
+
kairos_run_id: import_zod.z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation")
|
|
2391
2898
|
},
|
|
2392
|
-
async ({ workflow: workflowStr, activate }) => {
|
|
2899
|
+
async ({ workflow: workflowStr, activate, kairos_run_id }) => {
|
|
2393
2900
|
if (!isAllowed("deploy")) {
|
|
2394
2901
|
return {
|
|
2395
2902
|
content: [{
|
|
@@ -2449,6 +2956,28 @@ server.tool(
|
|
|
2449
2956
|
generationMode: "scratch",
|
|
2450
2957
|
generationAttempts: 1
|
|
2451
2958
|
});
|
|
2959
|
+
if (mcpTelemetry && kairos_run_id) {
|
|
2960
|
+
const session = mcpSessions.get(kairos_run_id);
|
|
2961
|
+
if (session) {
|
|
2962
|
+
await mcpTelemetry.emit("build_complete", {
|
|
2963
|
+
description: session.description,
|
|
2964
|
+
success: true,
|
|
2965
|
+
totalAttempts: session.validateAttempts,
|
|
2966
|
+
totalDurationMs: Date.now() - session.startTime,
|
|
2967
|
+
totalTokensInput: 0,
|
|
2968
|
+
totalTokensOutput: 0,
|
|
2969
|
+
workflowName: response.name,
|
|
2970
|
+
workflowId: response.id,
|
|
2971
|
+
dryRun: false,
|
|
2972
|
+
credentialsNeeded: 0,
|
|
2973
|
+
warnedRules: session.warnedRules,
|
|
2974
|
+
workflowType: session.workflowType
|
|
2975
|
+
}, kairos_run_id);
|
|
2976
|
+
mcpSessions.delete(kairos_run_id);
|
|
2977
|
+
PatternAnalyzer.fromEnv().analyzeAndSave().catch(() => {
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2452
2981
|
return {
|
|
2453
2982
|
content: [{
|
|
2454
2983
|
type: "text",
|