@vaudience/aigentflow-flow-validator 0.22.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/PARITY.md CHANGED
@@ -4,7 +4,47 @@ This document maps every rule in this JavaScript validator back to the AIgentFlo
4
4
  Go reference implementation, records the intentional divergences, and defines the
5
5
  discipline for keeping the two in sync.
6
6
 
7
- **Tracks AIgentFlow flow schema: `v2.828.0`** (`SPEC_VERSION` in [`src/spec/aigentflow-spec.json`](./src/spec/aigentflow-spec.json)), which adds the rule `flow_data_key_reserved` (package 0.22.0, below). Earlier: the `here` executor scheme, the `strict_templates` flow key and the rule `template_default_piped` (package 0.21.0). Earlier: the warning `compliance_residency_unenforced` (package 0.20.0). `orchestrator.region` / `orchestrator.enforce_region` (package 0.19.0). Earlier: the AigentChat executor was removed (package 0.18.0). Earlier additions: the `examples:` block (package 0.17.0), `server_owned_query_key` (package 0.15.1) from the AIgentFlow release after v2.790.0, `credential_endpoint_unpaired` (package 0.15.2) from AIgentFlow v2.793.0 and the release after it, and `display_name_too_long` (package 0.15.3) from AIgentFlow v2.802.0.
7
+ **Tracks AIgentFlow flow schema: `v2.831.0`** (`SPEC_VERSION` in [`src/spec/aigentflow-spec.json`](./src/spec/aigentflow-spec.json)), which adds the rule `unknown_function_operation` and a clearer `unknown_executor_scheme` message (package 0.23.0, below). Earlier: the rule `flow_data_key_reserved` (package 0.22.0). Earlier: the `here` executor scheme, the `strict_templates` flow key and the rule `template_default_piped` (package 0.21.0). Earlier: the warning `compliance_residency_unenforced` (package 0.20.0). `orchestrator.region` / `orchestrator.enforce_region` (package 0.19.0). Earlier: the AigentChat executor was removed (package 0.18.0). Earlier additions: the `examples:` block (package 0.17.0), `server_owned_query_key` (package 0.15.1) from the AIgentFlow release after v2.790.0, `credential_endpoint_unpaired` (package 0.15.2) from AIgentFlow v2.793.0 and the release after it, and `display_name_too_long` (package 0.15.3) from AIgentFlow v2.802.0.
8
+
9
+ > package 0.24.0 — **`potential_infinite_loop` fires only on a cycle with no way out**, in step
10
+ > with the reference. It used to fire on every cycle reachable from the start step, so a deliberate
11
+ > ask-again loop (a step that routes back until a condition sends it onward) carried a warning that
12
+ > told the author nothing. The reference now computes the strongly connected sets (Tarjan) over
13
+ > `next.default` and `next.conditions[].goto` edges reachable from start (markers `null` /
14
+ > `orchestrator` and unknown steps are not followed; `end` is an ordinary step) and warns once when
15
+ > a cyclic set (more than one step, or a self-loop) has no exit. A member leaves the set when it has
16
+ > no `next`, its `next` has `parallel` (a fan-out is treated as an exit, not guessed at), its
17
+ > `next.default` is empty (when no condition matches the flow ends), or any default or condition
18
+ > target is `null`, `orchestrator` or a step outside the set. Code and severity are unchanged; the
19
+ > message now ends "a cycle of steps has no way out" and the suggestion asks for a `next.conditions`
20
+ > branch that leaves the cycle. It cannot prove an exit ever fires; that is undecidable from the
21
+ > YAML. Module: `validators/connectivity.ts`. Gates: ten `valid-cycle-*` / `warn-cycle-*` fixtures
22
+ > (the valid ones use `forbidWarningCodes`, the only way to assert a false-positive risk), shared
23
+ > byte for byte with the Go port. On the 227 bundled example flows both ports report 0 findings.
24
+
25
+ > package 0.23.0 — **`unknown_function_operation`**, in step with the reference (AIgentFlow
26
+ > v2.831.0; `specVersion` 2.831.0). One new **error**: a step or loop sub-step whose executor
27
+ > is `function://<a>/<b>` where `<a>/<b>` is not one of the 32 registered built-ins. Before
28
+ > this, `function://utils/noop` validated and then failed every run with "function
29
+ > 'utils/noop' not found".
30
+ >
31
+ > | Rule (reference) | Code | Severity | Module |
32
+ > | -------------------------------------------- | ---------------------------- | -------- | ---------------------------------------------------- |
33
+ > | `validateFunctionOperationKnown` (parser.go) | `unknown_function_operation` | error | `validators/executors.ts`, spec `functionOperations` |
34
+ >
35
+ > - **Operation** = authority + `/` + path of the executor URL (so `binary-processor/validate-and-store`).
36
+ > The set is closed and sorted in spec `functionOperations.operations`. Templated executors
37
+ > (containing `{{`), unparsable URLs (`invalid_executor_url` owns them) and other schemes are
38
+ > skipped; the scheme match is exact and case-sensitive.
39
+ > - **Finding.** Field `steps.<id>.executor` (loop sub-step: `steps.<id>.loop.steps[i].executor`,
40
+ > step id `<id>.<sub>`); message `step "<id>" uses function://<op>, which is not a built-in
41
+ function. Available: <sorted list, ", ">`; the suggestion carries the list.
42
+ > - **Divergence 23** (below): the reference tolerates this code on its load door.
43
+ > - **`unknown_executor_scheme` text (still a warning).** For `decide` the message appends
44
+ > `Decision models run through ai://{provider}/decide (for example ai://openai/decide), not
45
+ through a scheme of their own.`; for any other unknown scheme with a known scheme within
46
+ > edit distance 2 (case-insensitive; the closest, ties to the alphabetically first) it
47
+ > appends `Did you mean '<scheme>'?`. No verdict changes.
8
48
 
