@flowgram.ai/runtime-js 0.2.25 → 0.2.26

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/index.js CHANGED
@@ -345,7 +345,7 @@ var WorkflowRuntimeType;
345
345
  const expectedType = types[0];
346
346
  types.forEach((type) => {
347
347
  if (type !== expectedType) {
348
- throw new Error(`array items type must be same, expect ${expectedType}, but got ${type}`);
348
+ throw new Error(`Array items type must be same, expect ${expectedType}, but got ${type}`);
349
349
  }
350
350
  });
351
351
  return expectedType;
@@ -503,8 +503,8 @@ var validateObject = (value, schema, path) => {
503
503
  }
504
504
  }
505
505
  if (schema.properties) {
506
- for (const [propertyName, propertySchema] of Object.entries(schema.properties)) {
507
- const isRequired = propertySchema.isPropertyRequired === true;
506
+ for (const [propertyName] of Object.entries(schema.properties)) {
507
+ const isRequired = schema.required?.includes(propertyName) ?? false;
508
508
  if (isRequired && !(propertyName in objectValue)) {
509
509
  return {
510
510
  result: false,
@@ -614,7 +614,7 @@ var LoopExecutor = class {
614
614
  const subNodes = context.node.children;
615
615
  const blockStartNode = subNodes.find((node) => node.type === FlowGramNode.BlockStart);
616
616
  if (!blockStartNode) {
617
- throw new Error("block start node not found");
617
+ throw new Error("Loop block start node not found");
618
618
  }
619
619
  const blockOutputs = [];
620
620
  for (let index = 0; index < loopArray.length; index++) {
@@ -632,10 +632,14 @@ var LoopExecutor = class {
632
632
  type: WorkflowVariableType.Number,
633
633
  value: index
634
634
  });
635
- await engine.executeNode({
636
- context: subContext,
637
- node: blockStartNode
638
- });
635
+ try {
636
+ await engine.executeNode({
637
+ context: subContext,
638
+ node: blockStartNode
639
+ });
640
+ } catch (e) {
641
+ throw new Error(`Loop block execute error`);
642
+ }
639
643
  const blockOutput = this.getBlockOutput(context, subContext);
640
644
  blockOutputs.push(blockOutput);
641
645
  }
@@ -656,15 +660,15 @@ var LoopExecutor = class {
656
660
  checkLoopArray(LoopArrayVariable) {
657
661
  const loopArray = LoopArrayVariable?.value;
658
662
  if (!loopArray || (0, import_lodash_es.isNil)(loopArray) || !Array.isArray(loopArray)) {
659
- throw new Error("loopFor is required");
663
+ throw new Error('Loop "loopFor" is required');
660
664
  }
661
665
  const loopArrayType = LoopArrayVariable.type;
662
666
  if (loopArrayType !== WorkflowVariableType.Array) {
663
- throw new Error("loopFor must be an array");
667
+ throw new Error('Loop "loopFor" must be an array');
664
668
  }
665
669
  const loopArrayItemType = LoopArrayVariable.itemsType;
666
670
  if ((0, import_lodash_es.isNil)(loopArrayItemType)) {
667
- throw new Error("loopFor items must be array items");
671
+ throw new Error('Loop "loopFor.items" must be array items');
668
672
  }
669
673
  }
670
674
  getBlockOutput(executionContext, subContext) {
@@ -725,13 +729,63 @@ var LoopExecutor = class {
725
729
  var import_lodash_es2 = require("lodash-es");
726
730
  var import_openai = require("@langchain/openai");
727
731
  var import_messages = require("@langchain/core/messages");
732
+
733
+ // src/nodes/llm/api-validator.ts
734
+ var APIValidator;
735
+ ((APIValidator2) => {
736
+ APIValidator2.isValidFormat = (apiHost) => {
737
+ if (!apiHost || typeof apiHost !== "string") {
738
+ return false;
739
+ }
740
+ try {
741
+ const url = new URL(apiHost);
742
+ return url.protocol === "http:" || url.protocol === "https:";
743
+ } catch (error) {
744
+ return false;
745
+ }
746
+ };
747
+ APIValidator2.isExist = async (apiHost) => {
748
+ try {
749
+ const controller = new AbortController();
750
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
751
+ await fetch(apiHost, {
752
+ method: "HEAD",
753
+ // Use HEAD to minimize data transfer
754
+ signal: controller.signal,
755
+ // Disable following redirects to get the actual host response
756
+ redirect: "manual"
757
+ });
758
+ clearTimeout(timeoutId);
759
+ return true;
760
+ } catch (error) {
761
+ if (error.name === "AbortError") {
762
+ return false;
763
+ }
764
+ const errorMessage = error.message?.toLowerCase() || "";
765
+ const networkFailurePatterns = [
766
+ "network error",
767
+ "connection refused",
768
+ "dns",
769
+ "resolve",
770
+ "timeout",
771
+ "unreachable"
772
+ ];
773
+ const isNetworkFailure = networkFailurePatterns.some(
774
+ (pattern) => errorMessage.includes(pattern)
775
+ );
776
+ return !isNetworkFailure;
777
+ }
778
+ };
779
+ })(APIValidator || (APIValidator = {}));
780
+
781
+ // src/nodes/llm/index.ts
728
782
  var LLMExecutor = class {
729
783
  constructor() {
730
784
  this.type = FlowGramNode.LLM;
731
785
  }
732
786
  async execute(context) {
733
787
  const inputs = context.inputs;
734
- this.checkInputs(inputs);
788
+ await this.checkInputs(inputs);
735
789
  const { modelName, temperature, apiKey, apiHost, systemPrompt, prompt } = inputs;
736
790
  const model = new import_openai.ChatOpenAI({
737
791
  modelName,
@@ -746,7 +800,16 @@ var LLMExecutor = class {
746
800
  messages.push(new import_messages.SystemMessage(systemPrompt));
747
801
  }
748
802
  messages.push(new import_messages.HumanMessage(prompt));
749
- const apiMessage = await model.invoke(messages);
803
+ let apiMessage;
804
+ try {
805
+ apiMessage = await model.invoke(messages);
806
+ } catch (error) {
807
+ const errorMessage = error?.message;
808
+ if (errorMessage === "Connection error.") {
809
+ throw new Error(`Network error: unreachable api "${apiHost}"`);
810
+ }
811
+ throw error;
812
+ }
750
813
  const result = apiMessage.content;
751
814
  return {
752
815
  outputs: {
@@ -754,16 +817,23 @@ var LLMExecutor = class {
754
817
  }
755
818
  };
756
819
  }
757
- checkInputs(inputs) {
820
+ async checkInputs(inputs) {
758
821
  const { modelName, temperature, apiKey, apiHost, prompt } = inputs;
759
822
  const missingInputs = [];
760
- if ((0, import_lodash_es2.isNil)(modelName)) missingInputs.push("modelName");
823
+ if (!modelName) missingInputs.push("modelName");
761
824
  if ((0, import_lodash_es2.isNil)(temperature)) missingInputs.push("temperature");
762
- if ((0, import_lodash_es2.isNil)(apiKey)) missingInputs.push("apiKey");
763
- if ((0, import_lodash_es2.isNil)(apiHost)) missingInputs.push("apiHost");
764
- if ((0, import_lodash_es2.isNil)(prompt)) missingInputs.push("prompt");
825
+ if (!apiKey) missingInputs.push("apiKey");
826
+ if (!apiHost) missingInputs.push("apiHost");
827
+ if (!prompt) missingInputs.push("prompt");
765
828
  if (missingInputs.length > 0) {
766
- throw new Error(`LLM node missing required inputs: ${missingInputs.join(", ")}`);
829
+ throw new Error(`LLM node missing required inputs: "${missingInputs.join('", "')}"`);
830
+ }
831
+ if (!APIValidator.isValidFormat(apiHost)) {
832
+ throw new Error(`Invalid API host format - ${apiHost}`);
833
+ }
834
+ const apiHostExists = await APIValidator.isExist(apiHost);
835
+ if (!apiHostExists) {
836
+ throw new Error(`Unreachable API host - ${apiHost}`);
767
837
  }
768
838
  }
769
839
  };
@@ -1058,7 +1128,7 @@ var ConditionExecutor = class {
1058
1128
  const parsedConditions = conditions.map((item) => this.parseCondition(item, context)).filter((item) => this.checkCondition(item));
1059
1129
  const activatedCondition = parsedConditions.find((item) => this.handleCondition(item));
1060
1130
  if (!activatedCondition) {
1061
- throw new Error("no condition is activated");
1131
+ throw new Error("No condition is activated");
1062
1132
  }
1063
1133
  return {
1064
1134
  outputs: {},
@@ -1086,11 +1156,13 @@ var ConditionExecutor = class {
1086
1156
  checkCondition(condition) {
1087
1157
  const rule = conditionRules[condition.leftType];
1088
1158
  if ((0, import_lodash_es9.isNil)(rule)) {
1089
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1159
+ throw new Error(`Condition left type "${condition.leftType}" is not supported`);
1090
1160
  }
1091
1161
  const ruleType = rule[condition.operator];
1092
1162
  if ((0, import_lodash_es9.isNil)(ruleType)) {
1093
- throw new Error(`condition operator ${condition.operator} is not supported`);
1163
+ throw new Error(
1164
+ `Condition left type "${condition.leftType}" has no operator "${condition.operator}"`
1165
+ );
1094
1166
  }
1095
1167
  if (ruleType !== condition.rightType) {
1096
1168
  return false;
@@ -1100,7 +1172,7 @@ var ConditionExecutor = class {
1100
1172
  handleCondition(condition) {
1101
1173
  const handler = conditionHandlers[condition.leftType];
1102
1174
  if (!handler) {
1103
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1175
+ throw new Error(`Condition left type ${condition.leftType} is not supported`);
1104
1176
  }
1105
1177
  const isActive = handler(condition);
1106
1178
  return isActive;
@@ -1118,6 +1190,241 @@ var WorkflowRuntimeNodeExecutors = [
1118
1190
  BlockEndExecutor
1119
1191
  ];
1120
1192
 
1193
+ // src/domain/validation/validators/cycle-detection.ts
1194
+ var cycleDetection = (schema) => {
1195
+ const { nodes, edges } = schema;
1196
+ const adjacencyList = /* @__PURE__ */ new Map();
1197
+ const nodeIds = new Set(nodes.map((node) => node.id));
1198
+ nodeIds.forEach((nodeId) => {
1199
+ adjacencyList.set(nodeId, []);
1200
+ });
1201
+ edges.forEach((edge) => {
1202
+ const sourceList = adjacencyList.get(edge.sourceNodeID);
1203
+ if (sourceList) {
1204
+ sourceList.push(edge.targetNodeID);
1205
+ }
1206
+ });
1207
+ let NodeStatus;
1208
+ ((NodeStatus2) => {
1209
+ NodeStatus2[NodeStatus2["Unvisited"] = 0] = "Unvisited";
1210
+ NodeStatus2[NodeStatus2["Visiting"] = 1] = "Visiting";
1211
+ NodeStatus2[NodeStatus2["Visited"] = 2] = "Visited";
1212
+ })(NodeStatus || (NodeStatus = {}));
1213
+ const nodeStatusMap = /* @__PURE__ */ new Map();
1214
+ nodeIds.forEach((nodeId) => {
1215
+ nodeStatusMap.set(nodeId, 0 /* Unvisited */);
1216
+ });
1217
+ const detectCycleFromNode = (nodeId) => {
1218
+ nodeStatusMap.set(nodeId, 1 /* Visiting */);
1219
+ const neighbors = adjacencyList.get(nodeId) || [];
1220
+ for (const neighbor of neighbors) {
1221
+ const neighborColor = nodeStatusMap.get(neighbor);
1222
+ if (neighborColor === 1 /* Visiting */) {
1223
+ return true;
1224
+ }
1225
+ if (neighborColor === 0 /* Unvisited */ && detectCycleFromNode(neighbor)) {
1226
+ return true;
1227
+ }
1228
+ }
1229
+ nodeStatusMap.set(nodeId, 2 /* Visited */);
1230
+ return false;
1231
+ };
1232
+ for (const nodeId of nodeIds) {
1233
+ if (nodeStatusMap.get(nodeId) === 0 /* Unvisited */) {
1234
+ if (detectCycleFromNode(nodeId)) {
1235
+ throw new Error("Workflow schema contains a cycle, which is not allowed");
1236
+ }
1237
+ }
1238
+ }
1239
+ nodes.forEach((node) => {
1240
+ if (node.blocks) {
1241
+ cycleDetection({
1242
+ nodes: node.blocks,
1243
+ edges: node.edges ?? []
1244
+ });
1245
+ }
1246
+ });
1247
+ };
1248
+
1249
+ // src/domain/validation/validators/start-end-node.ts
1250
+ var blockStartEndNode = (schema) => {
1251
+ const { blockStartNodes, blockEndNodes } = schema.nodes.reduce(
1252
+ (acc, node) => {
1253
+ if (node.type === FlowGramNode.BlockStart) {
1254
+ acc.blockStartNodes.push(node);
1255
+ } else if (node.type === FlowGramNode.BlockEnd) {
1256
+ acc.blockEndNodes.push(node);
1257
+ }
1258
+ return acc;
1259
+ },
1260
+ { blockStartNodes: [], blockEndNodes: [] }
1261
+ );
1262
+ if (!blockStartNodes.length && !blockEndNodes.length) {
1263
+ throw new Error("Workflow block schema must have a block-start node and a block-end node");
1264
+ }
1265
+ if (!blockStartNodes.length) {
1266
+ throw new Error("Workflow block schema must have a block-start node");
1267
+ }
1268
+ if (!blockEndNodes.length) {
1269
+ throw new Error("Workflow block schema must have an block-end node");
1270
+ }
1271
+ if (blockStartNodes.length > 1) {
1272
+ throw new Error("Workflow block schema must have only one block-start node");
1273
+ }
1274
+ if (blockEndNodes.length > 1) {
1275
+ throw new Error("Workflow block schema must have only one block-end node");
1276
+ }
1277
+ schema.nodes.forEach((node) => {
1278
+ if (node.blocks) {
1279
+ blockStartEndNode({
1280
+ nodes: node.blocks,
1281
+ edges: node.edges ?? []
1282
+ });
1283
+ }
1284
+ });
1285
+ };
1286
+ var startEndNode = (schema) => {
1287
+ const { startNodes, endNodes } = schema.nodes.reduce(
1288
+ (acc, node) => {
1289
+ if (node.type === FlowGramNode.Start) {
1290
+ acc.startNodes.push(node);
1291
+ } else if (node.type === FlowGramNode.End) {
1292
+ acc.endNodes.push(node);
1293
+ }
1294
+ return acc;
1295
+ },
1296
+ { startNodes: [], endNodes: [] }
1297
+ );
1298
+ if (!startNodes.length && !endNodes.length) {
1299
+ throw new Error("Workflow schema must have a start node and an end node");
1300
+ }
1301
+ if (!startNodes.length) {
1302
+ throw new Error("Workflow schema must have a start node");
1303
+ }
1304
+ if (!endNodes.length) {
1305
+ throw new Error("Workflow schema must have an end node");
1306
+ }
1307
+ if (startNodes.length > 1) {
1308
+ throw new Error("Workflow schema must have only one start node");
1309
+ }
1310
+ if (endNodes.length > 1) {
1311
+ throw new Error("Workflow schema must have only one end node");
1312
+ }
1313
+ schema.nodes.forEach((node) => {
1314
+ if (node.blocks) {
1315
+ blockStartEndNode({
1316
+ nodes: node.blocks,
1317
+ edges: node.edges ?? []
1318
+ });
1319
+ }
1320
+ });
1321
+ };
1322
+
1323
+ // src/domain/validation/validators/edge-source-target-exist.ts
1324
+ var edgeSourceTargetExist = (schema) => {
1325
+ const { nodes, edges } = schema;
1326
+ const nodeSet = new Set(nodes.map((node) => node.id));
1327
+ edges.forEach((edge) => {
1328
+ if (!nodeSet.has(edge.sourceNodeID)) {
1329
+ throw new Error(`Workflow schema edge source node "${edge.sourceNodeID}" not exist`);
1330
+ }
1331
+ if (!nodeSet.has(edge.targetNodeID)) {
1332
+ throw new Error(`Workflow schema edge target node "${edge.targetNodeID}" not exist`);
1333
+ }
1334
+ });
1335
+ nodes.forEach((node) => {
1336
+ if (node.blocks) {
1337
+ edgeSourceTargetExist({
1338
+ nodes: node.blocks,
1339
+ edges: node.edges ?? []
1340
+ });
1341
+ }
1342
+ });
1343
+ };
1344
+
1345
+ // src/domain/validation/validators/schema-format.ts
1346
+ var schemaFormat = (schema) => {
1347
+ if (!schema || typeof schema !== "object") {
1348
+ throw new Error("Workflow schema must be a valid object");
1349
+ }
1350
+ if (!Array.isArray(schema.nodes)) {
1351
+ throw new Error("Workflow schema must have a valid nodes array");
1352
+ }
1353
+ if (!Array.isArray(schema.edges)) {
1354
+ throw new Error("Workflow schema must have a valid edges array");
1355
+ }
1356
+ schema.nodes.forEach((node, index) => {
1357
+ validateNodeFormat(node, `nodes[${index}]`);
1358
+ });
1359
+ schema.edges.forEach((edge, index) => {
1360
+ validateEdgeFormat(edge, `edges[${index}]`);
1361
+ });
1362
+ schema.nodes.forEach((node, nodeIndex) => {
1363
+ if (node.blocks) {
1364
+ if (!Array.isArray(node.blocks)) {
1365
+ throw new Error(`Node nodes[${nodeIndex}].blocks must be an array`);
1366
+ }
1367
+ const nestedSchema = {
1368
+ nodes: node.blocks,
1369
+ edges: node.edges || []
1370
+ };
1371
+ schemaFormat(nestedSchema);
1372
+ }
1373
+ });
1374
+ };
1375
+ var validateNodeFormat = (node, path) => {
1376
+ if (!node || typeof node !== "object") {
1377
+ throw new Error(`${path} must be a valid object`);
1378
+ }
1379
+ if (typeof node.id !== "string" || !node.id.trim()) {
1380
+ throw new Error(`${path}.id must be a non-empty string`);
1381
+ }
1382
+ if (typeof node.type !== "string" || !node.type.trim()) {
1383
+ throw new Error(`${path}.type must be a non-empty string`);
1384
+ }
1385
+ if (!node.meta || typeof node.meta !== "object") {
1386
+ throw new Error(`${path}.meta must be a valid object`);
1387
+ }
1388
+ if (!node.data || typeof node.data !== "object") {
1389
+ throw new Error(`${path}.data must be a valid object`);
1390
+ }
1391
+ if (node.blocks !== void 0 && !Array.isArray(node.blocks)) {
1392
+ throw new Error(`${path}.blocks must be an array if present`);
1393
+ }
1394
+ if (node.edges !== void 0 && !Array.isArray(node.edges)) {
1395
+ throw new Error(`${path}.edges must be an array if present`);
1396
+ }
1397
+ if (node.data.inputs !== void 0 && (typeof node.data.inputs !== "object" || node.data.inputs === null)) {
1398
+ throw new Error(`${path}.data.inputs must be a valid object if present`);
1399
+ }
1400
+ if (node.data.outputs !== void 0 && (typeof node.data.outputs !== "object" || node.data.outputs === null)) {
1401
+ throw new Error(`${path}.data.outputs must be a valid object if present`);
1402
+ }
1403
+ if (node.data.inputsValues !== void 0 && (typeof node.data.inputsValues !== "object" || node.data.inputsValues === null)) {
1404
+ throw new Error(`${path}.data.inputsValues must be a valid object if present`);
1405
+ }
1406
+ if (node.data.title !== void 0 && typeof node.data.title !== "string") {
1407
+ throw new Error(`${path}.data.title must be a string if present`);
1408
+ }
1409
+ };
1410
+ var validateEdgeFormat = (edge, path) => {
1411
+ if (!edge || typeof edge !== "object") {
1412
+ throw new Error(`${path} must be a valid object`);
1413
+ }
1414
+ if (typeof edge.sourceNodeID !== "string" || !edge.sourceNodeID.trim()) {
1415
+ throw new Error(`${path}.sourceNodeID must be a non-empty string`);
1416
+ }
1417
+ if (typeof edge.targetNodeID !== "string" || !edge.targetNodeID.trim()) {
1418
+ throw new Error(`${path}.targetNodeID must be a non-empty string`);
1419
+ }
1420
+ if (edge.sourcePortID !== void 0 && typeof edge.sourcePortID !== "string") {
1421
+ throw new Error(`${path}.sourcePortID must be a string if present`);
1422
+ }
1423
+ if (edge.targetPortID !== void 0 && typeof edge.targetPortID !== "string") {
1424
+ throw new Error(`${path}.targetPortID must be a string if present`);
1425
+ }
1426
+ };
1427
+
1121
1428
  // src/domain/validation/index.ts
1122
1429
  var WorkflowRuntimeValidation = class {
1123
1430
  invoke(params) {
@@ -1135,8 +1442,23 @@ var WorkflowRuntimeValidation = class {
1135
1442
  };
1136
1443
  }
1137
1444
  schema(schema) {
1445
+ const errors = [];
1446
+ const validations = [
1447
+ () => schemaFormat(schema),
1448
+ () => cycleDetection(schema),
1449
+ () => edgeSourceTargetExist(schema),
1450
+ () => startEndNode(schema)
1451
+ ];
1452
+ validations.forEach((validation) => {
1453
+ try {
1454
+ validation();
1455
+ } catch (error) {
1456
+ errors.push(error instanceof Error ? error.message : String(error));
1457
+ }
1458
+ });
1138
1459
  return {
1139
- valid: true
1460
+ valid: errors.length === 0,
1461
+ errors: errors.length > 0 ? errors : void 0
1140
1462
  };
1141
1463
  }
1142
1464
  inputs(inputsSchema, inputs) {
@@ -1179,7 +1501,7 @@ var WorkflowRuntimeExecutor = class {
1179
1501
  const nodeType = context.node.type;
1180
1502
  const nodeExecutor = this.nodeExecutors.get(nodeType);
1181
1503
  if (!nodeExecutor) {
1182
- throw new Error(`no executor found for node type ${nodeType}`);
1504
+ throw new Error(`No executor found for node type ${nodeType}`);
1183
1505
  }
1184
1506
  const output = await nodeExecutor.execute(context);
1185
1507
  return output;
@@ -1554,7 +1876,7 @@ var WorkflowRuntimeState = class {
1554
1876
  }
1555
1877
  parseRef(ref) {
1556
1878
  if (ref?.type !== "ref") {
1557
- throw new Error(`invalid ref value: ${ref}`);
1879
+ throw new Error(`Invalid ref value: ${ref}`);
1558
1880
  }
1559
1881
  if (!ref.content || ref.content.length < 2) {
1560
1882
  return null;
@@ -1572,7 +1894,7 @@ var WorkflowRuntimeState = class {
1572
1894
  }
1573
1895
  parseTemplate(template) {
1574
1896
  if (template?.type !== "template") {
1575
- throw new Error(`invalid template value: ${template}`);
1897
+ throw new Error(`Invalid template value: ${template}`);
1576
1898
  }
1577
1899
  if (!template.content) {
1578
1900
  return null;
@@ -1598,7 +1920,7 @@ var WorkflowRuntimeState = class {
1598
1920
  }
1599
1921
  parseValue(flowValue) {
1600
1922
  if (!flowValue?.type) {
1601
- throw new Error(`invalid flow value type: ${flowValue.type}`);
1923
+ throw new Error(`Invalid flow value type: ${flowValue.type}`);
1602
1924
  }
1603
1925
  if (flowValue.type === "constant") {
1604
1926
  const value = flowValue.content;
@@ -1617,7 +1939,7 @@ var WorkflowRuntimeState = class {
1617
1939
  if (flowValue.type === "template") {
1618
1940
  return this.parseTemplate(flowValue);
1619
1941
  }
1620
- throw new Error(`unknown flow value type: ${flowValue.type}`);
1942
+ throw new Error(`Unknown flow value type: ${flowValue.type}`);
1621
1943
  }
1622
1944
  isExecutedNode(node) {
1623
1945
  return this.executedNodes.has(node.id);
@@ -2002,7 +2324,7 @@ var createStore = (params) => {
2002
2324
  const from = store.nodes.get(sourceNodeID);
2003
2325
  const to = store.nodes.get(targetNodeID);
2004
2326
  if (!from || !to) {
2005
- throw new Error(`invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
2327
+ throw new Error(`Invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
2006
2328
  }
2007
2329
  const edge = createEdge(store, {
2008
2330
  id,
@@ -2277,7 +2599,7 @@ var WorkflowRuntimeEngine = class {
2277
2599
  }
2278
2600
  const targetPort = node.ports.outputs.find((port) => port.id === branch);
2279
2601
  if (!targetPort) {
2280
- throw new Error(`branch ${branch} not found`);
2602
+ throw new Error(`Engine branch ${branch} not found`);
2281
2603
  }
2282
2604
  const nextNodeIDs = new Set(targetPort.edges.map((edge) => edge.to.id));
2283
2605
  const nextNodes = allNextNodes.filter((nextNode) => nextNodeIDs.has(nextNode.id));
@@ -2292,11 +2614,11 @@ var WorkflowRuntimeEngine = class {
2292
2614
  }
2293
2615
  async executeNext(params) {
2294
2616
  const { context, node, nextNodes } = params;
2295
- if (node.type === FlowGramNode.End) {
2617
+ if (node.type === FlowGramNode.End || node.type === FlowGramNode.BlockEnd) {
2296
2618
  return;
2297
2619
  }
2298
2620
  if (nextNodes.length === 0) {
2299
- return;
2621
+ throw new Error(`Node ${node.id} has no next nodes`);
2300
2622
  }
2301
2623
  await Promise.all(
2302
2624
  nextNodes.map(