@vaudience/aigentflow-flow-validator 0.23.0 → 0.25.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
@@ -457,6 +457,7 @@ var aigentflow_spec_default = {
457
457
  "ai",
458
458
  "aiv",
459
459
  "async",
460
+ "atlas",
460
461
  "browser",
461
462
  "chart",
462
463
  "db",
@@ -986,6 +987,48 @@ var aigentflow_spec_default = {
986
987
  "utils/parse-date"
987
988
  ]
988
989
  },
990
+ atlasExecutor: {
991
+ scheme: "atlas",
992
+ providerAgent: "agent",
993
+ providerCapability: "capability",
994
+ agentResource: "run",
995
+ capabilityRefPattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$",
996
+ operationUnknown: {
997
+ code: "atlas_operation_unknown",
998
+ messageFormat: "step %s uses %s, which is not an Atlas operation. The providers are agent (resource run) and capability (resource: a capability reference).",
999
+ suggestion: "Use atlas://agent/run or atlas://capability/<capability-ref>."
1000
+ },
1001
+ agentMissing: {
1002
+ code: "atlas_agent_missing",
1003
+ messageFormat: "step %s runs an Atlas agent but query.agent is missing or empty.",
1004
+ suggestion: "Set query.agent to the agent to run, written out as a literal such as @acme/reporter."
1005
+ },
1006
+ agentNotLiteral: {
1007
+ code: "atlas_agent_not_literal",
1008
+ messageFormat: "step %s computes query.agent from a template. The agent must be a fixed literal, never derived from input.",
1009
+ suggestion: "Write the agent out as a literal such as @acme/reporter. Data may go into the prompt or the inputs instead."
1010
+ },
1011
+ capabilitiesNotLiteral: {
1012
+ code: "atlas_capabilities_not_literal",
1013
+ messageFormat: "step %s computes an entry of query.capabilities from a template. Capabilities must be fixed literals, never derived from input.",
1014
+ suggestion: "Write each capability out as a literal. Data may go into the prompt or the inputs instead."
1015
+ },
1016
+ promptMissing: {
1017
+ code: "atlas_prompt_missing",
1018
+ messageFormat: "step %s runs an Atlas agent with neither query.prompt nor query.prompt_template.",
1019
+ suggestion: "Set exactly one of query.prompt or query.prompt_template."
1020
+ },
1021
+ promptBoth: {
1022
+ code: "atlas_prompt_both",
1023
+ messageFormat: "step %s sets both query.prompt and query.prompt_template.",
1024
+ suggestion: "Keep exactly one of query.prompt or query.prompt_template."
1025
+ },
1026
+ capabilityRefInvalid: {
1027
+ code: "atlas_capability_ref_invalid",
1028
+ messageFormat: "step %s uses atlas://capability/%s, which is not a valid capability reference.",
1029
+ suggestion: "A capability reference starts with a letter or digit and continues with letters, digits and _ . : - up to 128 characters in all."
1030
+ }
1031
+ },
989
1032
  flowDataReservedKeys: {
990
1033
  code: "flow_data_key_reserved",
991
1034
  fieldFormat: "data.%s",
@@ -2028,6 +2071,21 @@ var FUNCTION_OPERATIONS = {
2028
2071
  operations: new Set(aigentflow_spec_default.functionOperations.operations),
2029
2072
  sorted: aigentflow_spec_default.functionOperations.operations
2030
2073
  };
2074
+ var atlas = aigentflow_spec_default.atlasExecutor;
2075
+ var ATLAS_EXECUTOR = {
2076
+ scheme: atlas.scheme,
2077
+ providerAgent: atlas.providerAgent,
2078
+ providerCapability: atlas.providerCapability,
2079
+ agentResource: atlas.agentResource,
2080
+ capabilityRefPattern: new RegExp(atlas.capabilityRefPattern),
2081
+ operationUnknown: atlas.operationUnknown,
2082
+ agentMissing: atlas.agentMissing,
2083
+ agentNotLiteral: atlas.agentNotLiteral,
2084
+ capabilitiesNotLiteral: atlas.capabilitiesNotLiteral,
2085
+ promptMissing: atlas.promptMissing,
2086
+ promptBoth: atlas.promptBoth,
2087
+ capabilityRefInvalid: atlas.capabilityRefInvalid
2088
+ };
2031
2089
 
2032
2090
  // src/validators/executors.ts
2033
2091
  var SCHEME_SEPARATOR = "://";
@@ -2091,6 +2149,51 @@ function checkExecutor(executor, field, stepID, issues) {
2091
2149
  }
2092
2150
  checkFunctionOperation(executor, field, stepID, templated, issues);
2093
2151
  }