9
49
  > package 0.22.0 — **`flow_data_key_reserved`**, in step with the reference (AIgentFlow
10
50
  > v2.828.0; `specVersion` 2.828.0). One new **error**: a key of the top-level `data:` map
@@ -1110,6 +1150,7 @@ Comparison contract: **error `code` + `valid` verdict**, not message wording. Th
1110
1150
  | `tool_discovery` vocabulary, mock scenario `delay`, empty `output:` entry | `validateToolDiscoveryVocabulary`, `validateMockScenarioDelays`, `validateOutputParameters` (parser.go) | `validators/saveDoor.ts` | `validate.test.ts`, `conformance.test.ts` |
1111
1151
  | `executor_config` `${NAME}` references outside the key's scope (`executor_config_env_scope`) | `ValidateExecutorConfigEnvScopes` (parser.go) | `validators/executorConfigEnv.ts` | `v0150-save-door.test.ts`, `conformance.test.ts` |
1112
1152
  | A step or loop sub-step `query:` declaring a server-owned key (`server_owned_query_key`) | `validateNoServerOwnedStepQueryKeys` (validation.go), `IsServerOwnedParamKey` | `validators/serverOwnedQueryKeys.ts` | `v0151-save-door.test.ts`, `conformance.test.ts` |
1153
+ | A `function://` operation outside the built-in set (`unknown_function_operation`) | `validateFunctionOperationKnown` (parser.go) | `validators/executors.ts` | `v0230-function-operation.test.ts`, `conformance.test.ts` |
1113
1154
  | A top-level `data:` key naming engine state (`flow_data_key_reserved`) | `FlowDataKeyReservation`, `validateFlowDataKeysAreNotReserved` (validation.flowdata.go) | `validators/flowDataKeys.ts` | `v0220-flow-data-keys.test.ts`, `conformance.test.ts` |
1114
1155
  | An endpoint a server-supplied credential will never be sent to (`credential_endpoint_unpaired`, warning) | `validateCredentialEndpointPairing`, `validateFamilyCredentialEndpointPairing` (validation.go) | `validators/credentialEndpoint.ts`, `validators/goUrl.ts` | `v0152-credential-endpoint.test.ts`, `conformance.test.ts` |
1115
1156
  | The `examples:` block: 58 codes, one walker (ids, inputs, references, expectations, checkpoints, variants) | the examples walker (`ValidateExamples`) | `validators/examples.ts`, `validators/goUrl.ts` | `examples.test.ts`, `conformance.test.ts` |
