@vaudience/aigentflow-flow-validator 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2631,31 +2631,81 @@ function validateConnectivity(flow, issues) {
2631
2631
  });
2632
2632
  }
2633
2633
  }
2634
- const visited = /* @__PURE__ */ new Set();
2635
- const recStack = /* @__PURE__ */ new Set();
2636
- const hasCycle = (stepID) => {
2637
- if (recStack.has(stepID)) return true;
2638
- if (visited.has(stepID)) return false;
2639
- visited.add(stepID);
2640
- recStack.add(stepID);
2641
- const step = steps[stepID];
2642
- if (isRecord(step) && isRecord(step.next)) {
2643
- const next = step.next;
2644
- for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
2645
- if (hasCycle(target)) return true;
2646
- }
2647
- }
2648
- recStack.delete(stepID);
2649
- return false;
2650
- };
2651
- if (hasCycle(start)) {
2634
+ if (cycleWithoutExit(steps, start, issues)) {
2652
2635
  issues.warn({
2653
2636
  field: "steps",
2654
- message: "Potential infinite loop detected in step flow",
2637
+ message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
2655
2638
  code: "potential_infinite_loop",
2656
- suggestion: "Review your step transitions to ensure they eventually terminate"
2639
+ suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
2640
+ });
2641
+ }
2642
+ }
2643
+ function cycleEdges(step, stepID, issues) {
2644
+ if (!isRecord(step) || !isRecord(step.next)) return [];
2645
+ const next = step.next;
2646
+ const nextPath = `steps.${stepID}.next`;
2647
+ const out = [];
2648
+ const def = issues.stringOf(next, "default", nextPath);
2649
+ if (def !== null && def !== "") out.push(def);
2650
+ if (Array.isArray(next.conditions)) {
2651
+ next.conditions.forEach((cond, i) => {
2652
+ if (!isRecord(cond)) return;
2653
+ const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
2654
+ if (goto !== null && goto !== "") out.push(goto);
2657
2655
  });
2658
2656
  }
2657
+ return out;
2658
+ }
2659
+ function stepLeavesCycle(steps, stepID, inCycle, issues) {
2660
+ const step = steps[stepID];
2661
+ if (!isRecord(step) || !isRecord(step.next)) return true;
2662
+ const next = step.next;
2663
+ if (next.parallel !== void 0 && next.parallel !== null) return true;
2664
+ const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
2665
+ if (def === null || def === "") return true;
2666
+ for (const target of cycleEdges(step, stepID, issues)) {
2667
+ if (isMarker(target) || !inCycle.has(target)) return true;
2668
+ }
2669
+ return false;
2670
+ }
2671
+ function cycleWithoutExit(steps, start, issues) {
2672
+ let index = 0;
2673
+ const indices = /* @__PURE__ */ new Map();
2674
+ const low = /* @__PURE__ */ new Map();
2675
+ const onStack = /* @__PURE__ */ new Set();
2676
+ const stack = [];
2677
+ let found = false;
2678
+ const strong = (id) => {
2679
+ indices.set(id, index);
2680
+ low.set(id, index);
2681
+ index++;
2682
+ stack.push(id);
2683
+ onStack.add(id);
2684
+ for (const target of cycleEdges(steps[id], id, issues)) {
2685
+ if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
2686
+ if (!indices.has(target)) {
2687
+ strong(target);
2688
+ low.set(id, Math.min(low.get(id), low.get(target)));
2689
+ } else if (onStack.has(target)) {
2690
+ low.set(id, Math.min(low.get(id), indices.get(target)));
2691
+ }
2692
+ }
2693
+ if (low.get(id) !== indices.get(id)) return;
2694
+ const component = [];
2695
+ for (; ; ) {
2696
+ const top = stack.pop();
2697
+ onStack.delete(top);
2698
+ component.push(top);
2699
+ if (top === id) break;
2700
+ }
2701
+ const inCycle = new Set(component);
2702
+ const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
2703
+ if (!cyclic) return;
2704
+ if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
2705
+ found = true;
2706
+ };
2707
+ if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
2708
+ return found;
2659
2709
  }
2660
2710
 
2661
2711
  // src/validators/nextLogic.ts