2152
+ function checkAtlasStep(executor, query, stepField, stepID, issues) {
2153
+ if (executor.includes(TEMPLATE_ACTION_OPEN)) return;
2154
+ const m = EXECUTOR_URL_PATTERN.exec(executor);
2155
+ if (m === null || m[1] !== ATLAS_EXECUTOR.scheme) return;
2156
+ const provider = m[2];
2157
+ const resource = m[3];
2158
+ const quoted = JSON.stringify(stepID);
2159
+ const report = (rule, ...args) => {
2160
+ let message = rule.messageFormat.replace("%s", () => quoted);
2161
+ for (const a of args) message = message.replace("%s", () => a);
2162
+ issues.error({
2163
+ field: stepField,
2164
+ message,
2165
+ code: rule.code,
2166
+ stepId: stepID,
2167
+ suggestion: rule.suggestion
2168
+ });
2169
+ };
2170
+ const q2 = isRecord(query) ? query : {};
2171
+ if (provider === ATLAS_EXECUTOR.providerCapability) {
2172
+ if (!ATLAS_EXECUTOR.capabilityRefPattern.test(resource)) {
2173
+ report(ATLAS_EXECUTOR.capabilityRefInvalid, resource);
2174
+ }
2175
+ } else if (provider === ATLAS_EXECUTOR.providerAgent && resource === ATLAS_EXECUTOR.agentResource) {
2176
+ const agent = q2.agent;
2177
+ if (typeof agent !== "string" || agent === "") {
2178
+ report(ATLAS_EXECUTOR.agentMissing);
2179
+ } else if (agent.includes(TEMPLATE_ACTION_OPEN)) {
2180
+ report(ATLAS_EXECUTOR.agentNotLiteral);
2181
+ }
2182
+ const prompt = q2.prompt;
2183
+ const promptTemplate = q2.prompt_template;
2184
+ const hasPrompt = prompt !== void 0 && prompt !== null && prompt !== "";
2185
+ const hasTemplate = promptTemplate !== void 0 && promptTemplate !== null && promptTemplate !== "";
2186
+ if (!hasPrompt && !hasTemplate) report(ATLAS_EXECUTOR.promptMissing);
2187
+ else if (hasPrompt && hasTemplate) report(ATLAS_EXECUTOR.promptBoth);
2188
+ } else {
2189
+ report(ATLAS_EXECUTOR.operationUnknown, executor);
2190
+ return;
2191
+ }
2192
+ const caps = q2.capabilities;
2193
+ if (Array.isArray(caps) && caps.some((c) => typeof c === "string" && c.includes(TEMPLATE_ACTION_OPEN))) {
2194
+ report(ATLAS_EXECUTOR.capabilitiesNotLiteral);
2195
+ }
2196
+ }
2094
2197
  function checkFunctionOperation(executor, field, stepID, templated, issues) {
2095
2198
  if (templated) return;
2096
2199
  const m = EXECUTOR_URL_PATTERN.exec(executor);
@@ -2115,6 +2218,7 @@ function validateExecutors(flow, issues) {
2115
2218
  const executor = issues.stringOf(step, "executor", `steps.${stepID}`);
2116
2219
  if (executor !== null && executor !== "") {
2117
2220
  checkExecutor(executor, `steps.${stepID}.executor`, stepID, issues);
2221
+ checkAtlasStep(executor, step.query, `steps.${stepID}`, stepID, issues);
2118
2222
  }
2119
2223
  const loop = step.loop;
2120
2224
  if (isRecord(loop) && Array.isArray(loop.steps)) {
@@ -2131,6 +2235,7 @@ function validateExecutors(flow, issues) {
2131
2235
  `${stepID}.${subID}`,
2132
2236
  issues
2133
2237
  );
2238
+ checkAtlasStep(subExecutor, subStep.query, subPath, `${stepID}.${subID}`, issues);
2134
2239
  }
2135
2240
  });