@@ -1380,6 +1421,11 @@ number their shared divergences alike._
1380
1421
  - The recursion bound of 32 on a nested value is the reference's own and is not in the
1381
1422
  spec.
1382
1423
 
1424
+ 23. **`unknown_function_operation` is tolerated on the reference's load door (0.23.0).** The
1425
+ reference refuses an unknown `function://` operation when a flow is _saved_ but loads
1426
+ (and then fails at run time) a stored flow that already carries one. This validator
1427
+ answers "would this save", so it is an error here, as for every other save-door rule.
1428
+
1383
1429
  ---
1384
1430
 
1385
1431
  ## The examples block
package/README.md CHANGED
@@ -116,7 +116,7 @@ Low-level: parse YAML into `{ flow?, parseErrors, parseWarnings }` without valid
116
116
 
117
117
  ### `SPEC_VERSION: string`
118
118
 
119
- The AIgentFlow flow-schema version this build tracks (e.g. `"2.828.0"`).
119
+ The AIgentFlow flow-schema version this build tracks (e.g. `"2.831.0"`).
120
120
 
121
121
  ### Options
122
122
 
package/dist/cli.js CHANGED
@@ -438,7 +438,7 @@ function validateBasicStructure(flow, issues) {
438
438
  message: "Executor is required for each step",
439
439
  code: "missing_required_field",
440
440
  stepId: stepID,
441
- suggestion: "Specify an executor URL (e.g., 'function://demo/processor')"
441
+ suggestion: "Specify an executor URL (e.g., 'function://utils/log')"
442
442
  });
