@datatechsolutions/ui 2.11.53 → 2.11.56

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 (35) hide show
  1. package/dist/astrlabe/contracts.d.mts +16 -3
  2. package/dist/astrlabe/contracts.d.ts +16 -3
  3. package/dist/astrlabe/index.d.mts +1 -1
  4. package/dist/astrlabe/index.d.ts +1 -1
  5. package/dist/astrlabe/index.js +133 -120
  6. package/dist/astrlabe/index.js.map +1 -1
  7. package/dist/astrlabe/index.mjs +13 -8
  8. package/dist/astrlabe/index.mjs.map +1 -1
  9. package/dist/astrlabe/utils.d.mts +27 -23
  10. package/dist/astrlabe/utils.d.ts +27 -23
  11. package/dist/astrlabe/utils.js +15 -7
  12. package/dist/astrlabe/utils.mjs +2 -2
  13. package/dist/astrlabe/workflow-canvas.js +3 -3
  14. package/dist/astrlabe/workflow-canvas.mjs +2 -2
  15. package/dist/chunk-53SRKVKQ.mjs +542 -0
  16. package/dist/chunk-53SRKVKQ.mjs.map +1 -0
  17. package/dist/chunk-5UU3RQRB.js +547 -0
  18. package/dist/chunk-5UU3RQRB.js.map +1 -0
  19. package/dist/{chunk-PWBWP5FJ.js → chunk-C7BI5LQ6.js} +7 -2
  20. package/dist/chunk-C7BI5LQ6.js.map +1 -0
  21. package/dist/{chunk-PEBQWL6R.mjs → chunk-FXJBJ77I.mjs} +7 -3
  22. package/dist/chunk-FXJBJ77I.mjs.map +1 -0
  23. package/dist/{chunk-7LCEP4X5.js → chunk-P67L6WXL.js} +14 -10
  24. package/dist/chunk-P67L6WXL.js.map +1 -0
  25. package/dist/{chunk-TLPPVL3W.mjs → chunk-WNCPAWLC.mjs} +7 -2
  26. package/dist/chunk-WNCPAWLC.mjs.map +1 -0
  27. package/package.json +1 -1
  28. package/dist/chunk-3GE3MBUZ.js +0 -279
  29. package/dist/chunk-3GE3MBUZ.js.map +0 -1
  30. package/dist/chunk-7LCEP4X5.js.map +0 -1
  31. package/dist/chunk-BLNXRUC4.mjs +0 -276
  32. package/dist/chunk-BLNXRUC4.mjs.map +0 -1
  33. package/dist/chunk-PEBQWL6R.mjs.map +0 -1
  34. package/dist/chunk-PWBWP5FJ.js.map +0 -1
  35. package/dist/chunk-TLPPVL3W.mjs.map +0 -1