2136
2241
  }
@@ -2631,31 +2736,81 @@ function validateConnectivity(flow, issues) {
2631
2736
  });
2632
2737
  }
2633
2738
  }
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)) {
2739
+ if (cycleWithoutExit(steps, start, issues)) {
2652
2740
  issues.warn({
2653
2741
  field: "steps",
2654
- message: "Potential infinite loop detected in step flow",
2742
+ message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
2655
2743
  code: "potential_infinite_loop",
2656
- suggestion: "Review your step transitions to ensure they eventually terminate"
2744
+ suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
2745
+ });
2746
+ }
2747
+ }
2748
+ function cycleEdges(step, stepID, issues) {
2749
+ if (!isRecord(step) || !isRecord(step.next)) return [];
2750
+ const next = step.next;
2751
+ const nextPath = `steps.${stepID}.next`;
2752
+ const out = [];
2753
+ const def = issues.stringOf(next, "default", nextPath);
2754
+ if (def !== null && def !== "") out.push(def);
2755
+ if (Array.isArray(next.conditions)) {
2756
+ next.conditions.forEach((cond, i) => {
2757
+ if (!isRecord(cond)) return;
2758
+ const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
2759
+ if (goto !== null && goto !== "") out.push(goto);
2657
2760
  });
2658
2761
  }
2762
+ return out;
2763
+ }
2764
+ function stepLeavesCycle(steps, stepID, inCycle, issues) {
2765
+ const step = steps[stepID];
2766
+ if (!isRecord(step) || !isRecord(step.next)) return true;
2767
+ const next = step.next;
2768
+ if (next.parallel !== void 0 && next.parallel !== null) return true;
2769
+ const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
2770
+ if (def === null || def === "") return true;
2771
+ for (const target of cycleEdges(step, stepID, issues)) {
2772
+ if (isMarker(target) || !inCycle.has(target)) return true;
2773
+ }
2774
+ return false;
2775
+ }
2776
+ function cycleWithoutExit(steps, start, issues) {
2777
+ let index = 0;
2778
+ const indices = /* @__PURE__ */ new Map();
2779
+ const low = /* @__PURE__ */ new Map();
2780
+ const onStack = /* @__PURE__ */ new Set();
2781
+ const stack = [];
2782
+ let found = false;
2783
+ const strong = (id) => {
2784
+ indices.set(id, index);
2785
+ low.set(id, index);
2786
+ index++;
2787
+ stack.push(id);
2788
+ onStack.add(id);
2789
+ for (const target of cycleEdges(steps[id], id, issues)) {
2790
+ if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
2791
+ if (!indices.has(target)) {
2792
+ strong(target);
2793
+ low.set(id, Math.min(low.get(id), low.get(target)));
2794
+ } else if (onStack.has(target)) {
2795
+ low.set(id, Math.min(low.get(id), indices.get(target)));
2796
+ }
2797
+ }
2798
+ if (low.get(id) !== indices.get(id)) return;
2799
+ const component = [];
2800
+ for (; ; ) {
2801
+ const top = stack.pop();
2802
+ onStack.delete(top);
2803
+ component.push(top);
2804
+ if (top === id) break;
2805
+ }
2806
+ const inCycle = new Set(component);
2807
+ const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
2808
+ if (!cyclic) return;
2809
+ if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
2810
+ found = true;
2811
+ };
2812
+ if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
2813
+ return found;
2659
2814
  }
2660
2815
 
2661
2816
  // src/validators/nextLogic.ts