443
443
  } else if (executorText === null) {
444
444
  issues.error({
@@ -455,7 +455,7 @@ function validateBasicStructure(flow, issues) {
455
455
  // src/spec/aigentflow-spec.json
456
456
  var aigentflow_spec_default = {
457
457
  $comment: "Vendored enum surface mirrored from the AIgentFlow Go reference implementation. Regenerate / review on every flow-YAML schema change. See PARITY.md. Values here are public protocol/type names only.",
458
- specVersion: "2.828.0",
458
+ specVersion: "2.831.0",
459
459
  inputSchemaVersion: 1,
460
460
  executorSchemes: [
461
461
  "a2a",
@@ -965,6 +965,47 @@ var aigentflow_spec_default = {
965
965
  stepFieldFormat: "steps.%s.query.%s",
966
966
  loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
967
967
  },
968
+ functionOperations: {
969
+ $comment: `unknown_function_operation (ERROR): a step or loop sub-step whose executor is function://<authority>/<path> where authority+"/"+path is not in operations. Templated executors (containing templateActionOpen), unparsable URLs (invalid_executor_url owns them) and other schemes are skipped; the scheme match is exact and case-sensitive, as in the reference. The message is messageFormat with the stepId, the operation, then operations joined by ', '. Reference: validateFunctionOperationKnown (parser.go), functionSchemaOperations.`,
970
+ code: "unknown_function_operation",
971
+ scheme: "function",
972
+ messageFormat: "step %s uses function://%s, which is not a built-in function. Available: %s",
973
+ suggestionFormat: "Use one of the built-in function operations: %s. For custom logic use a script://, http:// or ai:// executor instead.",
974
+ operations: [
975
+ "binary-processor/validate-and-store",
976
+ "data/create",
977
+ "data/from-json",
978
+ "data/to-json",
979
+ "data/validate",
980
+ "demo/adult-processor",
981
+ "demo/calculator",
982
+ "demo/data-processor",
983
+ "demo/finalizer",
984
+ "demo/senior-processor",
985
+ "demo/youth-processor",
986
+ "document/finalize",
987
+ "document/prepare",
988
+ "json/validate",
989
+ "math/basic",
990
+ "math/calculate",
991
+ "math/sum",
992
+ "results-compiler/aggregate",
993
+ "system/info",
994
+ "text/analysis",
995
+ "text/count-characters",
996
+ "text/count-lines",
997
+ "text/count-occurrences",
998
+ "text/count-words",
999
+ "text/find-pattern",
1000
+ "text/generate",
1001
+ "text/replace-text",
1002
+ "text/search",
1003
+ "utils/datetime",
1004
+ "utils/format-date",
1005
+ "utils/log",
1006
+ "utils/parse-date"
1007
+ ]
1008
+ },
968
1009
  flowDataReservedKeys: {
969
1010
  $comment: "flow_data_key_reserved (ERROR, no loop demotion): a key of the top-level data: map that names engine state. data: is seeded into the mission's .data namespace, where the engine keeps its own bookkeeping, so a flow declaring such a key would forge it. Matching is exact and case-sensitive. A key is reserved when it is one of exact (each with its reason), or starts with one of prefixes (checked in order, first wins; each with its reason). The finding's field is fieldFormat with the key; the message is messageFormat with the key (JSON-quoted, as Go's %q) then the reason; the suggestion is verbatim. Findings are sorted by key. Reference: FlowDataKeyReservation, validateFlowDataKeysAreNotReserved (aigentflow#247 review).",
970
1011
  code: "flow_data_key_reserved",
@@ -2080,9 +2121,51 @@ var TEMPLATE_DEFAULT_PIPED = {
2080
2121
  messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
2081
2122
  suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
2082
2123
  };
2124
+ var FUNCTION_OPERATIONS = {
2125
+ code: aigentflow_spec_default.functionOperations.code,
2126
+ scheme: aigentflow_spec_default.functionOperations.scheme,
2127
+ messageFormat: aigentflow_spec_default.functionOperations.messageFormat,
2128
+ suggestionFormat: aigentflow_spec_default.functionOperations.suggestionFormat,
2129
+ operations: new Set(aigentflow_spec_default.functionOperations.operations),
2130
+ sorted: aigentflow_spec_default.functionOperations.operations
2131
+ };
2083
2132
 
2084
2133
  // src/validators/executors.ts
2085
2134
  var SCHEME_SEPARATOR = "://";
2135
+ var UNKNOWN_SCHEME_BASE = (scheme) => `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`;
2136
+ var DECIDE_SCHEME = "decide";
2137
+ var DECIDE_HINT = "Decision models run through ai://{provider}/decide (for example ai://openai/decide), not through a scheme of their own.";
2138
+ var MAX_SCHEME_EDIT_DISTANCE = 2;
2139
+ function editDistance(a, b) {
2140
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
2141
+ for (let i = 1; i <= a.length; i++) {
2142
+ const cur = [i];
2143
+ for (let j = 1; j <= b.length; j++) {
2144
+ cur[j] = Math.min(
2145
+ prev[j] + 1,
2146
+ cur[j - 1] + 1,
2147
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
2148
+ );
2149
+ }
2150
+ prev = cur;
2151
+ }
2152
+ return prev[b.length];
2153
+ }
2154
+ function unknownSchemeMessage(scheme) {
2155
+ const lower = scheme.toLowerCase();
2156
+ const base = UNKNOWN_SCHEME_BASE(scheme);
2157
+ if (lower === DECIDE_SCHEME) return `${base} ${DECIDE_HINT}`;
2158
+ let best;
2159
+ let bestDist = MAX_SCHEME_EDIT_DISTANCE + 1;
2160
+ for (const known of [...EXECUTOR_SCHEMES].sort()) {
2161
+ const d = editDistance(lower, known);
2162
+ if (d < bestDist) {
2163
+ best = known;
2164
+ bestDist = d;
2165
+ }
2166
+ }
2167
+ return best === void 0 ? base : `${base} Did you mean '${best}'?`;
2168
+ }
2086
2169
  function checkExecutor(executor, field, stepID, issues) {
2087
2170
  const sepIdx = executor.indexOf(SCHEME_SEPARATOR);
2088
2171
  const scheme = sepIdx > 0 ? executor.slice(0, sepIdx) : "";
@@ -2101,11 +2184,28 @@ function checkExecutor(executor, field, stepID, issues) {
2101
2184
  if (!EXECUTOR_SCHEMES.has(scheme.toLowerCase())) {
2102
2185
  issues.warn({
2103
2186
  field,
2104
- message: `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`,
2187
+ message: unknownSchemeMessage(scheme),
2105
2188
  code: "unknown_executor_scheme",
2106
2189
  stepId: stepID
2107
2190
  });
2191
+ return;
2108
2192
  }
2193
+ checkFunctionOperation(executor, field, stepID, templated, issues);
2194
+ }
2195
+ function checkFunctionOperation(executor, field, stepID, templated, issues) {
2196
+ if (templated) return;
2197
+ const m = EXECUTOR_URL_PATTERN.exec(executor);
2198
+ if (m === null || m[1] !== FUNCTION_OPERATIONS.scheme) return;
2199
+ const op = `${m[2]}/${m[3]}`;
2200
+ if (FUNCTION_OPERATIONS.operations.has(op)) return;
2201
+ const list = FUNCTION_OPERATIONS.sorted.join(", ");
2202
+ issues.error({
2203
+ field,
2204
+ message: FUNCTION_OPERATIONS.messageFormat.replace("%s", () => JSON.stringify(stepID)).replace("%s", () => op).replace("%s", () => list),
2205
+ code: FUNCTION_OPERATIONS.code,
2206
+ stepId: stepID,
2207
+ suggestion: FUNCTION_OPERATIONS.suggestionFormat.replace("%s", () => list)
2208
+ });
2109
2209
  }
2110
2210
  function validateExecutors(flow, issues) {
2111
2211
  const steps = flow.steps;
@@ -2632,32 +2732,82 @@ function validateConnectivity(flow, issues) {
2632
2732
  });
2633
2733
  }
2634
2734
  }
2635
- const visited = /* @__PURE__ */ new Set();
2636
- const recStack = /* @__PURE__ */ new Set();
2637
- const hasCycle = (stepID) => {
2638
- if (recStack.has(stepID)) return true;
2639
- if (visited.has(stepID)) return false;
2640
- visited.add(stepID);
2641
- recStack.add(stepID);
2642
- const step = steps[stepID];
2643
- if (isRecord(step) && isRecord(step.next)) {
2644
- const next = step.next;
2645
- for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
2646
- if (hasCycle(target)) return true;
2647
- }
2648
- }
2649
- recStack.delete(stepID);
2650
- return false;
2651
- };
2652
- if (hasCycle(start)) {
2735
+ if (cycleWithoutExit(steps, start, issues)) {
2653
2736
  issues.warn({
2654
2737
  field: "steps",
2655
- message: "Potential infinite loop detected in step flow",
2738
+ message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
2656
2739
  code: "potential_infinite_loop",
2657
- suggestion: "Review your step transitions to ensure they eventually terminate"
2740
+ suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
2658
2741
  });
2659
2742
  }
2660
2743
  }
2744
+ function cycleEdges(step, stepID, issues) {
2745
+ if (!isRecord(step) || !isRecord(step.next)) return [];
2746
+ const next = step.next;
2747
+ const nextPath = `steps.${stepID}.next`;
2748
+ const out = [];
2749
+ const def = issues.stringOf(next, "default", nextPath);
2750
+ if (def !== null && def !== "") out.push(def);
2751
+ if (Array.isArray(next.conditions)) {
2752
+ next.conditions.forEach((cond, i) => {
2753
+ if (!isRecord(cond)) return;
2754
+ const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
2755
+ if (goto !== null && goto !== "") out.push(goto);
2756
+ });
2757
+ }
2758
+ return out;
2759
+ }
2760
+ function stepLeavesCycle(steps, stepID, inCycle, issues) {
2761
+ const step = steps[stepID];
2762
+ if (!isRecord(step) || !isRecord(step.next)) return true;
2763
+ const next = step.next;
2764
+ if (next.parallel !== void 0 && next.parallel !== null) return true;
2765
+ const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
2766
+ if (def === null || def === "") return true;
2767
+ for (const target of cycleEdges(step, stepID, issues)) {
2768
+ if (isMarker(target) || !inCycle.has(target)) return true;
2769
+ }
2770
+ return false;
2771
+ }
2772
+ function cycleWithoutExit(steps, start, issues) {
2773
+ let index = 0;
2774
+ const indices = /* @__PURE__ */ new Map();
2775
+ const low = /* @__PURE__ */ new Map();
2776
+ const onStack = /* @__PURE__ */ new Set();
2777
+ const stack = [];
2778
+ let found = false;
2779
+ const strong = (id) => {
2780
+ indices.set(id, index);
2781
+ low.set(id, index);
2782
+ index++;
2783
+ stack.push(id);
2784
+ onStack.add(id);
2785
+ for (const target of cycleEdges(steps[id], id, issues)) {
2786
+ if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
2787
+ if (!indices.has(target)) {
2788
+ strong(target);
2789
+ low.set(id, Math.min(low.get(id), low.get(target)));
2790
+ } else if (onStack.has(target)) {
2791
+ low.set(id, Math.min(low.get(id), indices.get(target)));
2792
+ }
2793
+ }
2794
+ if (low.get(id) !== indices.get(id)) return;
2795
+ const component = [];
2796
+ for (; ; ) {
2797
+ const top = stack.pop();
2798
+ onStack.delete(top);
2799
+ component.push(top);
2800
+ if (top === id) break;
2801
+ }
2802
+ const inCycle = new Set(component);
2803
+ const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
2804
+ if (!cyclic) return;
2805
+ if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
2806
+ found = true;
2807
+ };
2808
+ if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
2809
+ return found;
2810
+ }
2661
2811
 
2662
2812
  // src/validators/nextLogic.ts
2663
2813
  var NEXT_NULL = "null";
package/dist/index.cjs CHANGED
@@ -435,7 +435,7 @@ function validateBasicStructure(flow, issues) {
435
435
  message: "Executor is required for each step",
436
436
  code: "missing_required_field",
437
437
  stepId: stepID,
438
- suggestion: "Specify an executor URL (e.g., 'function://demo/processor')"
438
+ suggestion: "Specify an executor URL (e.g., 'function://utils/log')"
439
439
  });
440
440
  } else if (executorText === null) {
441
441
  issues.error({
@@ -451,7 +451,7 @@ function validateBasicStructure(flow, issues) {
451
451
 
452
452
  // src/spec/aigentflow-spec.json
453
453
  var aigentflow_spec_default = {
454
- specVersion: "2.828.0",
454
+ specVersion: "2.831.0",
455
455
  inputSchemaVersion: 1,
456
456
  executorSchemes: [
457
457
  "a2a",
@@ -948,6 +948,46 @@ var aigentflow_spec_default = {
948
948
  stepFieldFormat: "steps.%s.query.%s",
949
949
  loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
950
950
  },
951
+ functionOperations: {
952
+ code: "unknown_function_operation",
953
+ scheme: "function",
954
+ messageFormat: "step %s uses function://%s, which is not a built-in function. Available: %s",
955
+ suggestionFormat: "Use one of the built-in function operations: %s. For custom logic use a script://, http:// or ai:// executor instead.",
956
+ operations: [
957
+ "binary-processor/validate-and-store",
958
+ "data/create",
959
+ "data/from-json",
960
+ "data/to-json",
961
+ "data/validate",
962
+ "demo/adult-processor",
963
+ "demo/calculator",
964
+ "demo/data-processor",
965
+ "demo/finalizer",
966
+ "demo/senior-processor",
967
+ "demo/youth-processor",
968
+ "document/finalize",
969
+ "document/prepare",
970
+ "json/validate",
971
+ "math/basic",
972
+ "math/calculate",
973
+ "math/sum",
974
+ "results-compiler/aggregate",
975
+ "system/info",
976
+ "text/analysis",
977
+ "text/count-characters",
978
+ "text/count-lines",
979
+ "text/count-occurrences",
980
+ "text/count-words",
981
+ "text/find-pattern",
982
+ "text/generate",
983
+ "text/replace-text",
984
+ "text/search",
985
+ "utils/datetime",
986
+ "utils/format-date",
987
+ "utils/log",
988
+ "utils/parse-date"
989
+ ]
990
+ },
951
991
  flowDataReservedKeys: {
952
992
  code: "flow_data_key_reserved",
953
993
  fieldFormat: "data.%s",
@@ -1982,9 +2022,51 @@ var TEMPLATE_DEFAULT_PIPED = {
1982
2022
  messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
1983
2023
  suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
1984
2024
  };
2025
+ var FUNCTION_OPERATIONS = {
2026
+ code: aigentflow_spec_default.functionOperations.code,
2027
+ scheme: aigentflow_spec_default.functionOperations.scheme,
2028
+ messageFormat: aigentflow_spec_default.functionOperations.messageFormat,
2029
+ suggestionFormat: aigentflow_spec_default.functionOperations.suggestionFormat,
2030
+ operations: new Set(aigentflow_spec_default.functionOperations.operations),
2031
+ sorted: aigentflow_spec_default.functionOperations.operations
2032
+ };
1985
2033
 
1986
2034
  // src/validators/executors.ts
1987
2035
  var SCHEME_SEPARATOR = "://";
2036
+ var UNKNOWN_SCHEME_BASE = (scheme) => `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`;
2037
+ var DECIDE_SCHEME = "decide";
2038
+ var DECIDE_HINT = "Decision models run through ai://{provider}/decide (for example ai://openai/decide), not through a scheme of their own.";
2039
+ var MAX_SCHEME_EDIT_DISTANCE = 2;
2040
+ function editDistance(a, b) {
2041
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
2042
+ for (let i = 1; i <= a.length; i++) {
2043
+ const cur = [i];
2044
+ for (let j = 1; j <= b.length; j++) {
2045
+ cur[j] = Math.min(
2046
+ prev[j] + 1,
2047
+ cur[j - 1] + 1,
2048
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
2049
+ );
2050
+ }
2051
+ prev = cur;
2052
+ }
2053
+ return prev[b.length];
2054
+ }
2055
+ function unknownSchemeMessage(scheme) {
2056
+ const lower = scheme.toLowerCase();
2057
+ const base = UNKNOWN_SCHEME_BASE(scheme);
2058
+ if (lower === DECIDE_SCHEME) return `${base} ${DECIDE_HINT}`;
2059
+ let best;
2060
+ let bestDist = MAX_SCHEME_EDIT_DISTANCE + 1;
2061
+ for (const known of [...EXECUTOR_SCHEMES].sort()) {
2062
+ const d = editDistance(lower, known);
2063
+ if (d < bestDist) {
2064
+ best = known;
2065
+ bestDist = d;
2066
+ }
2067
+ }
2068
+ return best === void 0 ? base : `${base} Did you mean '${best}'?`;
2069
+ }
1988
2070
  function checkExecutor(executor, field, stepID, issues) {
1989
2071
  const sepIdx = executor.indexOf(SCHEME_SEPARATOR);
1990
2072
  const scheme = sepIdx > 0 ? executor.slice(0, sepIdx) : "";
@@ -2003,11 +2085,28 @@ function checkExecutor(executor, field, stepID, issues) {
2003
2085
  if (!EXECUTOR_SCHEMES.has(scheme.toLowerCase())) {
2004
2086
  issues.warn({
2005
2087
  field,
2006
- message: `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`,
2088
+ message: unknownSchemeMessage(scheme),
2007
2089
  code: "unknown_executor_scheme",
2008
2090
  stepId: stepID
2009
2091
  });
2092
+ return;
2010
2093
  }
2094
+ checkFunctionOperation(executor, field, stepID, templated, issues);
2095
+ }
2096
+ function checkFunctionOperation(executor, field, stepID, templated, issues) {
2097
+ if (templated) return;
2098
+ const m = EXECUTOR_URL_PATTERN.exec(executor);
2099
+ if (m === null || m[1] !== FUNCTION_OPERATIONS.scheme) return;
2100
+ const op = `${m[2]}/${m[3]}`;
2101
+ if (FUNCTION_OPERATIONS.operations.has(op)) return;
2102
+ const list = FUNCTION_OPERATIONS.sorted.join(", ");
2103
+ issues.error({
2104
+ field,
2105
+ message: FUNCTION_OPERATIONS.messageFormat.replace("%s", () => JSON.stringify(stepID)).replace("%s", () => op).replace("%s", () => list),
2106
+ code: FUNCTION_OPERATIONS.code,
2107
+ stepId: stepID,
2108
+ suggestion: FUNCTION_OPERATIONS.suggestionFormat.replace("%s", () => list)
2109
+ });
2011
2110
  }
2012
2111
  function validateExecutors(flow, issues) {
2013
2112
  const steps = flow.steps;
@@ -2534,32 +2633,82 @@ function validateConnectivity(flow, issues) {
2534
2633
  });
2535
2634
  }
2536
2635
  }
2537
- const visited = /* @__PURE__ */ new Set();
2538
- const recStack = /* @__PURE__ */ new Set();
2539
- const hasCycle = (stepID) => {
2540
- if (recStack.has(stepID)) return true;
2541
- if (visited.has(stepID)) return false;
2542
- visited.add(stepID);
2543
- recStack.add(stepID);
2544
- const step = steps[stepID];
2545
- if (isRecord(step) && isRecord(step.next)) {
2546
- const next = step.next;
2547
- for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
2548
- if (hasCycle(target)) return true;
2549
- }
2550
- }
2551
- recStack.delete(stepID);
2552
- return false;
2553
- };
2554
- if (hasCycle(start)) {
2636
+ if (cycleWithoutExit(steps, start, issues)) {
2555
2637
  issues.warn({
2556
2638
  field: "steps",
2557
- message: "Potential infinite loop detected in step flow",
2639
+ message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
2558
2640
  code: "potential_infinite_loop",
2559
- suggestion: "Review your step transitions to ensure they eventually terminate"
2641
+ suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
2560
2642
  });
2561
2643
  }
2562
2644
  }
2645
+ function cycleEdges(step, stepID, issues) {
2646
+ if (!isRecord(step) || !isRecord(step.next)) return [];
2647
+ const next = step.next;
2648
+ const nextPath = `steps.${stepID}.next`;
2649
+ const out = [];
2650
+ const def = issues.stringOf(next, "default", nextPath);
2651
+ if (def !== null && def !== "") out.push(def);
2652
+ if (Array.isArray(next.conditions)) {
2653
+ next.conditions.forEach((cond, i) => {
2654
+ if (!isRecord(cond)) return;
2655
+ const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
2656
+ if (goto !== null && goto !== "") out.push(goto);
2657
+ });
2658
+ }
2659
+ return out;
2660
+ }
2661
+ function stepLeavesCycle(steps, stepID, inCycle, issues) {
2662
+ const step = steps[stepID];
2663
+ if (!isRecord(step) || !isRecord(step.next)) return true;
2664
+ const next = step.next;
2665
+ if (next.parallel !== void 0 && next.parallel !== null) return true;
2666
+ const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
2667
+ if (def === null || def === "") return true;
2668
+ for (const target of cycleEdges(step, stepID, issues)) {
2669
+ if (isMarker(target) || !inCycle.has(target)) return true;
2670
+ }
2671
+ return false;
2672
+ }
2673
+ function cycleWithoutExit(steps, start, issues) {
2674
+ let index = 0;
2675
+ const indices = /* @__PURE__ */ new Map();
2676
+ const low = /* @__PURE__ */ new Map();
2677
+ const onStack = /* @__PURE__ */ new Set();
2678
+ const stack = [];
2679
+ let found = false;
2680
+ const strong = (id) => {
2681
+ indices.set(id, index);
2682
+ low.set(id, index);
2683
+ index++;
2684
+ stack.push(id);
2685
+ onStack.add(id);
2686
+ for (const target of cycleEdges(steps[id], id, issues)) {
2687
+ if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
2688
+ if (!indices.has(target)) {
2689
+ strong(target);
2690
+ low.set(id, Math.min(low.get(id), low.get(target)));
2691
+ } else if (onStack.has(target)) {
2692
+ low.set(id, Math.min(low.get(id), indices.get(target)));
2693
+ }
2694
+ }
2695
+ if (low.get(id) !== indices.get(id)) return;
2696
+ const component = [];
2697
+ for (; ; ) {
2698
+ const top = stack.pop();
2699
+ onStack.delete(top);
2700
+ component.push(top);
2701
+ if (top === id) break;
2702
+ }
2703
+ const inCycle = new Set(component);
2704
+ const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
2705
+ if (!cyclic) return;
2706
+ if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
2707
+ found = true;
2708
+ };
2709
+ if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
2710
+ return found;
2711
+ }
2563
2712
 
2564
2713
  // src/validators/nextLogic.ts
2565
2714
  var NEXT_NULL = "null";