@@ -1,276 +0,0 @@
1
- "use client";
2
- // src/astrlabe/utils/workflow-validator.ts
3
- function validateWorkflowGraph(graph) {
4
- const errors = [];
5
- const nodeMap = /* @__PURE__ */ new Map();
6
- for (const node of graph.nodes) {
7
- nodeMap.set(node.id, node);
8
- }
9
- const agentNodes = graph.nodes.filter((node) => node.type === "agent");
10
- if (agentNodes.length === 0) {
11
- errors.push("At least one agent node is required");
12
- }
13
- for (const edge of graph.edges) {
14
- if (!nodeMap.has(edge.source)) {
15
- errors.push(`Edge "${edge.id}" references non-existent source node "${edge.source}"`);
16
- }
17
- if (!nodeMap.has(edge.target)) {
18
- errors.push(`Edge "${edge.id}" references non-existent target node "${edge.target}"`);
19
- }
20
- }
21
- const connectedNodeIds = /* @__PURE__ */ new Set();
22
- for (const edge of graph.edges) {
23
- connectedNodeIds.add(edge.source);
24
- connectedNodeIds.add(edge.target);
25
- }
26
- for (const node of graph.nodes) {
27
- if (node.type === "note") continue;
28
- if (!connectedNodeIds.has(node.id)) {
29
- errors.push(`Node "${node.data.label}" (${node.type}) is not connected to any other node`);
30
- }
31
- }
32
- for (const edge of graph.edges) {
33
- const sourceNode = nodeMap.get(edge.source);
34
- const targetNode = nodeMap.get(edge.target);
35
- if (!sourceNode || !targetNode) continue;
36
- if (sourceNode.type === "tool" && targetNode.type !== "agent") {
37
- errors.push(`Tool "${sourceNode.data.label}" can only connect to agent nodes, not ${targetNode.type} nodes`);
38
- }
39
- }
40
- const ruleNodes = graph.nodes.filter((node) => node.type === "rule");
41
- for (const ruleNode of ruleNodes) {
42
- const incomingEdges = graph.edges.filter((edge) => edge.target === ruleNode.id);
43
- const hasAgentSource = incomingEdges.some((edge) => {
44
- const sourceNode = nodeMap.get(edge.source);
45
- return sourceNode?.type === "agent";
46
- });
47
- if (incomingEdges.length === 0 || !hasAgentSource) {
48
- errors.push(`Rule "${ruleNode.data.label}" must have an incoming connection from an agent`);
49
- }
50
- }
51
- if (agentNodes.length > 1) {
52
- const cycleError = detectCycle(agentNodes, graph.edges, nodeMap);
53
- if (cycleError) {
54
- errors.push(cycleError);
55
- }
56
- }
57
- const startNodes = graph.nodes.filter((node) => node.type === "start");
58
- if (startNodes.length > 1) {
59
- errors.push("Only one Start node is allowed");
60
- }
61
- const endNodes = graph.nodes.filter((node) => node.type === "end");
62
- if (endNodes.length > 1) {
63
- errors.push("Only one End node is allowed");
64
- }
65
- for (const startNode of startNodes) {
66
- const incomingEdges = graph.edges.filter((edge) => edge.target === startNode.id);
67
- if (incomingEdges.length > 0) {
68
- errors.push("Start node cannot have incoming connections");
69
- }
70
- }
71
- for (const endNode of endNodes) {
72
- const outgoingEdges = graph.edges.filter((edge) => edge.source === endNode.id);
73
- if (outgoingEdges.length > 0) {
74
- errors.push("End node cannot have outgoing connections");
75
- }
76
- }
77
- const ifElseNodes = graph.nodes.filter((node) => node.type === "if_else");
78
- for (const ifElseNode of ifElseNodes) {
79
- const config = ifElseNode.data.config;
80
- if (!config || config.conditions.length === 0) {
81
- errors.push("IF/ELSE node must have at least one condition");
82
- }
83
- }
84
- const codeNodes = graph.nodes.filter((node) => node.type === "code");
85
- for (const codeNode of codeNodes) {
86
- const config = codeNode.data.config;
87
- if (!config || !config.code.trim()) {
88
- errors.push("Code node must have code content");
89
- }
90
- }
91
- const httpNodes = graph.nodes.filter((node) => node.type === "http_request");
92
- for (const httpNode of httpNodes) {
93
- const config = httpNode.data.config;
94
- if (!config || !config.url.trim()) {
95
- errors.push("HTTP Request node must have a URL");
96
- }
97
- }
98
- const templateNodes = graph.nodes.filter((node) => node.type === "template_transform");
99
- for (const templateNode of templateNodes) {
100
- const config = templateNode.data.config;
101
- if (!config || !config.template.trim()) {
102
- errors.push("Template node must have template content");
103
- }
104
- }
105
- const knowledgeBaseNodes = graph.nodes.filter((node) => node.type === "knowledge_base");
106
- for (const knowledgeBaseNode of knowledgeBaseNodes) {
107
- const config = knowledgeBaseNode.data.config;
108
- if (!config || !config.sourceId.trim()) {
109
- errors.push("Knowledge Base node must have a source configured");
110
- }
111
- }
112
- const iterationNodes = graph.nodes.filter((node) => node.type === "iteration");
113
- for (const iterationNode of iterationNodes) {
114
- const outgoingEdges = graph.edges.filter((edge) => edge.source === iterationNode.id);
115
- if (outgoingEdges.length === 0) {
116
- errors.push("Iteration node must have at least one outgoing connection");
117
- }
118
- }
119
- const answerNodes = graph.nodes.filter((node) => node.type === "answer");
120
- for (const answerNode of answerNodes) {
121
- const config = answerNode.data.config;
122
- if (!config || !config.outputTemplate.trim()) {
123
- errors.push("Answer node must have a non-empty output template");
124
- }
125
- }
126
- const questionClassifierNodes = graph.nodes.filter((node) => node.type === "question_classifier");
127
- for (const questionClassifierNode of questionClassifierNodes) {
128
- const config = questionClassifierNode.data.config;
129
- if (!config || config.categories.length < 2) {
130
- errors.push("Question Classifier node must have at least 2 categories");
131
- } else {
132
- const emptyCategories = config.categories.filter((category) => !category.name.trim());
133
- if (emptyCategories.length > 0) {
134
- errors.push("Question Classifier node has categories with empty names");
135
- }
136
- }
137
- }
138
- const parameterExtractorNodes = graph.nodes.filter((node) => node.type === "parameter_extractor");
139
- for (const parameterExtractorNode of parameterExtractorNodes) {
140
- const config = parameterExtractorNode.data.config;
141
- if (!config || config.parameters.length === 0) {
142
- errors.push("Parameter Extractor node must have at least 1 parameter");
143
- } else {
144
- const emptyParameters = config.parameters.filter((parameter) => !parameter.name.trim());
145
- if (emptyParameters.length > 0) {
146
- errors.push("Parameter Extractor node has parameters with empty names");
147
- }
148
- }
149
- }
150
- const variableAssignerNodes = graph.nodes.filter((node) => node.type === "variable_assigner");
151
- for (const variableAssignerNode of variableAssignerNodes) {
152
- const config = variableAssignerNode.data.config;
153
- if (!config || config.assignments.length === 0) {
154
- errors.push("Variable Assigner node must have at least 1 assignment");
155
- }
156
- }
157
- const variableAggregatorNodes = graph.nodes.filter((node) => node.type === "variable_aggregator");
158
- for (const variableAggregatorNode of variableAggregatorNodes) {
159
- const config = variableAggregatorNode.data.config;
160
- if (!config || config.inputVariables.length === 0) {
161
- errors.push("Variable Aggregator node must have at least 1 input variable");
162
- }
163
- if (!config || !config.outputVariable.trim()) {
164
- errors.push("Variable Aggregator node must have a non-empty output variable");
165
- }
166
- }
167
- const documentExtractorNodes = graph.nodes.filter((node) => node.type === "document_extractor");
168
- for (const documentExtractorNode of documentExtractorNodes) {
169
- const config = documentExtractorNode.data.config;
170
- if (!config || !config.outputVariable.trim()) {
171
- errors.push("Document Extractor node must have a non-empty output variable");
172
- }
173
- }
174
- const listOperatorNodes = graph.nodes.filter((node) => node.type === "list_operator");
175
- for (const listOperatorNode of listOperatorNodes) {
176
- const config = listOperatorNode.data.config;
177
- if (!config || !config.inputVariable.trim()) {
178
- errors.push("List Operator node must have a non-empty input variable");
179
- }
180
- if (!config || !config.outputVariable.trim()) {
181
- errors.push("List Operator node must have a non-empty output variable");
182
- }
183
- }
184
- const iterationStartNodes = graph.nodes.filter((node) => node.type === "iteration_start");
185
- for (const iterationStartNode of iterationStartNodes) {
186
- const incomingEdges = graph.edges.filter((edge) => edge.target === iterationStartNode.id);
187
- const hasIterationSource = incomingEdges.some((edge) => {
188
- const sourceNode = nodeMap.get(edge.source);
189
- return sourceNode?.type === "iteration";
190
- });
191
- if (incomingEdges.length === 0 || !hasIterationSource) {
192
- errors.push("Iteration Start node must have an incoming connection from an Iteration node");
193
- }
194
- }
195
- return {
196
- valid: errors.length === 0,
197
- errors
198
- };
199
- }
200
- function detectCycle(agentNodes, edges, nodeMap) {
201
- const agentIds = new Set(agentNodes.map((node) => node.id));
202
- const adjacencyList = /* @__PURE__ */ new Map();
203
- for (const agentId of agentIds) {
204
- adjacencyList.set(agentId, []);
205
- }
206
- for (const edge of edges) {
207
- const sourceNode = nodeMap.get(edge.source);
208
- const targetNode = nodeMap.get(edge.target);
209
- if (sourceNode?.type === "agent" && targetNode?.type === "agent" && agentIds.has(edge.source) && agentIds.has(edge.target)) {
210
- adjacencyList.get(edge.source).push(edge.target);
211
- }
212
- }
213
- const visited = /* @__PURE__ */ new Set();
214
- const inStack = /* @__PURE__ */ new Set();
215
- function hasCycleDfs(nodeId) {
216
- visited.add(nodeId);
217
- inStack.add(nodeId);
218
- const neighbors = adjacencyList.get(nodeId) ?? [];
219
- for (const neighbor of neighbors) {
220
- if (!visited.has(neighbor)) {
221
- if (hasCycleDfs(neighbor)) return true;
222
- } else if (inStack.has(neighbor)) {
223
- return true;
224
- }
225
- }
226
- inStack.delete(nodeId);
227
- return false;
228
- }
229
- for (const agentId of agentIds) {
230
- if (!visited.has(agentId)) {
231
- if (hasCycleDfs(agentId)) {
232
- return "Cycle detected in agent pipeline \u2014 agents must form a directed acyclic graph (DAG)";
233
- }
234
- }
235
- }
236
- return null;
237
- }
238
- function topologicalSortAgents(graph) {
239
- const agentNodes = graph.nodes.filter((node) => node.type === "agent");
240
- const agentIdSet = new Set(agentNodes.map((node) => node.id));
241
- const inDegree = /* @__PURE__ */ new Map();
242
- const adjacencyList = /* @__PURE__ */ new Map();
243
- for (const node of agentNodes) {
244
- inDegree.set(node.id, 0);
245
- adjacencyList.set(node.id, []);
246
- }
247
- for (const edge of graph.edges) {
248
- if (agentIdSet.has(edge.source) && agentIdSet.has(edge.target)) {
249
- adjacencyList.get(edge.source).push(edge.target);
250
- inDegree.set(edge.target, (inDegree.get(edge.target) ?? 0) + 1);
251
- }
252
- }
253
- const queue = [];
254
- for (const [nodeId, degree] of inDegree) {
255
- if (degree === 0) queue.push(nodeId);
256
- }
257
- const sorted = [];
258
- while (queue.length > 0) {
259
- const current = queue.shift();
260
- sorted.push(current);
261
- for (const neighbor of adjacencyList.get(current) ?? []) {
262
- const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
263
- inDegree.set(neighbor, newDegree);
264
- if (newDegree === 0) queue.push(neighbor);
265
- }
266
- }
267
- const nodeIdToEntityId = /* @__PURE__ */ new Map();
268
- for (const node of agentNodes) {
269
- nodeIdToEntityId.set(node.id, node.data.entityId);
270
- }
271
- return sorted.map((nodeId) => nodeIdToEntityId.get(nodeId) ?? nodeId);
272
- }
273
-
274
- export { topologicalSortAgents, validateWorkflowGraph };
275
- //# sourceMappingURL=chunk-BLNXRUC4.mjs.map
276
- //# sourceMappingURL=chunk-BLNXRUC4.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/astrlabe/utils/workflow-validator.ts"],"names":[],"mappings":"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","file":"chunk-BLNXRUC4.mjs","sourcesContent":["/**\n * Workflow Validator\n * =================\n * Validates the workflow graph before publishing.\n * Ensures the graph forms a valid DAG with proper node connections.\n */\n\nimport type {\n WorkflowGraph,\n WorkflowNode,\n WorkflowEdge,\n IfElseNodeConfig,\n CodeNodeConfig,\n HttpRequestNodeConfig,\n TemplateTransformNodeConfig,\n KnowledgeBaseNodeConfig,\n AnswerNodeConfig,\n QuestionClassifierNodeConfig,\n ParameterExtractorNodeConfig,\n VariableAssignerNodeConfig,\n VariableAggregatorNodeConfig,\n DocumentExtractorNodeConfig,\n ListOperatorNodeConfig,\n} from '../contracts'\n\nexport type ValidationResult = {\n valid: boolean\n errors: string[]\n}\n\n/**\n * Validate a workflow graph for publishing.\n * Checks:\n * - At least one agent node exists\n * - All edges have valid source/target nodes\n * - No orphan nodes (every node has at least one connection, except note nodes)\n * - Tool nodes connect to agent nodes only\n * - Agent chain forms a valid DAG (no cycles)\n * - Rule nodes have incoming edges from agents\n * - Logic node constraints (start/end uniqueness, config validation)\n * - New node type validations (answer, question_classifier, parameter_extractor, etc.)\n */\nexport function validateWorkflowGraph(graph: WorkflowGraph): ValidationResult {\n const errors: string[] = []\n const nodeMap = new Map<string, WorkflowNode>()\n\n for (const node of graph.nodes) {\n nodeMap.set(node.id, node)\n }\n\n // 1. At least one agent node\n const agentNodes = graph.nodes.filter((node) => node.type === 'agent')\n if (agentNodes.length === 0) {\n errors.push('At least one agent node is required')\n }\n\n // 2. All edges reference valid nodes\n for (const edge of graph.edges) {\n if (!nodeMap.has(edge.source)) {\n errors.push(`Edge \"${edge.id}\" references non-existent source node \"${edge.source}\"`)\n }\n if (!nodeMap.has(edge.target)) {\n errors.push(`Edge \"${edge.id}\" references non-existent target node \"${edge.target}\"`)\n }\n }\n\n // 3. No orphan nodes (every node should have at least one connection, except note nodes)\n const connectedNodeIds = new Set<string>()\n for (const edge of graph.edges) {\n connectedNodeIds.add(edge.source)\n connectedNodeIds.add(edge.target)\n }\n\n for (const node of graph.nodes) {\n // Note nodes are annotations and not part of execution — skip orphan check\n if (node.type === 'note') continue\n if (!connectedNodeIds.has(node.id)) {\n errors.push(`Node \"${node.data.label}\" (${node.type}) is not connected to any other node`)\n }\n }\n\n // 4. Tool -> Agent only\n for (const edge of graph.edges) {\n const sourceNode = nodeMap.get(edge.source)\n const targetNode = nodeMap.get(edge.target)\n if (!sourceNode || !targetNode) continue\n\n if (sourceNode.type === 'tool' && targetNode.type !== 'agent') {\n errors.push(`Tool \"${sourceNode.data.label}\" can only connect to agent nodes, not ${targetNode.type} nodes`)\n }\n }\n\n // 5. Rule nodes should have incoming edges from agents\n const ruleNodes = graph.nodes.filter((node) => node.type === 'rule')\n for (const ruleNode of ruleNodes) {\n const incomingEdges = graph.edges.filter((edge) => edge.target === ruleNode.id)\n const hasAgentSource = incomingEdges.some((edge) => {\n const sourceNode = nodeMap.get(edge.source)\n return sourceNode?.type === 'agent'\n })\n if (incomingEdges.length === 0 || !hasAgentSource) {\n errors.push(`Rule \"${ruleNode.data.label}\" must have an incoming connection from an agent`)\n }\n }\n\n // 6. No cycles (topological sort of agent nodes)\n if (agentNodes.length > 1) {\n const cycleError = detectCycle(agentNodes, graph.edges, nodeMap)\n if (cycleError) {\n errors.push(cycleError)\n }\n }\n\n // 7. At most 1 start node\n const startNodes = graph.nodes.filter((node) => node.type === 'start')\n if (startNodes.length > 1) {\n errors.push('Only one Start node is allowed')\n }\n\n // 8. At most 1 end node\n const endNodes = graph.nodes.filter((node) => node.type === 'end')\n if (endNodes.length > 1) {\n errors.push('Only one End node is allowed')\n }\n\n // 9. Start node has no incoming edges\n for (const startNode of startNodes) {\n const incomingEdges = graph.edges.filter((edge) => edge.target === startNode.id)\n if (incomingEdges.length > 0) {\n errors.push('Start node cannot have incoming connections')\n }\n }\n\n // 10. End node has no outgoing edges\n for (const endNode of endNodes) {\n const outgoingEdges = graph.edges.filter((edge) => edge.source === endNode.id)\n if (outgoingEdges.length > 0) {\n errors.push('End node cannot have outgoing connections')\n }\n }\n\n // 11. IF/ELSE has at least 1 condition\n const ifElseNodes = graph.nodes.filter((node) => node.type === 'if_else')\n for (const ifElseNode of ifElseNodes) {\n const config = ifElseNode.data.config as IfElseNodeConfig | undefined\n if (!config || config.conditions.length === 0) {\n errors.push('IF/ELSE node must have at least one condition')\n }\n }\n\n // 12. Code node has non-empty code\n const codeNodes = graph.nodes.filter((node) => node.type === 'code')\n for (const codeNode of codeNodes) {\n const config = codeNode.data.config as CodeNodeConfig | undefined\n if (!config || !config.code.trim()) {\n errors.push('Code node must have code content')\n }\n }\n\n // 13. HTTP Request has valid URL\n const httpNodes = graph.nodes.filter((node) => node.type === 'http_request')\n for (const httpNode of httpNodes) {\n const config = httpNode.data.config as HttpRequestNodeConfig | undefined\n if (!config || !config.url.trim()) {\n errors.push('HTTP Request node must have a URL')\n }\n }\n\n // 14. Template has non-empty template\n const templateNodes = graph.nodes.filter((node) => node.type === 'template_transform')\n for (const templateNode of templateNodes) {\n const config = templateNode.data.config as TemplateTransformNodeConfig | undefined\n if (!config || !config.template.trim()) {\n errors.push('Template node must have template content')\n }\n }\n\n // 15. Knowledge Base has source ID\n const knowledgeBaseNodes = graph.nodes.filter((node) => node.type === 'knowledge_base')\n for (const knowledgeBaseNode of knowledgeBaseNodes) {\n const config = knowledgeBaseNode.data.config as KnowledgeBaseNodeConfig | undefined\n if (!config || !config.sourceId.trim()) {\n errors.push('Knowledge Base node must have a source configured')\n }\n }\n\n // 16. Iteration has outgoing edge\n const iterationNodes = graph.nodes.filter((node) => node.type === 'iteration')\n for (const iterationNode of iterationNodes) {\n const outgoingEdges = graph.edges.filter((edge) => edge.source === iterationNode.id)\n if (outgoingEdges.length === 0) {\n errors.push('Iteration node must have at least one outgoing connection')\n }\n }\n\n // ============================================================================\n // New node type validations (9 new node types)\n // ============================================================================\n\n // 17. Answer node: outputTemplate must be non-empty\n const answerNodes = graph.nodes.filter((node) => node.type === 'answer')\n for (const answerNode of answerNodes) {\n const config = answerNode.data.config as AnswerNodeConfig | undefined\n if (!config || !config.outputTemplate.trim()) {\n errors.push('Answer node must have a non-empty output template')\n }\n }\n\n // 18. Question Classifier: must have >= 2 categories with non-empty names\n const questionClassifierNodes = graph.nodes.filter((node) => node.type === 'question_classifier')\n for (const questionClassifierNode of questionClassifierNodes) {\n const config = questionClassifierNode.data.config as QuestionClassifierNodeConfig | undefined\n if (!config || config.categories.length < 2) {\n errors.push('Question Classifier node must have at least 2 categories')\n } else {\n const emptyCategories = config.categories.filter((category) => !category.name.trim())\n if (emptyCategories.length > 0) {\n errors.push('Question Classifier node has categories with empty names')\n }\n }\n }\n\n // 19. Parameter Extractor: must have >= 1 parameter with non-empty name\n const parameterExtractorNodes = graph.nodes.filter((node) => node.type === 'parameter_extractor')\n for (const parameterExtractorNode of parameterExtractorNodes) {\n const config = parameterExtractorNode.data.config as ParameterExtractorNodeConfig | undefined\n if (!config || config.parameters.length === 0) {\n errors.push('Parameter Extractor node must have at least 1 parameter')\n } else {\n const emptyParameters = config.parameters.filter((parameter) => !parameter.name.trim())\n if (emptyParameters.length > 0) {\n errors.push('Parameter Extractor node has parameters with empty names')\n }\n }\n }\n\n // 20. Variable Assigner: must have >= 1 assignment\n const variableAssignerNodes = graph.nodes.filter((node) => node.type === 'variable_assigner')\n for (const variableAssignerNode of variableAssignerNodes) {\n const config = variableAssignerNode.data.config as VariableAssignerNodeConfig | undefined\n if (!config || config.assignments.length === 0) {\n errors.push('Variable Assigner node must have at least 1 assignment')\n }\n }\n\n // 21. Variable Aggregator: must have >= 1 inputVariable and non-empty outputVariable\n const variableAggregatorNodes = graph.nodes.filter((node) => node.type === 'variable_aggregator')\n for (const variableAggregatorNode of variableAggregatorNodes) {\n const config = variableAggregatorNode.data.config as VariableAggregatorNodeConfig | undefined\n if (!config || config.inputVariables.length === 0) {\n errors.push('Variable Aggregator node must have at least 1 input variable')\n }\n if (!config || !config.outputVariable.trim()) {\n errors.push('Variable Aggregator node must have a non-empty output variable')\n }\n }\n\n // 22. Document Extractor: outputVariable must be non-empty\n const documentExtractorNodes = graph.nodes.filter((node) => node.type === 'document_extractor')\n for (const documentExtractorNode of documentExtractorNodes) {\n const config = documentExtractorNode.data.config as DocumentExtractorNodeConfig | undefined\n if (!config || !config.outputVariable.trim()) {\n errors.push('Document Extractor node must have a non-empty output variable')\n }\n }\n\n // 23. List Operator: inputVariable and outputVariable must be non-empty\n const listOperatorNodes = graph.nodes.filter((node) => node.type === 'list_operator')\n for (const listOperatorNode of listOperatorNodes) {\n const config = listOperatorNode.data.config as ListOperatorNodeConfig | undefined\n if (!config || !config.inputVariable.trim()) {\n errors.push('List Operator node must have a non-empty input variable')\n }\n if (!config || !config.outputVariable.trim()) {\n errors.push('List Operator node must have a non-empty output variable')\n }\n }\n\n // 24. Iteration Start: must follow an Iteration node (incoming edge source must be iteration type)\n const iterationStartNodes = graph.nodes.filter((node) => node.type === 'iteration_start')\n for (const iterationStartNode of iterationStartNodes) {\n const incomingEdges = graph.edges.filter((edge) => edge.target === iterationStartNode.id)\n const hasIterationSource = incomingEdges.some((edge) => {\n const sourceNode = nodeMap.get(edge.source)\n return sourceNode?.type === 'iteration'\n })\n if (incomingEdges.length === 0 || !hasIterationSource) {\n errors.push('Iteration Start node must have an incoming connection from an Iteration node')\n }\n }\n\n // Note: Note nodes skip validation — they are annotations, not part of execution.\n\n return {\n valid: errors.length === 0,\n errors,\n }\n}\n\n/**\n * Detect cycles in the agent subgraph using DFS.\n */\nfunction detectCycle(\n agentNodes: WorkflowNode[],\n edges: WorkflowEdge[],\n nodeMap: Map<string, WorkflowNode>\n): string | null {\n const agentIds = new Set(agentNodes.map((node) => node.id))\n const adjacencyList = new Map<string, string[]>()\n\n for (const agentId of agentIds) {\n adjacencyList.set(agentId, [])\n }\n\n for (const edge of edges) {\n const sourceNode = nodeMap.get(edge.source)\n const targetNode = nodeMap.get(edge.target)\n if (\n sourceNode?.type === 'agent' &&\n targetNode?.type === 'agent' &&\n agentIds.has(edge.source) &&\n agentIds.has(edge.target)\n ) {\n adjacencyList.get(edge.source)!.push(edge.target)\n }\n }\n\n const visited = new Set<string>()\n const inStack = new Set<string>()\n\n function hasCycleDfs(nodeId: string): boolean {\n visited.add(nodeId)\n inStack.add(nodeId)\n\n const neighbors = adjacencyList.get(nodeId) ?? []\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n if (hasCycleDfs(neighbor)) return true\n } else if (inStack.has(neighbor)) {\n return true\n }\n }\n\n inStack.delete(nodeId)\n return false\n }\n\n for (const agentId of agentIds) {\n if (!visited.has(agentId)) {\n if (hasCycleDfs(agentId)) {\n return 'Cycle detected in agent pipeline — agents must form a directed acyclic graph (DAG)'\n }\n }\n }\n\n return null\n}\n\n/**\n * Topologically sort agent nodes from the graph edges.\n * Returns agent entityIds in execution order.\n * Logic nodes are skipped — only agent entityIds are returned.\n */\nexport function topologicalSortAgents(graph: WorkflowGraph): string[] {\n const agentNodes = graph.nodes.filter((node) => node.type === 'agent')\n const agentIdSet = new Set(agentNodes.map((node) => node.id))\n\n // Build adjacency list for agent-to-agent edges\n const inDegree = new Map<string, number>()\n const adjacencyList = new Map<string, string[]>()\n\n for (const node of agentNodes) {\n inDegree.set(node.id, 0)\n adjacencyList.set(node.id, [])\n }\n\n for (const edge of graph.edges) {\n if (agentIdSet.has(edge.source) && agentIdSet.has(edge.target)) {\n adjacencyList.get(edge.source)!.push(edge.target)\n inDegree.set(edge.target, (inDegree.get(edge.target) ?? 0) + 1)\n }\n }\n\n // Kahn's algorithm\n const queue: string[] = []\n for (const [nodeId, degree] of inDegree) {\n if (degree === 0) queue.push(nodeId)\n }\n\n const sorted: string[] = []\n while (queue.length > 0) {\n const current = queue.shift()!\n sorted.push(current)\n\n for (const neighbor of adjacencyList.get(current) ?? []) {\n const newDegree = (inDegree.get(neighbor) ?? 1) - 1\n inDegree.set(neighbor, newDegree)\n if (newDegree === 0) queue.push(neighbor)\n }\n }\n\n // Map node IDs back to entityIds\n const nodeIdToEntityId = new Map<string, string>()\n for (const node of agentNodes) {\n nodeIdToEntityId.set(node.id, node.data.entityId)\n }\n\n return sorted.map((nodeId) => nodeIdToEntityId.get(nodeId) ?? nodeId)\n}\n"]}