@minded-ai/mindedjs 3.0.5 → 3.0.6-beta.1
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/dist/cli/index.js +6 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/validateFlow.d.ts +47 -0
- package/dist/cli/validateFlow.d.ts.map +1 -0
- package/dist/cli/validateFlow.js +454 -0
- package/dist/cli/validateFlow.js.map +1 -0
- package/docs/platform/security-architecture.md +0 -18
- package/package.json +3 -2
- package/src/cli/index.ts +5 -1
- package/src/cli/validateFlow.ts +502 -0
package/dist/cli/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const logger_1 = require("../utils/logger");
|
|
40
40
|
const child_process_1 = require("child_process");
|
|
41
41
|
const localOperatorSetup_1 = require("./localOperatorSetup");
|
|
42
|
+
const validateFlow_1 = require("./validateFlow");
|
|
42
43
|
const ENV_FILE = '.env';
|
|
43
44
|
function getEnvFilePath() {
|
|
44
45
|
return path.join(process.cwd(), ENV_FILE);
|
|
@@ -195,9 +196,13 @@ async function main() {
|
|
|
195
196
|
process.exit(1);
|
|
196
197
|
}
|
|
197
198
|
}
|
|
199
|
+
else if (command === 'validate') {
|
|
200
|
+
// Command to validate a flow YAML file
|
|
201
|
+
(0, validateFlow_1.runValidateCommand)();
|
|
202
|
+
}
|
|
198
203
|
else {
|
|
199
204
|
logger_1.logger.error({
|
|
200
|
-
msg: 'Unknown command. Available commands: token, setup-local-operator, check-local-operator, generate-lambda-ts-handler',
|
|
205
|
+
msg: 'Unknown command. Available commands: token, setup-local-operator, check-local-operator, generate-lambda-ts-handler, validate',
|
|
201
206
|
});
|
|
202
207
|
process.exit(1);
|
|
203
208
|
}
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,uCAAyB;AACzB,2CAA6B;AAC7B,4CAAyC;AACzC,iDAAyC;AACzC,6DAAgF;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface ValidationResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
flowName?: string;
|
|
4
|
+
stats?: {
|
|
5
|
+
nodes: number;
|
|
6
|
+
edges: number;
|
|
7
|
+
nodeTypes: string[];
|
|
8
|
+
edgeTypes: string[];
|
|
9
|
+
};
|
|
10
|
+
errors?: string[];
|
|
11
|
+
warnings?: string[];
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validates a cron expression format using cron-parser.
|
|
16
|
+
* Cron format: minute hour day month day-of-week (5 fields)
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateCronExpression(cronExpression: string): {
|
|
19
|
+
valid: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Validates an IANA timezone identifier.
|
|
24
|
+
* Uses Intl.DateTimeFormat to check if the timezone is valid.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateTimezone(timezone: string): {
|
|
27
|
+
valid: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Validates a schedule trigger node configuration.
|
|
32
|
+
* Returns an array of error messages (empty if valid).
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateScheduleTrigger(node: any, nodeIndex: string): {
|
|
35
|
+
errors: string[];
|
|
36
|
+
warnings: string[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Validates a MindedJS YAML flow file.
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateFlow(flowPath: string): ValidationResult;
|
|
42
|
+
/**
|
|
43
|
+
* CLI command handler for validate
|
|
44
|
+
*/
|
|
45
|
+
export declare function runValidateCommand(): void;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=validateFlow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateFlow.d.ts","sourceRoot":"","sources":["../../src/cli/validateFlow.ts"],"names":[],"mappings":"AAKA,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAcjG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBrF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,GAAG,EACT,SAAS,EAAE,MAAM,GAChB;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA2B1C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAwQ/D;AAiCD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAoGzC"}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateCronExpression = validateCronExpression;
|
|
37
|
+
exports.validateTimezone = validateTimezone;
|
|
38
|
+
exports.validateScheduleTrigger = validateScheduleTrigger;
|
|
39
|
+
exports.validateFlow = validateFlow;
|
|
40
|
+
exports.runValidateCommand = runValidateCommand;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const yaml = __importStar(require("js-yaml"));
|
|
44
|
+
const cron_parser_1 = require("cron-parser");
|
|
45
|
+
/**
|
|
46
|
+
* Validates a cron expression format using cron-parser.
|
|
47
|
+
* Cron format: minute hour day month day-of-week (5 fields)
|
|
48
|
+
*/
|
|
49
|
+
function validateCronExpression(cronExpression) {
|
|
50
|
+
if (typeof cronExpression !== 'string' || cronExpression.trim() === '') {
|
|
51
|
+
return { valid: false, error: 'Cron expression must be a non-empty string' };
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
cron_parser_1.CronExpressionParser.parse(cronExpression.trim());
|
|
55
|
+
return { valid: true };
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
error: error instanceof Error ? error.message : 'Invalid cron expression format',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validates an IANA timezone identifier.
|
|
66
|
+
* Uses Intl.DateTimeFormat to check if the timezone is valid.
|
|
67
|
+
*/
|
|
68
|
+
function validateTimezone(timezone) {
|
|
69
|
+
if (typeof timezone !== 'string' || timezone.trim() === '') {
|
|
70
|
+
return { valid: false, error: 'Timezone must be a non-empty string' };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
// Try to create a date formatter with the timezone
|
|
74
|
+
const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timezone });
|
|
75
|
+
// Test if the timezone is valid by trying to format a date
|
|
76
|
+
formatter.format(new Date());
|
|
77
|
+
return { valid: true };
|
|
78
|
+
}
|
|
79
|
+
catch (_a) {
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
error: `Invalid IANA timezone identifier: "${timezone}"`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Validates a schedule trigger node configuration.
|
|
88
|
+
* Returns an array of error messages (empty if valid).
|
|
89
|
+
*/
|
|
90
|
+
function validateScheduleTrigger(node, nodeIndex) {
|
|
91
|
+
const errors = [];
|
|
92
|
+
const warnings = [];
|
|
93
|
+
if (!node.cronExpression) {
|
|
94
|
+
errors.push(`${nodeIndex} "${node.name}": schedule trigger missing required field "cronExpression"`);
|
|
95
|
+
}
|
|
96
|
+
else if (typeof node.cronExpression !== 'string') {
|
|
97
|
+
errors.push(`${nodeIndex} "${node.name}": schedule trigger "cronExpression" must be a string`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const cronValidation = validateCronExpression(node.cronExpression);
|
|
101
|
+
if (!cronValidation.valid) {
|
|
102
|
+
errors.push(`${nodeIndex} "${node.name}": invalid cron expression - ${cronValidation.error}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (node.timezone !== undefined) {
|
|
106
|
+
if (typeof node.timezone !== 'string') {
|
|
107
|
+
errors.push(`${nodeIndex} "${node.name}": schedule trigger "timezone" must be a string`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const timezoneValidation = validateTimezone(node.timezone);
|
|
111
|
+
if (!timezoneValidation.valid) {
|
|
112
|
+
errors.push(`${nodeIndex} "${node.name}": ${timezoneValidation.error}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { errors, warnings };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Validates a MindedJS YAML flow file.
|
|
120
|
+
*/
|
|
121
|
+
function validateFlow(flowPath) {
|
|
122
|
+
// Resolve the path
|
|
123
|
+
const resolvedPath = path.isAbsolute(flowPath) ? flowPath : path.join(process.cwd(), flowPath);
|
|
124
|
+
// Read the file
|
|
125
|
+
let flowContent;
|
|
126
|
+
try {
|
|
127
|
+
flowContent = fs.readFileSync(resolvedPath, 'utf-8');
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Parse YAML
|
|
136
|
+
let flow;
|
|
137
|
+
try {
|
|
138
|
+
flow = yaml.load(flowContent);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
message: `Invalid YAML syntax: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Validate flow structure
|
|
147
|
+
const errors = [];
|
|
148
|
+
const warnings = [];
|
|
149
|
+
// Check for required top-level fields
|
|
150
|
+
if (!flow.name) {
|
|
151
|
+
errors.push('Flow is missing required field: "name"');
|
|
152
|
+
}
|
|
153
|
+
if (!flow.nodes || !Array.isArray(flow.nodes)) {
|
|
154
|
+
errors.push('Flow is missing required field: "nodes" (must be an array)');
|
|
155
|
+
}
|
|
156
|
+
if (!flow.edges || !Array.isArray(flow.edges)) {
|
|
157
|
+
errors.push('Flow is missing required field: "edges" (must be an array)');
|
|
158
|
+
}
|
|
159
|
+
// If basic structure is invalid, return early
|
|
160
|
+
if (errors.length > 0) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
errors,
|
|
164
|
+
warnings,
|
|
165
|
+
message: `Flow validation failed with ${errors.length} error(s)`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// Create a set of node names for validation
|
|
169
|
+
const nodeNames = new Set();
|
|
170
|
+
const nodesByName = new Map();
|
|
171
|
+
// Validate nodes
|
|
172
|
+
for (let i = 0; i < flow.nodes.length; i++) {
|
|
173
|
+
const node = flow.nodes[i];
|
|
174
|
+
const nodeIndex = `Node ${i + 1}`;
|
|
175
|
+
// Check for required name field
|
|
176
|
+
if (!node.name) {
|
|
177
|
+
errors.push(`${nodeIndex}: Missing required field "name"`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// Check for duplicate node names
|
|
181
|
+
if (nodeNames.has(node.name)) {
|
|
182
|
+
errors.push(`${nodeIndex} "${node.name}": Duplicate node name`);
|
|
183
|
+
}
|
|
184
|
+
nodeNames.add(node.name);
|
|
185
|
+
nodesByName.set(node.name, node);
|
|
186
|
+
// Check for required type field
|
|
187
|
+
if (!node.type) {
|
|
188
|
+
errors.push(`${nodeIndex} "${node.name}": Missing required field "type"`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
// Validate node based on type
|
|
192
|
+
switch (node.type) {
|
|
193
|
+
case 'trigger':
|
|
194
|
+
if (!node.triggerType) {
|
|
195
|
+
errors.push(`${nodeIndex} "${node.name}": trigger node missing required field "triggerType"`);
|
|
196
|
+
}
|
|
197
|
+
else if (!['manual', 'app', 'webhook', 'schedule'].includes(node.triggerType)) {
|
|
198
|
+
errors.push(`${nodeIndex} "${node.name}": invalid triggerType "${node.triggerType}". Must be one of: manual, app, webhook, schedule`);
|
|
199
|
+
}
|
|
200
|
+
if (node.triggerType === 'app' && !node.appTriggerId) {
|
|
201
|
+
errors.push(`${nodeIndex} "${node.name}": app trigger missing required field "appTriggerId"`);
|
|
202
|
+
}
|
|
203
|
+
if (node.triggerType === 'schedule') {
|
|
204
|
+
const scheduleValidation = validateScheduleTrigger(node, nodeIndex);
|
|
205
|
+
errors.push(...scheduleValidation.errors);
|
|
206
|
+
warnings.push(...scheduleValidation.warnings);
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
case 'promptNode':
|
|
210
|
+
if (!node.prompt) {
|
|
211
|
+
errors.push(`${nodeIndex} "${node.name}": promptNode missing required field "prompt"`);
|
|
212
|
+
}
|
|
213
|
+
if (!node.displayName || node.displayName.trim() === '') {
|
|
214
|
+
errors.push(`${nodeIndex} "${node.name}": promptNode missing required non-empty field "displayName"`);
|
|
215
|
+
}
|
|
216
|
+
if (node.llmConfig) {
|
|
217
|
+
if (!node.llmConfig.name) {
|
|
218
|
+
errors.push(`${nodeIndex} "${node.name}": llmConfig missing required field "name"`);
|
|
219
|
+
}
|
|
220
|
+
if (!node.llmConfig.properties) {
|
|
221
|
+
warnings.push(`${nodeIndex} "${node.name}": llmConfig missing "properties" field`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case 'thinking':
|
|
226
|
+
if (!node.prompt) {
|
|
227
|
+
errors.push(`${nodeIndex} "${node.name}": thinking node missing required field "prompt"`);
|
|
228
|
+
}
|
|
229
|
+
if (node.humanInTheLoop !== undefined) {
|
|
230
|
+
errors.push(`${nodeIndex} "${node.name}": thinking node does not support "humanInTheLoop" property (only prompt nodes support this)`);
|
|
231
|
+
}
|
|
232
|
+
if (node.canStayOnNode !== undefined) {
|
|
233
|
+
errors.push(`${nodeIndex} "${node.name}": thinking node does not support "canStayOnNode" property (only prompt nodes support this)`);
|
|
234
|
+
}
|
|
235
|
+
if (node.llmConfig) {
|
|
236
|
+
if (!node.llmConfig.name) {
|
|
237
|
+
errors.push(`${nodeIndex} "${node.name}": llmConfig missing required field "name"`);
|
|
238
|
+
}
|
|
239
|
+
if (!node.llmConfig.properties) {
|
|
240
|
+
warnings.push(`${nodeIndex} "${node.name}": llmConfig missing "properties" field`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case 'tool':
|
|
245
|
+
if (!node.toolName) {
|
|
246
|
+
errors.push(`${nodeIndex} "${node.name}": tool node missing required field "toolName"`);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
case 'junction':
|
|
250
|
+
// Junction only requires name and type, which we already checked
|
|
251
|
+
break;
|
|
252
|
+
case 'jumpToNode':
|
|
253
|
+
if (!node.targetNodeId) {
|
|
254
|
+
errors.push(`${nodeIndex} "${node.name}": jumpToNode missing required field "targetNodeId"`);
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
case 'browserTask':
|
|
258
|
+
if (!node.prompt) {
|
|
259
|
+
errors.push(`${nodeIndex} "${node.name}": browserTask node missing required field "prompt"`);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
default:
|
|
263
|
+
warnings.push(`${nodeIndex} "${node.name}": unknown node type "${node.type}"`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Create a map to track which nodes have edges
|
|
267
|
+
const nodesWithEdges = new Set();
|
|
268
|
+
// Validate edges
|
|
269
|
+
for (let i = 0; i < flow.edges.length; i++) {
|
|
270
|
+
const edge = flow.edges[i];
|
|
271
|
+
const edgeIndex = `Edge ${i + 1}`;
|
|
272
|
+
// Check for required fields
|
|
273
|
+
if (!edge.source) {
|
|
274
|
+
errors.push(`${edgeIndex}: Missing required field "source"`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (!edge.target) {
|
|
278
|
+
errors.push(`${edgeIndex}: Missing required field "target"`);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (!edge.type) {
|
|
282
|
+
errors.push(`${edgeIndex}: Missing required field "type"`);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
// Track nodes that have edges
|
|
286
|
+
nodesWithEdges.add(edge.source);
|
|
287
|
+
nodesWithEdges.add(edge.target);
|
|
288
|
+
// Check if source and target nodes exist
|
|
289
|
+
if (!nodeNames.has(edge.source)) {
|
|
290
|
+
errors.push(`${edgeIndex}: source node "${edge.source}" does not exist`);
|
|
291
|
+
}
|
|
292
|
+
if (!nodeNames.has(edge.target)) {
|
|
293
|
+
errors.push(`${edgeIndex}: target node "${edge.target}" does not exist`);
|
|
294
|
+
}
|
|
295
|
+
// Validate edge based on type
|
|
296
|
+
switch (edge.type) {
|
|
297
|
+
case 'stepForward':
|
|
298
|
+
// stepForward only requires source, target, and type
|
|
299
|
+
break;
|
|
300
|
+
case 'promptCondition':
|
|
301
|
+
if (!edge.prompt) {
|
|
302
|
+
errors.push(`${edgeIndex}: promptCondition edge missing required field "prompt"`);
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
case 'logicalCondition':
|
|
306
|
+
if (!edge.condition) {
|
|
307
|
+
errors.push(`${edgeIndex}: logicalCondition edge missing required field "condition"`);
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
default:
|
|
311
|
+
warnings.push(`${edgeIndex}: unknown edge type "${edge.type}"`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Check for orphaned nodes (nodes without any edges)
|
|
315
|
+
// Note: Trigger nodes can be entry points without incoming edges
|
|
316
|
+
for (const nodeName of nodeNames) {
|
|
317
|
+
const node = nodesByName.get(nodeName);
|
|
318
|
+
if (!nodesWithEdges.has(nodeName) && node.type !== 'trigger') {
|
|
319
|
+
warnings.push(`Node "${nodeName}": orphaned node (not connected to any edges)`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Check for trigger nodes - at least one trigger should exist
|
|
323
|
+
const triggerNodes = flow.nodes.filter((n) => n.type === 'trigger');
|
|
324
|
+
if (triggerNodes.length === 0) {
|
|
325
|
+
warnings.push('Flow has no trigger nodes (entry points)');
|
|
326
|
+
}
|
|
327
|
+
const success = errors.length === 0;
|
|
328
|
+
return {
|
|
329
|
+
success,
|
|
330
|
+
flowName: flow.name,
|
|
331
|
+
stats: {
|
|
332
|
+
nodes: flow.nodes.length,
|
|
333
|
+
edges: flow.edges.length,
|
|
334
|
+
nodeTypes: [...new Set(flow.nodes.map((n) => n.type))],
|
|
335
|
+
edgeTypes: [...new Set(flow.edges.map((e) => e.type))],
|
|
336
|
+
},
|
|
337
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
338
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
339
|
+
message: errors.length === 0
|
|
340
|
+
? warnings.length === 0
|
|
341
|
+
? 'Flow is valid with no issues'
|
|
342
|
+
: `Flow is valid but has ${warnings.length} warning(s)`
|
|
343
|
+
: `Flow validation failed with ${errors.length} error(s)`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Finds all YAML files in a directory recursively.
|
|
348
|
+
*/
|
|
349
|
+
function findYamlFiles(dir) {
|
|
350
|
+
const files = [];
|
|
351
|
+
if (!fs.existsSync(dir)) {
|
|
352
|
+
return files;
|
|
353
|
+
}
|
|
354
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
355
|
+
for (const entry of entries) {
|
|
356
|
+
const fullPath = path.join(dir, entry.name);
|
|
357
|
+
if (entry.isDirectory()) {
|
|
358
|
+
files.push(...findYamlFiles(fullPath));
|
|
359
|
+
}
|
|
360
|
+
else if (entry.isFile() && (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
|
|
361
|
+
files.push(fullPath);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return files;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* CLI command handler for validate
|
|
368
|
+
*/
|
|
369
|
+
function runValidateCommand() {
|
|
370
|
+
var _a, _b, _c, _d;
|
|
371
|
+
const mindedConfigPath = path.join(process.cwd(), 'minded.json');
|
|
372
|
+
// Check if minded.json exists
|
|
373
|
+
if (!fs.existsSync(mindedConfigPath)) {
|
|
374
|
+
console.error('Error: minded.json not found in the current directory');
|
|
375
|
+
console.error('Make sure you are running this command from a MindedJS agent project root');
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
// Read and parse minded.json
|
|
379
|
+
let mindedConfig;
|
|
380
|
+
try {
|
|
381
|
+
const configContent = fs.readFileSync(mindedConfigPath, 'utf8');
|
|
382
|
+
mindedConfig = JSON.parse(configContent);
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
console.error(`Error: Failed to read or parse minded.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
// Get flows folders
|
|
389
|
+
const flowsFolders = mindedConfig.flows;
|
|
390
|
+
if (!flowsFolders || flowsFolders.length === 0) {
|
|
391
|
+
console.error('Error: No flows folders configured in minded.json');
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
// Find all YAML files in flow folders
|
|
395
|
+
const flowFiles = [];
|
|
396
|
+
for (const folder of flowsFolders) {
|
|
397
|
+
const resolvedFolder = path.isAbsolute(folder) ? folder : path.join(process.cwd(), folder);
|
|
398
|
+
flowFiles.push(...findYamlFiles(resolvedFolder));
|
|
399
|
+
}
|
|
400
|
+
if (flowFiles.length === 0) {
|
|
401
|
+
console.error('Error: No flow YAML files found in configured flows folders');
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
console.log(`\nValidating ${flowFiles.length} flow(s)...\n`);
|
|
405
|
+
// Validate each flow
|
|
406
|
+
const results = [];
|
|
407
|
+
let totalErrors = 0;
|
|
408
|
+
let totalWarnings = 0;
|
|
409
|
+
for (const flowFile of flowFiles) {
|
|
410
|
+
const result = validateFlow(flowFile);
|
|
411
|
+
results.push({ path: flowFile, result });
|
|
412
|
+
totalErrors += (_b = (_a = result.errors) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
|
|
413
|
+
totalWarnings += (_d = (_c = result.warnings) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
|
|
414
|
+
}
|
|
415
|
+
// Print results for each flow
|
|
416
|
+
for (const { path: flowPath, result } of results) {
|
|
417
|
+
const relativePath = path.relative(process.cwd(), flowPath);
|
|
418
|
+
const statusIcon = result.success ? '✅' : '❌';
|
|
419
|
+
console.log(`${statusIcon} ${relativePath}`);
|
|
420
|
+
if (result.flowName) {
|
|
421
|
+
console.log(` Flow: ${result.flowName}`);
|
|
422
|
+
}
|
|
423
|
+
if (result.stats) {
|
|
424
|
+
console.log(` Stats: ${result.stats.nodes} nodes, ${result.stats.edges} edges`);
|
|
425
|
+
}
|
|
426
|
+
if (result.errors && result.errors.length > 0) {
|
|
427
|
+
console.log(' Errors:');
|
|
428
|
+
result.errors.forEach((error) => {
|
|
429
|
+
console.log(` ❌ ${error}`);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
433
|
+
console.log(' Warnings:');
|
|
434
|
+
result.warnings.forEach((warning) => {
|
|
435
|
+
console.log(` ⚠️ ${warning}`);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
console.log('');
|
|
439
|
+
}
|
|
440
|
+
// Print summary
|
|
441
|
+
const allPassed = totalErrors === 0;
|
|
442
|
+
console.log('─'.repeat(50));
|
|
443
|
+
if (allPassed) {
|
|
444
|
+
console.log(`\n✅ All ${flowFiles.length} flow(s) validated successfully!`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
console.log(`\n❌ Validation failed!`);
|
|
448
|
+
}
|
|
449
|
+
console.log(` Total: ${flowFiles.length} flow(s), ${totalErrors} error(s), ${totalWarnings} warning(s)\n`);
|
|
450
|
+
if (!allPassed) {
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
//# sourceMappingURL=validateFlow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateFlow.js","sourceRoot":"","sources":["../../src/cli/validateFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,24 +6,6 @@ Minded AI agents automate complex workflows in customer environments while maint
|
|
|
6
6
|
|
|
7
7
|
Minded is designed to provide maximum automation power while maintaining zero-trust security. You can automate mission-critical workflows with confidence, while Minded ensures every action is verified, logged, and restricted through multiple security layers.
|
|
8
8
|
|
|
9
|
-
## Quick Navigation
|
|
10
|
-
|
|
11
|
-
- [System Overview](#️-system-overview)
|
|
12
|
-
- [Security Layers](#️-security-layers)
|
|
13
|
-
- [Network Isolation](#layer-1-network-isolation)
|
|
14
|
-
- [Authentication & Authorization](#layer-2-authentication--authorization)
|
|
15
|
-
- [RPA Security Controls](#layer-3-rpa-security-controls)
|
|
16
|
-
- [Runtime Security](#layer-4-runtime-security)
|
|
17
|
-
- [Monitoring & Audit](#layer-5-monitoring--audit)
|
|
18
|
-
- [Credential Lifecycle Flow](#️-credential-lifecycle-flow)
|
|
19
|
-
- [RPA Execution Flow](#️-rpa-execution-flow-with-security-controls)
|
|
20
|
-
- [Agent Lifecycle Security](#️-agent-lifecycle-security)
|
|
21
|
-
- [Tenant Isolation Architecture](#️-tenant-isolation-architecture)
|
|
22
|
-
- [Infrastructure Security](#️-infrastructure-security)
|
|
23
|
-
- [Agent Behavior Verification](#️-agent-behavior-verification--action-limits)
|
|
24
|
-
- [Security FAQs](#security-faqs)
|
|
25
|
-
- [See Also](#see-also)
|
|
26
|
-
|
|
27
9
|
---
|
|
28
10
|
|
|
29
11
|
## 🏗️ System Overview
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minded-ai/mindedjs",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6-beta.1",
|
|
4
4
|
"description": "MindedJS is a TypeScript library for building agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@types/js-yaml": "^4.0.9",
|
|
58
58
|
"axios": "^1.9.0",
|
|
59
59
|
"chrome-remote-interface": "^0.33.3",
|
|
60
|
+
"cron-parser": "^5.4.0",
|
|
60
61
|
"ejs": "^3.1.10",
|
|
61
62
|
"flatted": "^3.3.3",
|
|
62
63
|
"js-yaml": "^4.1.0",
|
|
@@ -72,4 +73,4 @@
|
|
|
72
73
|
"peerDependencies": {
|
|
73
74
|
"playwright": "^1.55.0"
|
|
74
75
|
}
|
|
75
|
-
}
|
|
76
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as path from 'path';
|
|
|
5
5
|
import { logger } from '../utils/logger';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
7
|
import { setupLocalOperator, isLocalOperatorSetup } from './localOperatorSetup';
|
|
8
|
+
import { runValidateCommand } from './validateFlow';
|
|
8
9
|
|
|
9
10
|
const ENV_FILE = '.env';
|
|
10
11
|
|
|
@@ -174,9 +175,12 @@ async function main() {
|
|
|
174
175
|
console.info('✗ Local operator is not set up. Run: npx minded setup-local-operator');
|
|
175
176
|
process.exit(1);
|
|
176
177
|
}
|
|
178
|
+
} else if (command === 'validate') {
|
|
179
|
+
// Command to validate a flow YAML file
|
|
180
|
+
runValidateCommand();
|
|
177
181
|
} else {
|
|
178
182
|
logger.error({
|
|
179
|
-
msg: 'Unknown command. Available commands: token, setup-local-operator, check-local-operator, generate-lambda-ts-handler',
|
|
183
|
+
msg: 'Unknown command. Available commands: token, setup-local-operator, check-local-operator, generate-lambda-ts-handler, validate',
|
|
180
184
|
});
|
|
181
185
|
process.exit(1);
|
|
182
186
|
}
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as yaml from 'js-yaml';
|
|
4
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
5
|
+
|
|
6
|
+
interface ValidationResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
flowName?: string;
|
|
9
|
+
stats?: {
|
|
10
|
+
nodes: number;
|
|
11
|
+
edges: number;
|
|
12
|
+
nodeTypes: string[];
|
|
13
|
+
edgeTypes: string[];
|
|
14
|
+
};
|
|
15
|
+
errors?: string[];
|
|
16
|
+
warnings?: string[];
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validates a cron expression format using cron-parser.
|
|
22
|
+
* Cron format: minute hour day month day-of-week (5 fields)
|
|
23
|
+
*/
|
|
24
|
+
export function validateCronExpression(cronExpression: string): { valid: boolean; error?: string } {
|
|
25
|
+
if (typeof cronExpression !== 'string' || cronExpression.trim() === '') {
|
|
26
|
+
return { valid: false, error: 'Cron expression must be a non-empty string' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
CronExpressionParser.parse(cronExpression.trim());
|
|
31
|
+
return { valid: true };
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return {
|
|
34
|
+
valid: false,
|
|
35
|
+
error: error instanceof Error ? error.message : 'Invalid cron expression format',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validates an IANA timezone identifier.
|
|
42
|
+
* Uses Intl.DateTimeFormat to check if the timezone is valid.
|
|
43
|
+
*/
|
|
44
|
+
export function validateTimezone(timezone: string): { valid: boolean; error?: string } {
|
|
45
|
+
if (typeof timezone !== 'string' || timezone.trim() === '') {
|
|
46
|
+
return { valid: false, error: 'Timezone must be a non-empty string' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Try to create a date formatter with the timezone
|
|
51
|
+
const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timezone });
|
|
52
|
+
// Test if the timezone is valid by trying to format a date
|
|
53
|
+
formatter.format(new Date());
|
|
54
|
+
return { valid: true };
|
|
55
|
+
} catch {
|
|
56
|
+
return {
|
|
57
|
+
valid: false,
|
|
58
|
+
error: `Invalid IANA timezone identifier: "${timezone}"`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Validates a schedule trigger node configuration.
|
|
65
|
+
* Returns an array of error messages (empty if valid).
|
|
66
|
+
*/
|
|
67
|
+
export function validateScheduleTrigger(
|
|
68
|
+
node: any,
|
|
69
|
+
nodeIndex: string
|
|
70
|
+
): { errors: string[]; warnings: string[] } {
|
|
71
|
+
const errors: string[] = [];
|
|
72
|
+
const warnings: string[] = [];
|
|
73
|
+
|
|
74
|
+
if (!node.cronExpression) {
|
|
75
|
+
errors.push(`${nodeIndex} "${node.name}": schedule trigger missing required field "cronExpression"`);
|
|
76
|
+
} else if (typeof node.cronExpression !== 'string') {
|
|
77
|
+
errors.push(`${nodeIndex} "${node.name}": schedule trigger "cronExpression" must be a string`);
|
|
78
|
+
} else {
|
|
79
|
+
const cronValidation = validateCronExpression(node.cronExpression);
|
|
80
|
+
if (!cronValidation.valid) {
|
|
81
|
+
errors.push(`${nodeIndex} "${node.name}": invalid cron expression - ${cronValidation.error}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (node.timezone !== undefined) {
|
|
86
|
+
if (typeof node.timezone !== 'string') {
|
|
87
|
+
errors.push(`${nodeIndex} "${node.name}": schedule trigger "timezone" must be a string`);
|
|
88
|
+
} else {
|
|
89
|
+
const timezoneValidation = validateTimezone(node.timezone);
|
|
90
|
+
if (!timezoneValidation.valid) {
|
|
91
|
+
errors.push(`${nodeIndex} "${node.name}": ${timezoneValidation.error}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { errors, warnings };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validates a MindedJS YAML flow file.
|
|
101
|
+
*/
|
|
102
|
+
export function validateFlow(flowPath: string): ValidationResult {
|
|
103
|
+
// Resolve the path
|
|
104
|
+
const resolvedPath = path.isAbsolute(flowPath) ? flowPath : path.join(process.cwd(), flowPath);
|
|
105
|
+
|
|
106
|
+
// Read the file
|
|
107
|
+
let flowContent: string;
|
|
108
|
+
try {
|
|
109
|
+
flowContent = fs.readFileSync(resolvedPath, 'utf-8');
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Parse YAML
|
|
118
|
+
let flow: any;
|
|
119
|
+
try {
|
|
120
|
+
flow = yaml.load(flowContent);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
message: `Invalid YAML syntax: ${error instanceof Error ? error.message : String(error)}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Validate flow structure
|
|
129
|
+
const errors: string[] = [];
|
|
130
|
+
const warnings: string[] = [];
|
|
131
|
+
|
|
132
|
+
// Check for required top-level fields
|
|
133
|
+
if (!flow.name) {
|
|
134
|
+
errors.push('Flow is missing required field: "name"');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!flow.nodes || !Array.isArray(flow.nodes)) {
|
|
138
|
+
errors.push('Flow is missing required field: "nodes" (must be an array)');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!flow.edges || !Array.isArray(flow.edges)) {
|
|
142
|
+
errors.push('Flow is missing required field: "edges" (must be an array)');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If basic structure is invalid, return early
|
|
146
|
+
if (errors.length > 0) {
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
errors,
|
|
150
|
+
warnings,
|
|
151
|
+
message: `Flow validation failed with ${errors.length} error(s)`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Create a set of node names for validation
|
|
156
|
+
const nodeNames = new Set<string>();
|
|
157
|
+
const nodesByName = new Map<string, any>();
|
|
158
|
+
|
|
159
|
+
// Validate nodes
|
|
160
|
+
for (let i = 0; i < flow.nodes.length; i++) {
|
|
161
|
+
const node = flow.nodes[i];
|
|
162
|
+
const nodeIndex = `Node ${i + 1}`;
|
|
163
|
+
|
|
164
|
+
// Check for required name field
|
|
165
|
+
if (!node.name) {
|
|
166
|
+
errors.push(`${nodeIndex}: Missing required field "name"`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check for duplicate node names
|
|
171
|
+
if (nodeNames.has(node.name)) {
|
|
172
|
+
errors.push(`${nodeIndex} "${node.name}": Duplicate node name`);
|
|
173
|
+
}
|
|
174
|
+
nodeNames.add(node.name);
|
|
175
|
+
nodesByName.set(node.name, node);
|
|
176
|
+
|
|
177
|
+
// Check for required type field
|
|
178
|
+
if (!node.type) {
|
|
179
|
+
errors.push(`${nodeIndex} "${node.name}": Missing required field "type"`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Validate node based on type
|
|
184
|
+
switch (node.type) {
|
|
185
|
+
case 'trigger':
|
|
186
|
+
if (!node.triggerType) {
|
|
187
|
+
errors.push(`${nodeIndex} "${node.name}": trigger node missing required field "triggerType"`);
|
|
188
|
+
} else if (!['manual', 'app', 'webhook', 'schedule'].includes(node.triggerType)) {
|
|
189
|
+
errors.push(
|
|
190
|
+
`${nodeIndex} "${node.name}": invalid triggerType "${node.triggerType}". Must be one of: manual, app, webhook, schedule`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (node.triggerType === 'app' && !node.appTriggerId) {
|
|
194
|
+
errors.push(`${nodeIndex} "${node.name}": app trigger missing required field "appTriggerId"`);
|
|
195
|
+
}
|
|
196
|
+
if (node.triggerType === 'schedule') {
|
|
197
|
+
const scheduleValidation = validateScheduleTrigger(node, nodeIndex);
|
|
198
|
+
errors.push(...scheduleValidation.errors);
|
|
199
|
+
warnings.push(...scheduleValidation.warnings);
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
|
|
203
|
+
case 'promptNode':
|
|
204
|
+
if (!node.prompt) {
|
|
205
|
+
errors.push(`${nodeIndex} "${node.name}": promptNode missing required field "prompt"`);
|
|
206
|
+
}
|
|
207
|
+
if (!node.displayName || node.displayName.trim() === '') {
|
|
208
|
+
errors.push(`${nodeIndex} "${node.name}": promptNode missing required non-empty field "displayName"`);
|
|
209
|
+
}
|
|
210
|
+
if (node.llmConfig) {
|
|
211
|
+
if (!node.llmConfig.name) {
|
|
212
|
+
errors.push(`${nodeIndex} "${node.name}": llmConfig missing required field "name"`);
|
|
213
|
+
}
|
|
214
|
+
if (!node.llmConfig.properties) {
|
|
215
|
+
warnings.push(`${nodeIndex} "${node.name}": llmConfig missing "properties" field`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'thinking':
|
|
221
|
+
if (!node.prompt) {
|
|
222
|
+
errors.push(`${nodeIndex} "${node.name}": thinking node missing required field "prompt"`);
|
|
223
|
+
}
|
|
224
|
+
if (node.humanInTheLoop !== undefined) {
|
|
225
|
+
errors.push(
|
|
226
|
+
`${nodeIndex} "${node.name}": thinking node does not support "humanInTheLoop" property (only prompt nodes support this)`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (node.canStayOnNode !== undefined) {
|
|
230
|
+
errors.push(
|
|
231
|
+
`${nodeIndex} "${node.name}": thinking node does not support "canStayOnNode" property (only prompt nodes support this)`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (node.llmConfig) {
|
|
235
|
+
if (!node.llmConfig.name) {
|
|
236
|
+
errors.push(`${nodeIndex} "${node.name}": llmConfig missing required field "name"`);
|
|
237
|
+
}
|
|
238
|
+
if (!node.llmConfig.properties) {
|
|
239
|
+
warnings.push(`${nodeIndex} "${node.name}": llmConfig missing "properties" field`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
|
|
244
|
+
case 'tool':
|
|
245
|
+
if (!node.toolName) {
|
|
246
|
+
errors.push(`${nodeIndex} "${node.name}": tool node missing required field "toolName"`);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
case 'junction':
|
|
251
|
+
// Junction only requires name and type, which we already checked
|
|
252
|
+
break;
|
|
253
|
+
|
|
254
|
+
case 'jumpToNode':
|
|
255
|
+
if (!node.targetNodeId) {
|
|
256
|
+
errors.push(`${nodeIndex} "${node.name}": jumpToNode missing required field "targetNodeId"`);
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
|
|
260
|
+
case 'browserTask':
|
|
261
|
+
if (!node.prompt) {
|
|
262
|
+
errors.push(`${nodeIndex} "${node.name}": browserTask node missing required field "prompt"`);
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
default:
|
|
267
|
+
warnings.push(`${nodeIndex} "${node.name}": unknown node type "${node.type}"`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Create a map to track which nodes have edges
|
|
272
|
+
const nodesWithEdges = new Set<string>();
|
|
273
|
+
|
|
274
|
+
// Validate edges
|
|
275
|
+
for (let i = 0; i < flow.edges.length; i++) {
|
|
276
|
+
const edge = flow.edges[i];
|
|
277
|
+
const edgeIndex = `Edge ${i + 1}`;
|
|
278
|
+
|
|
279
|
+
// Check for required fields
|
|
280
|
+
if (!edge.source) {
|
|
281
|
+
errors.push(`${edgeIndex}: Missing required field "source"`);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!edge.target) {
|
|
286
|
+
errors.push(`${edgeIndex}: Missing required field "target"`);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!edge.type) {
|
|
291
|
+
errors.push(`${edgeIndex}: Missing required field "type"`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Track nodes that have edges
|
|
296
|
+
nodesWithEdges.add(edge.source);
|
|
297
|
+
nodesWithEdges.add(edge.target);
|
|
298
|
+
|
|
299
|
+
// Check if source and target nodes exist
|
|
300
|
+
if (!nodeNames.has(edge.source)) {
|
|
301
|
+
errors.push(`${edgeIndex}: source node "${edge.source}" does not exist`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!nodeNames.has(edge.target)) {
|
|
305
|
+
errors.push(`${edgeIndex}: target node "${edge.target}" does not exist`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Validate edge based on type
|
|
309
|
+
switch (edge.type) {
|
|
310
|
+
case 'stepForward':
|
|
311
|
+
// stepForward only requires source, target, and type
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
case 'promptCondition':
|
|
315
|
+
if (!edge.prompt) {
|
|
316
|
+
errors.push(`${edgeIndex}: promptCondition edge missing required field "prompt"`);
|
|
317
|
+
}
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'logicalCondition':
|
|
321
|
+
if (!edge.condition) {
|
|
322
|
+
errors.push(`${edgeIndex}: logicalCondition edge missing required field "condition"`);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
default:
|
|
327
|
+
warnings.push(`${edgeIndex}: unknown edge type "${edge.type}"`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check for orphaned nodes (nodes without any edges)
|
|
332
|
+
// Note: Trigger nodes can be entry points without incoming edges
|
|
333
|
+
for (const nodeName of nodeNames) {
|
|
334
|
+
const node = nodesByName.get(nodeName);
|
|
335
|
+
if (!nodesWithEdges.has(nodeName) && node.type !== 'trigger') {
|
|
336
|
+
warnings.push(`Node "${nodeName}": orphaned node (not connected to any edges)`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check for trigger nodes - at least one trigger should exist
|
|
341
|
+
const triggerNodes = flow.nodes.filter((n: any) => n.type === 'trigger');
|
|
342
|
+
if (triggerNodes.length === 0) {
|
|
343
|
+
warnings.push('Flow has no trigger nodes (entry points)');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const success = errors.length === 0;
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
success,
|
|
350
|
+
flowName: flow.name,
|
|
351
|
+
stats: {
|
|
352
|
+
nodes: flow.nodes.length,
|
|
353
|
+
edges: flow.edges.length,
|
|
354
|
+
nodeTypes: [...new Set(flow.nodes.map((n: any) => n.type))] as string[],
|
|
355
|
+
edgeTypes: [...new Set(flow.edges.map((e: any) => e.type))] as string[],
|
|
356
|
+
},
|
|
357
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
358
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
359
|
+
message:
|
|
360
|
+
errors.length === 0
|
|
361
|
+
? warnings.length === 0
|
|
362
|
+
? 'Flow is valid with no issues'
|
|
363
|
+
: `Flow is valid but has ${warnings.length} warning(s)`
|
|
364
|
+
: `Flow validation failed with ${errors.length} error(s)`,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
interface MindedConfig {
|
|
369
|
+
flows?: string[];
|
|
370
|
+
tools?: string[];
|
|
371
|
+
playbooks?: string[];
|
|
372
|
+
agent?: string;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Finds all YAML files in a directory recursively.
|
|
377
|
+
*/
|
|
378
|
+
function findYamlFiles(dir: string): string[] {
|
|
379
|
+
const files: string[] = [];
|
|
380
|
+
|
|
381
|
+
if (!fs.existsSync(dir)) {
|
|
382
|
+
return files;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
386
|
+
|
|
387
|
+
for (const entry of entries) {
|
|
388
|
+
const fullPath = path.join(dir, entry.name);
|
|
389
|
+
if (entry.isDirectory()) {
|
|
390
|
+
files.push(...findYamlFiles(fullPath));
|
|
391
|
+
} else if (entry.isFile() && (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
|
|
392
|
+
files.push(fullPath);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return files;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* CLI command handler for validate
|
|
401
|
+
*/
|
|
402
|
+
export function runValidateCommand(): void {
|
|
403
|
+
const mindedConfigPath = path.join(process.cwd(), 'minded.json');
|
|
404
|
+
|
|
405
|
+
// Check if minded.json exists
|
|
406
|
+
if (!fs.existsSync(mindedConfigPath)) {
|
|
407
|
+
console.error('Error: minded.json not found in the current directory');
|
|
408
|
+
console.error('Make sure you are running this command from a MindedJS agent project root');
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Read and parse minded.json
|
|
413
|
+
let mindedConfig: MindedConfig;
|
|
414
|
+
try {
|
|
415
|
+
const configContent = fs.readFileSync(mindedConfigPath, 'utf8');
|
|
416
|
+
mindedConfig = JSON.parse(configContent);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.error(`Error: Failed to read or parse minded.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Get flows folders
|
|
423
|
+
const flowsFolders = mindedConfig.flows;
|
|
424
|
+
if (!flowsFolders || flowsFolders.length === 0) {
|
|
425
|
+
console.error('Error: No flows folders configured in minded.json');
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Find all YAML files in flow folders
|
|
430
|
+
const flowFiles: string[] = [];
|
|
431
|
+
for (const folder of flowsFolders) {
|
|
432
|
+
const resolvedFolder = path.isAbsolute(folder) ? folder : path.join(process.cwd(), folder);
|
|
433
|
+
flowFiles.push(...findYamlFiles(resolvedFolder));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (flowFiles.length === 0) {
|
|
437
|
+
console.error('Error: No flow YAML files found in configured flows folders');
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
console.log(`\nValidating ${flowFiles.length} flow(s)...\n`);
|
|
442
|
+
|
|
443
|
+
// Validate each flow
|
|
444
|
+
const results: { path: string; result: ValidationResult }[] = [];
|
|
445
|
+
let totalErrors = 0;
|
|
446
|
+
let totalWarnings = 0;
|
|
447
|
+
|
|
448
|
+
for (const flowFile of flowFiles) {
|
|
449
|
+
const result = validateFlow(flowFile);
|
|
450
|
+
results.push({ path: flowFile, result });
|
|
451
|
+
totalErrors += result.errors?.length ?? 0;
|
|
452
|
+
totalWarnings += result.warnings?.length ?? 0;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Print results for each flow
|
|
456
|
+
for (const { path: flowPath, result } of results) {
|
|
457
|
+
const relativePath = path.relative(process.cwd(), flowPath);
|
|
458
|
+
const statusIcon = result.success ? '✅' : '❌';
|
|
459
|
+
|
|
460
|
+
console.log(`${statusIcon} ${relativePath}`);
|
|
461
|
+
|
|
462
|
+
if (result.flowName) {
|
|
463
|
+
console.log(` Flow: ${result.flowName}`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (result.stats) {
|
|
467
|
+
console.log(` Stats: ${result.stats.nodes} nodes, ${result.stats.edges} edges`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (result.errors && result.errors.length > 0) {
|
|
471
|
+
console.log(' Errors:');
|
|
472
|
+
result.errors.forEach((error) => {
|
|
473
|
+
console.log(` ❌ ${error}`);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
478
|
+
console.log(' Warnings:');
|
|
479
|
+
result.warnings.forEach((warning) => {
|
|
480
|
+
console.log(` ⚠️ ${warning}`);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
console.log('');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Print summary
|
|
488
|
+
const allPassed = totalErrors === 0;
|
|
489
|
+
console.log('─'.repeat(50));
|
|
490
|
+
|
|
491
|
+
if (allPassed) {
|
|
492
|
+
console.log(`\n✅ All ${flowFiles.length} flow(s) validated successfully!`);
|
|
493
|
+
} else {
|
|
494
|
+
console.log(`\n❌ Validation failed!`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
console.log(` Total: ${flowFiles.length} flow(s), ${totalErrors} error(s), ${totalWarnings} warning(s)\n`);
|
|
498
|
+
|
|
499
|
+
if (!allPassed) {
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
}
|