@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
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/utils/uuid.ts
|
|
2
|
+
function generateUUID() {
|
|
3
|
+
return crypto.randomUUID();
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
// src/errors/base.ts
|
|
2
7
|
var KairosError = class extends Error {
|
|
3
8
|
constructor(message, cause) {
|
|
@@ -397,6 +402,9 @@ var N8nValidator = class {
|
|
|
397
402
|
this.checkRule21(workflow, issues);
|
|
398
403
|
this.checkRule22(workflow, issues);
|
|
399
404
|
this.checkRule23(workflow, issues);
|
|
405
|
+
this.checkRule24(workflow, issues);
|
|
406
|
+
this.checkRule25(workflow, issues);
|
|
407
|
+
this.checkRule26(workflow, issues);
|
|
400
408
|
if (Array.isArray(workflow.nodes)) {
|
|
401
409
|
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
402
410
|
for (const issue of issues) {
|
|
@@ -529,10 +537,14 @@ var N8nValidator = class {
|
|
|
529
537
|
checkRule11(w, issues) {
|
|
530
538
|
if (!Array.isArray(w.nodes) || typeof w.connections !== "object" || w.connections === null) return;
|
|
531
539
|
const reachable = /* @__PURE__ */ new Set();
|
|
532
|
-
|
|
540
|
+
const aiSubNodeSources = /* @__PURE__ */ new Set();
|
|
541
|
+
for (const [sourceName, outputs] of Object.entries(w.connections)) {
|
|
533
542
|
if (typeof outputs !== "object" || outputs === null) continue;
|
|
534
|
-
|
|
543
|
+
let hasAiPort = false;
|
|
544
|
+
for (const [portName, portGroup] of Object.entries(outputs)) {
|
|
535
545
|
if (!Array.isArray(portGroup)) continue;
|
|
546
|
+
const isAiPort = portName.startsWith("ai_");
|
|
547
|
+
if (isAiPort) hasAiPort = true;
|
|
536
548
|
for (const targets of portGroup) {
|
|
537
549
|
if (!Array.isArray(targets)) continue;
|
|
538
550
|
for (const target of targets) {
|
|
@@ -541,10 +553,13 @@ var N8nValidator = class {
|
|
|
541
553
|
}
|
|
542
554
|
}
|
|
543
555
|
}
|
|
556
|
+
if (hasAiPort) aiSubNodeSources.add(sourceName);
|
|
544
557
|
}
|
|
545
558
|
for (const node of w.nodes) {
|
|
546
559
|
if (node.type.includes("stickyNote")) continue;
|
|
547
|
-
if (
|
|
560
|
+
if (this.isTriggerNode(node)) continue;
|
|
561
|
+
if (aiSubNodeSources.has(node.name)) continue;
|
|
562
|
+
if (!reachable.has(node.name)) {
|
|
548
563
|
this.warn(issues, 11, `Node "${node.name}" has no incoming connections and may never execute`, node.id);
|
|
549
564
|
}
|
|
550
565
|
}
|
|
@@ -740,6 +755,76 @@ var N8nValidator = class {
|
|
|
740
755
|
}
|
|
741
756
|
}
|
|
742
757
|
}
|
|
758
|
+
// Rule 24 (WARN): deprecated accessor syntax in expressions
|
|
759
|
+
checkRule24(w, issues) {
|
|
760
|
+
if (!Array.isArray(w.nodes)) return;
|
|
761
|
+
const deprecated = /\$node\s*\[/;
|
|
762
|
+
for (const node of w.nodes) {
|
|
763
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
764
|
+
if (deprecated.test(expr)) {
|
|
765
|
+
this.warn(
|
|
766
|
+
issues,
|
|
767
|
+
24,
|
|
768
|
+
`Node "${node.name}" uses deprecated accessor $node["..."] \u2014 use $('NodeName').item.json.field instead`,
|
|
769
|
+
node.id
|
|
770
|
+
);
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// Rule 25 (WARN): wrong item index assumptions in expressions
|
|
777
|
+
checkRule25(w, issues) {
|
|
778
|
+
if (!Array.isArray(w.nodes)) return;
|
|
779
|
+
const itemIndex = /\$json\s*\.\s*items\s*\[/;
|
|
780
|
+
for (const node of w.nodes) {
|
|
781
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
782
|
+
if (itemIndex.test(expr)) {
|
|
783
|
+
this.warn(
|
|
784
|
+
issues,
|
|
785
|
+
25,
|
|
786
|
+
`Node "${node.name}" accesses $json.items[n] \u2014 n8n flattens items automatically, use $json.field directly`,
|
|
787
|
+
node.id
|
|
788
|
+
);
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// Rule 26 (WARN): missing .first() or .all() on node references
|
|
795
|
+
checkRule26(w, issues) {
|
|
796
|
+
if (!Array.isArray(w.nodes)) return;
|
|
797
|
+
const bareRef = /\$\(\s*'[^']+'\s*\)\s*\.json/;
|
|
798
|
+
for (const node of w.nodes) {
|
|
799
|
+
for (const expr of this.extractExpressions(node.parameters)) {
|
|
800
|
+
if (bareRef.test(expr)) {
|
|
801
|
+
this.warn(
|
|
802
|
+
issues,
|
|
803
|
+
26,
|
|
804
|
+
`Node "${node.name}" references $('NodeName').json without .first() or .all() \u2014 use $('NodeName').first().json.field`,
|
|
805
|
+
node.id
|
|
806
|
+
);
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
extractExpressions(params) {
|
|
813
|
+
const expressions = [];
|
|
814
|
+
const walk = (val) => {
|
|
815
|
+
if (typeof val === "string") {
|
|
816
|
+
if (val.includes("={{") || val.includes("$node") || val.includes("$('")) {
|
|
817
|
+
expressions.push(val);
|
|
818
|
+
}
|
|
819
|
+
} else if (Array.isArray(val)) {
|
|
820
|
+
for (const item of val) walk(item);
|
|
821
|
+
} else if (val !== null && typeof val === "object") {
|
|
822
|
+
for (const v of Object.values(val)) walk(v);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
walk(params);
|
|
826
|
+
return expressions;
|
|
827
|
+
}
|
|
743
828
|
// Rule 21 (WARN): webhook with responseMode="responseNode" must have respondToWebhook node
|
|
744
829
|
checkRule21(w, issues) {
|
|
745
830
|
if (!Array.isArray(w.nodes)) return;
|
|
@@ -763,470 +848,40 @@ var N8nValidator = class {
|
|
|
763
848
|
}
|
|
764
849
|
};
|
|
765
850
|
|
|
766
|
-
// src/
|
|
767
|
-
import {
|
|
851
|
+
// src/telemetry/collector.ts
|
|
852
|
+
import { appendFile, mkdir } from "fs/promises";
|
|
768
853
|
import { join } from "path";
|
|
769
854
|
import { homedir } from "os";
|
|
770
855
|
|
|
771
|
-
// src/
|
|
772
|
-
var
|
|
773
|
-
|
|
774
|
-
## HARD RULES \u2014 violating any of these causes immediate deployment failure
|
|
775
|
-
|
|
776
|
-
### Forbidden fields \u2014 NEVER include these in the workflow object:
|
|
777
|
-
id, active, createdAt, updatedAt, versionId, meta, isArchived, activeVersionId, activeVersion, pinData, triggerCount, shared, staticData
|
|
778
|
-
|
|
779
|
-
### Required top-level structure:
|
|
780
|
-
{
|
|
781
|
-
"name": "<descriptive name>",
|
|
782
|
-
"nodes": [...],
|
|
783
|
-
"connections": {...},
|
|
784
|
-
"settings": {
|
|
785
|
-
"saveExecutionProgress": true,
|
|
786
|
-
"saveManualExecutions": true,
|
|
787
|
-
"saveDataErrorExecution": "all",
|
|
788
|
-
"saveDataSuccessExecution": "all",
|
|
789
|
-
"executionTimeout": 3600,
|
|
790
|
-
"timezone": "UTC",
|
|
791
|
-
"executionOrder": "v1"
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
### Node IDs:
|
|
796
|
-
- Every node.id must be a valid UUID v4 (random hex, format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
|
797
|
-
- Never reuse IDs, never use sequential fake IDs like "node-1"
|
|
798
|
-
|
|
799
|
-
### Credentials:
|
|
800
|
-
- Only reference credentials with exact type names (see catalog below)
|
|
801
|
-
- If credential ID is unknown, OMIT the credentials block entirely \u2014 never invent credential IDs
|
|
802
|
-
- Never put API keys or tokens in parameters when a credential type exists
|
|
803
|
-
|
|
804
|
-
### Node names:
|
|
805
|
-
- All node names must be unique within the workflow
|
|
806
|
-
- Use descriptive names: "Fetch Open Invoices" not "HTTP Request 2"
|
|
807
|
-
|
|
808
|
-
### Positioning:
|
|
809
|
-
- Trigger node: [250, 300]
|
|
810
|
-
- Each subsequent step: x + 220 minimum
|
|
811
|
-
- Parallel branches: offset y by \xB1150
|
|
812
|
-
- AI sub-nodes: place below their root node (y + 200)
|
|
813
|
-
|
|
814
|
-
---
|
|
815
|
-
|
|
816
|
-
## CONNECTION RULES \u2014 the most common source of errors
|
|
817
|
-
|
|
818
|
-
### Standard connections (main data flow):
|
|
819
|
-
"NodeA": { "main": [ [ { "node": "NodeB", "type": "main", "index": 0 } ] ] }
|
|
820
|
-
|
|
821
|
-
### AI connections \u2014 CRITICAL: the SUB-NODE is the SOURCE, NOT the agent/chain:
|
|
822
|
-
"OpenAI Chat Model": { "ai_languageModel": [ [ { "node": "AI Agent", "type": "ai_languageModel", "index": 0 } ] ] }
|
|
823
|
-
"Simple Memory": { "ai_memory": [ [ { "node": "AI Agent", "type": "ai_memory", "index": 0 } ] ] }
|
|
824
|
-
"Calculator Tool": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] }
|
|
825
|
-
|
|
826
|
-
The AI Agent node does NOT appear in connections as a source for ai_* types.
|
|
827
|
-
Every AI Agent must have at least one ai_languageModel sub-node connected.
|
|
828
|
-
|
|
829
|
-
### IF node \u2014 two output ports (0 = true, 1 = false):
|
|
830
|
-
"IF Check": { "main": [ [{ "node": "True Path", "type": "main", "index": 0 }], [{ "node": "False Path", "type": "main", "index": 0 }] ] }
|
|
831
|
-
|
|
832
|
-
### SplitInBatches \u2014 two output ports (0 = done/finished, 1 = loop body per batch):
|
|
833
|
-
Connect output 0 to the node that runs AFTER all batches complete.
|
|
834
|
-
Connect output 1 to the processing chain for each batch. The last node in the chain loops back to SplitInBatches via main input.
|
|
835
|
-
|
|
836
|
-
### Webhook + RespondToWebhook pattern:
|
|
837
|
-
When webhook responseMode is "responseNode", you MUST include a respondToWebhook node in the flow.
|
|
838
|
-
"Webhook": { "main": [[{ "node": "Process Data", "type": "main", "index": 0 }]] }
|
|
839
|
-
"Process Data": { "main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]] }
|
|
840
|
-
|
|
841
|
-
### Triggers have no incoming connections.
|
|
842
|
-
### Connection keys are NODE NAMES, never node IDs.
|
|
843
|
-
|
|
844
|
-
### Nested parameters:
|
|
845
|
-
Node parameters like conditions, assignments, and rule intervals MUST include all required nested fields. Do not leave nested objects empty or partially filled.
|
|
846
|
-
|
|
847
|
-
---
|
|
848
|
-
|
|
849
|
-
## NODE CATALOG \u2014 exact type strings and safe typeVersions
|
|
850
|
-
|
|
851
|
-
### Triggers (always at least one required):
|
|
852
|
-
n8n-nodes-base.manualTrigger typeVersion: 1 \u2014 testing only
|
|
853
|
-
n8n-nodes-base.scheduleTrigger typeVersion: 1.2 \u2014 params: rule.interval[{field, ...}]
|
|
854
|
-
n8n-nodes-base.webhook typeVersion: 2 \u2014 params: httpMethod, path, responseMode
|
|
855
|
-
n8n-nodes-base.formTrigger typeVersion: 2.2
|
|
856
|
-
n8n-nodes-base.emailReadImap typeVersion: 2 \u2014 cred: imap
|
|
857
|
-
n8n-nodes-base.errorTrigger typeVersion: 1
|
|
858
|
-
n8n-nodes-base.executeWorkflowTrigger typeVersion: 1.1
|
|
859
|
-
n8n-nodes-base.gmailTrigger typeVersion: 1.2 \u2014 cred: gmailOAuth2
|
|
860
|
-
n8n-nodes-base.slackTrigger typeVersion: 1 \u2014 cred: slackApi
|
|
861
|
-
n8n-nodes-base.telegramTrigger typeVersion: 1.2 \u2014 cred: telegramApi
|
|
862
|
-
n8n-nodes-base.githubTrigger typeVersion: 1 \u2014 cred: githubApi
|
|
863
|
-
n8n-nodes-base.airtableTrigger typeVersion: 1 \u2014 cred: airtableTokenApi
|
|
864
|
-
n8n-nodes-base.notionTrigger typeVersion: 1 \u2014 cred: notionApi
|
|
865
|
-
@n8n/n8n-nodes-langchain.chatTrigger typeVersion: 1.1 \u2014 pairs with AI Agent
|
|
866
|
-
|
|
867
|
-
### Core logic:
|
|
868
|
-
n8n-nodes-base.code typeVersion: 2 \u2014 params: mode, jsCode
|
|
869
|
-
n8n-nodes-base.httpRequest typeVersion: 4.2 \u2014 params: method, url, [sendBody, jsonBody, sendHeaders, headerParameters]
|
|
870
|
-
n8n-nodes-base.set typeVersion: 3.4 \u2014 params: assignments.assignments[{id, name, value, type}]
|
|
871
|
-
n8n-nodes-base.if typeVersion: 2.2 \u2014 params: conditions.conditions[{id, leftValue, rightValue, operator}], combinator
|
|
872
|
-
n8n-nodes-base.switch typeVersion: 3.2 \u2014 multi-branch routing
|
|
873
|
-
n8n-nodes-base.filter typeVersion: 2.2 \u2014 params: conditions (same as IF), 1 output
|
|
874
|
-
n8n-nodes-base.merge typeVersion: 3 \u2014 modes: append/combine/chooseBranch
|
|
875
|
-
n8n-nodes-base.splitInBatches typeVersion: 3 \u2014 output 0=done, output 1=loop body
|
|
876
|
-
n8n-nodes-base.wait typeVersion: 1.1
|
|
877
|
-
n8n-nodes-base.executeWorkflow typeVersion: 1.2
|
|
878
|
-
n8n-nodes-base.respondToWebhook typeVersion: 1.1 \u2014 required when webhook responseMode is "responseNode"
|
|
879
|
-
n8n-nodes-base.noOp typeVersion: 1
|
|
880
|
-
n8n-nodes-base.splitOut typeVersion: 1
|
|
881
|
-
n8n-nodes-base.aggregate typeVersion: 1
|
|
882
|
-
n8n-nodes-base.stickyNote typeVersion: 1 \u2014 never connected, canvas annotation only
|
|
883
|
-
|
|
884
|
-
### Email / messaging:
|
|
885
|
-
n8n-nodes-base.emailSend typeVersion: 2.1 \u2014 cred: smtp
|
|
886
|
-
n8n-nodes-base.slack typeVersion: 2.2 \u2014 cred: slackOAuth2Api \u2014 params: resource, operation, select, channelId{__rl}, text
|
|
887
|
-
n8n-nodes-base.telegram typeVersion: 1.2 \u2014 cred: telegramApi
|
|
888
|
-
n8n-nodes-base.discord typeVersion: 2 \u2014 cred: discordWebhookApi
|
|
889
|
-
|
|
890
|
-
### Google:
|
|
891
|
-
n8n-nodes-base.gmail typeVersion: 2.1 \u2014 cred: gmailOAuth2 \u2014 params: resource, operation
|
|
892
|
-
n8n-nodes-base.googleSheets typeVersion: 4.5 \u2014 cred: googleSheetsOAuth2Api \u2014 params: resource, operation, documentId{__rl}, sheetName{__rl}
|
|
893
|
-
n8n-nodes-base.googleDrive typeVersion: 3 \u2014 cred: googleDriveOAuth2Api
|
|
894
|
-
n8n-nodes-base.googleCalendar typeVersion: 1.3 \u2014 cred: googleCalendarOAuth2Api
|
|
895
|
-
|
|
896
|
-
### Productivity:
|
|
897
|
-
n8n-nodes-base.notion typeVersion: 2.2 \u2014 cred: notionApi
|
|
898
|
-
n8n-nodes-base.airtable typeVersion: 2.1 \u2014 cred: airtableTokenApi
|
|
899
|
-
n8n-nodes-base.github typeVersion: 1.1 \u2014 cred: githubApi
|
|
900
|
-
n8n-nodes-base.jira typeVersion: 1 \u2014 cred: jiraSoftwareCloudApi
|
|
901
|
-
n8n-nodes-base.hubspot typeVersion: 2.1 \u2014 cred: hubspotOAuth2Api
|
|
902
|
-
|
|
903
|
-
### Databases:
|
|
904
|
-
n8n-nodes-base.postgres typeVersion: 2.5 \u2014 cred: postgres
|
|
905
|
-
n8n-nodes-base.mySql typeVersion: 2.4 \u2014 cred: mySql
|
|
906
|
-
n8n-nodes-base.redis typeVersion: 1 \u2014 cred: redis
|
|
907
|
-
n8n-nodes-base.supabase typeVersion: 1 \u2014 cred: supabaseApi
|
|
908
|
-
n8n-nodes-base.awsS3 typeVersion: 2 \u2014 cred: aws
|
|
909
|
-
|
|
910
|
-
### AI \u2014 Root nodes (sit on main data flow, receive ai_* connections as TARGETS):
|
|
911
|
-
@n8n/n8n-nodes-langchain.agent typeVersion: 1.9 \u2014 params: promptType, text (if define), options.systemMessage
|
|
912
|
-
@n8n/n8n-nodes-langchain.chainLlm typeVersion: 1.5
|
|
913
|
-
@n8n/n8n-nodes-langchain.chainRetrievalQa typeVersion: 1.4
|
|
914
|
-
@n8n/n8n-nodes-langchain.openAi typeVersion: 1.8 \u2014 cred: openAiApi \u2014 standalone node, calls OpenAI directly without sub-nodes
|
|
915
|
-
@n8n/n8n-nodes-langchain.anthropic typeVersion: 1 \u2014 cred: anthropicApi \u2014 standalone node, calls Anthropic directly without sub-nodes
|
|
916
|
-
|
|
917
|
-
### AI \u2014 Sub-nodes (sources of ai_* connections, wire INTO root nodes above):
|
|
918
|
-
@n8n/n8n-nodes-langchain.lmChatOpenAi typeVersion: 1.7 \u2014 cred: openAiApi \u2014 ai_languageModel \u2014 use with agent/chain, NOT standalone
|
|
919
|
-
@n8n/n8n-nodes-langchain.lmChatAnthropic typeVersion: 1.3 \u2014 cred: anthropicApi \u2014 ai_languageModel \u2014 use with agent/chain, NOT standalone
|
|
920
|
-
@n8n/n8n-nodes-langchain.lmChatGoogleGemini typeVersion: 1 \u2014 cred: googlePalmApi \u2014 ai_languageModel
|
|
921
|
-
@n8n/n8n-nodes-langchain.memoryBufferWindow typeVersion: 1.3 \u2014 \u2014 ai_memory
|
|
922
|
-
@n8n/n8n-nodes-langchain.toolWorkflow typeVersion: 2 \u2014 \u2014 ai_tool
|
|
923
|
-
@n8n/n8n-nodes-langchain.toolCode typeVersion: 1.1 \u2014 \u2014 ai_tool
|
|
924
|
-
@n8n/n8n-nodes-langchain.toolHttpRequest typeVersion: 1.1 \u2014 \u2014 ai_tool
|
|
925
|
-
@n8n/n8n-nodes-langchain.toolCalculator typeVersion: 1 \u2014 \u2014 ai_tool
|
|
926
|
-
|
|
927
|
-
### Resource locator (__rl) format (Google / Slack / Notion modern nodes):
|
|
928
|
-
{ "__rl": true, "mode": "id", "value": "ACTUAL_ID" }
|
|
929
|
-
{ "__rl": true, "mode": "name", "value": "#channel-name" }
|
|
930
|
-
|
|
931
|
-
### App node parameter pattern:
|
|
932
|
-
{ "resource": "message", "operation": "send", ...operation-specific fields }
|
|
933
|
-
|
|
934
|
-
### Schedule Trigger \u2014 daily at 9am example:
|
|
935
|
-
{ "rule": { "interval": [{ "field": "days", "daysInterval": 1, "triggerAtHour": 9, "triggerAtMinute": 0 }] } }
|
|
936
|
-
Cron: { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 * * 1-5" }] } }
|
|
937
|
-
|
|
938
|
-
---
|
|
856
|
+
// src/telemetry/types.ts
|
|
857
|
+
var TELEMETRY_SCHEMA_VERSION = 2;
|
|
939
858
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
function scoreToMode(score) {
|
|
958
|
-
if (score >= DIRECT_THRESHOLD) return "direct";
|
|
959
|
-
if (score >= REFERENCE_THRESHOLD) return "reference";
|
|
960
|
-
return "scratch";
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// src/validation/rule-metadata.ts
|
|
964
|
-
var VALIDATOR_RULE_IDS = Array.from({ length: 23 }, (_, i) => i + 1);
|
|
965
|
-
var RULE_PIPELINE_STAGES = {
|
|
966
|
-
1: "node_generation",
|
|
967
|
-
2: "node_generation",
|
|
968
|
-
3: "node_generation",
|
|
969
|
-
4: "node_generation",
|
|
970
|
-
5: "node_generation",
|
|
971
|
-
6: "node_generation",
|
|
972
|
-
7: "node_generation",
|
|
973
|
-
8: "node_generation",
|
|
974
|
-
9: "connection_wiring",
|
|
975
|
-
10: "connection_wiring",
|
|
976
|
-
11: "connection_wiring",
|
|
977
|
-
12: "workflow_structure",
|
|
978
|
-
13: "node_generation",
|
|
979
|
-
14: "workflow_structure",
|
|
980
|
-
15: "node_generation",
|
|
981
|
-
16: "node_generation",
|
|
982
|
-
17: "credential_injection",
|
|
983
|
-
18: "connection_wiring",
|
|
984
|
-
19: "node_generation",
|
|
985
|
-
20: "connection_wiring",
|
|
986
|
-
21: "workflow_structure",
|
|
987
|
-
22: "workflow_structure",
|
|
988
|
-
23: "node_generation"
|
|
989
|
-
};
|
|
990
|
-
var RULE_MITIGATIONS = {
|
|
991
|
-
1: "Provide a non-empty workflow name string",
|
|
992
|
-
2: "Include at least one node in the nodes array",
|
|
993
|
-
3: "Every node must have a unique UUID v4 string as its id field",
|
|
994
|
-
4: "Ensure all node ids are unique \u2014 no two nodes can share the same id",
|
|
995
|
-
5: "Every node must have a non-empty type string",
|
|
996
|
-
6: "Every node must have a positive integer typeVersion",
|
|
997
|
-
7: "Every node must have a position array of exactly [x, y] numbers",
|
|
998
|
-
8: "Every node must have a non-empty name string",
|
|
999
|
-
9: "connections must be a plain object (use {} if no connections)",
|
|
1000
|
-
10: "Every node name in connections (source and target) must exactly match a name in the nodes array",
|
|
1001
|
-
11: "Every non-trigger node should have at least one incoming connection",
|
|
1002
|
-
12: "Remove forbidden fields: id, active, createdAt, updatedAt, versionId, meta, tags \u2014 these are server-assigned",
|
|
1003
|
-
13: "workflow.settings must be a plain object if present",
|
|
1004
|
-
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
1005
|
-
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
1006
|
-
16: "All node names must be unique within the workflow",
|
|
1007
|
-
17: 'Credentials must be an object with non-empty string id and name fields: { id: "placeholder-id", name: "My Credential" }',
|
|
1008
|
-
18: "AI sub-nodes (languageModel, memory, tool) must be the CONNECTION SOURCE pointing TO the agent \u2014 not the reverse",
|
|
1009
|
-
19: "Use known safe typeVersion values for each node type",
|
|
1010
|
-
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
1011
|
-
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
1012
|
-
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1013
|
-
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync"
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
// src/generation/prompt-builder.ts
|
|
1017
|
-
var CRITICAL_SCORE_THRESHOLD = 0.15;
|
|
1018
|
-
var PromptBuilder = class {
|
|
1019
|
-
patternsPath;
|
|
1020
|
-
constructor(patternsPath) {
|
|
1021
|
-
this.patternsPath = patternsPath ?? join(homedir(), ".kairos", "patterns.json");
|
|
1022
|
-
}
|
|
1023
|
-
build(request, matches, globalFailureRates = [], dynamicCatalog) {
|
|
1024
|
-
const mode = this.resolveMode(matches);
|
|
1025
|
-
const system = this.buildSystem(matches, mode, globalFailureRates, dynamicCatalog);
|
|
1026
|
-
const userMessage = this.buildUserMessage(request, matches, mode);
|
|
1027
|
-
return { system, userMessage, mode, matches };
|
|
1028
|
-
}
|
|
1029
|
-
buildCorrectionMessage(request, matches, allIssues, attempt) {
|
|
1030
|
-
const base = this.buildUserMessage(request, matches, this.resolveMode(matches));
|
|
1031
|
-
return `${base}
|
|
1032
|
-
|
|
1033
|
-
IMPORTANT: A previous generation attempt (attempt ${attempt}) failed validation with these issues:
|
|
1034
|
-
${allIssues.join("\n")}
|
|
1035
|
-
|
|
1036
|
-
Fix ALL of the above issues in your new response. Do not repeat any of these mistakes.`;
|
|
1037
|
-
}
|
|
1038
|
-
resolveMode(matches) {
|
|
1039
|
-
if (matches.length === 0) return "scratch";
|
|
1040
|
-
const top = matches[0];
|
|
1041
|
-
if (!top) return "scratch";
|
|
1042
|
-
return scoreToMode(top.score);
|
|
1043
|
-
}
|
|
1044
|
-
buildSystem(matches, mode, globalFailureRates = [], dynamicCatalog) {
|
|
1045
|
-
let basePrompt = SYSTEM_PROMPT_V1;
|
|
1046
|
-
if (dynamicCatalog) {
|
|
1047
|
-
basePrompt = basePrompt.replace(
|
|
1048
|
-
/## NODE CATALOG — exact type strings and safe typeVersions[\s\S]*?(?=## PRE-DELIVERY SELF-CHECK)/,
|
|
1049
|
-
dynamicCatalog + "\n\n"
|
|
1050
|
-
);
|
|
1051
|
-
}
|
|
1052
|
-
const blocks = [
|
|
1053
|
-
{
|
|
1054
|
-
type: "text",
|
|
1055
|
-
text: basePrompt,
|
|
1056
|
-
cache_control: { type: "ephemeral" }
|
|
1057
|
-
}
|
|
1058
|
-
];
|
|
1059
|
-
if (mode === "reference" && matches.length > 0) {
|
|
1060
|
-
const refText = matches.slice(0, 3).map((m) => {
|
|
1061
|
-
const nodes = m.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1062
|
-
return `Reference workflow: "${m.workflow.description}" (similarity: ${m.score.toFixed(2)})
|
|
1063
|
-
Nodes:
|
|
1064
|
-
${nodes}`;
|
|
1065
|
-
}).join("\n\n");
|
|
1066
|
-
blocks.push({
|
|
1067
|
-
type: "text",
|
|
1068
|
-
text: `## Similar Workflows From Library (for reference only \u2014 adapt, do not copy verbatim)
|
|
1069
|
-
|
|
1070
|
-
${refText}`
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
if (mode === "direct" && matches[0]) {
|
|
1074
|
-
const match = matches[0];
|
|
1075
|
-
const json = JSON.stringify(match.workflow.workflow, null, 2);
|
|
1076
|
-
if (json.length > 3e4) {
|
|
1077
|
-
const nodes = match.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
|
|
1078
|
-
blocks.push({
|
|
1079
|
-
type: "text",
|
|
1080
|
-
text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 too large for full JSON, using reference:
|
|
1081
|
-
Nodes:
|
|
1082
|
-
${nodes}`
|
|
1083
|
-
});
|
|
1084
|
-
} else {
|
|
1085
|
-
blocks.push({
|
|
1086
|
-
type: "text",
|
|
1087
|
-
text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 adapt this structure:
|
|
1088
|
-
|
|
1089
|
-
${json}`
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
if (mode === "scratch" && matches.length > 0 && matches[0].score >= 0.4) {
|
|
1094
|
-
const hint = matches[0];
|
|
1095
|
-
const nodeTypes = hint.workflow.workflow.nodes.map((n) => n.type.split(".").pop()).join(", ");
|
|
1096
|
-
blocks.push({
|
|
1097
|
-
type: "text",
|
|
1098
|
-
text: `## Weak Structural Hint
|
|
1099
|
-
A loosely similar workflow (score: ${hint.score.toFixed(2)}) used these node types: ${nodeTypes}`
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
const warnings = this.buildFailureWarnings(matches, globalFailureRates);
|
|
1103
|
-
if (warnings) {
|
|
1104
|
-
blocks.push({ type: "text", text: warnings });
|
|
1105
|
-
}
|
|
1106
|
-
return blocks;
|
|
1107
|
-
}
|
|
1108
|
-
loadPatterns() {
|
|
1109
|
-
try {
|
|
1110
|
-
const raw = readFileSync(this.patternsPath, "utf-8");
|
|
1111
|
-
const analysis = JSON.parse(raw);
|
|
1112
|
-
const patterns = analysis.topFailureRules ?? [];
|
|
1113
|
-
return patterns.filter((p) => typeof p.pipelineStage === "string" && typeof p.state === "string");
|
|
1114
|
-
} catch {
|
|
1115
|
-
return [];
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
getWarnedRules() {
|
|
1119
|
-
return this.getActivePatterns().map((p) => p.rule);
|
|
1120
|
-
}
|
|
1121
|
-
getActivePatterns() {
|
|
1122
|
-
const MAX_WARNED = 10;
|
|
1123
|
-
const all = this.loadPatterns().filter((p) => p.state !== "resolved" && p.confidence > 0);
|
|
1124
|
-
const regressed = all.filter((p) => p.regressed).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1125
|
-
const confirmed = all.filter((p) => !p.regressed && p.state === "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1126
|
-
const drafts = all.filter((p) => !p.regressed && p.state !== "confirmed").sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1127
|
-
return [...regressed, ...confirmed, ...drafts].slice(0, MAX_WARNED);
|
|
1128
|
-
}
|
|
1129
|
-
buildFailureWarnings(matches, globalFailureRates) {
|
|
1130
|
-
const richPatterns = this.getActivePatterns();
|
|
1131
|
-
if (richPatterns.length > 0) {
|
|
1132
|
-
return this.buildStageGroupedWarnings(richPatterns, matches);
|
|
1133
|
-
}
|
|
1134
|
-
return this.buildLegacyWarnings(matches, globalFailureRates);
|
|
1135
|
-
}
|
|
1136
|
-
buildStageGroupedWarnings(patterns, matches) {
|
|
1137
|
-
const stageLabels = {
|
|
1138
|
-
credential_injection: "CREDENTIAL FORMATTING",
|
|
1139
|
-
connection_wiring: "CONNECTION WIRING",
|
|
1140
|
-
node_generation: "NODE GENERATION",
|
|
1141
|
-
workflow_structure: "WORKFLOW STRUCTURE"
|
|
859
|
+
// src/telemetry/collector.ts
|
|
860
|
+
var TelemetryCollector = class {
|
|
861
|
+
dir;
|
|
862
|
+
sessionId;
|
|
863
|
+
dirReady = null;
|
|
864
|
+
constructor(dir) {
|
|
865
|
+
this.dir = dir ?? join(homedir(), ".kairos", "telemetry");
|
|
866
|
+
this.sessionId = generateUUID();
|
|
867
|
+
}
|
|
868
|
+
async emit(eventType, data, runId) {
|
|
869
|
+
const event = {
|
|
870
|
+
schemaVersion: TELEMETRY_SCHEMA_VERSION,
|
|
871
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
872
|
+
sessionId: this.sessionId,
|
|
873
|
+
...runId ? { runId } : {},
|
|
874
|
+
eventType,
|
|
875
|
+
data
|
|
1142
876
|
};
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
list.push(p);
|
|
1147
|
-
byStage.set(p.pipelineStage, list);
|
|
1148
|
-
}
|
|
1149
|
-
const sections = [];
|
|
1150
|
-
for (const [stage, stagePatterns] of byStage) {
|
|
1151
|
-
const label = stageLabels[stage] ?? stage;
|
|
1152
|
-
const byMitigation = /* @__PURE__ */ new Map();
|
|
1153
|
-
for (const p of stagePatterns) {
|
|
1154
|
-
const key = p.mitigation ?? `rule_${p.rule}`;
|
|
1155
|
-
const list = byMitigation.get(key) ?? [];
|
|
1156
|
-
list.push(p);
|
|
1157
|
-
byMitigation.set(key, list);
|
|
1158
|
-
}
|
|
1159
|
-
const lines = [];
|
|
1160
|
-
for (const group of byMitigation.values()) {
|
|
1161
|
-
if (group.length === 1) {
|
|
1162
|
-
const p = group[0];
|
|
1163
|
-
const urgency = p.regressed ? "CRITICAL REGRESSION: " : (p.compositeScore ?? 0) >= CRITICAL_SCORE_THRESHOLD ? "CRITICAL: " : "";
|
|
1164
|
-
const statePrefix = p.state === "confirmed" ? "[CONFIRMED] " : "";
|
|
1165
|
-
const trendSuffix = p.trend === "worsening" ? " (GETTING WORSE)" : p.trend === "improving" ? " (improving)" : "";
|
|
1166
|
-
const remedy = p.mitigation ?? RULE_MITIGATIONS[p.rule];
|
|
1167
|
-
const remedyStr = remedy ? `
|
|
1168
|
-
Fix: ${remedy}` : "";
|
|
1169
|
-
lines.push(`- ${urgency}${statePrefix}Rule ${p.rule}${trendSuffix}: ${p.exampleMessages[0] ?? "No example"}${remedyStr}`);
|
|
1170
|
-
} else {
|
|
1171
|
-
const ruleNums = group.map((p) => p.rule).join(", ");
|
|
1172
|
-
const totalFailures = group.reduce((s, p) => s + p.failureCount, 0);
|
|
1173
|
-
const hasConfirmed = group.some((p) => p.state === "confirmed");
|
|
1174
|
-
const statePrefix = hasConfirmed ? "[CONFIRMED] " : "";
|
|
1175
|
-
const remedy = group[0].mitigation;
|
|
1176
|
-
const remedyStr = remedy ? `
|
|
1177
|
-
Fix: ${remedy}` : "";
|
|
1178
|
-
lines.push(`- ${statePrefix}Rules ${ruleNums} (${totalFailures} failures combined): same root cause${remedyStr}`);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
sections.push(`### ${label}
|
|
1182
|
-
${lines.join("\n")}`);
|
|
1183
|
-
}
|
|
1184
|
-
for (const match of matches) {
|
|
1185
|
-
const fps = match.workflow.failurePatterns;
|
|
1186
|
-
if (!fps?.length) continue;
|
|
1187
|
-
const coveredRules = new Set(patterns.map((p) => p.rule));
|
|
1188
|
-
const extra = fps.filter((fp) => !coveredRules.has(fp.rule));
|
|
1189
|
-
for (const fp of extra) {
|
|
1190
|
-
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1191
|
-
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1192
|
-
sections.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen in similar workflows)`);
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
if (sections.length === 0) return null;
|
|
1196
|
-
return `## Known Failure Patterns \u2014 AVOID THESE
|
|
1197
|
-
|
|
1198
|
-
Grouped by generation stage. Fix these BEFORE outputting your response:
|
|
1199
|
-
|
|
1200
|
-
${sections.join("\n\n")}`;
|
|
1201
|
-
}
|
|
1202
|
-
buildLegacyWarnings(matches, globalFailureRates) {
|
|
1203
|
-
const lines = [];
|
|
1204
|
-
for (const match of matches) {
|
|
1205
|
-
const patterns = match.workflow.failurePatterns;
|
|
1206
|
-
if (!patterns?.length) continue;
|
|
1207
|
-
for (const fp of patterns) {
|
|
1208
|
-
const remedy = RULE_MITIGATIONS[fp.rule];
|
|
1209
|
-
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1210
|
-
lines.push(`- Rule ${fp.rule}: "${fp.message}"${remedyStr} (seen ${fp.occurrences}x in similar workflows)`);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
const highFreqRules = globalFailureRates.filter((r) => r.rate >= 0.15);
|
|
1214
|
-
for (const rule of highFreqRules) {
|
|
1215
|
-
const remedy = RULE_MITIGATIONS[rule.rule];
|
|
1216
|
-
const remedyStr = remedy ? ` \u2014 Fix: ${remedy}` : "";
|
|
1217
|
-
lines.push(`- Rule ${rule.rule}: "${rule.commonMessage}"${remedyStr} (fails in ${Math.round(rule.rate * 100)}% of all builds)`);
|
|
877
|
+
if (!this.dirReady) {
|
|
878
|
+
this.dirReady = mkdir(this.dir, { recursive: true }).then(() => {
|
|
879
|
+
});
|
|
1218
880
|
}
|
|
1219
|
-
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
Previous builds frequently failed the following validation rules. Ensure your output does NOT repeat these mistakes:
|
|
1224
|
-
${unique.join("\n")}`;
|
|
1225
|
-
}
|
|
1226
|
-
buildUserMessage(request, _matches, _mode) {
|
|
1227
|
-
const namePart = request.name ? `
|
|
1228
|
-
Workflow name: "${request.name}"` : "";
|
|
1229
|
-
return `Build a workflow that: ${request.description}${namePart}`;
|
|
881
|
+
await this.dirReady;
|
|
882
|
+
const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
|
|
883
|
+
const filepath = join(this.dir, filename);
|
|
884
|
+
await appendFile(filepath, JSON.stringify(event) + "\n", "utf-8");
|
|
1230
885
|
}
|
|
1231
886
|
};
|
|
1232
887
|
|
|
@@ -1334,13 +989,93 @@ var TelemetryReader = class {
|
|
|
1334
989
|
};
|
|
1335
990
|
|
|
1336
991
|
// src/telemetry/pattern-analyzer.ts
|
|
1337
|
-
import { writeFile, readFile as fsReadFile, appendFile, mkdir, rename } from "fs/promises";
|
|
992
|
+
import { writeFile, readFile as fsReadFile, appendFile as appendFile2, mkdir as mkdir2, rename } from "fs/promises";
|
|
1338
993
|
import { join as join4 } from "path";
|
|
1339
994
|
import { homedir as homedir3 } from "os";
|
|
995
|
+
|
|
996
|
+
// src/validation/rule-metadata.ts
|
|
997
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 26 }, (_, i) => i + 1);
|
|
998
|
+
var RULE_PIPELINE_STAGES = {
|
|
999
|
+
1: "node_generation",
|
|
1000
|
+
2: "node_generation",
|
|
1001
|
+
3: "node_generation",
|
|
1002
|
+
4: "node_generation",
|
|
1003
|
+
5: "node_generation",
|
|
1004
|
+
6: "node_generation",
|
|
1005
|
+
7: "node_generation",
|
|
1006
|
+
8: "node_generation",
|
|
1007
|
+
9: "connection_wiring",
|
|
1008
|
+
10: "connection_wiring",
|
|
1009
|
+
11: "connection_wiring",
|
|
1010
|
+
12: "workflow_structure",
|
|
1011
|
+
13: "node_generation",
|
|
1012
|
+
14: "workflow_structure",
|
|
1013
|
+
15: "node_generation",
|
|
1014
|
+
16: "node_generation",
|
|
1015
|
+
17: "credential_injection",
|
|
1016
|
+
18: "connection_wiring",
|
|
1017
|
+
19: "node_generation",
|
|
1018
|
+
20: "connection_wiring",
|
|
1019
|
+
21: "workflow_structure",
|
|
1020
|
+
22: "workflow_structure",
|
|
1021
|
+
23: "node_generation",
|
|
1022
|
+
24: "expression_syntax",
|
|
1023
|
+
25: "expression_syntax",
|
|
1024
|
+
26: "expression_syntax"
|
|
1025
|
+
};
|
|
1026
|
+
var RULE_EXAMPLES = {
|
|
1027
|
+
17: {
|
|
1028
|
+
bad: '"credentials": { "slackOAuth2Api": "my-token" }',
|
|
1029
|
+
good: '"credentials": { "slackOAuth2Api": { "id": "placeholder-id", "name": "My Slack OAuth" } }'
|
|
1030
|
+
},
|
|
1031
|
+
24: {
|
|
1032
|
+
bad: '$node["Fetch Data"].json.email',
|
|
1033
|
+
good: "$('Fetch Data').item.json.email"
|
|
1034
|
+
},
|
|
1035
|
+
25: {
|
|
1036
|
+
bad: "$json.items[0].email",
|
|
1037
|
+
good: "$json.email"
|
|
1038
|
+
},
|
|
1039
|
+
26: {
|
|
1040
|
+
bad: "$('Fetch Data').json.email",
|
|
1041
|
+
good: "$('Fetch Data').first().json.email"
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
var RULE_MITIGATIONS = {
|
|
1045
|
+
1: "Provide a non-empty workflow name string",
|
|
1046
|
+
2: "Include at least one node in the nodes array",
|
|
1047
|
+
3: "Every node must have a unique UUID v4 string as its id field",
|
|
1048
|
+
4: "Ensure all node ids are unique \u2014 no two nodes can share the same id",
|
|
1049
|
+
5: "Every node must have a non-empty type string",
|
|
1050
|
+
6: "Every node must have a positive integer typeVersion",
|
|
1051
|
+
7: "Every node must have a position array of exactly [x, y] numbers",
|
|
1052
|
+
8: "Every node must have a non-empty name string",
|
|
1053
|
+
9: "connections must be a plain object (use {} if no connections)",
|
|
1054
|
+
10: "Every node name in connections (source and target) must exactly match a name in the nodes array",
|
|
1055
|
+
11: "Every non-trigger node should have at least one incoming connection",
|
|
1056
|
+
12: "Remove forbidden fields: id, active, createdAt, updatedAt, versionId, meta, tags \u2014 these are server-assigned",
|
|
1057
|
+
13: "workflow.settings must be a plain object if present",
|
|
1058
|
+
14: "Include at least one trigger node (e.g. scheduleTrigger, webhookTrigger, manualTrigger, or service-specific)",
|
|
1059
|
+
15: 'Node type strings must be fully qualified: "n8n-nodes-base.httpRequest" not just "httpRequest"',
|
|
1060
|
+
16: "All node names must be unique within the workflow",
|
|
1061
|
+
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',
|
|
1062
|
+
18: "AI sub-nodes (languageModel, memory, tool) must be the CONNECTION SOURCE pointing TO the agent \u2014 not the reverse",
|
|
1063
|
+
19: "Use known safe typeVersion values for each node type",
|
|
1064
|
+
20: "Remove connection cycles \u2014 ensure no node can reach itself through the connection graph",
|
|
1065
|
+
21: 'When using webhook with responseMode "responseNode", include a respondToWebhook node in the flow',
|
|
1066
|
+
22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)",
|
|
1067
|
+
23: "Use node types that exist in the n8n registry \u2014 check with kairos_sync",
|
|
1068
|
+
24: 'Use modern accessor syntax: $("NodeName").item.json.field instead of deprecated $node["NodeName"].json.field',
|
|
1069
|
+
25: "Access item fields directly with $json.field \u2014 n8n flattens items automatically, do not use $json.items[0]",
|
|
1070
|
+
26: 'Use $("NodeName").first().json.field or $("NodeName").all() \u2014 bare $("NodeName").json without .first() or .all() throws at runtime'
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
// src/telemetry/pattern-analyzer.ts
|
|
1340
1074
|
var PATTERN_SCHEMA_VERSION = 2;
|
|
1341
1075
|
var PatternAnalyzer = class _PatternAnalyzer {
|
|
1342
1076
|
telemetryDir;
|
|
1343
1077
|
outputDir;
|
|
1078
|
+
_cachedEvents = null;
|
|
1344
1079
|
constructor(telemetryDir) {
|
|
1345
1080
|
const defaultDir = join4(homedir3(), ".kairos", "telemetry");
|
|
1346
1081
|
this.telemetryDir = telemetryDir ?? defaultDir;
|
|
@@ -1369,19 +1104,23 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1369
1104
|
}));
|
|
1370
1105
|
}
|
|
1371
1106
|
if (fromVersion < 2) {
|
|
1372
|
-
migrated = migrated.map((p) =>
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
...p
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1107
|
+
migrated = migrated.map((p) => {
|
|
1108
|
+
const sf = p.scoringFactors ?? { rawConfidence: 0, impact: 0, recency: 0, stickinessBoost: 0 };
|
|
1109
|
+
return {
|
|
1110
|
+
...p,
|
|
1111
|
+
scoringFactors: {
|
|
1112
|
+
...sf,
|
|
1113
|
+
stickinessBoost: sf.stickinessBoost ?? sf["validationBoost"] ?? 0
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
});
|
|
1379
1117
|
}
|
|
1380
1118
|
return migrated;
|
|
1381
1119
|
}
|
|
1382
1120
|
async analyze(days = 30) {
|
|
1383
1121
|
const previousPatterns = await this.loadPreviousPatterns();
|
|
1384
1122
|
const events = await this.readAllEvents(days);
|
|
1123
|
+
this._cachedEvents = events;
|
|
1385
1124
|
const starts = events.filter((e) => e.eventType === "build_start");
|
|
1386
1125
|
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
1387
1126
|
const passed = attempts.filter(
|
|
@@ -1394,13 +1133,18 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1394
1133
|
const credentialFailures = /* @__PURE__ */ new Map();
|
|
1395
1134
|
for (const a of failed) {
|
|
1396
1135
|
const weight = this.recencyWeight(a.fileDate);
|
|
1136
|
+
const buildId = a.runId ?? a.sessionId;
|
|
1397
1137
|
const data = a.data;
|
|
1398
1138
|
for (const issue of data.issues ?? []) {
|
|
1399
|
-
|
|
1139
|
+
if (issue.severity === "warn") continue;
|
|
1140
|
+
const entry = ruleFailures.get(issue.rule) ?? { count: 0, sessions: /* @__PURE__ */ new Set(), recencyWeights: [], allMessages: [], workflowTypes: /* @__PURE__ */ new Map() };
|
|
1400
1141
|
entry.count++;
|
|
1401
|
-
entry.sessions.add(
|
|
1142
|
+
entry.sessions.add(buildId);
|
|
1402
1143
|
entry.recencyWeights.push(weight);
|
|
1403
1144
|
entry.allMessages.push(issue.message);
|
|
1145
|
+
if (data.workflowType) {
|
|
1146
|
+
entry.workflowTypes.set(data.workflowType, (entry.workflowTypes.get(data.workflowType) ?? 0) + 1);
|
|
1147
|
+
}
|
|
1404
1148
|
ruleFailures.set(issue.rule, entry);
|
|
1405
1149
|
if (issue.rule === 17) {
|
|
1406
1150
|
const credPatterns = [
|
|
@@ -1453,9 +1197,10 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1453
1197
|
}
|
|
1454
1198
|
const sessions = /* @__PURE__ */ new Map();
|
|
1455
1199
|
for (const a of attempts) {
|
|
1456
|
-
const
|
|
1200
|
+
const buildId = a.runId ?? a.sessionId;
|
|
1201
|
+
const list = sessions.get(buildId) ?? [];
|
|
1457
1202
|
list.push(a);
|
|
1458
|
-
sessions.set(
|
|
1203
|
+
sessions.set(buildId, list);
|
|
1459
1204
|
}
|
|
1460
1205
|
let firstTryPass = 0;
|
|
1461
1206
|
let correctionNeeded = 0;
|
|
@@ -1502,7 +1247,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1502
1247
|
const avgRecency = entry.recencyWeights.length > 0 ? entry.recencyWeights.reduce((s, w) => s + w, 0) / entry.recencyWeights.length : 1;
|
|
1503
1248
|
const stickiness = stickinessCount.get(rule) ?? 0;
|
|
1504
1249
|
const { compositeScore, factors } = this.computeCompositeScore(rawConfidence, entry.count, state, avgRecency, stickiness);
|
|
1505
|
-
|
|
1250
|
+
const pattern = {
|
|
1506
1251
|
rule,
|
|
1507
1252
|
failureCount: entry.count,
|
|
1508
1253
|
confidence: Math.round(rawConfidence * 1e3) / 1e3,
|
|
@@ -1514,6 +1259,10 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1514
1259
|
exampleMessages: this.deduplicateMessages(entry.allMessages),
|
|
1515
1260
|
mitigation: RULE_MITIGATIONS[rule] ?? null
|
|
1516
1261
|
};
|
|
1262
|
+
if (entry.workflowTypes.size > 0) {
|
|
1263
|
+
pattern.workflowTypeBreakdown = Object.fromEntries(entry.workflowTypes);
|
|
1264
|
+
}
|
|
1265
|
+
return pattern;
|
|
1517
1266
|
}).sort((a, b) => b.compositeScore - a.compositeScore);
|
|
1518
1267
|
const activeRules = new Set(activePatterns.map((p) => p.rule));
|
|
1519
1268
|
for (const p of activePatterns) {
|
|
@@ -1570,7 +1319,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1570
1319
|
const warned = bcData.warnedRules ?? [];
|
|
1571
1320
|
if (warned.length === 0) continue;
|
|
1572
1321
|
const sessionFailedRules = /* @__PURE__ */ new Set();
|
|
1573
|
-
const sessionAttempts = sessions.get(bc.sessionId) ?? [];
|
|
1322
|
+
const sessionAttempts = sessions.get(bc.runId ?? bc.sessionId) ?? [];
|
|
1574
1323
|
for (const a of sessionAttempts) {
|
|
1575
1324
|
const ad = a.data;
|
|
1576
1325
|
if (ad.validationPassed === false) {
|
|
@@ -1637,7 +1386,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1637
1386
|
}
|
|
1638
1387
|
async analyzeAndSave(days = 30) {
|
|
1639
1388
|
const analysis = await this.analyze(days);
|
|
1640
|
-
await
|
|
1389
|
+
await mkdir2(this.outputDir, { recursive: true });
|
|
1641
1390
|
const outputPath = join4(this.outputDir, "patterns.json");
|
|
1642
1391
|
const tmpPath = `${outputPath}.tmp`;
|
|
1643
1392
|
await writeFile(tmpPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
@@ -1652,9 +1401,56 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1652
1401
|
topRules: analysis.topFailureRules.filter((p) => p.state !== "resolved").slice(0, 5).map((p) => ({ rule: p.rule, compositeScore: p.compositeScore, state: p.state }))
|
|
1653
1402
|
};
|
|
1654
1403
|
const historyPath = join4(this.outputDir, "pattern-history.jsonl");
|
|
1655
|
-
await
|
|
1404
|
+
await appendFile2(historyPath, JSON.stringify(historySummary) + "\n", "utf-8");
|
|
1405
|
+
const sessions = await this.buildSessionSummaries(days);
|
|
1406
|
+
const sessionHistoryPath = join4(this.outputDir, "session-history.json");
|
|
1407
|
+
const sessionHistoryTmp = `${sessionHistoryPath}.tmp`;
|
|
1408
|
+
await writeFile(sessionHistoryTmp, JSON.stringify(sessions, null, 2), "utf-8");
|
|
1409
|
+
await rename(sessionHistoryTmp, sessionHistoryPath);
|
|
1656
1410
|
return analysis;
|
|
1657
1411
|
}
|
|
1412
|
+
async getSessions(limit = 20) {
|
|
1413
|
+
try {
|
|
1414
|
+
const raw = await fsReadFile(join4(this.outputDir, "session-history.json"), "utf-8");
|
|
1415
|
+
const all = JSON.parse(raw);
|
|
1416
|
+
return all.slice(-limit);
|
|
1417
|
+
} catch {
|
|
1418
|
+
return [];
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
async buildSessionSummaries(days = 30) {
|
|
1422
|
+
const events = this._cachedEvents ?? await this.readAllEvents(days);
|
|
1423
|
+
const buildCompletes = events.filter((e) => e.eventType === "build_complete");
|
|
1424
|
+
const attemptsByBuild = /* @__PURE__ */ new Map();
|
|
1425
|
+
for (const e of events.filter((e2) => e2.eventType === "generation_attempt")) {
|
|
1426
|
+
const buildId = e.runId ?? e.sessionId;
|
|
1427
|
+
const list = attemptsByBuild.get(buildId) ?? [];
|
|
1428
|
+
list.push(e);
|
|
1429
|
+
attemptsByBuild.set(buildId, list);
|
|
1430
|
+
}
|
|
1431
|
+
const summaries = buildCompletes.map((bc) => {
|
|
1432
|
+
const data = bc.data;
|
|
1433
|
+
const sessionAttempts = attemptsByBuild.get(bc.runId ?? bc.sessionId) ?? [];
|
|
1434
|
+
const failedRules = Array.from(new Set(
|
|
1435
|
+
sessionAttempts.flatMap((a) => {
|
|
1436
|
+
const ad = a.data;
|
|
1437
|
+
if (ad.validationPassed !== false) return [];
|
|
1438
|
+
return (ad.issues ?? []).map((i) => i.rule);
|
|
1439
|
+
})
|
|
1440
|
+
));
|
|
1441
|
+
return {
|
|
1442
|
+
sessionId: bc.sessionId,
|
|
1443
|
+
date: bc.fileDate,
|
|
1444
|
+
description: data.description ?? "",
|
|
1445
|
+
workflowType: data.workflowType ?? null,
|
|
1446
|
+
attempts: data.totalAttempts ?? 1,
|
|
1447
|
+
success: data.success ?? false,
|
|
1448
|
+
failedRules,
|
|
1449
|
+
workflowName: data.workflowName ?? null
|
|
1450
|
+
};
|
|
1451
|
+
});
|
|
1452
|
+
return summaries.sort((a, b) => a.date.localeCompare(b.date));
|
|
1453
|
+
}
|
|
1658
1454
|
async getHistory(limit = 20) {
|
|
1659
1455
|
try {
|
|
1660
1456
|
const raw = await fsReadFile(join4(this.outputDir, "pattern-history.jsonl"), "utf-8");
|
|
@@ -1676,7 +1472,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
1676
1472
|
alerts.push({
|
|
1677
1473
|
type: "stale_pattern",
|
|
1678
1474
|
rule: p.rule,
|
|
1679
|
-
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-
|
|
1475
|
+
message: `Pattern references Rule ${p.rule} which does not exist in the current validator (rules 1-26)`
|
|
1680
1476
|
});
|
|
1681
1477
|
}
|
|
1682
1478
|
}
|
|
@@ -1979,13 +1775,17 @@ function rerank(candidates, clusters) {
|
|
|
1979
1775
|
}
|
|
1980
1776
|
|
|
1981
1777
|
// src/library/file-library.ts
|
|
1982
|
-
import { readFile, writeFile as writeFile2, rename as rename2, mkdir as
|
|
1778
|
+
import { readFile, writeFile as writeFile2, rename as rename2, mkdir as mkdir3, stat } from "fs/promises";
|
|
1983
1779
|
import { join as join5 } from "path";
|
|
1984
1780
|
import { homedir as homedir4 } from "os";
|
|
1985
1781
|
|
|
1986
|
-
// src/utils/
|
|
1987
|
-
|
|
1988
|
-
|
|
1782
|
+
// src/utils/thresholds.ts
|
|
1783
|
+
var DIRECT_THRESHOLD = 0.92;
|
|
1784
|
+
var REFERENCE_THRESHOLD = 0.72;
|
|
1785
|
+
function scoreToMode(score) {
|
|
1786
|
+
if (score >= DIRECT_THRESHOLD) return "direct";
|
|
1787
|
+
if (score >= REFERENCE_THRESHOLD) return "reference";
|
|
1788
|
+
return "scratch";
|
|
1989
1789
|
}
|
|
1990
1790
|
|
|
1991
1791
|
// src/library/file-library.ts
|
|
@@ -2001,14 +1801,28 @@ function buildSearchCorpus(w) {
|
|
|
2001
1801
|
return `${w.description} ${w.workflow.name} ${w.tags.join(" ")} ${nodeTokens.join(" ")}`;
|
|
2002
1802
|
}
|
|
2003
1803
|
var MAX_LIBRARY_SIZE = 500;
|
|
1804
|
+
function isValidMeta(item) {
|
|
1805
|
+
return typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.description === "string" && typeof item.workflowName === "string" && Array.isArray(item.cachedNodeTypes);
|
|
1806
|
+
}
|
|
1807
|
+
function isValidOldEntry(item) {
|
|
1808
|
+
return typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.description === "string" && typeof item.workflow === "object" && item.workflow !== null && Array.isArray(
|
|
1809
|
+
item.workflow.nodes
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
2004
1812
|
var FileLibrary = class {
|
|
2005
1813
|
dir;
|
|
2006
|
-
|
|
1814
|
+
meta = [];
|
|
2007
1815
|
initPromise = null;
|
|
2008
1816
|
writeQueue = Promise.resolve();
|
|
2009
1817
|
constructor(dir) {
|
|
2010
1818
|
this.dir = dir ?? join5(homedir4(), ".kairos", "library");
|
|
2011
1819
|
}
|
|
1820
|
+
get workflowsDir() {
|
|
1821
|
+
return join5(this.dir, "workflows");
|
|
1822
|
+
}
|
|
1823
|
+
workflowFilePath(id) {
|
|
1824
|
+
return join5(this.workflowsDir, `${id}.json`);
|
|
1825
|
+
}
|
|
2012
1826
|
async initialize() {
|
|
2013
1827
|
if (!this.initPromise) {
|
|
2014
1828
|
this.initPromise = this.doInitialize();
|
|
@@ -2016,62 +1830,149 @@ var FileLibrary = class {
|
|
|
2016
1830
|
return this.initPromise;
|
|
2017
1831
|
}
|
|
2018
1832
|
async doInitialize() {
|
|
2019
|
-
await
|
|
1833
|
+
await mkdir3(this.dir, { recursive: true });
|
|
2020
1834
|
const indexPath = join5(this.dir, "index.json");
|
|
1835
|
+
let workflowsDirExists = false;
|
|
2021
1836
|
try {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
);
|
|
1837
|
+
await stat(this.workflowsDir);
|
|
1838
|
+
workflowsDirExists = true;
|
|
1839
|
+
} catch {
|
|
1840
|
+
}
|
|
1841
|
+
if (workflowsDirExists) {
|
|
1842
|
+
try {
|
|
1843
|
+
const raw = await readFile(indexPath, "utf-8");
|
|
1844
|
+
const parsed = JSON.parse(raw);
|
|
1845
|
+
if (Array.isArray(parsed)) {
|
|
1846
|
+
this.meta = parsed.filter(isValidMeta);
|
|
1847
|
+
}
|
|
1848
|
+
} catch {
|
|
1849
|
+
this.meta = [];
|
|
2030
1850
|
}
|
|
1851
|
+
} else {
|
|
1852
|
+
try {
|
|
1853
|
+
const raw = await readFile(indexPath, "utf-8");
|
|
1854
|
+
const parsed = JSON.parse(raw);
|
|
1855
|
+
if (Array.isArray(parsed) && parsed.length > 0 && isValidOldEntry(parsed[0])) {
|
|
1856
|
+
await this.migrateFromMonolithic(parsed.filter(isValidOldEntry));
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
} catch {
|
|
1860
|
+
}
|
|
1861
|
+
this.meta = [];
|
|
1862
|
+
await mkdir3(this.workflowsDir, { recursive: true });
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* One-time transparent migration from v0.4.x monolithic index.json.
|
|
1867
|
+
* Splits each stored workflow into a per-file workflow JSON and a lightweight
|
|
1868
|
+
* meta entry. Rewrites index.json in the new format.
|
|
1869
|
+
*/
|
|
1870
|
+
async migrateFromMonolithic(oldEntries) {
|
|
1871
|
+
await mkdir3(this.workflowsDir, { recursive: true });
|
|
1872
|
+
const newMeta = [];
|
|
1873
|
+
for (const entry of oldEntries) {
|
|
1874
|
+
const wfPath = this.workflowFilePath(entry.id);
|
|
1875
|
+
const tmpPath = `${wfPath}.tmp`;
|
|
1876
|
+
await writeFile2(tmpPath, JSON.stringify(entry.workflow), "utf-8");
|
|
1877
|
+
await rename2(tmpPath, wfPath);
|
|
1878
|
+
const { workflow, ...metaFields } = entry;
|
|
1879
|
+
newMeta.push({
|
|
1880
|
+
...metaFields,
|
|
1881
|
+
workflowName: workflow.name,
|
|
1882
|
+
cachedNodeTypes: workflow.nodes.map((n) => n.type)
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
this.meta = newMeta;
|
|
1886
|
+
await this.persistNow();
|
|
1887
|
+
}
|
|
1888
|
+
async loadWorkflowFile(id) {
|
|
1889
|
+
try {
|
|
1890
|
+
const raw = await readFile(this.workflowFilePath(id), "utf-8");
|
|
1891
|
+
return JSON.parse(raw);
|
|
2031
1892
|
} catch {
|
|
2032
|
-
|
|
1893
|
+
return null;
|
|
2033
1894
|
}
|
|
2034
1895
|
}
|
|
1896
|
+
async writeWorkflowFile(id, workflow) {
|
|
1897
|
+
const wfPath = this.workflowFilePath(id);
|
|
1898
|
+
const tmpPath = `${wfPath}.tmp`;
|
|
1899
|
+
await writeFile2(tmpPath, JSON.stringify(workflow), "utf-8");
|
|
1900
|
+
await rename2(tmpPath, wfPath);
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Build a lightweight StoredWorkflow shell from a meta entry for use in
|
|
1904
|
+
* scoring / clustering. Only node.type is populated in each node — no other
|
|
1905
|
+
* node fields are used by hybridScore or clusterWorkflows.
|
|
1906
|
+
*/
|
|
1907
|
+
makeSearchShell(m) {
|
|
1908
|
+
return {
|
|
1909
|
+
...m,
|
|
1910
|
+
workflow: {
|
|
1911
|
+
name: m.workflowName,
|
|
1912
|
+
nodes: m.cachedNodeTypes.map((type) => ({
|
|
1913
|
+
id: "",
|
|
1914
|
+
name: "",
|
|
1915
|
+
type,
|
|
1916
|
+
typeVersion: 1,
|
|
1917
|
+
position: [0, 0],
|
|
1918
|
+
parameters: {}
|
|
1919
|
+
})),
|
|
1920
|
+
connections: {}
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
2035
1924
|
async search(description, options) {
|
|
2036
|
-
const
|
|
2037
|
-
if (
|
|
1925
|
+
const filteredMeta = this.meta.filter((m) => m.trustLevel !== "blocked");
|
|
1926
|
+
if (filteredMeta.length === 0) return [];
|
|
2038
1927
|
const limit = options?.limit ?? 3;
|
|
2039
1928
|
const queryTokens = tokenize(description);
|
|
2040
1929
|
if (queryTokens.length === 0) return [];
|
|
2041
|
-
const
|
|
1930
|
+
const shells = filteredMeta.map((m) => this.makeSearchShell(m));
|
|
1931
|
+
const docTokenArrays = shells.map((w) => tokenize(buildSearchCorpus(w)));
|
|
2042
1932
|
const docTokenSets = docTokenArrays.map((tokens) => new Set(tokens));
|
|
2043
|
-
const docCount =
|
|
1933
|
+
const docCount = shells.length;
|
|
2044
1934
|
const idf = /* @__PURE__ */ new Map();
|
|
2045
1935
|
const allTokens = new Set(queryTokens);
|
|
2046
1936
|
for (const token of allTokens) {
|
|
2047
1937
|
const docsWithToken = docTokenSets.filter((d) => d.has(token)).length;
|
|
2048
1938
|
idf.set(token, Math.log((docCount + 1) / (docsWithToken + 1)) + 1);
|
|
2049
1939
|
}
|
|
2050
|
-
const scored = hybridScore(queryTokens, description,
|
|
2051
|
-
const clusters = clusterWorkflows(
|
|
1940
|
+
const scored = hybridScore(queryTokens, description, shells, docTokenArrays, idf).filter((m) => m.score > 0).sort((a, b) => b.score - a.score);
|
|
1941
|
+
const clusters = clusterWorkflows(shells);
|
|
2052
1942
|
const reranked = rerank(scored, clusters).slice(0, limit);
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
1943
|
+
if (reranked.length === 0) return [];
|
|
1944
|
+
for (const r of reranked) {
|
|
1945
|
+
const m = this.meta.find((m2) => m2.id === r.workflow.id);
|
|
1946
|
+
if (m) m.timesRetrieved = (m.timesRetrieved ?? 0) + 1;
|
|
1947
|
+
}
|
|
1948
|
+
this.persist();
|
|
1949
|
+
const results = await Promise.all(
|
|
1950
|
+
reranked.map(async (r) => {
|
|
1951
|
+
const m = this.meta.find((meta) => meta.id === r.workflow.id);
|
|
1952
|
+
const workflow = await this.loadWorkflowFile(r.workflow.id);
|
|
1953
|
+
if (!workflow) return null;
|
|
1954
|
+
return {
|
|
1955
|
+
workflow: { ...m, workflow },
|
|
1956
|
+
score: r.score,
|
|
1957
|
+
mode: scoreToMode(r.score)
|
|
1958
|
+
};
|
|
1959
|
+
})
|
|
1960
|
+
);
|
|
1961
|
+
return results.filter((r) => r !== null);
|
|
2063
1962
|
}
|
|
2064
1963
|
async save(workflow, metadata) {
|
|
2065
1964
|
const id = generateUUID();
|
|
1965
|
+
await this.writeWorkflowFile(id, workflow);
|
|
2066
1966
|
const failurePatterns = this.deduplicateFailurePatterns(metadata.failurePatterns);
|
|
2067
|
-
const
|
|
1967
|
+
const meta = {
|
|
2068
1968
|
id,
|
|
2069
|
-
workflow,
|
|
2070
1969
|
description: metadata.description,
|
|
2071
1970
|
tags: metadata.tags ?? [],
|
|
2072
1971
|
platform: metadata.platform ?? "n8n",
|
|
2073
1972
|
deployCount: 0,
|
|
2074
1973
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1974
|
+
workflowName: workflow.name,
|
|
1975
|
+
cachedNodeTypes: workflow.nodes.map((n) => n.type),
|
|
2075
1976
|
...failurePatterns?.length ? { failurePatterns } : {},
|
|
2076
1977
|
...metadata.sourceWorkflowIds?.length ? { sourceWorkflowIds: metadata.sourceWorkflowIds } : {},
|
|
2077
1978
|
...metadata.generationMode ? { generationMode: metadata.generationMode } : {},
|
|
@@ -2083,31 +1984,35 @@ var FileLibrary = class {
|
|
|
2083
1984
|
...metadata.sourceUrl ? { sourceUrl: metadata.sourceUrl } : {},
|
|
2084
1985
|
...metadata.trustLevel ? { trustLevel: metadata.trustLevel } : {}
|
|
2085
1986
|
};
|
|
2086
|
-
this.
|
|
2087
|
-
if (this.
|
|
2088
|
-
this.
|
|
2089
|
-
|
|
1987
|
+
this.meta.push(meta);
|
|
1988
|
+
if (this.meta.length > MAX_LIBRARY_SIZE) {
|
|
1989
|
+
this.meta.sort((a, b) => {
|
|
1990
|
+
if (a.id === id) return -1;
|
|
1991
|
+
if (b.id === id) return 1;
|
|
1992
|
+
return (b.deployCount ?? 0) - (a.deployCount ?? 0);
|
|
1993
|
+
});
|
|
1994
|
+
this.meta = this.meta.slice(0, MAX_LIBRARY_SIZE);
|
|
2090
1995
|
}
|
|
2091
1996
|
await this.persist();
|
|
2092
1997
|
return id;
|
|
2093
1998
|
}
|
|
2094
1999
|
async recordDeployment(id) {
|
|
2095
|
-
const
|
|
2096
|
-
if (
|
|
2097
|
-
|
|
2098
|
-
|
|
2000
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
2001
|
+
if (m) {
|
|
2002
|
+
m.deployCount++;
|
|
2003
|
+
m.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2099
2004
|
await this.persist();
|
|
2100
2005
|
}
|
|
2101
2006
|
}
|
|
2102
2007
|
async recordOutcome(id, outcome) {
|
|
2103
|
-
const
|
|
2104
|
-
if (!
|
|
2008
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
2009
|
+
if (!m) return;
|
|
2105
2010
|
if (outcome.mode === "direct") {
|
|
2106
|
-
|
|
2011
|
+
m.timesUsedAsDirect = (m.timesUsedAsDirect ?? 0) + 1;
|
|
2107
2012
|
} else {
|
|
2108
|
-
|
|
2013
|
+
m.timesUsedAsReference = (m.timesUsedAsReference ?? 0) + 1;
|
|
2109
2014
|
}
|
|
2110
|
-
const stats =
|
|
2015
|
+
const stats = m.outcomeStats ?? { totalUses: 0, totalAttempts: 0, firstTryPasses: 0, failedRules: {} };
|
|
2111
2016
|
stats.totalUses++;
|
|
2112
2017
|
stats.totalAttempts += outcome.attempts;
|
|
2113
2018
|
if (outcome.firstTryPass) stats.firstTryPasses++;
|
|
@@ -2115,24 +2020,35 @@ var FileLibrary = class {
|
|
|
2115
2020
|
const key = String(rule);
|
|
2116
2021
|
stats.failedRules[key] = (stats.failedRules[key] ?? 0) + 1;
|
|
2117
2022
|
}
|
|
2118
|
-
|
|
2023
|
+
m.outcomeStats = stats;
|
|
2119
2024
|
await this.persist();
|
|
2120
2025
|
}
|
|
2121
2026
|
async drain() {
|
|
2122
2027
|
await this.writeQueue;
|
|
2123
2028
|
}
|
|
2124
2029
|
async get(id) {
|
|
2125
|
-
|
|
2030
|
+
const m = this.meta.find((m2) => m2.id === id);
|
|
2031
|
+
if (!m) return null;
|
|
2032
|
+
const workflow = await this.loadWorkflowFile(id);
|
|
2033
|
+
if (!workflow) return null;
|
|
2034
|
+
return { ...m, workflow };
|
|
2126
2035
|
}
|
|
2127
2036
|
async list(filters) {
|
|
2128
|
-
let
|
|
2037
|
+
let filtered = this.meta;
|
|
2129
2038
|
if (filters?.platform) {
|
|
2130
|
-
|
|
2039
|
+
filtered = filtered.filter((m) => m.platform === filters.platform);
|
|
2131
2040
|
}
|
|
2132
2041
|
if (filters?.tags && filters.tags.length > 0) {
|
|
2133
|
-
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2042
|
+
filtered = filtered.filter((m) => filters.tags.some((t) => m.tags.includes(t)));
|
|
2043
|
+
}
|
|
2044
|
+
const results = await Promise.all(
|
|
2045
|
+
filtered.map(async (m) => {
|
|
2046
|
+
const workflow = await this.loadWorkflowFile(m.id);
|
|
2047
|
+
if (!workflow) return null;
|
|
2048
|
+
return { ...m, workflow };
|
|
2049
|
+
})
|
|
2050
|
+
);
|
|
2051
|
+
return results.filter((r) => r !== null);
|
|
2136
2052
|
}
|
|
2137
2053
|
deduplicateFailurePatterns(patterns) {
|
|
2138
2054
|
if (!patterns?.length) return void 0;
|
|
@@ -2147,11 +2063,36 @@ var FileLibrary = class {
|
|
|
2147
2063
|
}
|
|
2148
2064
|
return [...map.values()];
|
|
2149
2065
|
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Direct write used only during migration (before writeQueue is needed).
|
|
2068
|
+
*/
|
|
2069
|
+
async persistNow() {
|
|
2070
|
+
const indexPath = join5(this.dir, "index.json");
|
|
2071
|
+
const tmpPath = `${indexPath}.tmp`;
|
|
2072
|
+
await writeFile2(tmpPath, JSON.stringify(this.meta, null, 2), "utf-8");
|
|
2073
|
+
await rename2(tmpPath, indexPath);
|
|
2074
|
+
}
|
|
2150
2075
|
persist() {
|
|
2151
2076
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
2152
2077
|
const indexPath = join5(this.dir, "index.json");
|
|
2078
|
+
let onDisk = [];
|
|
2079
|
+
try {
|
|
2080
|
+
const raw = await readFile(indexPath, "utf-8");
|
|
2081
|
+
const parsed = JSON.parse(raw);
|
|
2082
|
+
if (Array.isArray(parsed)) {
|
|
2083
|
+
onDisk = parsed.filter(isValidMeta);
|
|
2084
|
+
}
|
|
2085
|
+
} catch {
|
|
2086
|
+
}
|
|
2087
|
+
const ourIds = new Set(this.meta.map((m) => m.id));
|
|
2088
|
+
const external = onDisk.filter((m) => !ourIds.has(m.id));
|
|
2089
|
+
let merged = [...this.meta, ...external];
|
|
2090
|
+
if (merged.length > MAX_LIBRARY_SIZE) {
|
|
2091
|
+
merged.sort((a, b) => (b.deployCount ?? 0) - (a.deployCount ?? 0));
|
|
2092
|
+
merged = merged.slice(0, MAX_LIBRARY_SIZE);
|
|
2093
|
+
}
|
|
2153
2094
|
const tmpPath = `${indexPath}.tmp`;
|
|
2154
|
-
await writeFile2(tmpPath, JSON.stringify(
|
|
2095
|
+
await writeFile2(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
2155
2096
|
await rename2(tmpPath, indexPath);
|
|
2156
2097
|
});
|
|
2157
2098
|
return this.writeQueue;
|
|
@@ -2169,7 +2110,9 @@ export {
|
|
|
2169
2110
|
NodeRegistry,
|
|
2170
2111
|
N8nValidator,
|
|
2171
2112
|
scoreToMode,
|
|
2172
|
-
|
|
2113
|
+
RULE_EXAMPLES,
|
|
2114
|
+
RULE_MITIGATIONS,
|
|
2115
|
+
TelemetryCollector,
|
|
2173
2116
|
TelemetryReader,
|
|
2174
2117
|
PatternAnalyzer,
|
|
2175
2118
|
nullLogger,
|
|
@@ -2180,4 +2123,4 @@ export {
|
|
|
2180
2123
|
buildSearchCorpus,
|
|
2181
2124
|
FileLibrary
|
|
2182
2125
|
};
|
|
2183
|
-
//# sourceMappingURL=chunk-
|
|
2126
|
+
//# sourceMappingURL=chunk-6IXW3WCC.js.map
|