@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.
- package/README.md +131 -16
- package/dist/{chunk-KIFT5LA7.js → chunk-2ZHNO37N.js} +49 -5
- package/dist/chunk-2ZHNO37N.js.map +1 -0
- package/dist/chunk-GG4B4TYG.js +153 -0
- package/dist/chunk-GG4B4TYG.js.map +1 -0
- package/dist/{chunk-5GAY7CSJ.js → chunk-PCNW5ZUD.js} +2 -2
- package/dist/chunk-SC6CLQZB.js +144 -0
- package/dist/chunk-SC6CLQZB.js.map +1 -0
- package/dist/chunk-SQS4QHDH.js +44 -0
- package/dist/chunk-SQS4QHDH.js.map +1 -0
- package/dist/{chunk-EVOAYH2K.js → chunk-STG7Z2SS.js} +2 -2
- package/dist/{chunk-HBGZTUUZ.js → chunk-YOQTEVDB.js} +5 -7
- package/dist/chunk-YOQTEVDB.js.map +1 -0
- package/dist/cli.cjs +702 -40
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +262 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +417 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -4
- package/dist/index.d.ts +86 -4
- package/dist/index.js +19 -5
- package/dist/mcp-server.cjs +139 -24
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/mcp-server.js +90 -19
- package/dist/mcp-server.js.map +1 -1
- package/dist/pack-builder-RTQWXGIS.js +9 -0
- package/dist/pack-builder-RTQWXGIS.js.map +1 -0
- package/dist/pack-exporter-KFNLSP5V.js +7 -0
- package/dist/pack-exporter-KFNLSP5V.js.map +1 -0
- package/dist/pack-validator-HZPB2XJ3.js +7 -0
- package/dist/pack-validator-HZPB2XJ3.js.map +1 -0
- package/dist/{reader-B5mV20H6.d.ts → reader-CfWGpL4V.d.cts} +2 -1
- package/dist/{reader-B5mV20H6.d.cts → reader-CfWGpL4V.d.ts} +2 -1
- package/dist/standalone.cjs +44 -4
- package/dist/standalone.cjs.map +1 -1
- package/dist/standalone.d.cts +1 -1
- package/dist/standalone.d.ts +1 -1
- package/dist/standalone.js +2 -2
- package/package.json +12 -5
- package/dist/chunk-HBGZTUUZ.js.map +0 -1
- package/dist/chunk-KIFT5LA7.js.map +0 -1
- /package/dist/{chunk-5GAY7CSJ.js.map → chunk-PCNW5ZUD.js.map} +0 -0
- /package/dist/{chunk-EVOAYH2K.js.map → chunk-STG7Z2SS.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -43,6 +43,7 @@ __export(index_exports, {
|
|
|
43
43
|
N8nValidator: () => N8nValidator,
|
|
44
44
|
NodeRegistry: () => NodeRegistry,
|
|
45
45
|
NullLibrary: () => NullLibrary,
|
|
46
|
+
PackBuilder: () => PackBuilder,
|
|
46
47
|
ProviderError: () => ProviderError,
|
|
47
48
|
ResponseParseError: () => ResponseParseError,
|
|
48
49
|
TelemetryCollector: () => TelemetryCollector,
|
|
@@ -51,10 +52,13 @@ __export(index_exports, {
|
|
|
51
52
|
ValidationError: () => ValidationError,
|
|
52
53
|
buildSearchCorpus: () => buildSearchCorpus,
|
|
53
54
|
clusterWorkflows: () => clusterWorkflows,
|
|
55
|
+
derivePackStatus: () => derivePackStatus,
|
|
56
|
+
generateHandoff: () => generateHandoff,
|
|
54
57
|
hybridScore: () => hybridScore,
|
|
55
58
|
nullLogger: () => nullLogger,
|
|
56
59
|
rerank: () => rerank,
|
|
57
|
-
tokenize: () => tokenize
|
|
60
|
+
tokenize: () => tokenize,
|
|
61
|
+
validatePack: () => validatePack
|
|
58
62
|
});
|
|
59
63
|
module.exports = __toCommonJS(index_exports);
|
|
60
64
|
|
|
@@ -537,6 +541,36 @@ var N8nProvider = class {
|
|
|
537
541
|
}
|
|
538
542
|
};
|
|
539
543
|
|
|
544
|
+
// src/errors/generation-error.ts
|
|
545
|
+
var GenerationError = class extends KairosError {
|
|
546
|
+
constructor(message, cause) {
|
|
547
|
+
super(message, cause);
|
|
548
|
+
this.name = "GenerationError";
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/errors/response-parse-error.ts
|
|
553
|
+
var ResponseParseError = class extends KairosError {
|
|
554
|
+
constructor(message, cause) {
|
|
555
|
+
super(message, cause);
|
|
556
|
+
this.name = "ResponseParseError";
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/errors/validation-error.ts
|
|
561
|
+
var ValidationError = class extends KairosError {
|
|
562
|
+
constructor(message, issues, attemptMetadata, warnedRules) {
|
|
563
|
+
super(message);
|
|
564
|
+
this.issues = issues;
|
|
565
|
+
this.attemptMetadata = attemptMetadata;
|
|
566
|
+
this.warnedRules = warnedRules;
|
|
567
|
+
this.name = "ValidationError";
|
|
568
|
+
}
|
|
569
|
+
issues;
|
|
570
|
+
attemptMetadata;
|
|
571
|
+
warnedRules;
|
|
572
|
+
};
|
|
573
|
+
|
|
540
574
|
// src/validation/registry.ts
|
|
541
575
|
var DEFAULT_REGISTRY = [
|
|
542
576
|
// Trigger nodes
|
|
@@ -705,6 +739,7 @@ var N8nValidator = class {
|
|
|
705
739
|
this.checkRule32(workflow, issues);
|
|
706
740
|
this.checkRule33(workflow, issues);
|
|
707
741
|
this.checkRule34(workflow, issues);
|
|
742
|
+
this.checkRule35(workflow, issues);
|
|
708
743
|
if (Array.isArray(workflow.nodes)) {
|
|
709
744
|
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
710
745
|
for (const issue of issues) {
|
|
@@ -1277,6 +1312,43 @@ var N8nValidator = class {
|
|
|
1277
1312
|
}
|
|
1278
1313
|
}
|
|
1279
1314
|
}
|
|
1315
|
+
// Rule 35 (WARN): email-sending node with no duplicate-prevention signal
|
|
1316
|
+
checkRule35(w, issues) {
|
|
1317
|
+
if (!Array.isArray(w.nodes)) return;
|
|
1318
|
+
const sendNodes = w.nodes.filter((node) => {
|
|
1319
|
+
if (node.type === "n8n-nodes-base.gmail") {
|
|
1320
|
+
const op = node.parameters?.["operation"];
|
|
1321
|
+
return !op || op === "send" || op === "sendEmail" || op === "reply";
|
|
1322
|
+
}
|
|
1323
|
+
return node.type === "n8n-nodes-base.emailSend" || node.type === "n8n-nodes-base.sendEmail";
|
|
1324
|
+
});
|
|
1325
|
+
if (sendNodes.length === 0) return;
|
|
1326
|
+
const workflowText = JSON.stringify(w).toLowerCase();
|
|
1327
|
+
const IDEMPOTENCY_SIGNALS = [
|
|
1328
|
+
"sent_at",
|
|
1329
|
+
"last_sent",
|
|
1330
|
+
"last_reminder",
|
|
1331
|
+
"processed_at",
|
|
1332
|
+
"already_sent",
|
|
1333
|
+
"email_sent",
|
|
1334
|
+
"notified_at",
|
|
1335
|
+
"reminder_sent",
|
|
1336
|
+
"contacted_at",
|
|
1337
|
+
"dedupe",
|
|
1338
|
+
"idempotent"
|
|
1339
|
+
];
|
|
1340
|
+
const hasIdempotencySignal = IDEMPOTENCY_SIGNALS.some((s) => workflowText.includes(s));
|
|
1341
|
+
if (!hasIdempotencySignal) {
|
|
1342
|
+
for (const node of sendNodes) {
|
|
1343
|
+
this.warn(
|
|
1344
|
+
issues,
|
|
1345
|
+
35,
|
|
1346
|
+
`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`,
|
|
1347
|
+
node.id
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1280
1352
|
// Rule 34 (WARN): webhook path contains spaces, starts with slash, or looks like a full URL
|
|
1281
1353
|
checkRule34(w, issues) {
|
|
1282
1354
|
if (!Array.isArray(w.nodes)) return;
|
|
@@ -1311,36 +1383,6 @@ var N8nValidator = class {
|
|
|
1311
1383
|
}
|
|
1312
1384
|
};
|
|
1313
1385
|
|
|
1314
|
-
// src/errors/generation-error.ts
|
|
1315
|
-
var GenerationError = class extends KairosError {
|
|
1316
|
-
constructor(message, cause) {
|
|
1317
|
-
super(message, cause);
|
|
1318
|
-
this.name = "GenerationError";
|
|
1319
|
-
}
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
|
-
// src/errors/response-parse-error.ts
|
|
1323
|
-
var ResponseParseError = class extends KairosError {
|
|
1324
|
-
constructor(message, cause) {
|
|
1325
|
-
super(message, cause);
|
|
1326
|
-
this.name = "ResponseParseError";
|
|
1327
|
-
}
|
|
1328
|
-
};
|
|
1329
|
-
|
|
1330
|
-
// src/errors/validation-error.ts
|
|
1331
|
-
var ValidationError = class extends KairosError {
|
|
1332
|
-
constructor(message, issues, attemptMetadata, warnedRules) {
|
|
1333
|
-
super(message);
|
|
1334
|
-
this.issues = issues;
|
|
1335
|
-
this.attemptMetadata = attemptMetadata;
|
|
1336
|
-
this.warnedRules = warnedRules;
|
|
1337
|
-
this.name = "ValidationError";
|
|
1338
|
-
}
|
|
1339
|
-
issues;
|
|
1340
|
-
attemptMetadata;
|
|
1341
|
-
warnedRules;
|
|
1342
|
-
};
|
|
1343
|
-
|
|
1344
1386
|
// src/generation/prompt-builder.ts
|
|
1345
1387
|
var import_node_fs = require("fs");
|
|
1346
1388
|
var import_node_path = require("path");
|
|
@@ -1569,7 +1611,7 @@ function scoreToMode(score) {
|
|
|
1569
1611
|
}
|
|
1570
1612
|
|
|
1571
1613
|
// src/validation/rule-metadata.ts
|
|
1572
|
-
var VALIDATOR_RULE_IDS = Array.from({ length:
|
|
1614
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 35 }, (_, i) => i + 1);
|
|
1573
1615
|
var RULE_PIPELINE_STAGES = {
|
|
1574
1616
|
1: "node_generation",
|
|
1575
1617
|
2: "node_generation",
|
|
@@ -1604,7 +1646,8 @@ var RULE_PIPELINE_STAGES = {
|
|
|
1604
1646
|
31: "node_generation",
|
|
1605
1647
|
32: "node_generation",
|
|
1606
1648
|
33: "node_generation",
|
|
1607
|
-
34: "node_generation"
|
|
1649
|
+
34: "node_generation",
|
|
1650
|
+
35: "node_generation"
|
|
1608
1651
|
};
|
|
1609
1652
|
var RULE_EXAMPLES = {
|
|
1610
1653
|
17: {
|
|
@@ -1654,6 +1697,10 @@ var RULE_EXAMPLES = {
|
|
|
1654
1697
|
34: {
|
|
1655
1698
|
bad: '"path": "/my webhook"',
|
|
1656
1699
|
good: '"path": "my-webhook"'
|
|
1700
|
+
},
|
|
1701
|
+
35: {
|
|
1702
|
+
bad: '"type": "n8n-nodes-base.gmail", "parameters": { "operation": "send" } // no sent_at tracking',
|
|
1703
|
+
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'
|
|
1657
1704
|
}
|
|
1658
1705
|
};
|
|
1659
1706
|
var RULE_MITIGATIONS = {
|
|
@@ -1690,7 +1737,8 @@ var RULE_MITIGATIONS = {
|
|
|
1690
1737
|
31: "Add at least one condition to the if node \u2014 conditions.conditions array must be non-empty",
|
|
1691
1738
|
32: "Add field assignments to the set node \u2014 assignments.assignments array must be non-empty for typeVersion 3.x",
|
|
1692
1739
|
33: "Add at least one schedule rule to scheduleTrigger \u2014 rule.interval array must have at least one entry",
|
|
1693
|
-
34: 'Webhook path must be a relative path without spaces, leading slashes, or protocol prefixes (e.g. "my-hook")'
|
|
1740
|
+
34: 'Webhook path must be a relative path without spaces, leading slashes, or protocol prefixes (e.g. "my-hook")',
|
|
1741
|
+
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"
|
|
1694
1742
|
};
|
|
1695
1743
|
|
|
1696
1744
|
// src/generation/prompt-builder.ts
|
|
@@ -2345,7 +2393,7 @@ var PatternAnalyzer = class _PatternAnalyzer {
|
|
|
2345
2393
|
this._cachedEvents = events;
|
|
2346
2394
|
const starts = events.filter((e) => e.eventType === "build_start");
|
|
2347
2395
|
const attempts = events.filter((e) => e.eventType === "generation_attempt");
|
|
2348
|
-
const
|
|
2396
|
+
const _passed = attempts.filter(
|
|
2349
2397
|
(a) => a.data.validationPassed === true
|
|
2350
2398
|
);
|
|
2351
2399
|
const failed = attempts.filter(
|
|
@@ -2839,7 +2887,6 @@ var DEFAULT_MODEL = process.env["KAIROS_MODEL"] ?? "claude-sonnet-4-6";
|
|
|
2839
2887
|
var Kairos = class {
|
|
2840
2888
|
provider;
|
|
2841
2889
|
designer;
|
|
2842
|
-
validator;
|
|
2843
2890
|
library;
|
|
2844
2891
|
logger;
|
|
2845
2892
|
telemetry;
|
|
@@ -2865,7 +2912,6 @@ var Kairos = class {
|
|
|
2865
2912
|
const anthropic = new import_sdk.default({ apiKey: options.anthropicApiKey });
|
|
2866
2913
|
const patternsPath = typeof options.telemetry === "string" ? (0, import_node_path6.join)(options.telemetry, "..", "patterns.json") : (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".kairos", "patterns.json");
|
|
2867
2914
|
this.designer = new WorkflowDesigner(anthropic, this.model, logger, patternsPath);
|
|
2868
|
-
this.validator = new N8nValidator();
|
|
2869
2915
|
this.library = options.library ?? new NullLibrary();
|
|
2870
2916
|
this.logger = logger;
|
|
2871
2917
|
if (options.telemetry === true) {
|
|
@@ -2962,7 +3008,6 @@ var Kairos = class {
|
|
|
2962
3008
|
}
|
|
2963
3009
|
await this.emitAttemptTelemetry(description, designResult, workflowType, runId);
|
|
2964
3010
|
const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
|
|
2965
|
-
this.saveToLibrary(workflow, description, designResult, matches);
|
|
2966
3011
|
if (options?.dryRun) {
|
|
2967
3012
|
const totalTokensInput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
|
|
2968
3013
|
const totalTokensOutput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
|
|
@@ -2998,6 +3043,7 @@ var Kairos = class {
|
|
|
2998
3043
|
if (options?.activate) {
|
|
2999
3044
|
await provider.activate(deployed.workflowId);
|
|
3000
3045
|
}
|
|
3046
|
+
this.saveToLibrary(workflow, description, designResult, matches, deployed.workflowId);
|
|
3001
3047
|
let smokeTestResult;
|
|
3002
3048
|
if (options?.smokeTest) {
|
|
3003
3049
|
smokeTestResult = await provider.smokeTest(deployed.workflowId, workflow).catch((err) => {
|
|
@@ -3927,6 +3973,334 @@ var FileLibrary = class {
|
|
|
3927
3973
|
}
|
|
3928
3974
|
};
|
|
3929
3975
|
|
|
3976
|
+
// src/pack/pack-builder.ts
|
|
3977
|
+
var import_sdk2 = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
3978
|
+
function derivePackStatus(pack) {
|
|
3979
|
+
const hasBlocking = pack.assumptions.some((a) => a.type === "blocking");
|
|
3980
|
+
const hasFailures = pack.workflows.some((w) => w.error);
|
|
3981
|
+
const allDeployed = pack.workflows.length > 0 && pack.workflows.every((w) => w.deployed);
|
|
3982
|
+
const hasNeedsConfirmation = pack.assumptions.some((a) => a.type === "needs_confirmation");
|
|
3983
|
+
if (pack.status === "active" && !hasBlocking && !hasFailures && allDeployed) return "active";
|
|
3984
|
+
if (pack.workflows.length === 0 || !allDeployed && !hasFailures) return "draft";
|
|
3985
|
+
if (hasBlocking) return "blocked";
|
|
3986
|
+
if (hasFailures) return "needs_attention";
|
|
3987
|
+
if (hasNeedsConfirmation) return "ready_for_test";
|
|
3988
|
+
return "ready_for_activation";
|
|
3989
|
+
}
|
|
3990
|
+
var PLAN_PROMPT = `You are planning an n8n workflow automation pack for a business.
|
|
3991
|
+
|
|
3992
|
+
Business context: {CONTEXT}
|
|
3993
|
+
|
|
3994
|
+
Generate a list of 4-8 n8n workflows that would meaningfully automate this business's operations. Focus on workflows that save time on repetitive tasks, improve customer communication, prevent things falling through the cracks, and are realistic to implement with n8n nodes.
|
|
3995
|
+
|
|
3996
|
+
For each workflow, write a detailed build description (2-4 sentences) suitable for passing directly to an n8n workflow generator. Be specific: name the trigger type, data sources (Google Sheets columns if applicable), actions, and outputs.
|
|
3997
|
+
|
|
3998
|
+
For assumptions, classify each one:
|
|
3999
|
+
- "safe": a clearly reasonable default the business likely expects (e.g. "Schedule runs Monday 9 AM")
|
|
4000
|
+
- "needs_confirmation": should be confirmed before going live but won't break things immediately (e.g. "Assumed professional email tone \u2014 confirm brand voice")
|
|
4001
|
+
- "blocking": MUST be resolved before activation or the workflow will fail, send duplicates, or surprise customers (e.g. "Google Sheet ID not provided", "emails auto-send without approval gate \u2014 add confirmation step")
|
|
4002
|
+
|
|
4003
|
+
Treat any open question that would block safe deployment as a blocking assumption.
|
|
4004
|
+
|
|
4005
|
+
Return ONLY valid JSON with no markdown or extra text:
|
|
4006
|
+
{
|
|
4007
|
+
"workflows": [
|
|
4008
|
+
{
|
|
4009
|
+
"name": "Short descriptive name",
|
|
4010
|
+
"description": "Detailed generator-ready description specifying trigger, data sources, actions, outputs",
|
|
4011
|
+
"purpose": "One sentence explaining the business value"
|
|
4012
|
+
}
|
|
4013
|
+
],
|
|
4014
|
+
"assumptions": [
|
|
4015
|
+
{ "type": "safe" | "needs_confirmation" | "blocking", "text": "Description of the assumption" }
|
|
4016
|
+
],
|
|
4017
|
+
"sheetsColumns": [
|
|
4018
|
+
{ "sheet": "Sheet name", "columns": ["col1", "col2"] }
|
|
4019
|
+
],
|
|
4020
|
+
"testChecklist": [
|
|
4021
|
+
{ "workflow": "Workflow name", "steps": ["How to manually test this workflow"] }
|
|
4022
|
+
]
|
|
4023
|
+
}`;
|
|
4024
|
+
function normalizeAssumptions(raw) {
|
|
4025
|
+
const validTypes = /* @__PURE__ */ new Set(["safe", "needs_confirmation", "blocking"]);
|
|
4026
|
+
return raw.map((a) => {
|
|
4027
|
+
if (typeof a === "string") {
|
|
4028
|
+
return { type: "needs_confirmation", text: a };
|
|
4029
|
+
}
|
|
4030
|
+
if (typeof a === "object" && a !== null) {
|
|
4031
|
+
const obj = a;
|
|
4032
|
+
const type = typeof obj["type"] === "string" && validTypes.has(obj["type"]) ? obj["type"] : "needs_confirmation";
|
|
4033
|
+
const text = typeof obj["text"] === "string" ? obj["text"] : JSON.stringify(obj);
|
|
4034
|
+
return { type, text };
|
|
4035
|
+
}
|
|
4036
|
+
return { type: "needs_confirmation", text: String(a) };
|
|
4037
|
+
});
|
|
4038
|
+
}
|
|
4039
|
+
var PackBuilder = class {
|
|
4040
|
+
client;
|
|
4041
|
+
kairos;
|
|
4042
|
+
model;
|
|
4043
|
+
constructor(options) {
|
|
4044
|
+
this.client = new import_sdk2.default({ apiKey: options.anthropicApiKey });
|
|
4045
|
+
this.kairos = options.kairos;
|
|
4046
|
+
this.model = options.model ?? "claude-sonnet-4-6";
|
|
4047
|
+
}
|
|
4048
|
+
async plan(businessContext) {
|
|
4049
|
+
const prompt = PLAN_PROMPT.replace("{CONTEXT}", businessContext);
|
|
4050
|
+
const response = await this.client.messages.create({
|
|
4051
|
+
model: this.model,
|
|
4052
|
+
max_tokens: 4096,
|
|
4053
|
+
messages: [{ role: "user", content: prompt }]
|
|
4054
|
+
});
|
|
4055
|
+
const text = response.content[0]?.type === "text" ? response.content[0].text.trim() : "";
|
|
4056
|
+
const cleaned = text.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
4057
|
+
const parsed = JSON.parse(cleaned);
|
|
4058
|
+
const rawAssumptions = Array.isArray(parsed["assumptions"]) ? parsed["assumptions"] : [];
|
|
4059
|
+
const rawOpenQuestions = Array.isArray(parsed["openQuestions"]) ? parsed["openQuestions"] : [];
|
|
4060
|
+
const assumptions = normalizeAssumptions([...rawAssumptions, ...rawOpenQuestions.map(
|
|
4061
|
+
(q) => typeof q === "string" ? { type: "needs_confirmation", text: q } : q
|
|
4062
|
+
)]);
|
|
4063
|
+
return {
|
|
4064
|
+
businessContext,
|
|
4065
|
+
workflows: Array.isArray(parsed["workflows"]) ? parsed["workflows"] : [],
|
|
4066
|
+
assumptions,
|
|
4067
|
+
sheetsColumns: Array.isArray(parsed["sheetsColumns"]) ? parsed["sheetsColumns"] : [],
|
|
4068
|
+
testChecklist: Array.isArray(parsed["testChecklist"]) ? parsed["testChecklist"] : []
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
async build(plan, options = {}) {
|
|
4072
|
+
const hasBlockingAssumptions = plan.assumptions.some((a) => a.type === "blocking");
|
|
4073
|
+
const effectiveActivate = hasBlockingAssumptions ? false : options.activate ?? false;
|
|
4074
|
+
const results = [];
|
|
4075
|
+
const credentialMap = /* @__PURE__ */ new Map();
|
|
4076
|
+
for (let i = 0; i < plan.workflows.length; i++) {
|
|
4077
|
+
const wf = plan.workflows[i];
|
|
4078
|
+
options.onProgress?.(wf, i, plan.workflows.length);
|
|
4079
|
+
try {
|
|
4080
|
+
const result = await this.kairos.build(wf.description, {
|
|
4081
|
+
name: wf.name,
|
|
4082
|
+
dryRun: options.dryRun ?? false,
|
|
4083
|
+
activate: effectiveActivate
|
|
4084
|
+
});
|
|
4085
|
+
for (const cred of result.credentialsNeeded) {
|
|
4086
|
+
credentialMap.set(cred.service, { service: cred.service, credentialType: cred.credentialType });
|
|
4087
|
+
}
|
|
4088
|
+
results.push({
|
|
4089
|
+
name: wf.name,
|
|
4090
|
+
purpose: wf.purpose,
|
|
4091
|
+
workflowId: result.workflowId,
|
|
4092
|
+
deployed: !result.dryRun,
|
|
4093
|
+
generationAttempts: result.generationAttempts,
|
|
4094
|
+
credentialsNeeded: result.credentialsNeeded
|
|
4095
|
+
});
|
|
4096
|
+
} catch (err) {
|
|
4097
|
+
results.push({
|
|
4098
|
+
name: wf.name,
|
|
4099
|
+
purpose: wf.purpose,
|
|
4100
|
+
workflowId: null,
|
|
4101
|
+
deployed: false,
|
|
4102
|
+
generationAttempts: 0,
|
|
4103
|
+
credentialsNeeded: [],
|
|
4104
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
const packName = plan.businessContext.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4109
|
+
const partial = {
|
|
4110
|
+
businessContext: plan.businessContext,
|
|
4111
|
+
packName,
|
|
4112
|
+
status: "draft",
|
|
4113
|
+
workflows: results,
|
|
4114
|
+
allCredentials: Array.from(credentialMap.values()),
|
|
4115
|
+
sheetsColumns: plan.sheetsColumns,
|
|
4116
|
+
assumptions: plan.assumptions,
|
|
4117
|
+
testChecklist: plan.testChecklist,
|
|
4118
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4119
|
+
};
|
|
4120
|
+
return { ...partial, status: derivePackStatus(partial) };
|
|
4121
|
+
}
|
|
4122
|
+
};
|
|
4123
|
+
|
|
4124
|
+
// src/pack/pack-exporter.ts
|
|
4125
|
+
var STATUS_LABELS = {
|
|
4126
|
+
draft: "Draft",
|
|
4127
|
+
blocked: "Blocked \u2014 resolve issues before activation",
|
|
4128
|
+
ready_for_test: "Ready for Testing",
|
|
4129
|
+
ready_for_activation: "Ready for Activation",
|
|
4130
|
+
active: "Active",
|
|
4131
|
+
needs_attention: "Needs Attention"
|
|
4132
|
+
};
|
|
4133
|
+
function generateHandoff(pack) {
|
|
4134
|
+
const lines = [];
|
|
4135
|
+
const line = () => lines.push("");
|
|
4136
|
+
lines.push(`# ${pack.businessContext} \u2014 Workflow Pack`);
|
|
4137
|
+
line();
|
|
4138
|
+
lines.push(`**Status:** ${STATUS_LABELS[pack.status] ?? pack.status}`);
|
|
4139
|
+
lines.push(`**Generated:** ${new Date(pack.builtAt).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}`);
|
|
4140
|
+
lines.push(`**Workflows:** ${pack.workflows.length} (${pack.workflows.filter((w) => w.deployed).length} deployed)`);
|
|
4141
|
+
line();
|
|
4142
|
+
lines.push(`## Overview`);
|
|
4143
|
+
line();
|
|
4144
|
+
lines.push(
|
|
4145
|
+
`This workflow pack automates operations for **${pack.businessContext}**. It was built with Kairos and requires credential setup before workflows can be activated in n8n.`
|
|
4146
|
+
);
|
|
4147
|
+
line();
|
|
4148
|
+
const blocking = pack.assumptions.filter((a) => a.type === "blocking");
|
|
4149
|
+
if (blocking.length > 0) {
|
|
4150
|
+
lines.push(`## Blocking Issues`);
|
|
4151
|
+
line();
|
|
4152
|
+
lines.push(`> These must be resolved before any workflows are activated.`);
|
|
4153
|
+
line();
|
|
4154
|
+
for (const a of blocking) {
|
|
4155
|
+
lines.push(`- [ ] ${a.text}`);
|
|
4156
|
+
}
|
|
4157
|
+
line();
|
|
4158
|
+
}
|
|
4159
|
+
lines.push(`## Workflows`);
|
|
4160
|
+
line();
|
|
4161
|
+
for (const wf of pack.workflows) {
|
|
4162
|
+
const icon = wf.error ? "\u2717" : "\u2713";
|
|
4163
|
+
lines.push(`### ${icon} ${wf.name}`);
|
|
4164
|
+
line();
|
|
4165
|
+
lines.push(`**Purpose:** ${wf.purpose}`);
|
|
4166
|
+
if (wf.workflowId) lines.push(`**n8n ID:** \`${wf.workflowId}\``);
|
|
4167
|
+
if (!wf.deployed && !wf.error) lines.push(`**Status:** Not deployed (dry run)`);
|
|
4168
|
+
if (wf.error) lines.push(`**Error:** ${wf.error}`);
|
|
4169
|
+
line();
|
|
4170
|
+
}
|
|
4171
|
+
if (pack.allCredentials.length > 0) {
|
|
4172
|
+
lines.push(`## Required Credentials`);
|
|
4173
|
+
line();
|
|
4174
|
+
lines.push(`Connect these in n8n before activating workflows:`);
|
|
4175
|
+
line();
|
|
4176
|
+
for (const cred of pack.allCredentials) {
|
|
4177
|
+
lines.push(`- [ ] **${cred.service}** (${cred.credentialType})`);
|
|
4178
|
+
}
|
|
4179
|
+
line();
|
|
4180
|
+
}
|
|
4181
|
+
if (pack.sheetsColumns.length > 0) {
|
|
4182
|
+
lines.push(`## Required Google Sheets`);
|
|
4183
|
+
line();
|
|
4184
|
+
for (const sheet of pack.sheetsColumns) {
|
|
4185
|
+
lines.push(`### ${sheet.sheet}`);
|
|
4186
|
+
line();
|
|
4187
|
+
lines.push(`Columns: ${sheet.columns.map((c) => `\`${c}\``).join(", ")}`);
|
|
4188
|
+
line();
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
const needsConfirmation = pack.assumptions.filter((a) => a.type === "needs_confirmation");
|
|
4192
|
+
if (needsConfirmation.length > 0) {
|
|
4193
|
+
lines.push(`## Needs Confirmation`);
|
|
4194
|
+
line();
|
|
4195
|
+
lines.push(`Verify these with the client before going live:`);
|
|
4196
|
+
line();
|
|
4197
|
+
for (const a of needsConfirmation) {
|
|
4198
|
+
lines.push(`- [ ] ${a.text}`);
|
|
4199
|
+
}
|
|
4200
|
+
line();
|
|
4201
|
+
}
|
|
4202
|
+
const safe = pack.assumptions.filter((a) => a.type === "safe");
|
|
4203
|
+
if (safe.length > 0) {
|
|
4204
|
+
lines.push(`## Safe Assumptions`);
|
|
4205
|
+
line();
|
|
4206
|
+
lines.push(`These defaults were used during generation \u2014 no action needed:`);
|
|
4207
|
+
line();
|
|
4208
|
+
for (const a of safe) {
|
|
4209
|
+
lines.push(`- ${a.text}`);
|
|
4210
|
+
}
|
|
4211
|
+
line();
|
|
4212
|
+
}
|
|
4213
|
+
lines.push(`## Setup Checklist`);
|
|
4214
|
+
line();
|
|
4215
|
+
lines.push(`Complete before testing:`);
|
|
4216
|
+
line();
|
|
4217
|
+
for (const cred of pack.allCredentials) {
|
|
4218
|
+
lines.push(`- [ ] Connect **${cred.service}** credential in n8n Settings \u2192 Credentials`);
|
|
4219
|
+
}
|
|
4220
|
+
for (const sheet of pack.sheetsColumns) {
|
|
4221
|
+
lines.push(`- [ ] Create Google Sheet: "${sheet.sheet}" with columns: ${sheet.columns.join(", ")}`);
|
|
4222
|
+
}
|
|
4223
|
+
for (const a of blocking) {
|
|
4224
|
+
lines.push(`- [ ] Resolve: ${a.text}`);
|
|
4225
|
+
}
|
|
4226
|
+
for (const a of needsConfirmation) {
|
|
4227
|
+
lines.push(`- [ ] Confirm: ${a.text}`);
|
|
4228
|
+
}
|
|
4229
|
+
line();
|
|
4230
|
+
if (pack.testChecklist.length > 0) {
|
|
4231
|
+
lines.push(`## Testing Checklist`);
|
|
4232
|
+
line();
|
|
4233
|
+
for (const item of pack.testChecklist) {
|
|
4234
|
+
lines.push(`### ${item.workflow}`);
|
|
4235
|
+
line();
|
|
4236
|
+
for (const step of item.steps) {
|
|
4237
|
+
lines.push(`- [ ] ${step}`);
|
|
4238
|
+
}
|
|
4239
|
+
line();
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
const deployedWorkflows = pack.workflows.filter((w) => w.deployed && !w.error);
|
|
4243
|
+
if (deployedWorkflows.length > 0) {
|
|
4244
|
+
lines.push(`## Activation Checklist`);
|
|
4245
|
+
line();
|
|
4246
|
+
lines.push(`Activate in n8n after testing is complete:`);
|
|
4247
|
+
line();
|
|
4248
|
+
for (const wf of deployedWorkflows) {
|
|
4249
|
+
const idSuffix = wf.workflowId ? ` (n8n ID: \`${wf.workflowId}\`)` : "";
|
|
4250
|
+
lines.push(`- [ ] Activate: **${wf.name}**${idSuffix}`);
|
|
4251
|
+
}
|
|
4252
|
+
line();
|
|
4253
|
+
}
|
|
4254
|
+
lines.push(`## Maintenance Notes`);
|
|
4255
|
+
line();
|
|
4256
|
+
lines.push(`- Monitor n8n executions weekly \u2014 check the Executions tab for failures`);
|
|
4257
|
+
lines.push(`- Re-run \`kairos build-pack\` to regenerate workflows if business needs change`);
|
|
4258
|
+
lines.push(`- Update Google Sheets data as business information changes`);
|
|
4259
|
+
lines.push(`- Rotate API credentials before expiration (n8n Settings \u2192 Credentials)`);
|
|
4260
|
+
lines.push(`- Run \`kairos validate-pack <name>\` before activating after any changes`);
|
|
4261
|
+
return lines.join("\n");
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
// src/pack/pack-validator.ts
|
|
4265
|
+
function validatePack(pack) {
|
|
4266
|
+
const issues = [];
|
|
4267
|
+
const names = pack.workflows.map((w) => w.name);
|
|
4268
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4269
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
4270
|
+
for (const name of names) {
|
|
4271
|
+
if (seen.has(name)) duplicates.add(name);
|
|
4272
|
+
seen.add(name);
|
|
4273
|
+
}
|
|
4274
|
+
if (duplicates.size > 0) {
|
|
4275
|
+
issues.push({
|
|
4276
|
+
type: "duplicate_name",
|
|
4277
|
+
severity: "error",
|
|
4278
|
+
message: `Duplicate workflow names: ${[...duplicates].join(", ")} \u2014 n8n may overwrite existing workflows on deploy`,
|
|
4279
|
+
workflows: [...duplicates]
|
|
4280
|
+
});
|
|
4281
|
+
}
|
|
4282
|
+
const blocking = pack.assumptions.filter((a) => a.type === "blocking");
|
|
4283
|
+
if (blocking.length > 0) {
|
|
4284
|
+
const plural = blocking.length === 1 ? "assumption" : "assumptions";
|
|
4285
|
+
issues.push({
|
|
4286
|
+
type: "blocking_assumption",
|
|
4287
|
+
severity: "error",
|
|
4288
|
+
message: `${blocking.length} blocking ${plural} must be resolved before activation:
|
|
4289
|
+
${blocking.map((a) => `\u2022 ${a.text}`).join("\n ")}`
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
const failed = pack.workflows.filter((w) => w.error);
|
|
4293
|
+
for (const wf of failed) {
|
|
4294
|
+
issues.push({
|
|
4295
|
+
type: "unsafe_activation",
|
|
4296
|
+
severity: "error",
|
|
4297
|
+
message: `Workflow "${wf.name}" failed to deploy: ${wf.error ?? "unknown error"}`,
|
|
4298
|
+
workflows: [wf.name]
|
|
4299
|
+
});
|
|
4300
|
+
}
|
|
4301
|
+
return issues;
|
|
4302
|
+
}
|
|
4303
|
+
|
|
3930
4304
|
// src/templates/safety.ts
|
|
3931
4305
|
var BLOCKED_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
3932
4306
|
"n8n-nodes-base.code",
|
|
@@ -4152,6 +4526,7 @@ var TemplateSyncer = class {
|
|
|
4152
4526
|
N8nValidator,
|
|
4153
4527
|
NodeRegistry,
|
|
4154
4528
|
NullLibrary,
|
|
4529
|
+
PackBuilder,
|
|
4155
4530
|
ProviderError,
|
|
4156
4531
|
ResponseParseError,
|
|
4157
4532
|
TelemetryCollector,
|
|
@@ -4160,9 +4535,12 @@ var TemplateSyncer = class {
|
|
|
4160
4535
|
ValidationError,
|
|
4161
4536
|
buildSearchCorpus,
|
|
4162
4537
|
clusterWorkflows,
|
|
4538
|
+
derivePackStatus,
|
|
4539
|
+
generateHandoff,
|
|
4163
4540
|
hybridScore,
|
|
4164
4541
|
nullLogger,
|
|
4165
4542
|
rerank,
|
|
4166
|
-
tokenize
|
|
4543
|
+
tokenize,
|
|
4544
|
+
validatePack
|
|
4167
4545
|
});
|
|
4168
4546
|
//# sourceMappingURL=index.cjs.map
|