@kairos-sdk/core 0.5.0 → 0.6.0

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.
Files changed (44) hide show
  1. package/README.md +131 -16
  2. package/dist/{chunk-KIFT5LA7.js → chunk-2ZHNO37N.js} +49 -5
  3. package/dist/chunk-2ZHNO37N.js.map +1 -0
  4. package/dist/chunk-GG4B4TYG.js +153 -0
  5. package/dist/chunk-GG4B4TYG.js.map +1 -0
  6. package/dist/{chunk-5GAY7CSJ.js → chunk-PCNW5ZUD.js} +2 -2
  7. package/dist/chunk-SC6CLQZB.js +144 -0
  8. package/dist/chunk-SC6CLQZB.js.map +1 -0
  9. package/dist/chunk-SQS4QHDH.js +44 -0
  10. package/dist/chunk-SQS4QHDH.js.map +1 -0
  11. package/dist/{chunk-EVOAYH2K.js → chunk-STG7Z2SS.js} +2 -2
  12. package/dist/{chunk-HBGZTUUZ.js → chunk-YOQTEVDB.js} +5 -7
  13. package/dist/chunk-YOQTEVDB.js.map +1 -0
  14. package/dist/cli.cjs +702 -40
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +262 -7
  17. package/dist/cli.js.map +1 -1
  18. package/dist/index.cjs +417 -39
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +86 -4
  21. package/dist/index.d.ts +86 -4
  22. package/dist/index.js +19 -5
  23. package/dist/mcp-server.cjs +139 -24
  24. package/dist/mcp-server.cjs.map +1 -1
  25. package/dist/mcp-server.js +90 -19
  26. package/dist/mcp-server.js.map +1 -1
  27. package/dist/pack-builder-RTQWXGIS.js +9 -0
  28. package/dist/pack-builder-RTQWXGIS.js.map +1 -0
  29. package/dist/pack-exporter-KFNLSP5V.js +7 -0
  30. package/dist/pack-exporter-KFNLSP5V.js.map +1 -0
  31. package/dist/pack-validator-HZPB2XJ3.js +7 -0
  32. package/dist/pack-validator-HZPB2XJ3.js.map +1 -0
  33. package/dist/{reader-B5mV20H6.d.ts → reader-CfWGpL4V.d.cts} +2 -1
  34. package/dist/{reader-B5mV20H6.d.cts → reader-CfWGpL4V.d.ts} +2 -1
  35. package/dist/standalone.cjs +44 -4
  36. package/dist/standalone.cjs.map +1 -1
  37. package/dist/standalone.d.cts +1 -1
  38. package/dist/standalone.d.ts +1 -1
  39. package/dist/standalone.js +2 -2
  40. package/package.json +12 -5
  41. package/dist/chunk-HBGZTUUZ.js.map +0 -1
  42. package/dist/chunk-KIFT5LA7.js.map +0 -1
  43. /package/dist/{chunk-5GAY7CSJ.js.map → chunk-PCNW5ZUD.js.map} +0 -0
  44. /package/dist/{chunk-EVOAYH2K.js.map → chunk-STG7Z2SS.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -1,10 +1,9 @@
1
- import { C as ClientOptions, B as BuildOptions, a as BuildResult, N as N8nWorkflow, W as WorkflowListItem, D as DeleteOptions, E as ExecutionFilter, b as ExecutionSummary, c as ExecutionDetail, T as Tag } from './reader-B5mV20H6.cjs';
2
- export { A as ApiError, d as AttemptMetadata, e as CredentialRequirement, f as DEFAULT_REGISTRY, g as DeployResult, F as FailurePattern, h as FileLibrary, G as GenerationError, i as GuardError, I as ILogger, j as IProvider, k as IWorkflowLibrary, K as KairosError, l as N8nApiClient, m as N8nConnections, n as N8nFieldStripper, o as N8nNode, p as N8nProvider, q as N8nSettings, r as N8nValidator, s as NodeRegistry, t as NullLibrary, O as OutcomeData, u as OutcomeStats, P as ProviderError, R as ResponseParseError, v as RuleFailureRate, S as ScoredEntry, w as SmokeTestResult, x as SourceKind, y as StoredWorkflow, z as SyncProgress, H as TelemetryCollector, J as TelemetryEvent, L as TelemetryReader, M as TemplateSyncer, Q as TrustLevel, V as ValidationError, U as ValidationIssue, X as ValidationResult, Y as WorkflowCluster, Z as WorkflowMatch, _ as WorkflowMetadataInput, $ as buildSearchCorpus, a0 as clusterWorkflows, a1 as hybridScore, a2 as nullLogger, a3 as rerank, a4 as tokenize } from './reader-B5mV20H6.cjs';
1
+ import { C as ClientOptions, B as BuildOptions, b as BuildResult, r as N8nWorkflow, Z as WorkflowListItem, d as DeleteOptions, f as ExecutionFilter, g as ExecutionSummary, E as ExecutionDetail, T as Tag, c as CredentialRequirement } from './reader-CfWGpL4V.cjs';
2
+ export { A as ApiError, a as AttemptMetadata, D as DEFAULT_REGISTRY, e as DeployResult, F as FailurePattern, h as FileLibrary, G as GenerationError, i as GuardError, I as ILogger, j as IProvider, k as IWorkflowLibrary, K as KairosError, N as N8nApiClient, l as N8nConnections, m as N8nFieldStripper, n as N8nNode, o as N8nProvider, p as N8nSettings, q as N8nValidator, s as NodeRegistry, t as NullLibrary, O as OutcomeData, u as OutcomeStats, P as ProviderError, R as ResponseParseError, v as RuleFailureRate, S as ScoredEntry, w as SmokeTestResult, y as SourceKind, z as StoredWorkflow, H as SyncProgress, J as TelemetryCollector, L as TelemetryEvent, M as TelemetryReader, Q as TemplateSyncer, U as TrustLevel, V as ValidationError, W as ValidationIssue, X as ValidationResult, Y as WorkflowCluster, _ as WorkflowMatch, $ as WorkflowMetadataInput, a0 as buildSearchCorpus, a1 as clusterWorkflows, a2 as hybridScore, a3 as nullLogger, a4 as rerank, a5 as tokenize } from './reader-CfWGpL4V.cjs';
3
3
 
4
4
  declare class Kairos {
5
5
  private readonly provider;
6
6
  private readonly designer;
7
- private readonly validator;
8
7
  private readonly library;
9
8
  private readonly logger;
10
9
  private readonly telemetry;
@@ -35,4 +34,87 @@ declare class Kairos {
35
34
  untag(workflowId: string, tagIds: string[]): Promise<void>;
36
35
  }
37
36
 
38
- export { BuildOptions, BuildResult, ClientOptions, DeleteOptions, ExecutionDetail, ExecutionFilter, ExecutionSummary, Kairos, N8nWorkflow, Tag, WorkflowListItem };
37
+ type AssumptionType = 'safe' | 'needs_confirmation' | 'blocking';
38
+ type PackStatus = 'draft' | 'blocked' | 'ready_for_test' | 'ready_for_activation' | 'active' | 'needs_attention';
39
+ interface TypedAssumption {
40
+ type: AssumptionType;
41
+ text: string;
42
+ }
43
+ interface WorkflowPlan {
44
+ name: string;
45
+ description: string;
46
+ purpose: string;
47
+ }
48
+ interface PackPlan {
49
+ businessContext: string;
50
+ workflows: WorkflowPlan[];
51
+ assumptions: TypedAssumption[];
52
+ sheetsColumns: Array<{
53
+ sheet: string;
54
+ columns: string[];
55
+ }>;
56
+ testChecklist: Array<{
57
+ workflow: string;
58
+ steps: string[];
59
+ }>;
60
+ }
61
+ interface PackWorkflowResult {
62
+ name: string;
63
+ purpose: string;
64
+ workflowId: string | null;
65
+ deployed: boolean;
66
+ generationAttempts: number;
67
+ credentialsNeeded: CredentialRequirement[];
68
+ error?: string;
69
+ }
70
+ interface WorkflowPackResult {
71
+ businessContext: string;
72
+ packName: string;
73
+ status: PackStatus;
74
+ workflows: PackWorkflowResult[];
75
+ allCredentials: Array<{
76
+ service: string;
77
+ credentialType: string;
78
+ }>;
79
+ sheetsColumns: Array<{
80
+ sheet: string;
81
+ columns: string[];
82
+ }>;
83
+ assumptions: TypedAssumption[];
84
+ testChecklist: Array<{
85
+ workflow: string;
86
+ steps: string[];
87
+ }>;
88
+ builtAt: string;
89
+ }
90
+ declare function derivePackStatus(pack: Pick<WorkflowPackResult, 'assumptions' | 'workflows'> & {
91
+ status?: PackStatus;
92
+ }): PackStatus;
93
+ declare class PackBuilder {
94
+ private client;
95
+ private kairos;
96
+ private model;
97
+ constructor(options: {
98
+ anthropicApiKey: string;
99
+ kairos: Kairos;
100
+ model?: string;
101
+ });
102
+ plan(businessContext: string): Promise<PackPlan>;
103
+ build(plan: PackPlan, options?: {
104
+ dryRun?: boolean;
105
+ activate?: boolean;
106
+ onProgress?: (workflow: WorkflowPlan, index: number, total: number) => void;
107
+ }): Promise<WorkflowPackResult>;
108
+ }
109
+
110
+ declare function generateHandoff(pack: WorkflowPackResult): string;
111
+
112
+ interface PackValidationIssue {
113
+ type: 'duplicate_name' | 'blocking_assumption' | 'unsafe_activation' | 'schedule_conflict';
114
+ severity: 'error' | 'warning';
115
+ message: string;
116
+ workflows?: string[];
117
+ }
118
+ declare function validatePack(pack: WorkflowPackResult): PackValidationIssue[];
119
+
120
+ export { type AssumptionType, BuildOptions, BuildResult, ClientOptions, CredentialRequirement, DeleteOptions, ExecutionDetail, ExecutionFilter, ExecutionSummary, Kairos, N8nWorkflow, PackBuilder, type PackPlan, type PackStatus, type PackValidationIssue, type PackWorkflowResult, Tag, type TypedAssumption, WorkflowListItem, type WorkflowPackResult, type WorkflowPlan, derivePackStatus, generateHandoff, validatePack };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { C as ClientOptions, B as BuildOptions, a as BuildResult, N as N8nWorkflow, W as WorkflowListItem, D as DeleteOptions, E as ExecutionFilter, b as ExecutionSummary, c as ExecutionDetail, T as Tag } from './reader-B5mV20H6.js';
2
- export { A as ApiError, d as AttemptMetadata, e as CredentialRequirement, f as DEFAULT_REGISTRY, g as DeployResult, F as FailurePattern, h as FileLibrary, G as GenerationError, i as GuardError, I as ILogger, j as IProvider, k as IWorkflowLibrary, K as KairosError, l as N8nApiClient, m as N8nConnections, n as N8nFieldStripper, o as N8nNode, p as N8nProvider, q as N8nSettings, r as N8nValidator, s as NodeRegistry, t as NullLibrary, O as OutcomeData, u as OutcomeStats, P as ProviderError, R as ResponseParseError, v as RuleFailureRate, S as ScoredEntry, w as SmokeTestResult, x as SourceKind, y as StoredWorkflow, z as SyncProgress, H as TelemetryCollector, J as TelemetryEvent, L as TelemetryReader, M as TemplateSyncer, Q as TrustLevel, V as ValidationError, U as ValidationIssue, X as ValidationResult, Y as WorkflowCluster, Z as WorkflowMatch, _ as WorkflowMetadataInput, $ as buildSearchCorpus, a0 as clusterWorkflows, a1 as hybridScore, a2 as nullLogger, a3 as rerank, a4 as tokenize } from './reader-B5mV20H6.js';
1
+ import { C as ClientOptions, B as BuildOptions, b as BuildResult, r as N8nWorkflow, Z as WorkflowListItem, d as DeleteOptions, f as ExecutionFilter, g as ExecutionSummary, E as ExecutionDetail, T as Tag, c as CredentialRequirement } from './reader-CfWGpL4V.js';
2
+ export { A as ApiError, a as AttemptMetadata, D as DEFAULT_REGISTRY, e as DeployResult, F as FailurePattern, h as FileLibrary, G as GenerationError, i as GuardError, I as ILogger, j as IProvider, k as IWorkflowLibrary, K as KairosError, N as N8nApiClient, l as N8nConnections, m as N8nFieldStripper, n as N8nNode, o as N8nProvider, p as N8nSettings, q as N8nValidator, s as NodeRegistry, t as NullLibrary, O as OutcomeData, u as OutcomeStats, P as ProviderError, R as ResponseParseError, v as RuleFailureRate, S as ScoredEntry, w as SmokeTestResult, y as SourceKind, z as StoredWorkflow, H as SyncProgress, J as TelemetryCollector, L as TelemetryEvent, M as TelemetryReader, Q as TemplateSyncer, U as TrustLevel, V as ValidationError, W as ValidationIssue, X as ValidationResult, Y as WorkflowCluster, _ as WorkflowMatch, $ as WorkflowMetadataInput, a0 as buildSearchCorpus, a1 as clusterWorkflows, a2 as hybridScore, a3 as nullLogger, a4 as rerank, a5 as tokenize } from './reader-CfWGpL4V.js';
3
3
 
4
4
  declare class Kairos {
5
5
  private readonly provider;
6
6
  private readonly designer;
7
- private readonly validator;
8
7
  private readonly library;
9
8
  private readonly logger;
10
9
  private readonly telemetry;
@@ -35,4 +34,87 @@ declare class Kairos {
35
34
  untag(workflowId: string, tagIds: string[]): Promise<void>;
36
35
  }
37
36
 
38
- export { BuildOptions, BuildResult, ClientOptions, DeleteOptions, ExecutionDetail, ExecutionFilter, ExecutionSummary, Kairos, N8nWorkflow, Tag, WorkflowListItem };
37
+ type AssumptionType = 'safe' | 'needs_confirmation' | 'blocking';
38
+ type PackStatus = 'draft' | 'blocked' | 'ready_for_test' | 'ready_for_activation' | 'active' | 'needs_attention';
39
+ interface TypedAssumption {
40
+ type: AssumptionType;
41
+ text: string;
42
+ }
43
+ interface WorkflowPlan {
44
+ name: string;
45
+ description: string;
46
+ purpose: string;
47
+ }
48
+ interface PackPlan {
49
+ businessContext: string;
50
+ workflows: WorkflowPlan[];
51
+ assumptions: TypedAssumption[];
52
+ sheetsColumns: Array<{
53
+ sheet: string;
54
+ columns: string[];
55
+ }>;
56
+ testChecklist: Array<{
57
+ workflow: string;
58
+ steps: string[];
59
+ }>;
60
+ }
61
+ interface PackWorkflowResult {
62
+ name: string;
63
+ purpose: string;
64
+ workflowId: string | null;
65
+ deployed: boolean;
66
+ generationAttempts: number;
67
+ credentialsNeeded: CredentialRequirement[];
68
+ error?: string;
69
+ }
70
+ interface WorkflowPackResult {
71
+ businessContext: string;
72
+ packName: string;
73
+ status: PackStatus;
74
+ workflows: PackWorkflowResult[];
75
+ allCredentials: Array<{
76
+ service: string;
77
+ credentialType: string;
78
+ }>;
79
+ sheetsColumns: Array<{
80
+ sheet: string;
81
+ columns: string[];
82
+ }>;
83
+ assumptions: TypedAssumption[];
84
+ testChecklist: Array<{
85
+ workflow: string;
86
+ steps: string[];
87
+ }>;
88
+ builtAt: string;
89
+ }
90
+ declare function derivePackStatus(pack: Pick<WorkflowPackResult, 'assumptions' | 'workflows'> & {
91
+ status?: PackStatus;
92
+ }): PackStatus;
93
+ declare class PackBuilder {
94
+ private client;
95
+ private kairos;
96
+ private model;
97
+ constructor(options: {
98
+ anthropicApiKey: string;
99
+ kairos: Kairos;
100
+ model?: string;
101
+ });
102
+ plan(businessContext: string): Promise<PackPlan>;
103
+ build(plan: PackPlan, options?: {
104
+ dryRun?: boolean;
105
+ activate?: boolean;
106
+ onProgress?: (workflow: WorkflowPlan, index: number, total: number) => void;
107
+ }): Promise<WorkflowPackResult>;
108
+ }
109
+
110
+ declare function generateHandoff(pack: WorkflowPackResult): string;
111
+
112
+ interface PackValidationIssue {
113
+ type: 'duplicate_name' | 'blocking_assumption' | 'unsafe_activation' | 'schedule_conflict';
114
+ severity: 'error' | 'warning';
115
+ message: string;
116
+ workflows?: string[];
117
+ }
118
+ declare function validatePack(pack: WorkflowPackResult): PackValidationIssue[];
119
+
120
+ export { type AssumptionType, BuildOptions, BuildResult, ClientOptions, CredentialRequirement, DeleteOptions, ExecutionDetail, ExecutionFilter, ExecutionSummary, Kairos, N8nWorkflow, PackBuilder, type PackPlan, type PackStatus, type PackValidationIssue, type PackWorkflowResult, Tag, type TypedAssumption, WorkflowListItem, type WorkflowPackResult, type WorkflowPlan, derivePackStatus, generateHandoff, validatePack };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Kairos
3
- } from "./chunk-HBGZTUUZ.js";
4
- import "./chunk-EVOAYH2K.js";
3
+ } from "./chunk-YOQTEVDB.js";
4
+ import "./chunk-STG7Z2SS.js";
5
5
  import "./chunk-6FOFWVMG.js";
6
6
  import {
7
7
  GenerationError,
@@ -10,7 +10,7 @@ import {
10
10
  ResponseParseError,
11
11
  TemplateSyncer,
12
12
  ValidationError
13
- } from "./chunk-5GAY7CSJ.js";
13
+ } from "./chunk-PCNW5ZUD.js";
14
14
  import {
15
15
  ApiError,
16
16
  DEFAULT_REGISTRY,
@@ -30,7 +30,17 @@ import {
30
30
  nullLogger,
31
31
  rerank,
32
32
  tokenize
33
- } from "./chunk-KIFT5LA7.js";
33
+ } from "./chunk-2ZHNO37N.js";
34
+ import {
35
+ PackBuilder,
36
+ derivePackStatus
37
+ } from "./chunk-GG4B4TYG.js";
38
+ import {
39
+ generateHandoff
40
+ } from "./chunk-SC6CLQZB.js";
41
+ import {
42
+ validatePack
43
+ } from "./chunk-SQS4QHDH.js";
34
44
  export {
35
45
  ApiError,
36
46
  DEFAULT_REGISTRY,
@@ -45,6 +55,7 @@ export {
45
55
  N8nValidator,
46
56
  NodeRegistry,
47
57
  NullLibrary,
58
+ PackBuilder,
48
59
  ProviderError,
49
60
  ResponseParseError,
50
61
  TelemetryCollector,
@@ -53,9 +64,12 @@ export {
53
64
  ValidationError,
54
65
  buildSearchCorpus,
55
66
  clusterWorkflows,
67
+ derivePackStatus,
68
+ generateHandoff,
56
69
  hybridScore,
57
70
  nullLogger,
58
71
  rerank,
59
- tokenize
72
+ tokenize,
73
+ validatePack
60
74
  };
61
75
  //# sourceMappingURL=index.js.map
@@ -4,6 +4,8 @@
4
4
  // src/mcp-server.ts
5
5
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
6
6
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
7
+ var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
8
+ var import_node_http = require("http");
7
9
  var import_zod = require("zod");
8
10
 
9
11
  // src/library/file-library.ts
@@ -899,6 +901,7 @@ var N8nValidator = class {
899
901
  this.checkRule32(workflow, issues);
900
902
  this.checkRule33(workflow, issues);
901
903
  this.checkRule34(workflow, issues);
904
+ this.checkRule35(workflow, issues);
902
905
  if (Array.isArray(workflow.nodes)) {
903
906
  const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
904
907
  for (const issue of issues) {
@@ -1471,6 +1474,43 @@ var N8nValidator = class {
1471
1474
  }
1472
1475
  }
1473
1476
  }
1477
+ // Rule 35 (WARN): email-sending node with no duplicate-prevention signal
1478
+ checkRule35(w, issues) {
1479
+ if (!Array.isArray(w.nodes)) return;
1480
+ const sendNodes = w.nodes.filter((node) => {
1481
+ if (node.type === "n8n-nodes-base.gmail") {
1482
+ const op = node.parameters?.["operation"];
1483
+ return !op || op === "send" || op === "sendEmail" || op === "reply";
1484
+ }
1485
+ return node.type === "n8n-nodes-base.emailSend" || node.type === "n8n-nodes-base.sendEmail";
1486
+ });
1487
+ if (sendNodes.length === 0) return;
1488
+ const workflowText = JSON.stringify(w).toLowerCase();
1489
+ const IDEMPOTENCY_SIGNALS = [
1490
+ "sent_at",
1491
+ "last_sent",
1492
+ "last_reminder",
1493
+ "processed_at",
1494
+ "already_sent",
1495
+ "email_sent",
1496
+ "notified_at",
1497
+ "reminder_sent",
1498
+ "contacted_at",
1499
+ "dedupe",
1500
+ "idempotent"
1501
+ ];
1502
+ const hasIdempotencySignal = IDEMPOTENCY_SIGNALS.some((s) => workflowText.includes(s));
1503
+ if (!hasIdempotencySignal) {
1504
+ for (const node of sendNodes) {
1505
+ this.warn(
1506
+ issues,
1507
+ 35,
1508
+ `Node "${node.name}" sends email but no duplicate-prevention signal detected \u2014 add a sent_at timestamp field, a prior-send IF check, or a deduplication key to avoid repeat sends`,
1509
+ node.id
1510
+ );
1511
+ }
1512
+ }
1513
+ }
1474
1514
  // Rule 34 (WARN): webhook path contains spaces, starts with slash, or looks like a full URL
1475
1515
  checkRule34(w, issues) {
1476
1516
  if (!Array.isArray(w.nodes)) return;
@@ -2018,7 +2058,7 @@ Respond ONLY with a generate_workflow tool call. No prose. No markdown outside t
2018
2058
  If the request is impossible or unclear, set the error field instead of generating a workflow.`;
2019
2059
 
2020
2060
  // src/validation/rule-metadata.ts
2021
- var VALIDATOR_RULE_IDS = Array.from({ length: 34 }, (_, i) => i + 1);
2061
+ var VALIDATOR_RULE_IDS = Array.from({ length: 35 }, (_, i) => i + 1);
2022
2062
  var RULE_PIPELINE_STAGES = {
2023
2063
  1: "node_generation",
2024
2064
  2: "node_generation",
@@ -2053,7 +2093,8 @@ var RULE_PIPELINE_STAGES = {
2053
2093
  31: "node_generation",
2054
2094
  32: "node_generation",
2055
2095
  33: "node_generation",
2056
- 34: "node_generation"
2096
+ 34: "node_generation",
2097
+ 35: "node_generation"
2057
2098
  };
2058
2099
  var RULE_EXAMPLES = {
2059
2100
  17: {
@@ -2103,6 +2144,10 @@ var RULE_EXAMPLES = {
2103
2144
  34: {
2104
2145
  bad: '"path": "/my webhook"',
2105
2146
  good: '"path": "my-webhook"'
2147
+ },
2148
+ 35: {
2149
+ bad: '"type": "n8n-nodes-base.gmail", "parameters": { "operation": "send" } // no sent_at tracking',
2150
+ good: 'Add a Set node after send that writes "sent_at": "={{ $now }}" back to the sheet, or an IF node that checks sent_at before sending'
2106
2151
  }
2107
2152
  };
2108
2153
  var RULE_MITIGATIONS = {
@@ -2139,7 +2184,8 @@ var RULE_MITIGATIONS = {
2139
2184
  31: "Add at least one condition to the if node \u2014 conditions.conditions array must be non-empty",
2140
2185
  32: "Add field assignments to the set node \u2014 assignments.assignments array must be non-empty for typeVersion 3.x",
2141
2186
  33: "Add at least one schedule rule to scheduleTrigger \u2014 rule.interval array must have at least one entry",
2142
- 34: 'Webhook path must be a relative path without spaces, leading slashes, or protocol prefixes (e.g. "my-hook")'
2187
+ 34: 'Webhook path must be a relative path without spaces, leading slashes, or protocol prefixes (e.g. "my-hook")',
2188
+ 35: "Add duplicate-prevention to email-sending workflows: a sent_at timestamp field updated after each send, or an IF node that checks prior-send status before sending"
2143
2189
  };
2144
2190
 
2145
2191
  // src/generation/prompt-builder.ts
@@ -2594,7 +2640,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
2594
2640
  this._cachedEvents = events;
2595
2641
  const starts = events.filter((e) => e.eventType === "build_start");
2596
2642
  const attempts = events.filter((e) => e.eventType === "generation_attempt");
2597
- const passed = attempts.filter(
2643
+ const _passed = attempts.filter(
2598
2644
  (a) => a.data.validationPassed === true
2599
2645
  );
2600
2646
  const failed = attempts.filter(
@@ -3177,12 +3223,37 @@ function inferWorkflowType(description) {
3177
3223
 
3178
3224
  // src/mcp-server.ts
3179
3225
  var import_node_fs3 = require("fs");
3180
- var import_node_path7 = require("path");
3226
+ var import_node_path8 = require("path");
3181
3227
  var import_node_os6 = require("os");
3182
3228
  var import_node_url = require("url");
3229
+
3230
+ // src/utils/node-catalog-cache.ts
3231
+ var import_promises5 = require("fs/promises");
3232
+ var import_node_path7 = require("path");
3233
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
3234
+ async function readCatalogCache(cachePath) {
3235
+ try {
3236
+ const raw = await (0, import_promises5.readFile)(cachePath, "utf-8");
3237
+ const cached = JSON.parse(raw);
3238
+ if (Date.now() - cached.cachedAt > CACHE_TTL_MS) return null;
3239
+ return cached.syncResult;
3240
+ } catch {
3241
+ return null;
3242
+ }
3243
+ }
3244
+ async function writeCatalogCache(cachePath, syncResult) {
3245
+ try {
3246
+ await (0, import_promises5.mkdir)((0, import_node_path7.dirname)(cachePath), { recursive: true });
3247
+ const payload = { cachedAt: Date.now(), syncResult };
3248
+ await (0, import_promises5.writeFile)(cachePath, JSON.stringify(payload), "utf-8");
3249
+ } catch {
3250
+ }
3251
+ }
3252
+
3253
+ // src/mcp-server.ts
3183
3254
  var import_meta = {};
3184
- var __dirname = (0, import_node_path7.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
3185
- var pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path7.join)(__dirname, "..", "package.json"), "utf-8"));
3255
+ var __dirname = (0, import_node_path8.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
3256
+ var pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path8.join)(__dirname, "..", "package.json"), "utf-8"));
3186
3257
  var library = new FileLibrary();
3187
3258
  var _validator = new N8nValidator();
3188
3259
  function getValidator() {
@@ -3201,9 +3272,9 @@ function getMcpTelemetry() {
3201
3272
  function getMcpPatternsPath() {
3202
3273
  const val = process.env["KAIROS_TELEMETRY"];
3203
3274
  if (val && val !== "false" && val !== "true") {
3204
- return (0, import_node_path7.join)(val, "..", "patterns.json");
3275
+ return (0, import_node_path8.join)(val, "..", "patterns.json");
3205
3276
  }
3206
- return (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".kairos", "patterns.json");
3277
+ return (0, import_node_path8.join)((0, import_node_os6.homedir)(), ".kairos", "patterns.json");
3207
3278
  }
3208
3279
  var mcpTelemetry = getMcpTelemetry();
3209
3280
  var mcpSessions = /* @__PURE__ */ new Map();
@@ -3221,7 +3292,14 @@ function getTelemetryReader() {
3221
3292
  return null;
3222
3293
  }
3223
3294
  }
3295
+ function getMcpMode() {
3296
+ const mode = process.env["KAIROS_MCP_MODE"]?.toLowerCase();
3297
+ if (mode === "readonly" || mode === "validate") return mode;
3298
+ return "deploy";
3299
+ }
3224
3300
  function isAllowed(action) {
3301
+ const mode = getMcpMode();
3302
+ if (mode === "readonly" || mode === "validate") return false;
3225
3303
  const key = `KAIROS_MCP_ALLOW_${action.toUpperCase()}`;
3226
3304
  return process.env[key] === "true";
3227
3305
  }
@@ -3245,8 +3323,20 @@ function getApiClient() {
3245
3323
  }
3246
3324
  return new N8nApiClient(baseUrl, apiKey, nullLogger);
3247
3325
  }
3326
+ function getCatalogCachePath() {
3327
+ const telemetry = process.env["KAIROS_TELEMETRY"];
3328
+ const base = telemetry ? (0, import_node_path8.join)(telemetry, "..") : (0, import_node_path8.join)((0, import_node_os6.homedir)(), ".kairos");
3329
+ return (0, import_node_path8.join)(base, "node-catalog-cache.json");
3330
+ }
3248
3331
  async function autoSync() {
3249
3332
  if (lastSync) return lastSync;
3333
+ const cachePath = getCatalogCachePath();
3334
+ const cached = await readCatalogCache(cachePath);
3335
+ if (cached) {
3336
+ lastSync = cached;
3337
+ _validator = new N8nValidator(lastSync.registry);
3338
+ return lastSync;
3339
+ }
3250
3340
  const baseUrl = process.env["N8N_BASE_URL"];
3251
3341
  const apiKey = process.env["N8N_API_KEY"];
3252
3342
  if (!baseUrl || !apiKey) return null;
@@ -3256,6 +3346,8 @@ async function autoSync() {
3256
3346
  if (nodeTypes.length === 0) return null;
3257
3347
  lastSync = nodeSyncer.sync(nodeTypes);
3258
3348
  _validator = new N8nValidator(lastSync.registry);
3349
+ writeCatalogCache(cachePath, lastSync).catch(() => {
3350
+ });
3259
3351
  return lastSync;
3260
3352
  } catch {
3261
3353
  return null;
@@ -3274,13 +3366,9 @@ server.tool(
3274
3366
  },
3275
3367
  async ({ description, name }) => {
3276
3368
  evictStaleSessions();
3277
- const baseUrl = process.env["N8N_BASE_URL"];
3278
- const apiKey = process.env["N8N_API_KEY"];
3279
- if (!baseUrl || !apiKey) {
3280
- return mcpError(JSON.stringify({ error: "N8N_BASE_URL and N8N_API_KEY are required. Kairos needs to sync your n8n instance's node types to generate accurate workflows." }));
3281
- }
3282
3369
  const runId = generateUUID();
3283
3370
  const workflowType = inferWorkflowType(description);
3371
+ const hasN8nCreds = !!(process.env["N8N_BASE_URL"] && process.env["N8N_API_KEY"]);
3284
3372
  const syncPromise = autoSync();
3285
3373
  const syncTimeout = new Promise((resolve) => setTimeout(() => resolve(null), AUTO_SYNC_TIMEOUT_MS));
3286
3374
  await library.initialize();
@@ -3300,7 +3388,8 @@ server.tool(
3300
3388
  startTime: Date.now(),
3301
3389
  validateAttempts: 0,
3302
3390
  warnedRules: promptBuilder.getWarnedRules(),
3303
- workflowType
3391
+ workflowType,
3392
+ matchCount: matches.length
3304
3393
  });
3305
3394
  await mcpTelemetry.emit("build_start", { description, model: "mcp-decomposed", dryRun: false }, runId);
3306
3395
  }
@@ -3312,7 +3401,9 @@ server.tool(
3312
3401
  topMatchScore: matches[0]?.score ?? null,
3313
3402
  nodeCatalog: syncResult ? "synced" : "static",
3314
3403
  nodeCount: syncResult?.nodeCount ?? null,
3315
- ...syncResult ? {} : { syncWarning: "Could not sync node types from your n8n instance. Using static fallback catalog \u2014 generated workflows may not match your exact n8n setup." },
3404
+ ...syncResult ? {} : {
3405
+ syncWarning: hasN8nCreds ? "Could not sync node types from your n8n instance. Using static fallback catalog \u2014 generated workflows may not match your exact n8n setup." : "N8N_BASE_URL and N8N_API_KEY are not set. Using static fallback catalog \u2014 node types may not match your n8n instance. Set these env vars to enable accurate generation and deployment."
3406
+ },
3316
3407
  systemPrompt: systemText,
3317
3408
  userMessage: built.userMessage,
3318
3409
  outputFormat: {
@@ -3385,10 +3476,11 @@ server.tool(
3385
3476
  {
3386
3477
  workflow: import_zod.z.string().describe("The validated workflow JSON string to deploy"),
3387
3478
  activate: import_zod.z.boolean().default(false).describe("Activate the workflow immediately after deployment"),
3479
+ description: import_zod.z.string().optional().describe("The original user intent / description for this workflow \u2014 used to improve library search quality over time"),
3388
3480
  kairos_run_id: import_zod.z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation"),
3389
3481
  kairos_secret: import_zod.z.string().optional().describe("Required when KAIROS_MCP_SECRET env var is set")
3390
3482
  },
3391
- async ({ workflow: workflowStr, activate, kairos_run_id, kairos_secret }) => {
3483
+ async ({ workflow: workflowStr, activate, description: userDescription, kairos_run_id, kairos_secret }) => {
3392
3484
  const authError = checkMcpAuth(kairos_secret);
3393
3485
  if (authError) return authError;
3394
3486
  if (!isAllowed("deploy")) {
@@ -3429,8 +3521,8 @@ server.tool(
3429
3521
  Note: kairos_run_id "${kairos_run_id}" was provided but no active session was found. This usually means kairos_deploy was called without a prior kairos_prompt call, or the session expired. Telemetry and pattern learning for this build were skipped.` : "";
3430
3522
  await library.initialize();
3431
3523
  await library.save(parsed, {
3432
- description: session?.description ?? parsed.name,
3433
- generationMode: "scratch",
3524
+ description: session?.description ?? userDescription ?? parsed.name,
3525
+ generationMode: session && session.matchCount > 0 ? "reference" : "scratch",
3434
3526
  generationAttempts: session?.validateAttempts ?? 1,
3435
3527
  n8nWorkflowId: response.id
3436
3528
  });
@@ -3467,12 +3559,16 @@ server.tool(
3467
3559
  {
3468
3560
  workflow_id: import_zod.z.string().describe("The n8n workflow ID to replace"),
3469
3561
  workflow: import_zod.z.string().describe("The validated workflow JSON string"),
3562
+ description: import_zod.z.string().optional().describe("The original user intent / description for this workflow \u2014 used to improve library search quality over time"),
3470
3563
  kairos_run_id: import_zod.z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation"),
3471
3564
  kairos_secret: import_zod.z.string().optional().describe("Required when KAIROS_MCP_SECRET env var is set")
3472
3565
  },
3473
- async ({ workflow_id, workflow: workflowStr, kairos_run_id, kairos_secret }) => {
3566
+ async ({ workflow_id, workflow: workflowStr, description: userDescription, kairos_run_id, kairos_secret }) => {
3474
3567
  const authError = checkMcpAuth(kairos_secret);
3475
3568
  if (authError) return authError;
3569
+ if (!isAllowed("deploy")) {
3570
+ return mcpError(JSON.stringify({ error: "Replace is disabled. Set KAIROS_MCP_ALLOW_DEPLOY=true or KAIROS_MCP_MODE=deploy to enable." }));
3571
+ }
3476
3572
  let parsed;
3477
3573
  try {
3478
3574
  parsed = JSON.parse(workflowStr);
@@ -3496,8 +3592,8 @@ server.tool(
3496
3592
  Note: kairos_run_id "${kairos_run_id}" was provided but no active session was found.` : "";
3497
3593
  await library.initialize();
3498
3594
  await library.save(parsed, {
3499
- description: session?.description ?? parsed.name,
3500
- generationMode: "scratch",
3595
+ description: session?.description ?? userDescription ?? parsed.name,
3596
+ generationMode: session && session.matchCount > 0 ? "reference" : "scratch",
3501
3597
  generationAttempts: session?.validateAttempts ?? 1,
3502
3598
  n8nWorkflowId: workflow_id
3503
3599
  });
@@ -3743,8 +3839,27 @@ async function main() {
3743
3839
  "[kairos-mcp] WARNING: ANTHROPIC_API_KEY is not set \u2014 kairos_prompt will fail. Set it before using workflow generation tools.\n"
3744
3840
  );
3745
3841
  }
3746
- const transport = new import_stdio.StdioServerTransport();
3747
- await server.connect(transport);
3842
+ const useHttp = process.argv.includes("--http");
3843
+ if (useHttp) {
3844
+ const port = parseInt(process.env["KAIROS_MCP_PORT"] ?? "3000", 10);
3845
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport();
3846
+ await server.connect(transport);
3847
+ const httpServer = (0, import_node_http.createServer)(async (req, res) => {
3848
+ if (req.method === "GET" || req.method === "POST" || req.method === "DELETE") {
3849
+ await transport.handleRequest(req, res);
3850
+ } else {
3851
+ res.writeHead(405, { "Content-Type": "application/json" });
3852
+ res.end(JSON.stringify({ error: "Method not allowed" }));
3853
+ }
3854
+ });
3855
+ httpServer.listen(port, () => {
3856
+ process.stderr.write(`[kairos-mcp] HTTP transport listening on port ${port}
3857
+ `);
3858
+ });
3859
+ } else {
3860
+ const transport = new import_stdio.StdioServerTransport();
3861
+ await server.connect(transport);
3862
+ }
3748
3863
  }
3749
3864
  main().catch((err) => {
3750
3865
  console.error("Kairos MCP server failed to start:", err);