@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/cli.cjs
CHANGED
|
@@ -524,6 +524,9 @@ var N8nValidator = class {
|
|
|
524
524
|
this.checkRule21(workflow, issues);
|
|
525
525
|
this.checkRule22(workflow, issues);
|
|
526
526
|
this.checkRule23(workflow, issues);
|
|
527
|
+
this.checkRule24(workflow, issues);
|
|
528
|
+
this.checkRule25(workflow, issues);
|
|
529
|
+
this.checkRule26(workflow, issues);
|
|
527
530
|
if (Array.isArray(workflow.nodes)) {
|
|
528
531
|
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
529
532
|
for (const issue of issues) {
|
|
@@ -656,10 +659,14 @@ var N8nValidator = class {
|
|
|
656
659
|
checkRule11(w, issues) {
|
|
657
660
|
if (!Array.isArray(w.nodes) || typeof w.connections !== "object" || w.connections === null) return;
|
|
658
661
|
const reachable = /* @__PURE__ */ new Set();
|
|
659
|
-
|
|
662
|
+
const aiSubNodeSources = /* @__PURE__ */ new Set();
|
|
663
|
+
for (const [sourceName, outputs] of Object.entries(w.connections)) {
|
|
660
664
|
if (typeof outputs !== "object" || outputs === null) continue;
|
|
661
|
-
|
|
665
|
+
let hasAiPort = false;
|
|
666
|
+
for (const [portName, portGroup] of Object.entries(outputs)) {
|
|
662
667
|
if (!Array.isArray(portGroup)) continue;
|
|
668
|
+
const isAiPort = portName.startsWith("ai_");
|
|
669
|
+
if (isAiPort) hasAiPort = true;
|
|
663
670
|
for (const targets of portGroup) {
|
|
664
671
|
if (!Array.isArray(targets)) continue;
|
|
665
672
|
for (const target of targets) {
|
|
@@ -668,10 +675,13 @@ var N8nValidator = class {
|
|
|
668
675
|
}
|
|
669
676
|
}
|
|
670
677
|
}
|
|
678
|
+
if (hasAiPort) aiSubNodeSources.add(sourceName);
|
|
671
679
|
}
|
|
672
680
|
for (const node of w.nodes) {
|
|
673
681
|
if (node.type.includes("stickyNote")) continue;
|
|
674
|
-
if (
|
|
682
|
+
if (this.isTriggerNode(node)) continue;
|
|
683
|
+
if (aiSubNodeSources.has(node.name)) continue;
|
|
684
|
+
if (!reachable.has(node.name)) {
|
|
675
685
|
this.warn(issues, 11, `Node "${node.name}" has no incoming connections and may never execute`, node.id);
|
|
676
686
|
}
|
|
677
687
|
}
|
|
@@ -867,6 +877,76 @@ var N8nValidator = class {
|
|
|
867
877
|
}
|
|
868
878
|
}
|
|
869
879
|
}
|
|
880
|
+
// Rule 24 (WARN): deprecated accessor syntax in expressions
|
|
881
|
+
checkRule24(w, issues) {
|
|
882
|
+
if (!Array.isArray(w.nodes)) return;
|
|
883
|
+
const deprecated = /\$node\s*\[/;
|
|
884
|
+
for (const node of w.nodes) {
|
|
885
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
886
|
+
if (deprecated.test(expr)) {
|
|
887
|
+
this.warn(
|
|
888
|
+
issues,
|
|
889
|
+
24,
|
|
890
|
+
`Node "${node.name}" uses deprecated accessor $node["..."] \u2014 use $('NodeName').item.json.field instead`,
|
|
891
|
+
node.id
|
|
892
|
+
);
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
// Rule 25 (WARN): wrong item index assumptions in expressions
|
|
899
|
+
checkRule25(w, issues) {
|
|
900
|
+
if (!Array.isArray(w.nodes)) return;
|
|
901
|
+
const itemIndex = /\$json\s*\.\s*items\s*\[/;
|
|
902
|
+
for (const node of w.nodes) {
|
|
903
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
904
|
+
if (itemIndex.test(expr)) {
|
|
905
|
+
this.warn(
|
|
906
|
+
issues,
|
|
907
|
+
25,
|
|
908
|
+
`Node "${node.name}" accesses $json.items[n] \u2014 n8n flattens items automatically, use $json.field directly`,
|
|
909
|
+
node.id
|
|
910
|
+
);
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
// Rule 26 (WARN): missing .first() or .all() on node references
|
|
917
|
+
checkRule26(w, issues) {
|
|
918
|
+
if (!Array.isArray(w.nodes)) return;
|
|
919
|
+
const bareRef = /\$\(\s*'[^']+'\s*\)\s*\.json/;
|
|
920
|
+
for (const node of w.nodes) {
|
|
921
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
922
|
+
if (bareRef.test(expr)) {
|
|
923
|
+
this.warn(
|
|
924
|
+
issues,
|
|
925
|
+
26,
|
|
926
|
+
`Node "${node.name}" references $('NodeName').json without .first() or .all() \u2014 use $('NodeName').first().json.field`,
|
|
927
|
+
node.id
|
|
928
|
+
);
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
extractExpressions(params) {
|
|
935
|
+
const expressions = [];
|
|
936
|
+
const walk = (val) => {
|
|
937
|
+
if (typeof val === "string") {
|
|
938
|
+
if (val.includes("={{") || val.includes("$node") || val.includes("$('")) {
|
|
939
|
+
expressions.push(val);
|
|
940
|
+
}
|
|
941
|
+
} else if (Array.isArray(val)) {
|
|
942
|
+
for (const item of val) walk(item);
|
|
943
|
+
} else if (val !== null && typeof val === "object") {
|
|
944
|
+
for (const v of Object.values(val)) walk(v);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
walk(params);
|
|
948
|
+
return expressions;
|
|
949
|
+
}
|
|
870
950
|
// Rule 21 (WARN): webhook with responseMode="responseNode" must have respondToWebhook node
|
|
871
951
|
checkRule21(w, issues) {
|
|
872
952
|
if (!Array.isArray(w.nodes)) return;
|
|
@@ -954,9 +1034,11 @@ id, active, createdAt, updatedAt, versionId, meta, isArchived, activeVersionId,
|
|
|
954
1034
|
- Never reuse IDs, never use sequential fake IDs like "node-1"
|
|
955
1035
|
|
|
956
1036
|
### Credentials:
|
|
957
|
-
-
|
|
958
|
-
|
|
959
|
-
-
|
|
1037
|
+
- Each credential is keyed by its type string, with an object value containing id and name:
|
|
1038
|
+
"credentials": { "slackOAuth2Api": { "id": "placeholder-id", "name": "My Slack Credential" } }
|
|
1039
|
+
- Use "placeholder-id" as the id \u2014 users replace this with their real credential ID from n8n after deployment
|
|
1040
|
+
- The credentialsNeeded field in your response declares what credentials the user must configure
|
|
1041
|
+
- Never put API keys or tokens directly in node parameters when a credential type exists
|
|
960
1042
|
|
|
961
1043
|
### Node names:
|
|
962
1044
|
- All node names must be unique within the workflow
|
|
@@ -1003,6 +1085,23 @@ Node parameters like conditions, assignments, and rule intervals MUST include al
|
|
|
1003
1085
|
|
|
1004
1086
|
---
|
|
1005
1087
|
|
|
1088
|
+
## EXPRESSION SYNTAX \u2014 how to reference upstream node data
|
|
1089
|
+
|
|
1090
|
+
### Accessing a field from an upstream node:
|
|
1091
|
+
- CORRECT: $('NodeName').item.json.field
|
|
1092
|
+
- WRONG: $node["NodeName"].json.field \u2190 deprecated accessor, fails at runtime (Rule 24)
|
|
1093
|
+
|
|
1094
|
+
### Accessing array items from $json:
|
|
1095
|
+
- CORRECT: $json.field \u2190 n8n auto-flattens items; each item is already a flat object
|
|
1096
|
+
- WRONG: $json.items[0].field \u2190 do not index into items[] (Rule 25)
|
|
1097
|
+
|
|
1098
|
+
### Calling node data \u2014 always qualify with .first() or .all():
|
|
1099
|
+
- CORRECT: $('NodeName').first().json.field \u2190 single item
|
|
1100
|
+
- CORRECT: $('NodeName').all() \u2190 array of all items
|
|
1101
|
+
- WRONG: $('NodeName').json \u2190 throws at runtime without .first() or .all() (Rule 26)
|
|
1102
|
+
|
|
1103
|
+
---
|
|
1104
|
+
|
|
1006
1105
|
## NODE CATALOG \u2014 exact type strings and safe typeVersions
|
|
1007
1106
|
|
|
1008
1107
|
### Triggers (always at least one required):
|
|
@@ -1102,6 +1201,9 @@ Cron: { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 *
|
|
|
1102
1201
|
5. At least one trigger node present
|
|
1103
1202
|
6. Every AI Agent has an ai_languageModel sub-node
|
|
1104
1203
|
7. settings block is complete with executionOrder: "v1"
|
|
1204
|
+
8. No deprecated $node["NodeName"].json \u2014 use $('NodeName').item.json.field
|
|
1205
|
+
9. No $json.items[0] array indexing \u2014 access fields directly as $json.field
|
|
1206
|
+
10. No bare $('NodeName').json \u2014 always use .first().json.field or .all()
|
|
1105
1207
|
|
|
1106
1208
|
---
|
|
1107
1209
|
|
|
@@ -1118,7 +1220,7 @@ function scoreToMode(score) {
|
|
|
1118
1220
|
}
|
|
1119
1221
|
|
|
1120
1222
|
// src/validation/rule-metadata.ts
|
|
1121
|
-
var VALIDATOR_RULE_IDS = Array.from({ length:
|
|
1223
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 26 }, (_, i) => i + 1);
|
|
1122
1224
|
var RULE_PIPELINE_STAGES = {
|
|
1123
1225
|
1: "node_generation",
|
|
1124
1226
|
2: "node_generation",
|
|
@@ -1142,7 +1244,28 @@ var RULE_PIPELINE_STAGES = {
|
|
|
1142
1244
|
20: "connection_wiring",
|
|
1143
1245
|
21: "workflow_structure",
|
|
1144
1246
|
22: "workflow_structure",
|
|
1145
|
-
23: "node_generation"
|
|
1247
|
+
23: "node_generation",
|
|
1248
|
+
24: "expression_syntax",
|
|
1249
|
+
25: "expression_syntax",
|
|
1250
|
+
26: "expression_syntax"
|
|
1251
|
+
};
|
|
1252
|
+
var RULE_EXAMPLES = {
|
|
1253
|
+
17: {
|
|
1254
|
+
bad: '"credentials": { "slackOAuth2Api": "my-token" }',
|
|
1255
|
+
good: '"credentials": { "slackOAuth2Api": { "id": "placeholder-id", "name": "My Slack OAuth" } }'
|
|
1256
|
+
},
|
|
1257
|
+
24: {
|
|
1258
|
+
bad: '$node["Fetch Data"].json.email',
|
|
1259
|
+
good: "$('Fetch Data').item.json.email"
|
|
1260
|
+
},
|
|
1261
|
+
25: {
|
|
1262
|
+
bad: "$json.items[0].email",
|
|
1263
|
+
good: "$json.email"
|
|
1264
|
+
},
|
|
1265
|
+
26: {
|
|
1266
|
+
bad: "$('Fetch Data').json.email",
|
|
1267
|
+
good: "$('Fetch Data').first().json.email"
|
|
1268
|
+
}
|
|
1146
1269
|
};
|
|
1147
1270
|
var RULE_MITIGATIONS = {
|
|
1148
1271
|
1: "Provide a non-empty workflow name string",
|
|
@@ -1161,21 +1284,44 @@ var RULE_MITIGATIONS = {
|
|
|
1161
1284
|
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
1162
1285
|
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
1163
1286
|
16: "All node names must be unique within the workflow",
|
|
1164
|
-
17: '
|
|
1287
|
+
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',
|
|
1165
1288
|
18: "AI sub-nodes (languageModel, memory, tool) must be the CONNECTION SOURCE pointing TO the agent \u2014 not the reverse",
|
|
1166
1289
|
19: "Use known safe typeVersion values for each node type",
|
|
1167
1290
|
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
1168
1291
|
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
1169
1292
|
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1170
|
-
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync"
|
|
1293
|
+
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync",
|
|
1294
|
+
24: 'Use modern accessor syntax: $("NodeName").item.json.field instead of deprecated $node["NodeName"].json.field',
|
|
1295
|
+
25: "Access item fields directly with $json.field \u2014 n8n flattens items automatically, do not use $json.items[0]",
|
|
1296
|
+
26: 'Use $("NodeName").first().json.field or $("NodeName").all() \u2014 bare $("NodeName").json without .first() or .all() throws at runtime'
|
|
1171
1297
|
};
|
|
1172
1298
|
|
|
1173
1299
|
// src/generation/prompt-builder.ts
|
|
1174
1300
|
var CRITICAL_SCORE_THRESHOLD = 0.15;
|
|
1301
|
+
function resolveProfile() {
|
|
1302
|
+
const env = process.env["KAIROS_PROMPT_PROFILE"];
|
|
1303
|
+
if (env === "minimal" || env === "standard" || env === "rich") return env;
|
|
1304
|
+
return "standard";
|
|
1305
|
+
}
|
|
1306
|
+
var PROACTIVE_EXPRESSION_GUIDANCE = `## Expression Syntax Quick Reference
|
|
1307
|
+
|
|
1308
|
+
Always use these patterns in expressions:
|
|
1309
|
+
- Access node data: $('NodeName').item.json.field (not $node["NodeName"].json)
|
|
1310
|
+
- Access JSON field: $json.field (not $json.items[0].field)
|
|
1311
|
+
- Single item: $('NodeName').first().json.field
|
|
1312
|
+
- All items: $('NodeName').all()`;
|
|
1175
1313
|
var PromptBuilder = class {
|
|
1176
1314
|
patternsPath;
|
|
1177
|
-
|
|
1315
|
+
profile;
|
|
1316
|
+
_lastActivePatterns = null;
|
|
1317
|
+
constructor(patternsPath, profile) {
|
|
1178
1318
|
this.patternsPath = patternsPath ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".kairos", "patterns.json");
|
|
1319
|
+
this.profile = profile ?? resolveProfile();
|
|
1320
|
+
}
|
|
1321
|
+
resolveMaxPatterns() {
|
|
1322
|
+
if (this.profile === "minimal") return 3;
|
|
1323
|
+
if (this.profile === "rich") return 15;
|
|
1324
|
+
return 10;
|
|
1179
1325
|
}
|
|
1180
1326
|
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
1181
1327
|
const mode = this.resolveMode(matches);
|
|
@@ -1213,53 +1359,62 @@ Fix ALL of the above issues in your new response. Do not repeat any of these mis
|
|
|
1213
1359
|
cache_control: { type: "ephemeral" }
|
|
1214
1360
|
}
|
|
1215
1361
|
];
|
|
1216
|
-
if (
|
|
1217
|
-
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1362
|
+
if (this.profile !== "minimal") {
|
|
1363
|
+
if (mode === "reference" && matches.length > 0) {
|
|
1364
|
+
const refText = matches.slice(0, 3).map((m) => {
|
|
1365
|
+
const nodes = m.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1366
|
+
return `Reference workflow: "${m.workflow.description}" (similarity: ${m.score.toFixed(2)})
|
|
1220
1367
|
Nodes:
|
|
1221
1368
|
${nodes}`;
|
|
1222
|
-
|
|
1223
|
-
blocks.push({
|
|
1224
|
-
type: "text",
|
|
1225
|
-
text: `## Similar Workflows From Library (for reference only \u2014 adapt, do not copy verbatim)
|
|
1226
|
-
|
|
1227
|
-
${refText}`
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
if (mode === "direct" && matches[0]) {
|
|
1231
|
-
const match = matches[0];
|
|
1232
|
-
const json = JSON.stringify(match.workflow.workflow, null, 2);
|
|
1233
|
-
if (json.length > 3e4) {
|
|
1234
|
-
const nodes = match.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1369
|
+
}).join("\n\n");
|
|
1235
1370
|
blocks.push({
|
|
1236
1371
|
type: "text",
|
|
1237
|
-
text: `##
|
|
1372
|
+
text: `## Similar Workflows From Library (for reference only \u2014 adapt, do not copy verbatim)
|
|
1373
|
+
|
|
1374
|
+
${refText}`
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
if (mode === "direct" && matches[0]) {
|
|
1378
|
+
const match = matches[0];
|
|
1379
|
+
const json = JSON.stringify(match.workflow.workflow, null, 2);
|
|
1380
|
+
if (json.length > 3e4) {
|
|
1381
|
+
const nodes = match.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1382
|
+
blocks.push({
|
|
1383
|
+
type: "text",
|
|
1384
|
+
text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 too large for full JSON, using reference:
|
|
1238
1385
|
Nodes:
|
|
1239
1386
|
${nodes}`
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1387
|
+
});
|
|
1388
|
+
} else {
|
|
1389
|
+
blocks.push({
|
|
1390
|
+
type: "text",
|
|
1391
|
+
text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 adapt this structure:
|
|
1245
1392
|
|
|
1246
1393
|
${json}`
|
|
1247
|
-
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1248
1396
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
text: `## Weak Structural Hint
|
|
1397
|
+
if (mode === "scratch" && matches.length > 0 && matches[0].score >= 0.4) {
|
|
1398
|
+
const hint = matches[0];
|
|
1399
|
+
const nodeTypes = hint.workflow.workflow.nodes.map((n) => n.type.split(".").pop()).join(", ");
|
|
1400
|
+
blocks.push({
|
|
1401
|
+
type: "text",
|
|
1402
|
+
text: `## Weak Structural Hint
|
|
1256
1403
|
A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node types: ${nodeTypes}`
|
|
1257
|
-
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1258
1406
|
}
|
|
1259
1407
|
const warnings = this.buildFailureWarnings(matches, globalFailureRates);
|
|
1260
1408
|
if (warnings) {
|
|
1261
1409
|
blocks.push({ type: "text", text: warnings });
|
|
1262
1410
|
}
|
|
1411
|
+
if (this.profile === "rich") {
|
|
1412
|
+
const expressionRules = /* @__PURE__ */ new Set([24, 25, 26]);
|
|
1413
|
+
const expressionAlreadyCovered = (this._lastActivePatterns ?? []).some((p) => expressionRules.has(p.rule));
|
|
1414
|
+
if (!expressionAlreadyCovered) {
|
|
1415
|
+
blocks.push({ type: "text", text: PROACTIVE_EXPRESSION_GUIDANCE });
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1263
1418
|
return blocks;
|
|
1264
1419
|
}
|
|
1265
1420
|
loadPatterns() {
|
|
@@ -1273,18 +1428,19 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1273
1428
|
}
|
|
1274
1429
|
}
|
|
1275
1430
|
getWarnedRules() {
|
|
1276
|
-
|
|
1431
|
+
const patterns = this._lastActivePatterns ?? this.getActivePatterns(this.resolveMaxPatterns());
|
|
1432
|
+
return patterns.map((p) => p.rule);
|
|
1277
1433
|
}
|
|
1278
|
-
getActivePatterns() {
|
|
1279
|
-
const MAX_WARNED = 10;
|
|
1434
|
+
getActivePatterns(maxCount = 10) {
|
|
1280
1435
|
const all = this.loadPatterns().filter((p) => p.state !== "resolved" && p.confidence > 0);
|
|
1281
1436
|
const regressed = all.filter((p) => p.regressed).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1282
1437
|
const confirmed = all.filter((p) => !p.regressed && p.state === "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1283
1438
|
const drafts = all.filter((p) => !p.regressed && p.state !== "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1284
|
-
return [...regressed, ...confirmed, ...drafts].slice(0,
|
|
1439
|
+
return [...regressed, ...confirmed, ...drafts].slice(0, maxCount);
|
|
1285
1440
|
}
|
|
1286
1441
|
buildFailureWarnings(matches, globalFailureRates) {
|
|
1287
|
-
const richPatterns = this.getActivePatterns();
|
|
1442
|
+
const richPatterns = this.getActivePatterns(this.resolveMaxPatterns());
|
|
1443
|
+
this._lastActivePatterns = richPatterns;
|
|
1288
1444
|
if (richPatterns.length > 0) {
|
|
1289
1445
|
return this.buildStageGroupedWarnings(richPatterns, matches);
|
|
1290
1446
|
}
|
|
@@ -1295,7 +1451,8 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1295
1451
|
credential_injection: "CREDENTIAL FORMATTING",
|
|
1296
1452
|
connection_wiring: "CONNECTION WIRING",
|
|
1297
1453
|
node_generation: "NODE GENERATION",
|
|
1298
|
-
workflow_structure: "WORKFLOW STRUCTURE"
|
|
1454
|
+
workflow_structure: "WORKFLOW STRUCTURE",
|
|
1455
|
+
expression_syntax: "EXPRESSION SYNTAX"
|
|
1299
1456
|
};
|
|
1300
1457
|
const byStage = /* @__PURE__ */ new Map();
|
|
1301
1458
|
for (const p of patterns) {
|
|
@@ -1323,7 +1480,11 @@ A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node typ
|
|
|
1323
1480
|
const remedy = p.mitigation ?? RULE_MITIGATIONS[p.rule];
|
|
1324
1481
|
const remedyStr = remedy ? `
|
|
1325
1482
|
Fix: ${remedy}` : "";
|
|
1326
|
-
|
|
1483
|
+
const ex = RULE_EXAMPLES[p.rule];
|
|
1484
|
+
const exampleStr = ex ? `
|
|
1485
|
+
Bad: ${ex.bad}
|
|
1486
|
+
Good: ${ex.good}` : "";
|
|
1487
|
+
lines.push(`- ${urgency}${statePrefix}Rule ${p.rule}${trendSuffix}: ${p.exampleMessages[0] ?? "No example"}${remedyStr}${exampleStr}`);
|
|
1327
1488
|
} else {
|
|
1328
1489
|
const ruleNums = group.map((p) => p.rule).join(", ");
|
|
1329
1490
|
const totalFailures = group.reduce((s, p) => s + p.failureCount, 0);
|
|
@@ -1430,12 +1591,12 @@ var GENERATE_WORKFLOW_TOOL = {
|
|
|
1430
1591
|
}
|
|
1431
1592
|
};
|
|
1432
1593
|
var WorkflowDesigner = class {
|
|
1433
|
-
constructor(anthropic, model, logger) {
|
|
1594
|
+
constructor(anthropic, model, logger, patternsPath) {
|
|
1434
1595
|
this.anthropic = anthropic;
|
|
1435
1596
|
this.model = model;
|
|
1436
1597
|
this.logger = logger;
|
|
1437
1598
|
this.validator = new N8nValidator();
|
|
1438
|
-
this.promptBuilder = new PromptBuilder();
|
|
1599
|
+
this.promptBuilder = new PromptBuilder(patternsPath);
|
|
1439
1600
|
}
|
|
1440
1601
|
anthropic;
|
|
1441
1602
|
model;
|
|
@@ -1519,6 +1680,11 @@ var WorkflowDesigner = class {
|
|
|
1519
1680
|
}
|
|
1520
1681
|
}
|
|
1521
1682
|
extractToolUse(message) {
|
|
1683
|
+
if (message.stop_reason === "max_tokens") {
|
|
1684
|
+
throw new GenerationError(
|
|
1685
|
+
"Claude response was truncated (max_tokens reached) \u2014 the workflow may be too large. Try a simpler description or break it into smaller workflows."
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1522
1688
|
const toolUseBlock = message.content.find(
|
|
1523
1689
|
(block) => block.type === "tool_use"
|
|
1524
1690
|
);
|
|
@@ -1561,11 +1727,12 @@ var TelemetryCollector = class {
|
|
|
1561
1727
|
this.dir = dir ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".kairos", "telemetry");
|
|
1562
1728
|
this.sessionId = generateUUID();
|
|
1563
1729
|
}
|
|
1564
|
-
async emit(eventType, data) {
|
|
1730
|
+
async emit(eventType, data, runId) {
|
|
1565
1731
|
const event = {
|
|
1566
1732
|
schemaVersion: TELEMETRY_SCHEMA_VERSION,
|
|
1567
1733
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1568
1734
|
sessionId: this.sessionId,
|
|
1735
|
+
...runId ? { runId } : {},
|
|
1569
1736
|
eventType,
|
|
1570
1737
|
data
|
|
1571
1738
|
};
|
|
@@ -1691,6 +1858,7 @@ var PATTERN_SCHEMA_VERSION = 2;
|
|
|
1691
1858
|
var PatternAnalyzer = class _PatternAnalyzer {
|
|
1692
1859
|
telemetryDir;
|
|
1693
1860
|
outputDir;
|
|
1861
|
+
_cachedEvents = null;
|
|
1694
1862
|
constructor(telemetryDir) {
|
|
1695
1863
|
const defaultDir = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".kairos", "telemetry");
|
|
1696
1864
|
this.telemetryDir = telemetryDir ?? defaultDir;
|
|
@@ -1719,19 +1887,23 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1719
1887
|
}));
|
|
1720
1888
|
}
|
|
1721
1889
|
if (fromVersion < 2) {
|
|
1722
|
-
migrated = migrated.map((p) =>
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
...p
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1890
|
+
migrated = migrated.map((p) => {
|
|
1891
|
+
const sf = p.scoringFactors ?? { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 };
|
|
1892
|
+
return {
|
|
1893
|
+
...p,
|
|
1894
|
+
scoringFactors: {
|
|
1895
|
+
...sf,
|
|
1896
|
+
stickinessBoost: sf.stickinessBoost ?? sf["validationBoost"] ?? 0
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
});
|
|
1729
1900
|
}
|
|
1730
1901
|
return migrated;
|
|
1731
1902
|
}
|
|
1732
1903
|
async analyze(days = 30) {
|
|
1733
1904
|
const previousPatterns = await this.loadPreviousPatterns();
|
|
1734
1905
|
const events = await this.readAllEvents(days);
|
|
1906
|
+
this._cachedEvents = events;
|
|
1735
1907
|
const starts = events.filter((e) => e.eventType === "build_start");
|
|
1736
1908
|
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
1737
1909
|
const passed = attempts.filter(
|
|
@@ -1744,13 +1916,18 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1744
1916
|
const credentialFailures = /* @__PURE__ */ new Map();
|
|
1745
1917
|
for (const a of failed) {
|
|
1746
1918
|
const weight = this.recencyWeight(a.fileDate);
|
|
1919
|
+
const buildId = a.runId ?? a.sessionId;
|
|
1747
1920
|
const data = a.data;
|
|
1748
1921
|
for (const issue of data.issues ?? []) {
|
|
1749
|
-
|
|
1922
|
+
if (issue.severity === "warn") continue;
|
|
1923
|
+
const entry = ruleFailures.get(issue.rule) ?? { count: 0, sessions: /* @__PURE__ */ new Set(), recencyWeights: [], allMessages: [], workflowTypes: /* @__PURE__ */ new Map() };
|
|
1750
1924
|
entry.count++;
|
|
1751
|
-
entry.sessions.add(
|
|
1925
|
+
entry.sessions.add(buildId);
|
|
1752
1926
|
entry.recencyWeights.push(weight);
|
|
1753
1927
|
entry.allMessages.push(issue.message);
|
|
1928
|
+
if (data.workflowType) {
|
|
1929
|
+
entry.workflowTypes.set(data.workflowType, (entry.workflowTypes.get(data.workflowType) ?? 0) + 1);
|
|
1930
|
+
}
|
|
1754
1931
|
ruleFailures.set(issue.rule, entry);
|
|
1755
1932
|
if (issue.rule === 17) {
|
|
1756
1933
|
const credPatterns = [
|
|
@@ -1803,9 +1980,10 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1803
1980
|
}
|
|
1804
1981
|
const sessions = /* @__PURE__ */ new Map();
|
|
1805
1982
|
for (const a of attempts) {
|
|
1806
|
-
const
|
|
1983
|
+
const buildId = a.runId ?? a.sessionId;
|
|
1984
|
+
const list = sessions.get(buildId) ?? [];
|
|
1807
1985
|
list.push(a);
|
|
1808
|
-
sessions.set(
|
|
1986
|
+
sessions.set(buildId, list);
|
|
1809
1987
|
}
|
|
1810
1988
|
let firstTryPass = 0;
|
|
1811
1989
|
let correctionNeeded = 0;
|
|
@@ -1852,7 +2030,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1852
2030
|
const avgRecency = entry.recencyWeights.length > 0 ? entry.recencyWeights.reduce((s, w) => s + w, 0) / entry.recencyWeights.length : 1;
|
|
1853
2031
|
const stickiness = stickinessCount.get(rule) ?? 0;
|
|
1854
2032
|
const { compositeScore, factors } = this.computeCompositeScore(rawConfidence, entry.count, state, avgRecency, stickiness);
|
|
1855
|
-
|
|
2033
|
+
const pattern = {
|
|
1856
2034
|
rule,
|
|
1857
2035
|
failureCount: entry.count,
|
|
1858
2036
|
confidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
@@ -1864,6 +2042,10 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1864
2042
|
exampleMessages: this.deduplicateMessages(entry.allMessages),
|
|
1865
2043
|
mitigation: RULE_MITIGATIONS[rule] ?? null
|
|
1866
2044
|
};
|
|
2045
|
+
if (entry.workflowTypes.size > 0) {
|
|
2046
|
+
pattern.workflowTypeBreakdown = Object.fromEntries(entry.workflowTypes);
|
|
2047
|
+
}
|
|
2048
|
+
return pattern;
|
|
1867
2049
|
}).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1868
2050
|
const activeRules = new Set(activePatterns.map((p) => p.rule));
|
|
1869
2051
|
for (const p of activePatterns) {
|
|
@@ -1920,7 +2102,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1920
2102
|
const warned = bcData.warnedRules ?? [];
|
|
1921
2103
|
if (warned.length === 0) continue;
|
|
1922
2104
|
const sessionFailedRules = /* @__PURE__ */ new Set();
|
|
1923
|
-
const sessionAttempts = sessions.get(bc.sessionId) ?? [];
|
|
2105
|
+
const sessionAttempts = sessions.get(bc.runId ?? bc.sessionId) ?? [];
|
|
1924
2106
|
for (const a of sessionAttempts) {
|
|
1925
2107
|
const ad = a.data;
|
|
1926
2108
|
if (ad.validationPassed === false) {
|
|
@@ -2003,8 +2185,55 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
2003
2185
|
};
|
|
2004
2186
|
const historyPath = (0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl");
|
|
2005
2187
|
await (0, import_promises3.appendFile)(historyPath, JSON.stringify(historySummary) + "\n", "utf-8");
|
|
2188
|
+
const sessions = await this.buildSessionSummaries(days);
|
|
2189
|
+
const sessionHistoryPath = (0, import_node_path5.join)(this.outputDir, "session-history.json");
|
|
2190
|
+
const sessionHistoryTmp = `${sessionHistoryPath}.tmp`;
|
|
2191
|
+
await (0, import_promises3.writeFile)(sessionHistoryTmp, JSON.stringify(sessions, null, 2), "utf-8");
|
|
2192
|
+
await (0, import_promises3.rename)(sessionHistoryTmp, sessionHistoryPath);
|
|
2006
2193
|
return analysis;
|
|
2007
2194
|
}
|
|
2195
|
+
async getSessions(limit = 20) {
|
|
2196
|
+
try {
|
|
2197
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "session-history.json"), "utf-8");
|
|
2198
|
+
const all = JSON.parse(raw);
|
|
2199
|
+
return all.slice(-limit);
|
|
2200
|
+
} catch {
|
|
2201
|
+
return [];
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
async buildSessionSummaries(days = 30) {
|
|
2205
|
+
const events = this._cachedEvents ?? await this.readAllEvents(days);
|
|
2206
|
+
const buildCompletes = events.filter((e) => e.eventType === "build_complete");
|
|
2207
|
+
const attemptsByBuild = /* @__PURE__ */ new Map();
|
|
2208
|
+
for (const e of events.filter((e2) => e2.eventType === "generation_attempt")) {
|
|
2209
|
+
const buildId = e.runId ?? e.sessionId;
|
|
2210
|
+
const list = attemptsByBuild.get(buildId) ?? [];
|
|
2211
|
+
list.push(e);
|
|
2212
|
+
attemptsByBuild.set(buildId, list);
|
|
2213
|
+
}
|
|
2214
|
+
const summaries = buildCompletes.map((bc) => {
|
|
2215
|
+
const data = bc.data;
|
|
2216
|
+
const sessionAttempts = attemptsByBuild.get(bc.runId ?? bc.sessionId) ?? [];
|
|
2217
|
+
const failedRules = Array.from(new Set(
|
|
2218
|
+
sessionAttempts.flatMap((a) => {
|
|
2219
|
+
const ad = a.data;
|
|
2220
|
+
if (ad.validationPassed !== false) return [];
|
|
2221
|
+
return (ad.issues ?? []).map((i) => i.rule);
|
|
2222
|
+
})
|
|
2223
|
+
));
|
|
2224
|
+
return {
|
|
2225
|
+
sessionId: bc.sessionId,
|
|
2226
|
+
date: bc.fileDate,
|
|
2227
|
+
description: data.description ?? "",
|
|
2228
|
+
workflowType: data.workflowType ?? null,
|
|
2229
|
+
attempts: data.totalAttempts ?? 1,
|
|
2230
|
+
success: data.success ?? false,
|
|
2231
|
+
failedRules,
|
|
2232
|
+
workflowName: data.workflowName ?? null
|
|
2233
|
+
};
|
|
2234
|
+
});
|
|
2235
|
+
return summaries.sort((a, b) => a.date.localeCompare(b.date));
|
|
2236
|
+
}
|
|
2008
2237
|
async getHistory(limit = 20) {
|
|
2009
2238
|
try {
|
|
2010
2239
|
const raw = await (0, import_promises3.readFile)((0, import_node_path5.join)(this.outputDir, "pattern-history.jsonl"), "utf-8");
|
|
@@ -2026,7 +2255,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
2026
2255
|
alerts.push({
|
|
2027
2256
|
type: "stale_pattern",
|
|
2028
2257
|
rule: p.rule,
|
|
2029
|
-
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-
|
|
2258
|
+
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-26)`
|
|
2030
2259
|
});
|
|
2031
2260
|
}
|
|
2032
2261
|
}
|
|
@@ -2113,7 +2342,59 @@ var nullLogger = {
|
|
|
2113
2342
|
}
|
|
2114
2343
|
};
|
|
2115
2344
|
|
|
2345
|
+
// src/utils/workflow-type.ts
|
|
2346
|
+
var TYPE_KEYWORDS = [
|
|
2347
|
+
["gmail", "email"],
|
|
2348
|
+
["imap", "email"],
|
|
2349
|
+
["smtp", "email"],
|
|
2350
|
+
[" email", "email"],
|
|
2351
|
+
["slack", "slack"],
|
|
2352
|
+
["telegram", "messaging"],
|
|
2353
|
+
["discord", "messaging"],
|
|
2354
|
+
[" sms", "messaging"],
|
|
2355
|
+
["twilio", "messaging"],
|
|
2356
|
+
["webhook", "webhook"],
|
|
2357
|
+
["google sheets", "data"],
|
|
2358
|
+
["spreadsheet", "data"],
|
|
2359
|
+
["airtable", "data"],
|
|
2360
|
+
["notion", "data"],
|
|
2361
|
+
["github", "devops"],
|
|
2362
|
+
["gitlab", "devops"],
|
|
2363
|
+
["schedule", "schedule"],
|
|
2364
|
+
[" cron", "schedule"],
|
|
2365
|
+
["daily", "schedule"],
|
|
2366
|
+
["weekly", "schedule"],
|
|
2367
|
+
["hourly", "schedule"],
|
|
2368
|
+
["every day", "schedule"],
|
|
2369
|
+
["every hour", "schedule"],
|
|
2370
|
+
["every morning", "schedule"],
|
|
2371
|
+
["postgres", "database"],
|
|
2372
|
+
["mysql", "database"],
|
|
2373
|
+
["supabase", "database"],
|
|
2374
|
+
["redis", "database"],
|
|
2375
|
+
[" database", "database"],
|
|
2376
|
+
[" llm", "ai"],
|
|
2377
|
+
[" gpt", "ai"],
|
|
2378
|
+
["claude", "ai"],
|
|
2379
|
+
[" agent", "ai"],
|
|
2380
|
+
["langchain", "ai"],
|
|
2381
|
+
[" ai ", "ai"],
|
|
2382
|
+
[" ai", "ai"],
|
|
2383
|
+
["http request", "api"],
|
|
2384
|
+
["rest api", "api"],
|
|
2385
|
+
[" api", "api"]
|
|
2386
|
+
];
|
|
2387
|
+
function inferWorkflowType(description) {
|
|
2388
|
+
const lower = " " + description.toLowerCase();
|
|
2389
|
+
for (const [keyword, type] of TYPE_KEYWORDS) {
|
|
2390
|
+
if (lower.includes(keyword)) return type;
|
|
2391
|
+
}
|
|
2392
|
+
return null;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2116
2395
|
// src/client.ts
|
|
2396
|
+
var import_node_os5 = require("os");
|
|
2397
|
+
var import_node_path6 = require("path");
|
|
2117
2398
|
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
2118
2399
|
var Kairos = class {
|
|
2119
2400
|
provider;
|
|
@@ -2142,7 +2423,8 @@ var Kairos = class {
|
|
|
2142
2423
|
this.provider = null;
|
|
2143
2424
|
}
|
|
2144
2425
|
const anthropic = new import_sdk.default({ apiKey: options.anthropicApiKey });
|
|
2145
|
-
|
|
2426
|
+
const patternsPath = typeof options.telemetry === "string" ? (0, import_node_path6.join)(options.telemetry, "..", "patterns.json") : (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".kairos", "patterns.json");
|
|
2427
|
+
this.designer = new WorkflowDesigner(anthropic, this.model, logger, patternsPath);
|
|
2146
2428
|
this.validator = new N8nValidator();
|
|
2147
2429
|
this.library = options.library ?? new NullLibrary();
|
|
2148
2430
|
this.logger = logger;
|
|
@@ -2175,11 +2457,13 @@ var Kairos = class {
|
|
|
2175
2457
|
this.validateDescription(description);
|
|
2176
2458
|
this.logger.info("Kairos.build", { description, dryRun: options?.dryRun });
|
|
2177
2459
|
const buildStart = Date.now();
|
|
2460
|
+
const runId = generateUUID();
|
|
2461
|
+
const workflowType = inferWorkflowType(description);
|
|
2178
2462
|
await this.telemetry?.emit("build_start", {
|
|
2179
2463
|
description,
|
|
2180
2464
|
model: this.model,
|
|
2181
2465
|
dryRun: options?.dryRun ?? false
|
|
2182
|
-
});
|
|
2466
|
+
}, runId);
|
|
2183
2467
|
await this.library.initialize();
|
|
2184
2468
|
const matches = await this.library.search(description);
|
|
2185
2469
|
if (matches.length > 0) {
|
|
@@ -2214,8 +2498,9 @@ var Kairos = class {
|
|
|
2214
2498
|
tokensOutput: meta.tokensOutput,
|
|
2215
2499
|
validationPassed: meta.validationPassed,
|
|
2216
2500
|
issueCount: meta.issues.length,
|
|
2217
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
2218
|
-
|
|
2501
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, severity: i.severity, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null })),
|
|
2502
|
+
workflowType
|
|
2503
|
+
}, runId);
|
|
2219
2504
|
}
|
|
2220
2505
|
await this.telemetry?.emit("build_complete", {
|
|
2221
2506
|
description,
|
|
@@ -2228,13 +2513,14 @@ var Kairos = class {
|
|
|
2228
2513
|
workflowId: null,
|
|
2229
2514
|
dryRun: options?.dryRun ?? false,
|
|
2230
2515
|
credentialsNeeded: 0,
|
|
2231
|
-
warnedRules: err.warnedRules ?? []
|
|
2232
|
-
|
|
2516
|
+
warnedRules: err.warnedRules ?? [],
|
|
2517
|
+
workflowType
|
|
2518
|
+
}, runId);
|
|
2233
2519
|
this.updatePatterns();
|
|
2234
2520
|
}
|
|
2235
2521
|
throw err;
|
|
2236
2522
|
}
|
|
2237
|
-
await this.emitAttemptTelemetry(description, designResult);
|
|
2523
|
+
await this.emitAttemptTelemetry(description, designResult, workflowType, runId);
|
|
2238
2524
|
const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
|
|
2239
2525
|
this.saveToLibrary(workflow, description, designResult, matches);
|
|
2240
2526
|
if (options?.dryRun) {
|
|
@@ -2251,8 +2537,9 @@ var Kairos = class {
|
|
|
2251
2537
|
workflowId: null,
|
|
2252
2538
|
dryRun: true,
|
|
2253
2539
|
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2254
|
-
warnedRules: designResult.warnedRules
|
|
2255
|
-
|
|
2540
|
+
warnedRules: designResult.warnedRules,
|
|
2541
|
+
workflowType
|
|
2542
|
+
}, runId);
|
|
2256
2543
|
this.updatePatterns();
|
|
2257
2544
|
return {
|
|
2258
2545
|
workflowId: null,
|
|
@@ -2283,8 +2570,9 @@ var Kairos = class {
|
|
|
2283
2570
|
workflowId: deployed.workflowId,
|
|
2284
2571
|
dryRun: false,
|
|
2285
2572
|
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2286
|
-
warnedRules: designResult.warnedRules
|
|
2287
|
-
|
|
2573
|
+
warnedRules: designResult.warnedRules,
|
|
2574
|
+
workflowType
|
|
2575
|
+
}, runId);
|
|
2288
2576
|
this.updatePatterns();
|
|
2289
2577
|
return {
|
|
2290
2578
|
workflowId: deployed.workflowId,
|
|
@@ -2300,11 +2588,13 @@ var Kairos = class {
|
|
|
2300
2588
|
this.validateDescription(description);
|
|
2301
2589
|
this.logger.info("Kairos.update", { id, description });
|
|
2302
2590
|
const buildStart = Date.now();
|
|
2591
|
+
const runId = generateUUID();
|
|
2592
|
+
const workflowType = inferWorkflowType(description);
|
|
2303
2593
|
await this.telemetry?.emit("build_start", {
|
|
2304
2594
|
description,
|
|
2305
2595
|
model: this.model,
|
|
2306
2596
|
dryRun: false
|
|
2307
|
-
});
|
|
2597
|
+
}, runId);
|
|
2308
2598
|
await this.library.initialize();
|
|
2309
2599
|
const matches = await this.library.search(description);
|
|
2310
2600
|
const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
|
|
@@ -2323,8 +2613,9 @@ var Kairos = class {
|
|
|
2323
2613
|
tokensOutput: meta.tokensOutput,
|
|
2324
2614
|
validationPassed: meta.validationPassed,
|
|
2325
2615
|
issueCount: meta.issues.length,
|
|
2326
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
2327
|
-
|
|
2616
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, severity: i.severity, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null })),
|
|
2617
|
+
workflowType
|
|
2618
|
+
}, runId);
|
|
2328
2619
|
}
|
|
2329
2620
|
await this.telemetry?.emit("build_complete", {
|
|
2330
2621
|
description,
|
|
@@ -2337,13 +2628,14 @@ var Kairos = class {
|
|
|
2337
2628
|
workflowId: null,
|
|
2338
2629
|
dryRun: false,
|
|
2339
2630
|
credentialsNeeded: 0,
|
|
2340
|
-
warnedRules: err.warnedRules ?? []
|
|
2341
|
-
|
|
2631
|
+
warnedRules: err.warnedRules ?? [],
|
|
2632
|
+
workflowType
|
|
2633
|
+
}, runId);
|
|
2342
2634
|
this.updatePatterns();
|
|
2343
2635
|
}
|
|
2344
2636
|
throw err;
|
|
2345
2637
|
}
|
|
2346
|
-
await this.emitAttemptTelemetry(description, designResult);
|
|
2638
|
+
await this.emitAttemptTelemetry(description, designResult, workflowType, runId);
|
|
2347
2639
|
const provider = this.requireProvider();
|
|
2348
2640
|
const deployed = await provider.update(id, designResult.workflow);
|
|
2349
2641
|
this.saveToLibrary(designResult.workflow, description, designResult, matches);
|
|
@@ -2361,8 +2653,9 @@ var Kairos = class {
|
|
|
2361
2653
|
workflowId: deployed.workflowId,
|
|
2362
2654
|
dryRun: false,
|
|
2363
2655
|
credentialsNeeded: designResult.credentialsNeeded.length,
|
|
2364
|
-
warnedRules: designResult.warnedRules
|
|
2365
|
-
|
|
2656
|
+
warnedRules: designResult.warnedRules,
|
|
2657
|
+
workflowType
|
|
2658
|
+
}, runId);
|
|
2366
2659
|
this.updatePatterns();
|
|
2367
2660
|
return {
|
|
2368
2661
|
workflowId: deployed.workflowId,
|
|
@@ -2385,7 +2678,7 @@ var Kairos = class {
|
|
|
2385
2678
|
return null;
|
|
2386
2679
|
});
|
|
2387
2680
|
}
|
|
2388
|
-
async emitAttemptTelemetry(description, designResult) {
|
|
2681
|
+
async emitAttemptTelemetry(description, designResult, workflowType, runId) {
|
|
2389
2682
|
for (const meta of designResult.attemptMetadata) {
|
|
2390
2683
|
await this.telemetry?.emit("generation_attempt", {
|
|
2391
2684
|
description,
|
|
@@ -2396,8 +2689,9 @@ var Kairos = class {
|
|
|
2396
2689
|
tokensOutput: meta.tokensOutput,
|
|
2397
2690
|
validationPassed: meta.validationPassed,
|
|
2398
2691
|
issueCount: meta.issues.length,
|
|
2399
|
-
issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null }))
|
|
2400
|
-
|
|
2692
|
+
issues: meta.issues.map((i) => ({ rule: i.rule, severity: i.severity, message: i.message, nodeId: i.nodeId ?? null, nodeType: i.nodeType ?? null })),
|
|
2693
|
+
workflowType
|
|
2694
|
+
}, runId);
|
|
2401
2695
|
}
|
|
2402
2696
|
}
|
|
2403
2697
|
recordDeploy() {
|
|
@@ -2496,8 +2790,8 @@ var Kairos = class {
|
|
|
2496
2790
|
|
|
2497
2791
|
// src/library/file-library.ts
|
|
2498
2792
|
var import_promises4 = require("fs/promises");
|
|
2499
|
-
var
|
|
2500
|
-
var
|
|
2793
|
+
var import_node_path7 = require("path");
|
|
2794
|
+
var import_node_os6 = require("os");
|
|
2501
2795
|
|
|
2502
2796
|
// src/library/scorer.ts
|
|
2503
2797
|
var WEIGHTS = {
|
|
@@ -2727,13 +3021,27 @@ function buildSearchCorpus(w) {
|
|
|
2727
3021
|
return `${w.description} ${w.workflow.name} ${w.tags.join(" ")} ${nodeTokens.join(" ")}`;
|
|
2728
3022
|
}
|
|
2729
3023
|
var MAX_LIBRARY_SIZE = 500;
|
|
3024
|
+
function isValidMeta(item) {
|
|
3025
|
+
return typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.description === "string" && typeof item.workflowName === "string" && Array.isArray(item.cachedNodeTypes);
|
|
3026
|
+
}
|
|
3027
|
+
function isValidOldEntry(item) {
|
|
3028
|
+
return typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.description === "string" && typeof item.workflow === "object" && item.workflow !== null && Array.isArray(
|
|
3029
|
+
item.workflow.nodes
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
2730
3032
|
var FileLibrary = class {
|
|
2731
3033
|
dir;
|
|
2732
|
-
|
|
3034
|
+
meta = [];
|
|
2733
3035
|
initPromise = null;
|
|
2734
3036
|
writeQueue = Promise.resolve();
|
|
2735
3037
|
constructor(dir) {
|
|
2736
|
-
this.dir = dir ?? (0,
|
|
3038
|
+
this.dir = dir ?? (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".kairos", "library");
|
|
3039
|
+
}
|
|
3040
|
+
get workflowsDir() {
|
|
3041
|
+
return (0, import_node_path7.join)(this.dir, "workflows");
|
|
3042
|
+
}
|
|
3043
|
+
workflowFilePath(id) {
|
|
3044
|
+
return (0, import_node_path7.join)(this.workflowsDir, `${id}.json`);
|
|
2737
3045
|
}
|
|
2738
3046
|
async initialize() {
|
|
2739
3047
|
if (!this.initPromise) {
|
|
@@ -2743,61 +3051,148 @@ var FileLibrary = class {
|
|
|
2743
3051
|
}
|
|
2744
3052
|
async doInitialize() {
|
|
2745
3053
|
await (0, import_promises4.mkdir)(this.dir, { recursive: true });
|
|
2746
|
-
const indexPath = (0,
|
|
3054
|
+
const indexPath = (0, import_node_path7.join)(this.dir, "index.json");
|
|
3055
|
+
let workflowsDirExists = false;
|
|
2747
3056
|
try {
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
);
|
|
3057
|
+
await (0, import_promises4.stat)(this.workflowsDir);
|
|
3058
|
+
workflowsDirExists = true;
|
|
3059
|
+
} catch {
|
|
3060
|
+
}
|
|
3061
|
+
if (workflowsDirExists) {
|
|
3062
|
+
try {
|
|
3063
|
+
const raw = await (0, import_promises4.readFile)(indexPath, "utf-8");
|
|
3064
|
+
const parsed = JSON.parse(raw);
|
|
3065
|
+
if (Array.isArray(parsed)) {
|
|
3066
|
+
this.meta = parsed.filter(isValidMeta);
|
|
3067
|
+
}
|
|
3068
|
+
} catch {
|
|
3069
|
+
this.meta = [];
|
|
3070
|
+
}
|
|
3071
|
+
} else {
|
|
3072
|
+
try {
|
|
3073
|
+
const raw = await (0, import_promises4.readFile)(indexPath, "utf-8");
|
|
3074
|
+
const parsed = JSON.parse(raw);
|
|
3075
|
+
if (Array.isArray(parsed) && parsed.length > 0 && isValidOldEntry(parsed[0])) {
|
|
3076
|
+
await this.migrateFromMonolithic(parsed.filter(isValidOldEntry));
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
} catch {
|
|
2756
3080
|
}
|
|
3081
|
+
this.meta = [];
|
|
3082
|
+
await (0, import_promises4.mkdir)(this.workflowsDir, { recursive: true });
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* One-time transparent migration from v0.4.x monolithic index.json.
|
|
3087
|
+
* Splits each stored workflow into a per-file workflow JSON and a lightweight
|
|
3088
|
+
* meta entry. Rewrites index.json in the new format.
|
|
3089
|
+
*/
|
|
3090
|
+
async migrateFromMonolithic(oldEntries) {
|
|
3091
|
+
await (0, import_promises4.mkdir)(this.workflowsDir, { recursive: true });
|
|
3092
|
+
const newMeta = [];
|
|
3093
|
+
for (const entry of oldEntries) {
|
|
3094
|
+
const wfPath = this.workflowFilePath(entry.id);
|
|
3095
|
+
const tmpPath = `${wfPath}.tmp`;
|
|
3096
|
+
await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(entry.workflow), "utf-8");
|
|
3097
|
+
await (0, import_promises4.rename)(tmpPath, wfPath);
|
|
3098
|
+
const { workflow, ...metaFields } = entry;
|
|
3099
|
+
newMeta.push({
|
|
3100
|
+
...metaFields,
|
|
3101
|
+
workflowName: workflow.name,
|
|
3102
|
+
cachedNodeTypes: workflow.nodes.map((n) => n.type)
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
this.meta = newMeta;
|
|
3106
|
+
await this.persistNow();
|
|
3107
|
+
}
|
|
3108
|
+
async loadWorkflowFile(id) {
|
|
3109
|
+
try {
|
|
3110
|
+
const raw = await (0, import_promises4.readFile)(this.workflowFilePath(id), "utf-8");
|
|
3111
|
+
return JSON.parse(raw);
|
|
2757
3112
|
} catch {
|
|
2758
|
-
|
|
3113
|
+
return null;
|
|
2759
3114
|
}
|
|
2760
3115
|
}
|
|
3116
|
+
async writeWorkflowFile(id, workflow) {
|
|
3117
|
+
const wfPath = this.workflowFilePath(id);
|
|
3118
|
+
const tmpPath = `${wfPath}.tmp`;
|
|
3119
|
+
await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(workflow), "utf-8");
|
|
3120
|
+
await (0, import_promises4.rename)(tmpPath, wfPath);
|
|
3121
|
+
}
|
|
3122
|
+
/**
|
|
3123
|
+
* Build a lightweight StoredWorkflow shell from a meta entry for use in
|
|
3124
|
+
* scoring / clustering. Only node.type is populated in each node — no other
|
|
3125
|
+
* node fields are used by hybridScore or clusterWorkflows.
|
|
3126
|
+
*/
|
|
3127
|
+
makeSearchShell(m) {
|
|
3128
|
+
return {
|
|
3129
|
+
...m,
|
|
3130
|
+
workflow: {
|
|
3131
|
+
name: m.workflowName,
|
|
3132
|
+
nodes: m.cachedNodeTypes.map((type) => ({
|
|
3133
|
+
id: "",
|
|
3134
|
+
name: "",
|
|
3135
|
+
type,
|
|
3136
|
+
typeVersion: 1,
|
|
3137
|
+
position: [0, 0],
|
|
3138
|
+
parameters: {}
|
|
3139
|
+
})),
|
|
3140
|
+
connections: {}
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
2761
3144
|
async search(description, options) {
|
|
2762
|
-
const
|
|
2763
|
-
if (
|
|
3145
|
+
const filteredMeta = this.meta.filter((m) => m.trustLevel !== "blocked");
|
|
3146
|
+
if (filteredMeta.length === 0) return [];
|
|
2764
3147
|
const limit = options?.limit ?? 3;
|
|
2765
3148
|
const queryTokens = tokenize(description);
|
|
2766
3149
|
if (queryTokens.length === 0) return [];
|
|
2767
|
-
const
|
|
3150
|
+
const shells = filteredMeta.map((m) => this.makeSearchShell(m));
|
|
3151
|
+
const docTokenArrays = shells.map((w) => tokenize(buildSearchCorpus(w)));
|
|
2768
3152
|
const docTokenSets = docTokenArrays.map((tokens) => new Set(tokens));
|
|
2769
|
-
const docCount =
|
|
3153
|
+
const docCount = shells.length;
|
|
2770
3154
|
const idf = /* @__PURE__ */ new Map();
|
|
2771
3155
|
const allTokens = new Set(queryTokens);
|
|
2772
3156
|
for (const token of allTokens) {
|
|
2773
3157
|
const docsWithToken = docTokenSets.filter((d) => d.has(token)).length;
|
|
2774
3158
|
idf.set(token, Math.log((docCount + 1) / (docsWithToken + 1)) + 1);
|
|
2775
3159
|
}
|
|
2776
|
-
const scored = hybridScore(queryTokens, description,
|
|
2777
|
-
const clusters = clusterWorkflows(
|
|
3160
|
+
const scored = hybridScore(queryTokens, description, shells, docTokenArrays, idf).filter((m) => m.score > 0).sort((a, b) => b.score - a.score);
|
|
3161
|
+
const clusters = clusterWorkflows(shells);
|
|
2778
3162
|
const reranked = rerank(scored, clusters).slice(0, limit);
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
3163
|
+
if (reranked.length === 0) return [];
|
|
3164
|
+
for (const r of reranked) {
|
|
3165
|
+
const m = this.meta.find((m2) => m2.id === r.workflow.id);
|
|
3166
|
+
if (m) m.timesRetrieved = (m.timesRetrieved ?? 0) + 1;
|
|
3167
|
+
}
|
|
3168
|
+
this.persist();
|
|
3169
|
+
const results = await Promise.all(
|
|
3170
|
+
reranked.map(async (r) => {
|
|
3171
|
+
const m = this.meta.find((meta) => meta.id === r.workflow.id);
|
|
3172
|
+
const workflow = await this.loadWorkflowFile(r.workflow.id);
|
|
3173
|
+
if (!workflow) return null;
|
|
3174
|
+
return {
|
|
3175
|
+
workflow: { ...m, workflow },
|
|
3176
|
+
score: r.score,
|
|
3177
|
+
mode: scoreToMode(r.score)
|
|
3178
|
+
};
|
|
3179
|
+
})
|
|
3180
|
+
);
|
|
3181
|
+
return results.filter((r) => r !== null);
|
|
2789
3182
|
}
|
|
2790
3183
|
async save(workflow, metadata) {
|
|
2791
3184
|
const id = generateUUID();
|
|
3185
|
+
await this.writeWorkflowFile(id, workflow);
|
|
2792
3186
|
const failurePatterns = this.deduplicateFailurePatterns(metadata.failurePatterns);
|
|
2793
|
-
const
|
|
3187
|
+
const meta = {
|
|
2794
3188
|
id,
|
|
2795
|
-
workflow,
|
|
2796
3189
|
description: metadata.description,
|
|
2797
3190
|
tags: metadata.tags ?? [],
|
|
2798
3191
|
platform: metadata.platform ?? "n8n",
|
|
2799
3192
|
deployCount: 0,
|
|
2800
3193
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3194
|
+
workflowName: workflow.name,
|
|
3195
|
+
cachedNodeTypes: workflow.nodes.map((n) => n.type),
|
|
2801
3196
|
...failurePatterns?.length ? { failurePatterns } : {},
|
|
2802
3197
|
...metadata.sourceWorkflowIds?.length ? { sourceWorkflowIds: metadata.sourceWorkflowIds } : {},
|
|
2803
3198
|
...metadata.generationMode ? { generationMode: metadata.generationMode } : {},
|
|
@@ -2809,31 +3204,35 @@ var FileLibrary = class {
|
|
|
2809
3204
|
...metadata.sourceUrl ? { sourceUrl: metadata.sourceUrl } : {},
|
|
2810
3205
|
...metadata.trustLevel ? { trustLevel: metadata.trustLevel } : {}
|
|
2811
3206
|
};
|
|
2812
|
-
this.
|
|
2813
|
-
if (this.
|
|
2814
|
-
this.
|
|
2815
|
-
|
|
3207
|
+
this.meta.push(meta);
|
|
3208
|
+
if (this.meta.length > MAX_LIBRARY_SIZE) {
|
|
3209
|
+
this.meta.sort((a, b) => {
|
|
3210
|
+
if (a.id === id) return -1;
|
|
3211
|
+
if (b.id === id) return 1;
|
|
3212
|
+
return (b.deployCount ?? 0) - (a.deployCount ?? 0);
|
|
3213
|
+
});
|
|
3214
|
+
this.meta = this.meta.slice(0, MAX_LIBRARY_SIZE);
|
|
2816
3215
|
}
|
|
2817
3216
|
await this.persist();
|
|
2818
3217
|
return id;
|
|
2819
3218
|
}
|
|
2820
3219
|
async recordDeployment(id) {
|
|
2821
|
-
const
|
|
2822
|
-
if (
|
|
2823
|
-
|
|
2824
|
-
|
|
3220
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
3221
|
+
if (m) {
|
|
3222
|
+
m.deployCount++;
|
|
3223
|
+
m.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2825
3224
|
await this.persist();
|
|
2826
3225
|
}
|
|
2827
3226
|
}
|
|
2828
3227
|
async recordOutcome(id, outcome) {
|
|
2829
|
-
const
|
|
2830
|
-
if (!
|
|
3228
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
3229
|
+
if (!m) return;
|
|
2831
3230
|
if (outcome.mode === "direct") {
|
|
2832
|
-
|
|
3231
|
+
m.timesUsedAsDirect = (m.timesUsedAsDirect ?? 0) + 1;
|
|
2833
3232
|
} else {
|
|
2834
|
-
|
|
3233
|
+
m.timesUsedAsReference = (m.timesUsedAsReference ?? 0) + 1;
|
|
2835
3234
|
}
|
|
2836
|
-
const stats =
|
|
3235
|
+
const stats = m.outcomeStats ?? { totalUses: 0, totalAttempts: 0, firstTryPasses: 0, failedRules: {} };
|
|
2837
3236
|
stats.totalUses++;
|
|
2838
3237
|
stats.totalAttempts += outcome.attempts;
|
|
2839
3238
|
if (outcome.firstTryPass) stats.firstTryPasses++;
|
|
@@ -2841,24 +3240,35 @@ var FileLibrary = class {
|
|
|
2841
3240
|
const key = String(rule);
|
|
2842
3241
|
stats.failedRules[key] = (stats.failedRules[key] ?? 0) + 1;
|
|
2843
3242
|
}
|
|
2844
|
-
|
|
3243
|
+
m.outcomeStats = stats;
|
|
2845
3244
|
await this.persist();
|
|
2846
3245
|
}
|
|
2847
3246
|
async drain() {
|
|
2848
3247
|
await this.writeQueue;
|
|
2849
3248
|
}
|
|
2850
3249
|
async get(id) {
|
|
2851
|
-
|
|
3250
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
3251
|
+
if (!m) return null;
|
|
3252
|
+
const workflow = await this.loadWorkflowFile(id);
|
|
3253
|
+
if (!workflow) return null;
|
|
3254
|
+
return { ...m, workflow };
|
|
2852
3255
|
}
|
|
2853
3256
|
async list(filters) {
|
|
2854
|
-
let
|
|
3257
|
+
let filtered = this.meta;
|
|
2855
3258
|
if (filters?.platform) {
|
|
2856
|
-
|
|
3259
|
+
filtered = filtered.filter((m) => m.platform === filters.platform);
|
|
2857
3260
|
}
|
|
2858
3261
|
if (filters?.tags && filters.tags.length > 0) {
|
|
2859
|
-
|
|
3262
|
+
filtered = filtered.filter((m) => filters.tags.some((t) => m.tags.includes(t)));
|
|
2860
3263
|
}
|
|
2861
|
-
|
|
3264
|
+
const results = await Promise.all(
|
|
3265
|
+
filtered.map(async (m) => {
|
|
3266
|
+
const workflow = await this.loadWorkflowFile(m.id);
|
|
3267
|
+
if (!workflow) return null;
|
|
3268
|
+
return { ...m, workflow };
|
|
3269
|
+
})
|
|
3270
|
+
);
|
|
3271
|
+
return results.filter((r) => r !== null);
|
|
2862
3272
|
}
|
|
2863
3273
|
deduplicateFailurePatterns(patterns) {
|
|
2864
3274
|
if (!patterns?.length) return void 0;
|
|
@@ -2873,11 +3283,36 @@ var FileLibrary = class {
|
|
|
2873
3283
|
}
|
|
2874
3284
|
return [...map.values()];
|
|
2875
3285
|
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Direct write used only during migration (before writeQueue is needed).
|
|
3288
|
+
*/
|
|
3289
|
+
async persistNow() {
|
|
3290
|
+
const indexPath = (0, import_node_path7.join)(this.dir, "index.json");
|
|
3291
|
+
const tmpPath = `${indexPath}.tmp`;
|
|
3292
|
+
await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(this.meta, null, 2), "utf-8");
|
|
3293
|
+
await (0, import_promises4.rename)(tmpPath, indexPath);
|
|
3294
|
+
}
|
|
2876
3295
|
persist() {
|
|
2877
3296
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
2878
|
-
const indexPath = (0,
|
|
3297
|
+
const indexPath = (0, import_node_path7.join)(this.dir, "index.json");
|
|
3298
|
+
let onDisk = [];
|
|
3299
|
+
try {
|
|
3300
|
+
const raw = await (0, import_promises4.readFile)(indexPath, "utf-8");
|
|
3301
|
+
const parsed = JSON.parse(raw);
|
|
3302
|
+
if (Array.isArray(parsed)) {
|
|
3303
|
+
onDisk = parsed.filter(isValidMeta);
|
|
3304
|
+
}
|
|
3305
|
+
} catch {
|
|
3306
|
+
}
|
|
3307
|
+
const ourIds = new Set(this.meta.map((m) => m.id));
|
|
3308
|
+
const external = onDisk.filter((m) => !ourIds.has(m.id));
|
|
3309
|
+
let merged = [...this.meta, ...external];
|
|
3310
|
+
if (merged.length > MAX_LIBRARY_SIZE) {
|
|
3311
|
+
merged.sort((a, b) => (b.deployCount ?? 0) - (a.deployCount ?? 0));
|
|
3312
|
+
merged = merged.slice(0, MAX_LIBRARY_SIZE);
|
|
3313
|
+
}
|
|
2879
3314
|
const tmpPath = `${indexPath}.tmp`;
|
|
2880
|
-
await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(
|
|
3315
|
+
await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
2881
3316
|
await (0, import_promises4.rename)(tmpPath, indexPath);
|
|
2882
3317
|
});
|
|
2883
3318
|
return this.writeQueue;
|
|
@@ -3067,6 +3502,7 @@ Usage:
|
|
|
3067
3502
|
kairos init First-time setup wizard
|
|
3068
3503
|
kairos build <description> [options]
|
|
3069
3504
|
kairos patterns [options]
|
|
3505
|
+
kairos sessions [options]
|
|
3070
3506
|
kairos list
|
|
3071
3507
|
kairos get <id>
|
|
3072
3508
|
kairos activate <id>
|
|
@@ -3083,15 +3519,23 @@ Patterns options:
|
|
|
3083
3519
|
--days <days> Analysis window (default: 30)
|
|
3084
3520
|
--json Output raw JSON instead of summary
|
|
3085
3521
|
|
|
3522
|
+
Sessions options:
|
|
3523
|
+
--limit <n> Number of recent sessions to show (default: 20)
|
|
3524
|
+
--json Output raw JSON instead of summary
|
|
3525
|
+
|
|
3086
3526
|
Sync options:
|
|
3087
3527
|
--max <count> Maximum templates to fetch (default: 500)
|
|
3088
3528
|
|
|
3089
3529
|
Environment variables:
|
|
3090
|
-
ANTHROPIC_API_KEY
|
|
3091
|
-
N8N_BASE_URL
|
|
3092
|
-
N8N_API_KEY
|
|
3093
|
-
KAIROS_MODEL
|
|
3094
|
-
KAIROS_TELEMETRY
|
|
3530
|
+
ANTHROPIC_API_KEY Anthropic API key (required)
|
|
3531
|
+
N8N_BASE_URL n8n instance URL (required for deploy, optional for --dry-run)
|
|
3532
|
+
N8N_API_KEY n8n API key (required for deploy, optional for --dry-run)
|
|
3533
|
+
KAIROS_MODEL Claude model override (default: claude-sonnet-4-6)
|
|
3534
|
+
KAIROS_TELEMETRY Set to "true" or a directory path to enable telemetry logging
|
|
3535
|
+
KAIROS_PROMPT_PROFILE minimal | standard | rich (default: standard)
|
|
3536
|
+
minimal: base prompt only, no library context, top 3 patterns
|
|
3537
|
+
standard: full library context, top 10 patterns (default)
|
|
3538
|
+
rich: full library context, top 15 patterns, proactive expression guidance
|
|
3095
3539
|
`;
|
|
3096
3540
|
function getEnvOrExit(name) {
|
|
3097
3541
|
const val = process.env[name];
|
|
@@ -3253,7 +3697,8 @@ async function handleDelete(positional, flags) {
|
|
|
3253
3697
|
console.log(`Deleted workflow ${id}`);
|
|
3254
3698
|
}
|
|
3255
3699
|
async function handleSyncTemplates(flags) {
|
|
3256
|
-
const
|
|
3700
|
+
const maxRaw = typeof flags["max"] === "string" ? parseInt(flags["max"], 10) : NaN;
|
|
3701
|
+
const max = Number.isNaN(maxRaw) ? 500 : maxRaw;
|
|
3257
3702
|
const library = new FileLibrary();
|
|
3258
3703
|
const logger = {
|
|
3259
3704
|
debug: () => {
|
|
@@ -3281,7 +3726,8 @@ async function handleSyncTemplates(flags) {
|
|
|
3281
3726
|
console.error(` Paid: ${result.skippedPaid} (skipped)`);
|
|
3282
3727
|
}
|
|
3283
3728
|
async function handlePatterns(flags) {
|
|
3284
|
-
const
|
|
3729
|
+
const daysRaw = typeof flags["days"] === "string" ? parseInt(flags["days"], 10) : NaN;
|
|
3730
|
+
const days = Number.isNaN(daysRaw) ? 30 : daysRaw;
|
|
3285
3731
|
const analyzer = PatternAnalyzer.fromEnv();
|
|
3286
3732
|
const analysis = await analyzer.analyzeAndSave(days);
|
|
3287
3733
|
if (flags["json"] === true) {
|
|
@@ -3315,6 +3761,10 @@ Active Failure Patterns:`);
|
|
|
3315
3761
|
console.log(` Factors: confidence=${f.rawConfidence} \xD7 impact=${f.impact} \xD7 recency=${f.recency} + boost=${f.stickinessBoost}`);
|
|
3316
3762
|
if (p.mitigation) console.log(` Fix: ${p.mitigation}`);
|
|
3317
3763
|
if (p.exampleMessages.length > 0) console.log(` e.g. ${p.exampleMessages[0]}`);
|
|
3764
|
+
if (p.workflowTypeBreakdown) {
|
|
3765
|
+
const topType = Object.entries(p.workflowTypeBreakdown).sort((a, b) => b[1] - a[1])[0];
|
|
3766
|
+
if (topType) console.log(` Top workflow type: ${topType[0]} (${topType[1]} failures)`);
|
|
3767
|
+
}
|
|
3318
3768
|
}
|
|
3319
3769
|
} else {
|
|
3320
3770
|
console.log(`
|
|
@@ -3355,10 +3805,35 @@ Drift Detection: ${drift.healthy ? "HEALTHY" : "ALERTS FOUND"}`);
|
|
|
3355
3805
|
console.log(`
|
|
3356
3806
|
Patterns saved to ~/.kairos/patterns.json`);
|
|
3357
3807
|
}
|
|
3808
|
+
async function handleSessions(flags) {
|
|
3809
|
+
const limitRaw = typeof flags["limit"] === "string" ? parseInt(flags["limit"], 10) : NaN;
|
|
3810
|
+
const limit = Number.isNaN(limitRaw) ? 20 : limitRaw;
|
|
3811
|
+
const analyzer = PatternAnalyzer.fromEnv();
|
|
3812
|
+
const sessions = await analyzer.getSessions(limit);
|
|
3813
|
+
if (flags["json"] === true) {
|
|
3814
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
3815
|
+
return;
|
|
3816
|
+
}
|
|
3817
|
+
if (sessions.length === 0) {
|
|
3818
|
+
console.log("No session history found. Run kairos patterns first to generate session data.");
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3821
|
+
console.log(`
|
|
3822
|
+
Recent Sessions (last ${sessions.length})`);
|
|
3823
|
+
console.log("\u2500".repeat(60));
|
|
3824
|
+
for (const s of [...sessions].reverse()) {
|
|
3825
|
+
const status = s.success ? "\u2713" : "\u2717";
|
|
3826
|
+
const typeTag = s.workflowType ? ` [${s.workflowType}]` : "";
|
|
3827
|
+
const attemptsStr = s.attempts > 1 ? ` (${s.attempts} attempts)` : "";
|
|
3828
|
+
const nameStr = s.workflowName ? ` ${s.workflowName}` : ` ${s.description.slice(0, 50)}`;
|
|
3829
|
+
const rulesStr = s.failedRules.length > 0 ? ` \u2014 rules ${s.failedRules.join(", ")} failed` : "";
|
|
3830
|
+
console.log(`${s.date} ${status}${nameStr}${attemptsStr}${typeTag}${rulesStr}`);
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3358
3833
|
async function handleInit() {
|
|
3359
3834
|
const { writeFile: writeFile3, readFile: readFile2, mkdir: mkdir4 } = await import("fs/promises");
|
|
3360
|
-
const { join:
|
|
3361
|
-
const { homedir:
|
|
3835
|
+
const { join: join8 } = await import("path");
|
|
3836
|
+
const { homedir: homedir7 } = await import("os");
|
|
3362
3837
|
const readline = await import("readline");
|
|
3363
3838
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
3364
3839
|
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
@@ -3366,7 +3841,7 @@ async function handleInit() {
|
|
|
3366
3841
|
console.error(" Kairos SDK \u2014 Setup Wizard");
|
|
3367
3842
|
console.error(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
3368
3843
|
console.error("");
|
|
3369
|
-
const envPath =
|
|
3844
|
+
const envPath = join8(process.cwd(), ".env");
|
|
3370
3845
|
let existingEnv = "";
|
|
3371
3846
|
try {
|
|
3372
3847
|
existingEnv = await readFile2(envPath, "utf-8");
|
|
@@ -3430,8 +3905,8 @@ async function handleInit() {
|
|
|
3430
3905
|
});
|
|
3431
3906
|
console.error(` Synced ${result.saved} templates (${result.blocked} blocked, ${result.skippedDuplicate} duplicates)`);
|
|
3432
3907
|
}
|
|
3433
|
-
const kairosDir =
|
|
3434
|
-
await mkdir4(
|
|
3908
|
+
const kairosDir = join8(homedir7(), ".kairos");
|
|
3909
|
+
await mkdir4(join8(kairosDir, "telemetry"), { recursive: true });
|
|
3435
3910
|
console.error("");
|
|
3436
3911
|
console.error(" Setup complete! Try:");
|
|
3437
3912
|
console.error("");
|
|
@@ -3454,6 +3929,9 @@ async function main() {
|
|
|
3454
3929
|
case "patterns":
|
|
3455
3930
|
await handlePatterns(flags);
|
|
3456
3931
|
break;
|
|
3932
|
+
case "sessions":
|
|
3933
|
+
await handleSessions(flags);
|
|
3934
|
+
break;
|
|
3457
3935
|
case "list":
|
|
3458
3936
|
await handleList();
|
|
3459
3937
|
break;
|