@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/PARITY.md +39 -0
- package/dist/cli.js +176 -20
- package/dist/index.cjs +175 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +175 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/PARITY.md
CHANGED
|
@@ -6,6 +6,45 @@ discipline for keeping the two in sync.
|
|
|
6
6
|
|
|
7
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
8
|
|
|
9
|
+
> package 0.25.0 — **the `atlas://` executor scheme and seven rules**. `atlas` becomes a known
|
|
10
|
+
> scheme (no `unknown_executor_scheme` warning). The URL form is `atlas://<provider>/<resource>`:
|
|
11
|
+
> provider `agent` has the single resource `run`, provider `capability` takes a capability reference
|
|
12
|
+
> as its resource. Seven new **errors**, all with the step as `field` (a loop sub-step reports its
|
|
13
|
+
> sub-step path and the composite `parent.child` step id):
|
|
14
|
+
> `atlas_operation_unknown` (any other provider, or `agent` with a resource other than `run`);
|
|
15
|
+
> `atlas_agent_missing` (an agent run whose `query.agent` is absent, empty or not a string);
|
|
16
|
+
> `atlas_agent_not_literal` (`query.agent` contains `{{`: the agent is always written out, never
|
|
17
|
+
> computed from input); `atlas_capabilities_not_literal` (a string entry of `query.capabilities`
|
|
18
|
+
> contains `{{`); `atlas_prompt_missing` and `atlas_prompt_both` (an agent run takes exactly one of
|
|
19
|
+
> `query.prompt` and `query.prompt_template`; an empty string counts as absent);
|
|
20
|
+
> `atlas_capability_ref_invalid` (the reference fails `^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$`).
|
|
21
|
+
> Templates in `prompt`, in `inputs` values and in a capability's `args` are fine. A templated
|
|
22
|
+
> executor is skipped, an unparsable URL stays `invalid_executor_url`'s, and the scheme match is
|
|
23
|
+
> exact (`ATLAS://` is a known scheme but gets none of these rules). The shape check runs first, so
|
|
24
|
+
> a reference containing a character outside letters, digits, `_`, `-` and `/` (for example a dot
|
|
25
|
+
> or colon) is reported as `invalid_executor_url` and never reaches the reference pattern.
|
|
26
|
+
> `specVersion` is unchanged (`2.831.0`). Module: `validators/executors.ts`; spec block
|
|
27
|
+
> `atlasExecutor`. Gates: thirteen `invalid-atlas-*` / `valid-atlas-*` fixtures (the valid ones
|
|
28
|
+
> forbid every atlas code and `unknown_executor_scheme`), shared byte for byte with the Go port, and
|
|
29
|
+
> `test/v0250-atlas-executor.test.ts`. On the 227 bundled example flows both ports report no new
|
|
30
|
+
> findings.
|
|
31
|
+
|
|
32
|
+
> package 0.24.0 — **`potential_infinite_loop` fires only on a cycle with no way out**, in step
|
|
33
|
+
> with the reference. It used to fire on every cycle reachable from the start step, so a deliberate
|
|
34
|
+
> ask-again loop (a step that routes back until a condition sends it onward) carried a warning that
|
|
35
|
+
> told the author nothing. The reference now computes the strongly connected sets (Tarjan) over
|
|
36
|
+
> `next.default` and `next.conditions[].goto` edges reachable from start (markers `null` /
|
|
37
|
+
> `orchestrator` and unknown steps are not followed; `end` is an ordinary step) and warns once when
|
|
38
|
+
> a cyclic set (more than one step, or a self-loop) has no exit. A member leaves the set when it has
|
|
39
|
+
> no `next`, its `next` has `parallel` (a fan-out is treated as an exit, not guessed at), its
|
|
40
|
+
> `next.default` is empty (when no condition matches the flow ends), or any default or condition
|
|
41
|
+
> target is `null`, `orchestrator` or a step outside the set. Code and severity are unchanged; the
|
|
42
|
+
> message now ends "a cycle of steps has no way out" and the suggestion asks for a `next.conditions`
|
|
43
|
+
> branch that leaves the cycle. It cannot prove an exit ever fires; that is undecidable from the
|
|
44
|
+
> YAML. Module: `validators/connectivity.ts`. Gates: ten `valid-cycle-*` / `warn-cycle-*` fixtures
|
|
45
|
+
> (the valid ones use `forbidWarningCodes`, the only way to assert a false-positive risk), shared
|
|
46
|
+
> byte for byte with the Go port. On the 227 bundled example flows both ports report 0 findings.
|
|
47
|
+
|
|
9
48
|
> package 0.23.0 — **`unknown_function_operation`**, in step with the reference (AIgentFlow
|
|
10
49
|
> v2.831.0; `specVersion` 2.831.0). One new **error**: a step or loop sub-step whose executor
|
|
11
50
|
> is `function://<a>/<b>` where `<a>/<b>` is not one of the 32 registered built-ins. Before
|
package/dist/cli.js
CHANGED
|
@@ -463,6 +463,7 @@ var aigentflow_spec_default = {
|
|
|
463
463
|
"ai",
|
|
464
464
|
"aiv",
|
|
465
465
|
"async",
|
|
466
|
+
"atlas",
|
|
466
467
|
"browser",
|
|
467
468
|
"chart",
|
|
468
469
|
"db",
|
|
@@ -1006,6 +1007,49 @@ var aigentflow_spec_default = {
|
|
|
1006
1007
|
"utils/parse-date"
|
|
1007
1008
|
]
|
|
1008
1009
|
},
|
|
1010
|
+
atlasExecutor: {
|
|
1011
|
+
$comment: "atlas://<provider>/<resource>: calls into the Atlas platform. Providers are agent (the one resource is run) and capability (the resource is a capability reference). All findings are ERRORS, field = the step (loop sub-steps use the composite step id and the sub-step path). Templated executors, unparsable URLs and other schemes are skipped. A literal agent and literal capabilities keep a step from being steered by data: query.agent and every string in query.capabilities may not contain templateActionOpen, while prompt, inputs and capability args may. A message format takes the quoted step id first.",
|
|
1012
|
+
scheme: "atlas",
|
|
1013
|
+
providerAgent: "agent",
|
|
1014
|
+
providerCapability: "capability",
|
|
1015
|
+
agentResource: "run",
|
|
1016
|
+
capabilityRefPattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$",
|
|
1017
|
+
operationUnknown: {
|
|
1018
|
+
code: "atlas_operation_unknown",
|
|
1019
|
+
messageFormat: "step %s uses %s, which is not an Atlas operation. The providers are agent (resource run) and capability (resource: a capability reference).",
|
|
1020
|
+
suggestion: "Use atlas://agent/run or atlas://capability/<capability-ref>."
|
|
1021
|
+
},
|
|
1022
|
+
agentMissing: {
|
|
1023
|
+
code: "atlas_agent_missing",
|
|
1024
|
+
messageFormat: "step %s runs an Atlas agent but query.agent is missing or empty.",
|
|
1025
|
+
suggestion: "Set query.agent to the agent to run, written out as a literal such as @acme/reporter."
|
|
1026
|
+
},
|
|
1027
|
+
agentNotLiteral: {
|
|
1028
|
+
code: "atlas_agent_not_literal",
|
|
1029
|
+
messageFormat: "step %s computes query.agent from a template. The agent must be a fixed literal, never derived from input.",
|
|
1030
|
+
suggestion: "Write the agent out as a literal such as @acme/reporter. Data may go into the prompt or the inputs instead."
|
|
1031
|
+
},
|
|
1032
|
+
capabilitiesNotLiteral: {
|
|
1033
|
+
code: "atlas_capabilities_not_literal",
|
|
1034
|
+
messageFormat: "step %s computes an entry of query.capabilities from a template. Capabilities must be fixed literals, never derived from input.",
|
|
1035
|
+
suggestion: "Write each capability out as a literal. Data may go into the prompt or the inputs instead."
|
|
1036
|
+
},
|
|
1037
|
+
promptMissing: {
|
|
1038
|
+
code: "atlas_prompt_missing",
|
|
1039
|
+
messageFormat: "step %s runs an Atlas agent with neither query.prompt nor query.prompt_template.",
|
|
1040
|
+
suggestion: "Set exactly one of query.prompt or query.prompt_template."
|
|
1041
|
+
},
|
|
1042
|
+
promptBoth: {
|
|
1043
|
+
code: "atlas_prompt_both",
|
|
1044
|
+
messageFormat: "step %s sets both query.prompt and query.prompt_template.",
|
|
1045
|
+
suggestion: "Keep exactly one of query.prompt or query.prompt_template."
|
|
1046
|
+
},
|
|
1047
|
+
capabilityRefInvalid: {
|
|
1048
|
+
code: "atlas_capability_ref_invalid",
|
|
1049
|
+
messageFormat: "step %s uses atlas://capability/%s, which is not a valid capability reference.",
|
|
1050
|
+
suggestion: "A capability reference starts with a letter or digit and continues with letters, digits and _ . : - up to 128 characters in all."
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1009
1053
|
flowDataReservedKeys: {
|
|
1010
1054
|
$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).",
|
|
1011
1055
|
code: "flow_data_key_reserved",
|
|
@@ -2129,6 +2173,21 @@ var FUNCTION_OPERATIONS = {
|
|
|
2129
2173
|
operations: new Set(aigentflow_spec_default.functionOperations.operations),
|
|
2130
2174
|
sorted: aigentflow_spec_default.functionOperations.operations
|
|
2131
2175
|
};
|
|
2176
|
+
var atlas = aigentflow_spec_default.atlasExecutor;
|
|
2177
|
+
var ATLAS_EXECUTOR = {
|
|
2178
|
+
scheme: atlas.scheme,
|
|
2179
|
+
providerAgent: atlas.providerAgent,
|
|
2180
|
+
providerCapability: atlas.providerCapability,
|
|
2181
|
+
agentResource: atlas.agentResource,
|
|
2182
|
+
capabilityRefPattern: new RegExp(atlas.capabilityRefPattern),
|
|
2183
|
+
operationUnknown: atlas.operationUnknown,
|
|
2184
|
+
agentMissing: atlas.agentMissing,
|
|
2185
|
+
agentNotLiteral: atlas.agentNotLiteral,
|
|
2186
|
+
capabilitiesNotLiteral: atlas.capabilitiesNotLiteral,
|
|
2187
|
+
promptMissing: atlas.promptMissing,
|
|
2188
|
+
promptBoth: atlas.promptBoth,
|
|
2189
|
+
capabilityRefInvalid: atlas.capabilityRefInvalid
|
|
2190
|
+
};
|
|
2132
2191
|
|
|
2133
2192
|
// src/validators/executors.ts
|
|
2134
2193
|
var SCHEME_SEPARATOR = "://";
|
|
@@ -2192,6 +2251,51 @@ function checkExecutor(executor, field, stepID, issues) {
|
|
|
2192
2251
|
}
|
|
2193
2252
|
checkFunctionOperation(executor, field, stepID, templated, issues);
|
|
2194
2253
|
}
|
|
2254
|
+
function checkAtlasStep(executor, query, stepField, stepID, issues) {
|
|
2255
|
+
if (executor.includes(TEMPLATE_ACTION_OPEN)) return;
|
|
2256
|
+
const m = EXECUTOR_URL_PATTERN.exec(executor);
|
|
2257
|
+
if (m === null || m[1] !== ATLAS_EXECUTOR.scheme) return;
|
|
2258
|
+
const provider = m[2];
|
|
2259
|
+
const resource = m[3];
|
|
2260
|
+
const quoted = JSON.stringify(stepID);
|
|
2261
|
+
const report = (rule, ...args) => {
|
|
2262
|
+
let message = rule.messageFormat.replace("%s", () => quoted);
|
|
2263
|
+
for (const a of args) message = message.replace("%s", () => a);
|
|
2264
|
+
issues.error({
|
|
2265
|
+
field: stepField,
|
|
2266
|
+
message,
|
|
2267
|
+
code: rule.code,
|
|
2268
|
+
stepId: stepID,
|
|
2269
|
+
suggestion: rule.suggestion
|
|
2270
|
+
});
|
|
2271
|
+
};
|
|
2272
|
+
const q2 = isRecord(query) ? query : {};
|
|
2273
|
+
if (provider === ATLAS_EXECUTOR.providerCapability) {
|
|
2274
|
+
if (!ATLAS_EXECUTOR.capabilityRefPattern.test(resource)) {
|
|
2275
|
+
report(ATLAS_EXECUTOR.capabilityRefInvalid, resource);
|
|
2276
|
+
}
|
|
2277
|
+
} else if (provider === ATLAS_EXECUTOR.providerAgent && resource === ATLAS_EXECUTOR.agentResource) {
|
|
2278
|
+
const agent = q2.agent;
|
|
2279
|
+
if (typeof agent !== "string" || agent === "") {
|
|
2280
|
+
report(ATLAS_EXECUTOR.agentMissing);
|
|
2281
|
+
} else if (agent.includes(TEMPLATE_ACTION_OPEN)) {
|
|
2282
|
+
report(ATLAS_EXECUTOR.agentNotLiteral);
|
|
2283
|
+
}
|
|
2284
|
+
const prompt = q2.prompt;
|
|
2285
|
+
const promptTemplate = q2.prompt_template;
|
|
2286
|
+
const hasPrompt = prompt !== void 0 && prompt !== null && prompt !== "";
|
|
2287
|
+
const hasTemplate = promptTemplate !== void 0 && promptTemplate !== null && promptTemplate !== "";
|
|
2288
|
+
if (!hasPrompt && !hasTemplate) report(ATLAS_EXECUTOR.promptMissing);
|
|
2289
|
+
else if (hasPrompt && hasTemplate) report(ATLAS_EXECUTOR.promptBoth);
|
|
2290
|
+
} else {
|
|
2291
|
+
report(ATLAS_EXECUTOR.operationUnknown, executor);
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
const caps = q2.capabilities;
|
|
2295
|
+
if (Array.isArray(caps) && caps.some((c) => typeof c === "string" && c.includes(TEMPLATE_ACTION_OPEN))) {
|
|
2296
|
+
report(ATLAS_EXECUTOR.capabilitiesNotLiteral);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2195
2299
|
function checkFunctionOperation(executor, field, stepID, templated, issues) {
|
|
2196
2300
|
if (templated) return;
|
|
2197
2301
|
const m = EXECUTOR_URL_PATTERN.exec(executor);
|
|
@@ -2216,6 +2320,7 @@ function validateExecutors(flow, issues) {
|
|
|
2216
2320
|
const executor = issues.stringOf(step, "executor", `steps.${stepID}`);
|
|
2217
2321
|
if (executor !== null && executor !== "") {
|
|
2218
2322
|
checkExecutor(executor, `steps.${stepID}.executor`, stepID, issues);
|
|
2323
|
+
checkAtlasStep(executor, step.query, `steps.${stepID}`, stepID, issues);
|
|
2219
2324
|
}
|
|
2220
2325
|
const loop = step.loop;
|
|
2221
2326
|
if (isRecord(loop) && Array.isArray(loop.steps)) {
|
|
@@ -2232,6 +2337,7 @@ function validateExecutors(flow, issues) {
|
|
|
2232
2337
|
`${stepID}.${subID}`,
|
|
2233
2338
|
issues
|
|
2234
2339
|
);
|
|
2340
|
+
checkAtlasStep(subExecutor, subStep.query, subPath, `${stepID}.${subID}`, issues);
|
|
2235
2341
|
}
|
|
2236
2342
|
});
|
|
2237
2343
|
}
|
|
@@ -2732,31 +2838,81 @@ function validateConnectivity(flow, issues) {
|
|
|
2732
2838
|
});
|
|
2733
2839
|
}
|
|
2734
2840
|
}
|
|
2735
|
-
|
|
2736
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
2737
|
-
const hasCycle = (stepID) => {
|
|
2738
|
-
if (recStack.has(stepID)) return true;
|
|
2739
|
-
if (visited.has(stepID)) return false;
|
|
2740
|
-
visited.add(stepID);
|
|
2741
|
-
recStack.add(stepID);
|
|
2742
|
-
const step = steps[stepID];
|
|
2743
|
-
if (isRecord(step) && isRecord(step.next)) {
|
|
2744
|
-
const next = step.next;
|
|
2745
|
-
for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
|
|
2746
|
-
if (hasCycle(target)) return true;
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
recStack.delete(stepID);
|
|
2750
|
-
return false;
|
|
2751
|
-
};
|
|
2752
|
-
if (hasCycle(start)) {
|
|
2841
|
+
if (cycleWithoutExit(steps, start, issues)) {
|
|
2753
2842
|
issues.warn({
|
|
2754
2843
|
field: "steps",
|
|
2755
|
-
message: "Potential infinite loop detected in step flow",
|
|
2844
|
+
message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
|
|
2756
2845
|
code: "potential_infinite_loop",
|
|
2757
|
-
suggestion: "
|
|
2846
|
+
suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
function cycleEdges(step, stepID, issues) {
|
|
2851
|
+
if (!isRecord(step) || !isRecord(step.next)) return [];
|
|
2852
|
+
const next = step.next;
|
|
2853
|
+
const nextPath = `steps.${stepID}.next`;
|
|
2854
|
+
const out = [];
|
|
2855
|
+
const def = issues.stringOf(next, "default", nextPath);
|
|
2856
|
+
if (def !== null && def !== "") out.push(def);
|
|
2857
|
+
if (Array.isArray(next.conditions)) {
|
|
2858
|
+
next.conditions.forEach((cond, i) => {
|
|
2859
|
+
if (!isRecord(cond)) return;
|
|
2860
|
+
const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
|
|
2861
|
+
if (goto !== null && goto !== "") out.push(goto);
|
|
2758
2862
|
});
|
|
2759
2863
|
}
|
|
2864
|
+
return out;
|
|
2865
|
+
}
|
|
2866
|
+
function stepLeavesCycle(steps, stepID, inCycle, issues) {
|
|
2867
|
+
const step = steps[stepID];
|
|
2868
|
+
if (!isRecord(step) || !isRecord(step.next)) return true;
|
|
2869
|
+
const next = step.next;
|
|
2870
|
+
if (next.parallel !== void 0 && next.parallel !== null) return true;
|
|
2871
|
+
const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
|
|
2872
|
+
if (def === null || def === "") return true;
|
|
2873
|
+
for (const target of cycleEdges(step, stepID, issues)) {
|
|
2874
|
+
if (isMarker(target) || !inCycle.has(target)) return true;
|
|
2875
|
+
}
|
|
2876
|
+
return false;
|
|
2877
|
+
}
|
|
2878
|
+
function cycleWithoutExit(steps, start, issues) {
|
|
2879
|
+
let index = 0;
|
|
2880
|
+
const indices = /* @__PURE__ */ new Map();
|
|
2881
|
+
const low = /* @__PURE__ */ new Map();
|
|
2882
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
2883
|
+
const stack = [];
|
|
2884
|
+
let found = false;
|
|
2885
|
+
const strong = (id) => {
|
|
2886
|
+
indices.set(id, index);
|
|
2887
|
+
low.set(id, index);
|
|
2888
|
+
index++;
|
|
2889
|
+
stack.push(id);
|
|
2890
|
+
onStack.add(id);
|
|
2891
|
+
for (const target of cycleEdges(steps[id], id, issues)) {
|
|
2892
|
+
if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
|
|
2893
|
+
if (!indices.has(target)) {
|
|
2894
|
+
strong(target);
|
|
2895
|
+
low.set(id, Math.min(low.get(id), low.get(target)));
|
|
2896
|
+
} else if (onStack.has(target)) {
|
|
2897
|
+
low.set(id, Math.min(low.get(id), indices.get(target)));
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
if (low.get(id) !== indices.get(id)) return;
|
|
2901
|
+
const component = [];
|
|
2902
|
+
for (; ; ) {
|
|
2903
|
+
const top = stack.pop();
|
|
2904
|
+
onStack.delete(top);
|
|
2905
|
+
component.push(top);
|
|
2906
|
+
if (top === id) break;
|
|
2907
|
+
}
|
|
2908
|
+
const inCycle = new Set(component);
|
|
2909
|
+
const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
|
|
2910
|
+
if (!cyclic) return;
|
|
2911
|
+
if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
|
|
2912
|
+
found = true;
|
|
2913
|
+
};
|
|
2914
|
+
if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
|
|
2915
|
+
return found;
|
|
2760
2916
|
}
|
|
2761
2917
|
|
|
2762
2918
|
// src/validators/nextLogic.ts
|
package/dist/index.cjs
CHANGED
|
@@ -459,6 +459,7 @@ var aigentflow_spec_default = {
|
|
|
459
459
|
"ai",
|
|
460
460
|
"aiv",
|
|
461
461
|
"async",
|
|
462
|
+
"atlas",
|
|
462
463
|
"browser",
|
|
463
464
|
"chart",
|
|
464
465
|
"db",
|
|
@@ -988,6 +989,48 @@ var aigentflow_spec_default = {
|
|
|
988
989
|
"utils/parse-date"
|
|
989
990
|
]
|
|
990
991
|
},
|
|
992
|
+
atlasExecutor: {
|
|
993
|
+
scheme: "atlas",
|
|
994
|
+
providerAgent: "agent",
|
|
995
|
+
providerCapability: "capability",
|
|
996
|
+
agentResource: "run",
|
|
997
|
+
capabilityRefPattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$",
|
|
998
|
+
operationUnknown: {
|
|
999
|
+
code: "atlas_operation_unknown",
|
|
1000
|
+
messageFormat: "step %s uses %s, which is not an Atlas operation. The providers are agent (resource run) and capability (resource: a capability reference).",
|
|
1001
|
+
suggestion: "Use atlas://agent/run or atlas://capability/<capability-ref>."
|
|
1002
|
+
},
|
|
1003
|
+
agentMissing: {
|
|
1004
|
+
code: "atlas_agent_missing",
|
|
1005
|
+
messageFormat: "step %s runs an Atlas agent but query.agent is missing or empty.",
|
|
1006
|
+
suggestion: "Set query.agent to the agent to run, written out as a literal such as @acme/reporter."
|
|
1007
|
+
},
|
|
1008
|
+
agentNotLiteral: {
|
|
1009
|
+
code: "atlas_agent_not_literal",
|
|
1010
|
+
messageFormat: "step %s computes query.agent from a template. The agent must be a fixed literal, never derived from input.",
|
|
1011
|
+
suggestion: "Write the agent out as a literal such as @acme/reporter. Data may go into the prompt or the inputs instead."
|
|
1012
|
+
},
|
|
1013
|
+
capabilitiesNotLiteral: {
|
|
1014
|
+
code: "atlas_capabilities_not_literal",
|
|
1015
|
+
messageFormat: "step %s computes an entry of query.capabilities from a template. Capabilities must be fixed literals, never derived from input.",
|
|
1016
|
+
suggestion: "Write each capability out as a literal. Data may go into the prompt or the inputs instead."
|
|
1017
|
+
},
|
|
1018
|
+
promptMissing: {
|
|
1019
|
+
code: "atlas_prompt_missing",
|
|
1020
|
+
messageFormat: "step %s runs an Atlas agent with neither query.prompt nor query.prompt_template.",
|
|
1021
|
+
suggestion: "Set exactly one of query.prompt or query.prompt_template."
|
|
1022
|
+
},
|
|
1023
|
+
promptBoth: {
|
|
1024
|
+
code: "atlas_prompt_both",
|
|
1025
|
+
messageFormat: "step %s sets both query.prompt and query.prompt_template.",
|
|
1026
|
+
suggestion: "Keep exactly one of query.prompt or query.prompt_template."
|
|
1027
|
+
},
|
|
1028
|
+
capabilityRefInvalid: {
|
|
1029
|
+
code: "atlas_capability_ref_invalid",
|
|
1030
|
+
messageFormat: "step %s uses atlas://capability/%s, which is not a valid capability reference.",
|
|
1031
|
+
suggestion: "A capability reference starts with a letter or digit and continues with letters, digits and _ . : - up to 128 characters in all."
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
991
1034
|
flowDataReservedKeys: {
|
|
992
1035
|
code: "flow_data_key_reserved",
|
|
993
1036
|
fieldFormat: "data.%s",
|
|
@@ -2030,6 +2073,21 @@ var FUNCTION_OPERATIONS = {
|
|
|
2030
2073
|
operations: new Set(aigentflow_spec_default.functionOperations.operations),
|
|
2031
2074
|
sorted: aigentflow_spec_default.functionOperations.operations
|
|
2032
2075
|
};
|
|
2076
|
+
var atlas = aigentflow_spec_default.atlasExecutor;
|
|
2077
|
+
var ATLAS_EXECUTOR = {
|
|
2078
|
+
scheme: atlas.scheme,
|
|
2079
|
+
providerAgent: atlas.providerAgent,
|
|
2080
|
+
providerCapability: atlas.providerCapability,
|
|
2081
|
+
agentResource: atlas.agentResource,
|
|
2082
|
+
capabilityRefPattern: new RegExp(atlas.capabilityRefPattern),
|
|
2083
|
+
operationUnknown: atlas.operationUnknown,
|
|
2084
|
+
agentMissing: atlas.agentMissing,
|
|
2085
|
+
agentNotLiteral: atlas.agentNotLiteral,
|
|
2086
|
+
capabilitiesNotLiteral: atlas.capabilitiesNotLiteral,
|
|
2087
|
+
promptMissing: atlas.promptMissing,
|
|
2088
|
+
promptBoth: atlas.promptBoth,
|
|
2089
|
+
capabilityRefInvalid: atlas.capabilityRefInvalid
|
|
2090
|
+
};
|
|
2033
2091
|
|
|
2034
2092
|
// src/validators/executors.ts
|
|
2035
2093
|
var SCHEME_SEPARATOR = "://";
|
|
@@ -2093,6 +2151,51 @@ function checkExecutor(executor, field, stepID, issues) {
|
|
|
2093
2151
|
}
|
|
2094
2152
|
checkFunctionOperation(executor, field, stepID, templated, issues);
|
|
2095
2153
|
}
|
|
2154
|
+
function checkAtlasStep(executor, query, stepField, stepID, issues) {
|
|
2155
|
+
if (executor.includes(TEMPLATE_ACTION_OPEN)) return;
|
|
2156
|
+
const m = EXECUTOR_URL_PATTERN.exec(executor);
|
|
2157
|
+
if (m === null || m[1] !== ATLAS_EXECUTOR.scheme) return;
|
|
2158
|
+
const provider = m[2];
|
|
2159
|
+
const resource = m[3];
|
|
2160
|
+
const quoted = JSON.stringify(stepID);
|
|
2161
|
+
const report = (rule, ...args) => {
|
|
2162
|
+
let message = rule.messageFormat.replace("%s", () => quoted);
|
|
2163
|
+
for (const a of args) message = message.replace("%s", () => a);
|
|
2164
|
+
issues.error({
|
|
2165
|
+
field: stepField,
|
|
2166
|
+
message,
|
|
2167
|
+
code: rule.code,
|
|
2168
|
+
stepId: stepID,
|
|
2169
|
+
suggestion: rule.suggestion
|
|
2170
|
+
});
|
|
2171
|
+
};
|
|
2172
|
+
const q2 = isRecord(query) ? query : {};
|
|
2173
|
+
if (provider === ATLAS_EXECUTOR.providerCapability) {
|
|
2174
|
+
if (!ATLAS_EXECUTOR.capabilityRefPattern.test(resource)) {
|
|
2175
|
+
report(ATLAS_EXECUTOR.capabilityRefInvalid, resource);
|
|
2176
|
+
}
|
|
2177
|
+
} else if (provider === ATLAS_EXECUTOR.providerAgent && resource === ATLAS_EXECUTOR.agentResource) {
|
|
2178
|
+
const agent = q2.agent;
|
|
2179
|
+
if (typeof agent !== "string" || agent === "") {
|
|
2180
|
+
report(ATLAS_EXECUTOR.agentMissing);
|
|
2181
|
+
} else if (agent.includes(TEMPLATE_ACTION_OPEN)) {
|
|
2182
|
+
report(ATLAS_EXECUTOR.agentNotLiteral);
|
|
2183
|
+
}
|
|
2184
|
+
const prompt = q2.prompt;
|
|
2185
|
+
const promptTemplate = q2.prompt_template;
|
|
2186
|
+
const hasPrompt = prompt !== void 0 && prompt !== null && prompt !== "";
|
|
2187
|
+
const hasTemplate = promptTemplate !== void 0 && promptTemplate !== null && promptTemplate !== "";
|
|
2188
|
+
if (!hasPrompt && !hasTemplate) report(ATLAS_EXECUTOR.promptMissing);
|
|
2189
|
+
else if (hasPrompt && hasTemplate) report(ATLAS_EXECUTOR.promptBoth);
|
|
2190
|
+
} else {
|
|
2191
|
+
report(ATLAS_EXECUTOR.operationUnknown, executor);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
const caps = q2.capabilities;
|
|
2195
|
+
if (Array.isArray(caps) && caps.some((c) => typeof c === "string" && c.includes(TEMPLATE_ACTION_OPEN))) {
|
|
2196
|
+
report(ATLAS_EXECUTOR.capabilitiesNotLiteral);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2096
2199
|
function checkFunctionOperation(executor, field, stepID, templated, issues) {
|
|
2097
2200
|
if (templated) return;
|
|
2098
2201
|
const m = EXECUTOR_URL_PATTERN.exec(executor);
|
|
@@ -2117,6 +2220,7 @@ function validateExecutors(flow, issues) {
|
|
|
2117
2220
|
const executor = issues.stringOf(step, "executor", `steps.${stepID}`);
|
|
2118
2221
|
if (executor !== null && executor !== "") {
|
|
2119
2222
|
checkExecutor(executor, `steps.${stepID}.executor`, stepID, issues);
|
|
2223
|
+
checkAtlasStep(executor, step.query, `steps.${stepID}`, stepID, issues);
|
|
2120
2224
|
}
|
|
2121
2225
|
const loop = step.loop;
|
|
2122
2226
|
if (isRecord(loop) && Array.isArray(loop.steps)) {
|
|
@@ -2133,6 +2237,7 @@ function validateExecutors(flow, issues) {
|
|
|
2133
2237
|
`${stepID}.${subID}`,
|
|
2134
2238
|
issues
|
|
2135
2239
|
);
|
|
2240
|
+
checkAtlasStep(subExecutor, subStep.query, subPath, `${stepID}.${subID}`, issues);
|
|
2136
2241
|
}
|
|
2137
2242
|
});
|
|
2138
2243
|
}
|
|
@@ -2633,31 +2738,81 @@ function validateConnectivity(flow, issues) {
|
|
|
2633
2738
|
});
|
|
2634
2739
|
}
|
|
2635
2740
|
}
|
|
2636
|
-
|
|
2637
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
2638
|
-
const hasCycle = (stepID) => {
|
|
2639
|
-
if (recStack.has(stepID)) return true;
|
|
2640
|
-
if (visited.has(stepID)) return false;
|
|
2641
|
-
visited.add(stepID);
|
|
2642
|
-
recStack.add(stepID);
|
|
2643
|
-
const step = steps[stepID];
|
|
2644
|
-
if (isRecord(step) && isRecord(step.next)) {
|
|
2645
|
-
const next = step.next;
|
|
2646
|
-
for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
|
|
2647
|
-
if (hasCycle(target)) return true;
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
recStack.delete(stepID);
|
|
2651
|
-
return false;
|
|
2652
|
-
};
|
|
2653
|
-
if (hasCycle(start)) {
|
|
2741
|
+
if (cycleWithoutExit(steps, start, issues)) {
|
|
2654
2742
|
issues.warn({
|
|
2655
2743
|
field: "steps",
|
|
2656
|
-
message: "Potential infinite loop detected in step flow",
|
|
2744
|
+
message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
|
|
2657
2745
|
code: "potential_infinite_loop",
|
|
2658
|
-
suggestion: "
|
|
2746
|
+
suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
function cycleEdges(step, stepID, issues) {
|
|
2751
|
+
if (!isRecord(step) || !isRecord(step.next)) return [];
|
|
2752
|
+
const next = step.next;
|
|
2753
|
+
const nextPath = `steps.${stepID}.next`;
|
|
2754
|
+
const out = [];
|
|
2755
|
+
const def = issues.stringOf(next, "default", nextPath);
|
|
2756
|
+
if (def !== null && def !== "") out.push(def);
|
|
2757
|
+
if (Array.isArray(next.conditions)) {
|
|
2758
|
+
next.conditions.forEach((cond, i) => {
|
|
2759
|
+
if (!isRecord(cond)) return;
|
|
2760
|
+
const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
|
|
2761
|
+
if (goto !== null && goto !== "") out.push(goto);
|
|
2659
2762
|
});
|
|
2660
2763
|
}
|
|
2764
|
+
return out;
|
|
2765
|
+
}
|
|
2766
|
+
function stepLeavesCycle(steps, stepID, inCycle, issues) {
|
|
2767
|
+
const step = steps[stepID];
|
|
2768
|
+
if (!isRecord(step) || !isRecord(step.next)) return true;
|
|
2769
|
+
const next = step.next;
|
|
2770
|
+
if (next.parallel !== void 0 && next.parallel !== null) return true;
|
|
2771
|
+
const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
|
|
2772
|
+
if (def === null || def === "") return true;
|
|
2773
|
+
for (const target of cycleEdges(step, stepID, issues)) {
|
|
2774
|
+
if (isMarker(target) || !inCycle.has(target)) return true;
|
|
2775
|
+
}
|
|
2776
|
+
return false;
|
|
2777
|
+
}
|
|
2778
|
+
function cycleWithoutExit(steps, start, issues) {
|
|
2779
|
+
let index = 0;
|
|
2780
|
+
const indices = /* @__PURE__ */ new Map();
|
|
2781
|
+
const low = /* @__PURE__ */ new Map();
|
|
2782
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
2783
|
+
const stack = [];
|
|
2784
|
+
let found = false;
|
|
2785
|
+
const strong = (id) => {
|
|
2786
|
+
indices.set(id, index);
|
|
2787
|
+
low.set(id, index);
|
|
2788
|
+
index++;
|
|
2789
|
+
stack.push(id);
|
|
2790
|
+
onStack.add(id);
|
|
2791
|
+
for (const target of cycleEdges(steps[id], id, issues)) {
|
|
2792
|
+
if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
|
|
2793
|
+
if (!indices.has(target)) {
|
|
2794
|
+
strong(target);
|
|
2795
|
+
low.set(id, Math.min(low.get(id), low.get(target)));
|
|
2796
|
+
} else if (onStack.has(target)) {
|
|
2797
|
+
low.set(id, Math.min(low.get(id), indices.get(target)));
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
if (low.get(id) !== indices.get(id)) return;
|
|
2801
|
+
const component = [];
|
|
2802
|
+
for (; ; ) {
|
|
2803
|
+
const top = stack.pop();
|
|
2804
|
+
onStack.delete(top);
|
|
2805
|
+
component.push(top);
|
|
2806
|
+
if (top === id) break;
|
|
2807
|
+
}
|
|
2808
|
+
const inCycle = new Set(component);
|
|
2809
|
+
const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
|
|
2810
|
+
if (!cyclic) return;
|
|
2811
|
+
if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
|
|
2812
|
+
found = true;
|
|
2813
|
+
};
|
|
2814
|
+
if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
|
|
2815
|
+
return found;
|
|
2661
2816
|
}
|
|
2662
2817
|
|
|
2663
2818
|
// src/validators/nextLogic.ts
|