@flowgram.ai/runtime-js 0.2.24 → 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/esm/index.js CHANGED
@@ -304,7 +304,7 @@ var WorkflowRuntimeType;
304
304
  const expectedType = types[0];
305
305
  types.forEach((type) => {
306
306
  if (type !== expectedType) {
307
- throw new Error(`array items type must be same, expect ${expectedType}, but got ${type}`);
307
+ throw new Error(`Array items type must be same, expect ${expectedType}, but got ${type}`);
308
308
  }
309
309
  });
310
310
  return expectedType;
@@ -462,8 +462,8 @@ var validateObject = (value, schema, path) => {
462
462
  }
463
463
  }
464
464
  if (schema.properties) {
465
- for (const [propertyName, propertySchema] of Object.entries(schema.properties)) {
466
- const isRequired = propertySchema.isPropertyRequired === true;
465
+ for (const [propertyName] of Object.entries(schema.properties)) {
466
+ const isRequired = schema.required?.includes(propertyName) ?? false;
467
467
  if (isRequired && !(propertyName in objectValue)) {
468
468
  return {
469
469
  result: false,
@@ -573,7 +573,7 @@ var LoopExecutor = class {
573
573
  const subNodes = context.node.children;
574
574
  const blockStartNode = subNodes.find((node) => node.type === FlowGramNode.BlockStart);
575
575
  if (!blockStartNode) {
576
- throw new Error("block start node not found");
576
+ throw new Error("Loop block start node not found");
577
577
  }
578
578
  const blockOutputs = [];
579
579
  for (let index = 0; index < loopArray.length; index++) {
@@ -591,10 +591,14 @@ var LoopExecutor = class {
591
591
  type: WorkflowVariableType.Number,
592
592
  value: index
593
593
  });
594
- await engine.executeNode({
595
- context: subContext,
596
- node: blockStartNode
597
- });
594
+ try {
595
+ await engine.executeNode({
596
+ context: subContext,
597
+ node: blockStartNode
598
+ });
599
+ } catch (e) {
600
+ throw new Error(`Loop block execute error`);
601
+ }
598
602
  const blockOutput = this.getBlockOutput(context, subContext);
599
603
  blockOutputs.push(blockOutput);
600
604
  }
@@ -615,15 +619,15 @@ var LoopExecutor = class {
615
619
  checkLoopArray(LoopArrayVariable) {
616
620
  const loopArray = LoopArrayVariable?.value;
617
621
  if (!loopArray || isNil(loopArray) || !Array.isArray(loopArray)) {
618
- throw new Error("loopFor is required");
622
+ throw new Error('Loop "loopFor" is required');
619
623
  }
620
624
  const loopArrayType = LoopArrayVariable.type;
621
625
  if (loopArrayType !== WorkflowVariableType.Array) {
622
- throw new Error("loopFor must be an array");
626
+ throw new Error('Loop "loopFor" must be an array');
623
627
  }
624
628
  const loopArrayItemType = LoopArrayVariable.itemsType;
625
629
  if (isNil(loopArrayItemType)) {
626
- throw new Error("loopFor items must be array items");
630
+ throw new Error('Loop "loopFor.items" must be array items');
627
631
  }
628
632
  }
629
633
  getBlockOutput(executionContext, subContext) {
@@ -684,13 +688,63 @@ var LoopExecutor = class {
684
688
  import { isNil as isNil2 } from "lodash-es";
685
689
  import { ChatOpenAI } from "@langchain/openai";
686
690
  import { SystemMessage, HumanMessage } from "@langchain/core/messages";
691
+
692
+ // src/nodes/llm/api-validator.ts
693
+ var APIValidator;
694
+ ((APIValidator2) => {
695
+ APIValidator2.isValidFormat = (apiHost) => {
696
+ if (!apiHost || typeof apiHost !== "string") {
697
+ return false;
698
+ }
699
+ try {
700
+ const url = new URL(apiHost);
701
+ return url.protocol === "http:" || url.protocol === "https:";
702
+ } catch (error) {
703
+ return false;
704
+ }
705
+ };
706
+ APIValidator2.isExist = async (apiHost) => {
707
+ try {
708
+ const controller = new AbortController();
709
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
710
+ await fetch(apiHost, {
711
+ method: "HEAD",
712
+ // Use HEAD to minimize data transfer
713
+ signal: controller.signal,
714
+ // Disable following redirects to get the actual host response
715
+ redirect: "manual"
716
+ });
717
+ clearTimeout(timeoutId);
718
+ return true;
719
+ } catch (error) {
720
+ if (error.name === "AbortError") {
721
+ return false;
722
+ }
723
+ const errorMessage = error.message?.toLowerCase() || "";
724
+ const networkFailurePatterns = [
725
+ "network error",
726
+ "connection refused",
727
+ "dns",
728
+ "resolve",
729
+ "timeout",
730
+ "unreachable"
731
+ ];
732
+ const isNetworkFailure = networkFailurePatterns.some(
733
+ (pattern) => errorMessage.includes(pattern)
734
+ );
735
+ return !isNetworkFailure;
736
+ }
737
+ };
738
+ })(APIValidator || (APIValidator = {}));
739
+
740
+ // src/nodes/llm/index.ts
687
741
  var LLMExecutor = class {
688
742
  constructor() {
689
743
  this.type = FlowGramNode.LLM;
690
744
  }
691
745
  async execute(context) {
692
746
  const inputs = context.inputs;
693
- this.checkInputs(inputs);
747
+ await this.checkInputs(inputs);
694
748
  const { modelName, temperature, apiKey, apiHost, systemPrompt, prompt } = inputs;
695
749
  const model = new ChatOpenAI({
696
750
  modelName,
@@ -705,7 +759,16 @@ var LLMExecutor = class {
705
759
  messages.push(new SystemMessage(systemPrompt));
706
760
  }
707
761
  messages.push(new HumanMessage(prompt));
708
- const apiMessage = await model.invoke(messages);
762
+ let apiMessage;
763
+ try {
764
+ apiMessage = await model.invoke(messages);
765
+ } catch (error) {
766
+ const errorMessage = error?.message;
767
+ if (errorMessage === "Connection error.") {
768
+ throw new Error(`Network error: unreachable api "${apiHost}"`);
769
+ }
770
+ throw error;
771
+ }
709
772
  const result = apiMessage.content;
710
773
  return {
711
774
  outputs: {
@@ -713,16 +776,23 @@ var LLMExecutor = class {
713
776
  }
714
777
  };
715
778
  }
716
- checkInputs(inputs) {
779
+ async checkInputs(inputs) {
717
780
  const { modelName, temperature, apiKey, apiHost, prompt } = inputs;
718
781
  const missingInputs = [];
719
- if (isNil2(modelName)) missingInputs.push("modelName");
782
+ if (!modelName) missingInputs.push("modelName");
720
783
  if (isNil2(temperature)) missingInputs.push("temperature");
721
- if (isNil2(apiKey)) missingInputs.push("apiKey");
722
- if (isNil2(apiHost)) missingInputs.push("apiHost");
723
- if (isNil2(prompt)) missingInputs.push("prompt");
784
+ if (!apiKey) missingInputs.push("apiKey");
785
+ if (!apiHost) missingInputs.push("apiHost");
786
+ if (!prompt) missingInputs.push("prompt");
724
787
  if (missingInputs.length > 0) {
725
- throw new Error(`LLM node missing required inputs: ${missingInputs.join(", ")}`);
788
+ throw new Error(`LLM node missing required inputs: "${missingInputs.join('", "')}"`);
789
+ }
790
+ if (!APIValidator.isValidFormat(apiHost)) {
791
+ throw new Error(`Invalid API host format - ${apiHost}`);
792
+ }
793
+ const apiHostExists = await APIValidator.isExist(apiHost);
794
+ if (!apiHostExists) {
795
+ throw new Error(`Unreachable API host - ${apiHost}`);
726
796
  }
727
797
  }
728
798
  };
@@ -1017,7 +1087,7 @@ var ConditionExecutor = class {
1017
1087
  const parsedConditions = conditions.map((item) => this.parseCondition(item, context)).filter((item) => this.checkCondition(item));
1018
1088
  const activatedCondition = parsedConditions.find((item) => this.handleCondition(item));
1019
1089
  if (!activatedCondition) {
1020
- throw new Error("no condition is activated");
1090
+ throw new Error("No condition is activated");
1021
1091
  }
1022
1092
  return {
1023
1093
  outputs: {},
@@ -1045,11 +1115,13 @@ var ConditionExecutor = class {
1045
1115
  checkCondition(condition) {
1046
1116
  const rule = conditionRules[condition.leftType];
1047
1117
  if (isNil9(rule)) {
1048
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1118
+ throw new Error(`Condition left type "${condition.leftType}" is not supported`);
1049
1119
  }
1050
1120
  const ruleType = rule[condition.operator];
1051
1121
  if (isNil9(ruleType)) {
1052
- throw new Error(`condition operator ${condition.operator} is not supported`);
1122
+ throw new Error(
1123
+ `Condition left type "${condition.leftType}" has no operator "${condition.operator}"`
1124
+ );
1053
1125
  }
1054
1126
  if (ruleType !== condition.rightType) {
1055
1127
  return false;
@@ -1059,7 +1131,7 @@ var ConditionExecutor = class {
1059
1131
  handleCondition(condition) {
1060
1132
  const handler = conditionHandlers[condition.leftType];
1061
1133
  if (!handler) {
1062
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1134
+ throw new Error(`Condition left type ${condition.leftType} is not supported`);
1063
1135
  }
1064
1136
  const isActive = handler(condition);
1065
1137
  return isActive;
@@ -1077,6 +1149,241 @@ var WorkflowRuntimeNodeExecutors = [
1077
1149
  BlockEndExecutor
1078
1150
  ];
1079
1151
 
1152
+ // src/domain/validation/validators/cycle-detection.ts
1153
+ var cycleDetection = (schema) => {
1154
+ const { nodes, edges } = schema;
1155
+ const adjacencyList = /* @__PURE__ */ new Map();
1156
+ const nodeIds = new Set(nodes.map((node) => node.id));
1157
+ nodeIds.forEach((nodeId) => {
1158
+ adjacencyList.set(nodeId, []);
1159
+ });
1160
+ edges.forEach((edge) => {
1161
+ const sourceList = adjacencyList.get(edge.sourceNodeID);
1162
+ if (sourceList) {
1163
+ sourceList.push(edge.targetNodeID);
1164
+ }
1165
+ });
1166
+ let NodeStatus;
1167
+ ((NodeStatus2) => {
1168
+ NodeStatus2[NodeStatus2["Unvisited"] = 0] = "Unvisited";
1169
+ NodeStatus2[NodeStatus2["Visiting"] = 1] = "Visiting";
1170
+ NodeStatus2[NodeStatus2["Visited"] = 2] = "Visited";
1171
+ })(NodeStatus || (NodeStatus = {}));
1172
+ const nodeStatusMap = /* @__PURE__ */ new Map();
1173
+ nodeIds.forEach((nodeId) => {
1174
+ nodeStatusMap.set(nodeId, 0 /* Unvisited */);
1175
+ });
1176
+ const detectCycleFromNode = (nodeId) => {
1177
+ nodeStatusMap.set(nodeId, 1 /* Visiting */);
1178
+ const neighbors = adjacencyList.get(nodeId) || [];
1179
+ for (const neighbor of neighbors) {
1180
+ const neighborColor = nodeStatusMap.get(neighbor);
1181
+ if (neighborColor === 1 /* Visiting */) {
1182
+ return true;
1183
+ }
1184
+ if (neighborColor === 0 /* Unvisited */ && detectCycleFromNode(neighbor)) {
1185
+ return true;
1186
+ }
1187
+ }
1188
+ nodeStatusMap.set(nodeId, 2 /* Visited */);
1189
+ return false;
1190
+ };
1191
+ for (const nodeId of nodeIds) {
1192
+ if (nodeStatusMap.get(nodeId) === 0 /* Unvisited */) {
1193
+ if (detectCycleFromNode(nodeId)) {
1194
+ throw new Error("Workflow schema contains a cycle, which is not allowed");
1195
+ }
1196
+ }
1197
+ }
1198
+ nodes.forEach((node) => {
1199
+ if (node.blocks) {
1200
+ cycleDetection({
1201
+ nodes: node.blocks,
1202
+ edges: node.edges ?? []
1203
+ });
1204
+ }
1205
+ });
1206
+ };
1207
+
1208
+ // src/domain/validation/validators/start-end-node.ts
1209
+ var blockStartEndNode = (schema) => {
1210
+ const { blockStartNodes, blockEndNodes } = schema.nodes.reduce(
1211
+ (acc, node) => {
1212
+ if (node.type === FlowGramNode.BlockStart) {
1213
+ acc.blockStartNodes.push(node);
1214
+ } else if (node.type === FlowGramNode.BlockEnd) {
1215
+ acc.blockEndNodes.push(node);
1216
+ }
1217
+ return acc;
1218
+ },
1219
+ { blockStartNodes: [], blockEndNodes: [] }
1220
+ );
1221
+ if (!blockStartNodes.length && !blockEndNodes.length) {
1222
+ throw new Error("Workflow block schema must have a block-start node and a block-end node");
1223
+ }
1224
+ if (!blockStartNodes.length) {
1225
+ throw new Error("Workflow block schema must have a block-start node");
1226
+ }
1227
+ if (!blockEndNodes.length) {
1228
+ throw new Error("Workflow block schema must have an block-end node");
1229
+ }
1230
+ if (blockStartNodes.length > 1) {
1231
+ throw new Error("Workflow block schema must have only one block-start node");
1232
+ }
1233
+ if (blockEndNodes.length > 1) {
1234
+ throw new Error("Workflow block schema must have only one block-end node");
1235
+ }
1236
+ schema.nodes.forEach((node) => {
1237
+ if (node.blocks) {
1238
+ blockStartEndNode({
1239
+ nodes: node.blocks,
1240
+ edges: node.edges ?? []
1241
+ });
1242
+ }
1243
+ });
1244
+ };
1245
+ var startEndNode = (schema) => {
1246
+ const { startNodes, endNodes } = schema.nodes.reduce(
1247
+ (acc, node) => {
1248
+ if (node.type === FlowGramNode.Start) {
1249
+ acc.startNodes.push(node);
1250
+ } else if (node.type === FlowGramNode.End) {
1251
+ acc.endNodes.push(node);
1252
+ }
1253
+ return acc;
1254
+ },
1255
+ { startNodes: [], endNodes: [] }
1256
+ );
1257
+ if (!startNodes.length && !endNodes.length) {
1258
+ throw new Error("Workflow schema must have a start node and an end node");
1259
+ }
1260
+ if (!startNodes.length) {
1261
+ throw new Error("Workflow schema must have a start node");
1262
+ }
1263
+ if (!endNodes.length) {
1264
+ throw new Error("Workflow schema must have an end node");
1265
+ }
1266
+ if (startNodes.length > 1) {
1267
+ throw new Error("Workflow schema must have only one start node");
1268
+ }
1269
+ if (endNodes.length > 1) {
1270
+ throw new Error("Workflow schema must have only one end node");
1271
+ }
1272
+ schema.nodes.forEach((node) => {
1273
+ if (node.blocks) {
1274
+ blockStartEndNode({
1275
+ nodes: node.blocks,
1276
+ edges: node.edges ?? []
1277
+ });
1278
+ }
1279
+ });
1280
+ };
1281
+
1282
+ // src/domain/validation/validators/edge-source-target-exist.ts
1283
+ var edgeSourceTargetExist = (schema) => {
1284
+ const { nodes, edges } = schema;
1285
+ const nodeSet = new Set(nodes.map((node) => node.id));
1286
+ edges.forEach((edge) => {
1287
+ if (!nodeSet.has(edge.sourceNodeID)) {
1288
+ throw new Error(`Workflow schema edge source node "${edge.sourceNodeID}" not exist`);
1289
+ }
1290
+ if (!nodeSet.has(edge.targetNodeID)) {
1291
+ throw new Error(`Workflow schema edge target node "${edge.targetNodeID}" not exist`);
1292
+ }
1293
+ });
1294
+ nodes.forEach((node) => {
1295
+ if (node.blocks) {
1296
+ edgeSourceTargetExist({
1297
+ nodes: node.blocks,
1298
+ edges: node.edges ?? []
1299
+ });
1300
+ }
1301
+ });
1302
+ };
1303
+
1304
+ // src/domain/validation/validators/schema-format.ts
1305
+ var schemaFormat = (schema) => {
1306
+ if (!schema || typeof schema !== "object") {
1307
+ throw new Error("Workflow schema must be a valid object");
1308
+ }
1309
+ if (!Array.isArray(schema.nodes)) {
1310
+ throw new Error("Workflow schema must have a valid nodes array");
1311
+ }
1312
+ if (!Array.isArray(schema.edges)) {
1313
+ throw new Error("Workflow schema must have a valid edges array");
1314
+ }
1315
+ schema.nodes.forEach((node, index) => {
1316
+ validateNodeFormat(node, `nodes[${index}]`);
1317
+ });
1318
+ schema.edges.forEach((edge, index) => {
1319
+ validateEdgeFormat(edge, `edges[${index}]`);
1320
+ });
1321
+ schema.nodes.forEach((node, nodeIndex) => {
1322
+ if (node.blocks) {
1323
+ if (!Array.isArray(node.blocks)) {
1324
+ throw new Error(`Node nodes[${nodeIndex}].blocks must be an array`);
1325
+ }
1326
+ const nestedSchema = {
1327
+ nodes: node.blocks,
1328
+ edges: node.edges || []
1329
+ };
1330
+ schemaFormat(nestedSchema);
1331
+ }
1332
+ });
1333
+ };
1334
+ var validateNodeFormat = (node, path) => {
1335
+ if (!node || typeof node !== "object") {
1336
+ throw new Error(`${path} must be a valid object`);
1337
+ }
1338
+ if (typeof node.id !== "string" || !node.id.trim()) {
1339
+ throw new Error(`${path}.id must be a non-empty string`);
1340
+ }
1341
+ if (typeof node.type !== "string" || !node.type.trim()) {
1342
+ throw new Error(`${path}.type must be a non-empty string`);
1343
+ }
1344
+ if (!node.meta || typeof node.meta !== "object") {
1345
+ throw new Error(`${path}.meta must be a valid object`);
1346
+ }
1347
+ if (!node.data || typeof node.data !== "object") {
1348
+ throw new Error(`${path}.data must be a valid object`);
1349
+ }
1350
+ if (node.blocks !== void 0 && !Array.isArray(node.blocks)) {
1351
+ throw new Error(`${path}.blocks must be an array if present`);
1352
+ }
1353
+ if (node.edges !== void 0 && !Array.isArray(node.edges)) {
1354
+ throw new Error(`${path}.edges must be an array if present`);
1355
+ }
1356
+ if (node.data.inputs !== void 0 && (typeof node.data.inputs !== "object" || node.data.inputs === null)) {
1357
+ throw new Error(`${path}.data.inputs must be a valid object if present`);
1358
+ }
1359
+ if (node.data.outputs !== void 0 && (typeof node.data.outputs !== "object" || node.data.outputs === null)) {
1360
+ throw new Error(`${path}.data.outputs must be a valid object if present`);
1361
+ }
1362
+ if (node.data.inputsValues !== void 0 && (typeof node.data.inputsValues !== "object" || node.data.inputsValues === null)) {
1363
+ throw new Error(`${path}.data.inputsValues must be a valid object if present`);
1364
+ }
1365
+ if (node.data.title !== void 0 && typeof node.data.title !== "string") {
1366
+ throw new Error(`${path}.data.title must be a string if present`);
1367
+ }
1368
+ };
1369
+ var validateEdgeFormat = (edge, path) => {
1370
+ if (!edge || typeof edge !== "object") {
1371
+ throw new Error(`${path} must be a valid object`);
1372
+ }
1373
+ if (typeof edge.sourceNodeID !== "string" || !edge.sourceNodeID.trim()) {
1374
+ throw new Error(`${path}.sourceNodeID must be a non-empty string`);
1375
+ }
1376
+ if (typeof edge.targetNodeID !== "string" || !edge.targetNodeID.trim()) {
1377
+ throw new Error(`${path}.targetNodeID must be a non-empty string`);
1378
+ }
1379
+ if (edge.sourcePortID !== void 0 && typeof edge.sourcePortID !== "string") {
1380
+ throw new Error(`${path}.sourcePortID must be a string if present`);
1381
+ }
1382
+ if (edge.targetPortID !== void 0 && typeof edge.targetPortID !== "string") {
1383
+ throw new Error(`${path}.targetPortID must be a string if present`);
1384
+ }
1385
+ };
1386
+
1080
1387
  // src/domain/validation/index.ts
1081
1388
  var WorkflowRuntimeValidation = class {
1082
1389
  invoke(params) {
@@ -1094,8 +1401,23 @@ var WorkflowRuntimeValidation = class {
1094
1401
  };
1095
1402
  }
1096
1403
  schema(schema) {
1404
+ const errors = [];
1405
+ const validations = [
1406
+ () => schemaFormat(schema),
1407
+ () => cycleDetection(schema),
1408
+ () => edgeSourceTargetExist(schema),
1409
+ () => startEndNode(schema)
1410
+ ];
1411
+ validations.forEach((validation) => {
1412
+ try {
1413
+ validation();
1414
+ } catch (error) {
1415
+ errors.push(error instanceof Error ? error.message : String(error));
1416
+ }
1417
+ });
1097
1418
  return {
1098
- valid: true
1419
+ valid: errors.length === 0,
1420
+ errors: errors.length > 0 ? errors : void 0
1099
1421
  };
1100
1422
  }
1101
1423
  inputs(inputsSchema, inputs) {
@@ -1138,7 +1460,7 @@ var WorkflowRuntimeExecutor = class {
1138
1460
  const nodeType = context.node.type;
1139
1461
  const nodeExecutor = this.nodeExecutors.get(nodeType);
1140
1462
  if (!nodeExecutor) {
1141
- throw new Error(`no executor found for node type ${nodeType}`);
1463
+ throw new Error(`No executor found for node type ${nodeType}`);
1142
1464
  }
1143
1465
  const output = await nodeExecutor.execute(context);
1144
1466
  return output;
@@ -1513,7 +1835,7 @@ var WorkflowRuntimeState = class {
1513
1835
  }
1514
1836
  parseRef(ref) {
1515
1837
  if (ref?.type !== "ref") {
1516
- throw new Error(`invalid ref value: ${ref}`);
1838
+ throw new Error(`Invalid ref value: ${ref}`);
1517
1839
  }
1518
1840
  if (!ref.content || ref.content.length < 2) {
1519
1841
  return null;
@@ -1531,7 +1853,7 @@ var WorkflowRuntimeState = class {
1531
1853
  }
1532
1854
  parseTemplate(template) {
1533
1855
  if (template?.type !== "template") {
1534
- throw new Error(`invalid template value: ${template}`);
1856
+ throw new Error(`Invalid template value: ${template}`);
1535
1857
  }
1536
1858
  if (!template.content) {
1537
1859
  return null;
@@ -1557,7 +1879,7 @@ var WorkflowRuntimeState = class {
1557
1879
  }
1558
1880
  parseValue(flowValue) {
1559
1881
  if (!flowValue?.type) {
1560
- throw new Error(`invalid flow value type: ${flowValue.type}`);
1882
+ throw new Error(`Invalid flow value type: ${flowValue.type}`);
1561
1883
  }
1562
1884
  if (flowValue.type === "constant") {
1563
1885
  const value = flowValue.content;
@@ -1576,7 +1898,7 @@ var WorkflowRuntimeState = class {
1576
1898
  if (flowValue.type === "template") {
1577
1899
  return this.parseTemplate(flowValue);
1578
1900
  }
1579
- throw new Error(`unknown flow value type: ${flowValue.type}`);
1901
+ throw new Error(`Unknown flow value type: ${flowValue.type}`);
1580
1902
  }
1581
1903
  isExecutedNode(node) {
1582
1904
  return this.executedNodes.has(node.id);
@@ -1961,7 +2283,7 @@ var createStore = (params) => {
1961
2283
  const from = store.nodes.get(sourceNodeID);
1962
2284
  const to = store.nodes.get(targetNodeID);
1963
2285
  if (!from || !to) {
1964
- throw new Error(`invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
2286
+ throw new Error(`Invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
1965
2287
  }
1966
2288
  const edge = createEdge(store, {
1967
2289
  id,
@@ -2236,7 +2558,7 @@ var WorkflowRuntimeEngine = class {
2236
2558
  }
2237
2559
  const targetPort = node.ports.outputs.find((port) => port.id === branch);
2238
2560
  if (!targetPort) {
2239
- throw new Error(`branch ${branch} not found`);
2561
+ throw new Error(`Engine branch ${branch} not found`);
2240
2562
  }
2241
2563
  const nextNodeIDs = new Set(targetPort.edges.map((edge) => edge.to.id));
2242
2564
  const nextNodes = allNextNodes.filter((nextNode) => nextNodeIDs.has(nextNode.id));
@@ -2251,11 +2573,11 @@ var WorkflowRuntimeEngine = class {
2251
2573
  }
2252
2574
  async executeNext(params) {
2253
2575
  const { context, node, nextNodes } = params;
2254
- if (node.type === FlowGramNode.End) {
2576
+ if (node.type === FlowGramNode.End || node.type === FlowGramNode.BlockEnd) {
2255
2577
  return;
2256
2578
  }
2257
2579
  if (nextNodes.length === 0) {
2258
- return;
2580
+ throw new Error(`Node ${node.id} has no next nodes`);
2259
2581
  }
2260
2582
  await Promise.all(
2261
2583
  nextNodes.map(