@vaudience/aigentflow-flow-validator 0.26.0 → 0.27.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 +33 -4
- package/README.md +2 -1
- package/dist/cli.js +342 -255
- package/dist/index.cjs +337 -245
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +337 -245
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -451,11 +451,10 @@ function validateBasicStructure(flow, issues) {
|
|
|
451
451
|
|
|
452
452
|
// src/spec/aigentflow-spec.json
|
|
453
453
|
var aigentflow_spec_default = {
|
|
454
|
-
specVersion: "2.
|
|
454
|
+
specVersion: "2.833.0",
|
|
455
455
|
inputSchemaVersion: 1,
|
|
456
456
|
executorSchemes: [
|
|
457
457
|
"a2a",
|
|
458
|
-
"agent",
|
|
459
458
|
"ai",
|
|
460
459
|
"aiv",
|
|
461
460
|
"async",
|
|
@@ -469,7 +468,6 @@ var aigentflow_spec_default = {
|
|
|
469
468
|
"email",
|
|
470
469
|
"eurostat",
|
|
471
470
|
"eval",
|
|
472
|
-
"exons",
|
|
473
471
|
"flow",
|
|
474
472
|
"function",
|
|
475
473
|
"gdelt",
|
|
@@ -768,6 +766,32 @@ var aigentflow_spec_default = {
|
|
|
768
766
|
exons: {
|
|
769
767
|
frontmatterDelimiter: "---"
|
|
770
768
|
},
|
|
769
|
+
removedAgentSurfaces: {
|
|
770
|
+
removedSchemes: ["exons", "agent"],
|
|
771
|
+
paramScheme: "ai",
|
|
772
|
+
aiParams: [
|
|
773
|
+
"agentic",
|
|
774
|
+
"deny_tools",
|
|
775
|
+
"discoverable_tools",
|
|
776
|
+
"enable_signals",
|
|
777
|
+
"guardrails",
|
|
778
|
+
"max_tool_calls",
|
|
779
|
+
"max_tool_turns",
|
|
780
|
+
"mcp_server_url",
|
|
781
|
+
"parallel_tool_calls",
|
|
782
|
+
"require_confirmation",
|
|
783
|
+
"timeout_seconds",
|
|
784
|
+
"tool_auth_token",
|
|
785
|
+
"tool_discovery"
|
|
786
|
+
],
|
|
787
|
+
flowKeys: ["exons_uploads"],
|
|
788
|
+
executorRemovedCode: "executor_removed",
|
|
789
|
+
agenticParamRemovedCode: "agentic_param_removed",
|
|
790
|
+
schemeMessageFormat: "step %q uses %s://, which AIgentFlow no longer runs: its own agent chains were removed",
|
|
791
|
+
paramsMessageFormat: "step %q sets %s on ai://: AIgentFlow's own agent loop (agentic mode) was removed, so these parameters no longer exist",
|
|
792
|
+
flowKeyMessageFormat: "the flow-level key %q belonged to a removed agent surface and is no longer read",
|
|
793
|
+
suggestion: 'Rebuild the step: a tool-using agent becomes atlas://agent/run (agent: "@org/name", prompt, capabilities); one tool becomes atlas://capability/<ref>; a plain prompt becomes ai://<provider>/chat or a prompt-only aiv://<org>/<name>. A flow that cannot be converted (started by a webhook, a schedule, a share link, a campaign or a flow:// sub-flow, none of which has a person for atlas to act for; or one that needs an inline .exons spec, an arbitrary MCP server URL or signals) has no replacement here: start it from a person, or use a routine in atlas'
|
|
794
|
+
},
|
|
771
795
|
executorConfigEnvScopes: {
|
|
772
796
|
referencePrefix: "${",
|
|
773
797
|
referenceSuffix: "}",
|
|
@@ -2020,6 +2044,19 @@ var EXONS = {
|
|
|
2020
2044
|
/** The YAML frontmatter delimiter line. */
|
|
2021
2045
|
frontmatterDelimiter: aigentflow_spec_default.exons.frontmatterDelimiter
|
|
2022
2046
|
};
|
|
2047
|
+
var removed = aigentflow_spec_default.removedAgentSurfaces;
|
|
2048
|
+
var REMOVED_AGENT_SURFACES = {
|
|
2049
|
+
schemes: new Set(removed.removedSchemes),
|
|
2050
|
+
paramScheme: removed.paramScheme,
|
|
2051
|
+
aiParams: new Set(removed.aiParams),
|
|
2052
|
+
flowKeys: removed.flowKeys,
|
|
2053
|
+
executorRemovedCode: removed.executorRemovedCode,
|
|
2054
|
+
agenticParamRemovedCode: removed.agenticParamRemovedCode,
|
|
2055
|
+
schemeMessageFormat: removed.schemeMessageFormat,
|
|
2056
|
+
paramsMessageFormat: removed.paramsMessageFormat,
|
|
2057
|
+
flowKeyMessageFormat: removed.flowKeyMessageFormat,
|
|
2058
|
+
suggestion: removed.suggestion
|
|
2059
|
+
};
|
|
2023
2060
|
var EXECUTOR_CONFIG_ENV_SCOPES = {
|
|
2024
2061
|
referencePrefix: aigentflow_spec_default.executorConfigEnvScopes.referencePrefix,
|
|
2025
2062
|
referenceSuffix: aigentflow_spec_default.executorConfigEnvScopes.referenceSuffix,
|
|
@@ -2141,6 +2178,227 @@ var RESERVED_INPUT_NAMES = {
|
|
|
2141
2178
|
queryFieldFormat: aigentflow_spec_default.reservedInputNames.queryFieldFormat,
|
|
2142
2179
|
schemaFieldFormat: aigentflow_spec_default.reservedInputNames.schemaFieldFormat
|
|
2143
2180
|
};
|
|
2181
|
+
var CODE_PARSE_FAILED = "orchestrator_exons_parse_failed";
|
|
2182
|
+
var CODE_NO_PROVIDER = "orchestrator_exons_no_provider";
|
|
2183
|
+
var CODE_RESOURCES = "exons_resources_unhonoured";
|
|
2184
|
+
var CODE_TOOL_WITHHELD = "orchestrator_tool_withheld";
|
|
2185
|
+
var EXONS_OPEN_DELIM = "{~";
|
|
2186
|
+
var EXONS_LEGACY_CONFIG_OPEN = "{~exons.config~}";
|
|
2187
|
+
var UTF8_BOM = "\uFEFF";
|
|
2188
|
+
var MAX_ALIAS_COUNT2 = 100;
|
|
2189
|
+
var UNREAD = {
|
|
2190
|
+
parseError: null,
|
|
2191
|
+
specRead: false,
|
|
2192
|
+
hasSpec: false,
|
|
2193
|
+
provider: "",
|
|
2194
|
+
toolsAllowDeclared: false,
|
|
2195
|
+
toolsAllow: [],
|
|
2196
|
+
resources: []
|
|
2197
|
+
};
|
|
2198
|
+
var GO_SPACE = "\\t\\n\\v\\f\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000";
|
|
2199
|
+
var GO_TRIM_SPACE = new RegExp(`^[${GO_SPACE}]+|[${GO_SPACE}]+$`, "g");
|
|
2200
|
+
function goTrimSpace(s) {
|
|
2201
|
+
return s.replace(GO_TRIM_SPACE, "");
|
|
2202
|
+
}
|
|
2203
|
+
function extractExonsFrontmatter(source) {
|
|
2204
|
+
const delim = EXONS.frontmatterDelimiter;
|
|
2205
|
+
let start = source.startsWith(UTF8_BOM) ? source.slice(UTF8_BOM.length) : source;
|
|
2206
|
+
start = start.replace(/^[ \t]+/, "");
|
|
2207
|
+
if (start.startsWith(EXONS_LEGACY_CONFIG_OPEN)) {
|
|
2208
|
+
return {
|
|
2209
|
+
frontmatter: "",
|
|
2210
|
+
hasFrontmatter: false,
|
|
2211
|
+
parseError: "legacy JSON config block detected; use YAML frontmatter"
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
if (!start.startsWith(delim)) return { frontmatter: "", hasFrontmatter: false, parseError: null };
|
|
2215
|
+
let rest = start.slice(delim.length);
|
|
2216
|
+
if (rest.startsWith("\n")) rest = rest.slice(1);
|
|
2217
|
+
else if (rest.startsWith("\r\n")) rest = rest.slice(2);
|
|
2218
|
+
else return { frontmatter: "", hasFrontmatter: false, parseError: null };
|
|
2219
|
+
let pos = 0;
|
|
2220
|
+
while (pos <= rest.length) {
|
|
2221
|
+
const line = rest.slice(pos);
|
|
2222
|
+
if (line.startsWith(delim)) {
|
|
2223
|
+
const after = line.slice(delim.length);
|
|
2224
|
+
if (after === "" || after[0] === "\n" || after[0] === "\r") {
|
|
2225
|
+
return {
|
|
2226
|
+
frontmatter: goTrimSpace(rest.slice(0, pos)),
|
|
2227
|
+
hasFrontmatter: true,
|
|
2228
|
+
parseError: null
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
const nl = line.indexOf("\n");
|
|
2233
|
+
if (nl < 0) break;
|
|
2234
|
+
pos += nl + 1;
|
|
2235
|
+
}
|
|
2236
|
+
return {
|
|
2237
|
+
frontmatter: "",
|
|
2238
|
+
hasFrontmatter: true,
|
|
2239
|
+
parseError: "YAML frontmatter not properly closed"
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
function retagScalars(node) {
|
|
2243
|
+
if (yaml.isMap(node)) {
|
|
2244
|
+
for (const pair of node.items) {
|
|
2245
|
+
retagScalars(pair.key);
|
|
2246
|
+
retagScalars(pair.value);
|
|
2247
|
+
}
|
|
2248
|
+
} else if (yaml.isSeq(node)) {
|
|
2249
|
+
for (const item of node.items) retagScalars(item);
|
|
2250
|
+
} else if (yaml.isScalar(node) && (typeof node.value === "number" || typeof node.value === "boolean") && typeof node.source === "string") {
|
|
2251
|
+
node.value = node.source;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
var DecodeError = class extends Error {
|
|
2255
|
+
};
|
|
2256
|
+
function goString(v) {
|
|
2257
|
+
if (v === null || v === void 0) return "";
|
|
2258
|
+
if (isString(v)) return v;
|
|
2259
|
+
throw new DecodeError("not a string");
|
|
2260
|
+
}
|
|
2261
|
+
function goStringList(v) {
|
|
2262
|
+
if (v === null || v === void 0) return null;
|
|
2263
|
+
if (!isArray(v)) throw new DecodeError("not a list");
|
|
2264
|
+
return v.filter((item) => item !== null).map(goString);
|
|
2265
|
+
}
|
|
2266
|
+
function goStruct(v) {
|
|
2267
|
+
if (v === null || v === void 0) return null;
|
|
2268
|
+
if (!isRecord(v)) throw new DecodeError("not a mapping");
|
|
2269
|
+
return v;
|
|
2270
|
+
}
|
|
2271
|
+
function decodeSpecFields(frontmatter) {
|
|
2272
|
+
let value;
|
|
2273
|
+
try {
|
|
2274
|
+
const docs = yaml.parseAllDocuments(frontmatter, { merge: true });
|
|
2275
|
+
const doc = Array.isArray(docs) ? docs[0] : void 0;
|
|
2276
|
+
if (doc === void 0) {
|
|
2277
|
+
value = null;
|
|
2278
|
+
} else {
|
|
2279
|
+
if (doc.errors.length > 0) return null;
|
|
2280
|
+
retagScalars(doc.contents);
|
|
2281
|
+
value = doc.toJS({ maxAliasCount: MAX_ALIAS_COUNT2 });
|
|
2282
|
+
}
|
|
2283
|
+
} catch {
|
|
2284
|
+
return null;
|
|
2285
|
+
}
|
|
2286
|
+
try {
|
|
2287
|
+
const root = goStruct(value) ?? {};
|
|
2288
|
+
const execution = goStruct(root.execution);
|
|
2289
|
+
const tools = goStruct(root.tools);
|
|
2290
|
+
const requirements = goStruct(root.requirements);
|
|
2291
|
+
const allow = tools === null ? null : goStringList(tools.allow);
|
|
2292
|
+
let resources = [];
|
|
2293
|
+
if (requirements !== null) {
|
|
2294
|
+
const raw = requirements.resources;
|
|
2295
|
+
if (raw !== null && raw !== void 0) {
|
|
2296
|
+
if (!isArray(raw)) throw new DecodeError("not a list");
|
|
2297
|
+
resources = raw.filter((entry) => entry !== null).map((entry) => {
|
|
2298
|
+
const r = goStruct(entry) ?? {};
|
|
2299
|
+
return { ref: goString(r.ref), kind: goString(r.kind), scope: goString(r.scope) };
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return {
|
|
2304
|
+
provider: execution === null ? "" : goString(execution.provider),
|
|
2305
|
+
toolsAllowDeclared: allow !== null,
|
|
2306
|
+
toolsAllow: allow ?? [],
|
|
2307
|
+
resources
|
|
2308
|
+
};
|
|
2309
|
+
} catch (e) {
|
|
2310
|
+
if (e instanceof DecodeError) return null;
|
|
2311
|
+
throw e;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
function inspectExonsFrontmatter(source) {
|
|
2315
|
+
const fm = extractExonsFrontmatter(source);
|
|
2316
|
+
if (fm.parseError !== null) return { ...UNREAD, parseError: fm.parseError };
|
|
2317
|
+
if (!fm.hasFrontmatter || fm.frontmatter === "") return { ...UNREAD, specRead: true };
|
|
2318
|
+
if (fm.frontmatter.includes(EXONS_OPEN_DELIM)) return { ...UNREAD };
|
|
2319
|
+
const fields = decodeSpecFields(fm.frontmatter);
|
|
2320
|
+
if (fields === null) return { ...UNREAD };
|
|
2321
|
+
return { parseError: null, specRead: true, hasSpec: true, ...fields };
|
|
2322
|
+
}
|
|
2323
|
+
function validateOrchestratorExons(flow, issues) {
|
|
2324
|
+
const orch = flow.orchestrator;
|
|
2325
|
+
if (!isRecord(orch)) return;
|
|
2326
|
+
const source = issues.stringOf(orch, "exons", "orchestrator");
|
|
2327
|
+
if (source === null || source === "") return;
|
|
2328
|
+
const field = "orchestrator.exons";
|
|
2329
|
+
const report = inspectExonsFrontmatter(source);
|
|
2330
|
+
if (report.parseError !== null) {
|
|
2331
|
+
issues.error({
|
|
2332
|
+
field,
|
|
2333
|
+
code: CODE_PARSE_FAILED,
|
|
2334
|
+
message: `orchestrator: failed to parse exons spec: ${report.parseError}`
|
|
2335
|
+
});
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
if (!report.specRead) return;
|
|
2339
|
+
if (!report.hasSpec) {
|
|
2340
|
+
issues.error({
|
|
2341
|
+
field,
|
|
2342
|
+
code: CODE_PARSE_FAILED,
|
|
2343
|
+
message: "orchestrator: failed to parse exons spec: the document has no frontmatter spec",
|
|
2344
|
+
suggestion: "Start the document with a --- frontmatter block that sets execution.provider"
|
|
2345
|
+
});
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
if (report.provider === "") {
|
|
2349
|
+
issues.error({
|
|
2350
|
+
field,
|
|
2351
|
+
code: CODE_NO_PROVIDER,
|
|
2352
|
+
message: "orchestrator: exons spec must have execution.provider set"
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
if (report.resources.length > 0) {
|
|
2356
|
+
const refs = report.resources.map((r) => `${JSON.stringify(r.ref)} (kind ${r.kind})`);
|
|
2357
|
+
issues.error({
|
|
2358
|
+
field,
|
|
2359
|
+
code: CODE_RESOURCES,
|
|
2360
|
+
message: "the orchestrator's exons spec declares requirements.resources that AIgentFlow cannot honour, so it is refused rather than run without the narrowing it asks for: " + refs.join("; "),
|
|
2361
|
+
suggestion: "Remove requirements.resources, and narrow the agent with tools.mcp_servers and tools.allow"
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
warnOrchestratorToolWithheld(flow, orch, report, issues);
|
|
2365
|
+
}
|
|
2366
|
+
function warnOrchestratorToolWithheld(flow, orch, report, issues) {
|
|
2367
|
+
if (!report.toolsAllowDeclared) return;
|
|
2368
|
+
const cfg = ORCHESTRATOR_TOOL_ALLOW;
|
|
2369
|
+
const exempt = new Set(cfg.lifecycleTools);
|
|
2370
|
+
const signals = typeof orch.enable_signals === "boolean" ? orch.enable_signals : cfg.enableSignalsDefault;
|
|
2371
|
+
if (signals) for (const t of cfg.signalTools) exempt.add(t);
|
|
2372
|
+
const allow = new Set(report.toolsAllow);
|
|
2373
|
+
const withheld = (name) => !allow.has(name) && !exempt.has(name);
|
|
2374
|
+
const warn = (message) => issues.warn({ field: cfg.warningField, code: CODE_TOOL_WITHHELD, message });
|
|
2375
|
+
const named = [];
|
|
2376
|
+
if (isArray(orch.tools)) {
|
|
2377
|
+
orch.tools.forEach((raw, i) => {
|
|
2378
|
+
const name = issues.stringAt(raw, `orchestrator.tools[${i}]`);
|
|
2379
|
+
if (name !== null) named.push(name);
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
if (named.length > 0) {
|
|
2383
|
+
for (const name of named) {
|
|
2384
|
+
if (!withheld(name)) continue;
|
|
2385
|
+
warn(
|
|
2386
|
+
`orchestrator tool '${name}' is named in orchestrator.tools but the orchestrator's exons definition's tools.allow does not name it, so it is never offered: a tool must pass both lists. Add '${name}' to tools.allow, or remove it from orchestrator.tools.`
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
if (withheld(cfg.askHumanTool)) {
|
|
2392
|
+
warn(
|
|
2393
|
+
`the orchestrator's exons definition has a tools.allow list that does not name ${cfg.askHumanTool}, so this orchestrator can never ask a person a question: human intervention is off. Add ${cfg.askHumanTool} to tools.allow if a human should be reachable, or ignore this if the orchestrator is meant to run unattended.`
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
if (isRecord(flow.campaign) && !cfg.campaignTools.some((name) => !withheld(name))) {
|
|
2397
|
+
warn(
|
|
2398
|
+
`this flow declares a campaign, but the orchestrator's exons definition has a tools.allow list that names none of the campaign tools (${cfg.campaignTools.join(", ")}), so no child mission can be spawned or tracked. Add the campaign tools the orchestrator needs to tools.allow.`
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2144
2402
|
|
|
2145
2403
|
// src/validators/executors.ts
|
|
2146
2404
|
var SCHEME_SEPARATOR = "://";
|
|
@@ -2204,6 +2462,56 @@ function checkExecutor(executor, field, stepID, issues) {
|
|
|
2204
2462
|
}
|
|
2205
2463
|
checkFunctionOperation(executor, field, stepID, templated, issues);
|
|
2206
2464
|
}
|
|
2465
|
+
function removedExecutorScheme(executor) {
|
|
2466
|
+
if (executor.includes(TEMPLATE_ACTION_OPEN)) return "";
|
|
2467
|
+
const trimmed = goTrimSpace(executor);
|
|
2468
|
+
const at = trimmed.indexOf(SCHEME_SEPARATOR);
|
|
2469
|
+
return at < 0 ? "" : trimmed.slice(0, at).toLowerCase();
|
|
2470
|
+
}
|
|
2471
|
+
function checkRemovedSurfaces(executor, query, field, stepID, issues) {
|
|
2472
|
+
const cfg = REMOVED_AGENT_SURFACES;
|
|
2473
|
+
const quoted = JSON.stringify(stepID);
|
|
2474
|
+
const scheme = removedExecutorScheme(executor);
|
|
2475
|
+
if (cfg.schemes.has(scheme)) {
|
|
2476
|
+
issues.error({
|
|
2477
|
+
field,
|
|
2478
|
+
message: fillRemovedMessage(cfg.schemeMessageFormat, quoted, scheme),
|
|
2479
|
+
code: cfg.executorRemovedCode,
|
|
2480
|
+
stepId: stepID,
|
|
2481
|
+
suggestion: cfg.suggestion
|
|
2482
|
+
});
|
|
2483
|
+
return true;
|
|
2484
|
+
}
|
|
2485
|
+
if (scheme !== cfg.paramScheme || !isRecord(query)) return false;
|
|
2486
|
+
const found = Object.keys(query).filter((key) => cfg.aiParams.has(key)).sort();
|
|
2487
|
+
if (found.length > 0) {
|
|
2488
|
+
issues.error({
|
|
2489
|
+
field,
|
|
2490
|
+
message: fillRemovedMessage(cfg.paramsMessageFormat, quoted, found.join(", ")),
|
|
2491
|
+
code: cfg.agenticParamRemovedCode,
|
|
2492
|
+
stepId: stepID,
|
|
2493
|
+
suggestion: cfg.suggestion
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
return false;
|
|
2497
|
+
}
|
|
2498
|
+
function fillRemovedMessage(format, ...args) {
|
|
2499
|
+
let i = 0;
|
|
2500
|
+
return format.replace(/%[qs]/g, () => args[i++] ?? "");
|
|
2501
|
+
}
|
|
2502
|
+
function validateRemovedFlowKeys(flow, issues) {
|
|
2503
|
+
const cfg = REMOVED_AGENT_SURFACES;
|
|
2504
|
+
for (const key of cfg.flowKeys) {
|
|
2505
|
+
if (Object.prototype.hasOwnProperty.call(flow, key)) {
|
|
2506
|
+
issues.error({
|
|
2507
|
+
field: key,
|
|
2508
|
+
message: fillRemovedMessage(cfg.flowKeyMessageFormat, JSON.stringify(key)),
|
|
2509
|
+
code: cfg.executorRemovedCode,
|
|
2510
|
+
suggestion: cfg.suggestion
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2207
2515
|
function checkAtlasStep(executor, query, stepField, stepID, issues) {
|
|
2208
2516
|
if (executor.includes(TEMPLATE_ACTION_OPEN)) return;
|
|
2209
2517
|
const prefix = `${ATLAS_EXECUTOR.scheme}://`;
|
|
@@ -2284,7 +2592,9 @@ function validateExecutors(flow, issues) {
|
|
|
2284
2592
|
const step = rawStep;
|
|
2285
2593
|
const executor = issues.stringOf(step, "executor", `steps.${stepID}`);
|
|
2286
2594
|
if (executor !== null && executor !== "") {
|
|
2287
|
-
|
|
2595
|
+
if (!checkRemovedSurfaces(executor, step.query, `steps.${stepID}`, stepID, issues)) {
|
|
2596
|
+
checkExecutor(executor, `steps.${stepID}.executor`, stepID, issues);
|
|
2597
|
+
}
|
|
2288
2598
|
checkAtlasStep(executor, step.query, `steps.${stepID}`, stepID, issues);
|
|
2289
2599
|
}
|
|
2290
2600
|
const loop = step.loop;
|
|
@@ -2296,12 +2606,14 @@ function validateExecutors(flow, issues) {
|
|
|
2296
2606
|
const subID = issues.stringOf(subStep, "id", subPath) ?? String(i);
|
|
2297
2607
|
const subExecutor = issues.stringOf(subStep, "executor", subPath);
|
|
2298
2608
|
if (subExecutor !== null && subExecutor !== "") {
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2609
|
+
if (!checkRemovedSurfaces(subExecutor, subStep.query, subPath, `${stepID}.${subID}`, issues)) {
|
|
2610
|
+
checkExecutor(
|
|
2611
|
+
subExecutor,
|
|
2612
|
+
`steps.${stepID}.loop.steps[${i}].executor`,
|
|
2613
|
+
`${stepID}.${subID}`,
|
|
2614
|
+
issues
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2305
2617
|
checkAtlasStep(subExecutor, subStep.query, subPath, `${stepID}.${subID}`, issues);
|
|
2306
2618
|
}
|
|
2307
2619
|
});
|
|
@@ -5139,9 +5451,9 @@ function refuse(issues, stepId, key, field) {
|
|
|
5139
5451
|
}
|
|
5140
5452
|
|
|
5141
5453
|
// src/validators/goUrl.ts
|
|
5142
|
-
var
|
|
5143
|
-
var GO_TRIM_SPACE_RE = new RegExp(`^[${
|
|
5144
|
-
function
|
|
5454
|
+
var GO_SPACE2 = "\\t\\n\\v\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000";
|
|
5455
|
+
var GO_TRIM_SPACE_RE = new RegExp(`^[${GO_SPACE2}]+|[${GO_SPACE2}]+$`, "g");
|
|
5456
|
+
function goTrimSpace2(s) {
|
|
5145
5457
|
return s.replace(GO_TRIM_SPACE_RE, "");
|
|
5146
5458
|
}
|
|
5147
5459
|
function goToLower(s) {
|
|
@@ -5362,7 +5674,7 @@ function goQueryKeys(rawQuery) {
|
|
|
5362
5674
|
return keys;
|
|
5363
5675
|
}
|
|
5364
5676
|
function goEndpointOrigin(raw, defaultPorts, protocolSeparator) {
|
|
5365
|
-
const trimmed =
|
|
5677
|
+
const trimmed = goTrimSpace2(raw);
|
|
5366
5678
|
const u = goParseSchemeHost(trimmed);
|
|
5367
5679
|
if (u === null || u.scheme === "" || u.host === "") {
|
|
5368
5680
|
return goToLower(trimmed.replace(/\/+$/, ""));
|
|
@@ -5596,227 +5908,6 @@ function validateFamilies(flow, steps, issues) {
|
|
|
5596
5908
|
});
|
|
5597
5909
|
}
|
|
5598
5910
|
}
|
|
5599
|
-
var CODE_PARSE_FAILED = "orchestrator_exons_parse_failed";
|
|
5600
|
-
var CODE_NO_PROVIDER = "orchestrator_exons_no_provider";
|
|
5601
|
-
var CODE_RESOURCES = "exons_resources_unhonoured";
|
|
5602
|
-
var CODE_TOOL_WITHHELD = "orchestrator_tool_withheld";
|
|
5603
|
-
var EXONS_OPEN_DELIM = "{~";
|
|
5604
|
-
var EXONS_LEGACY_CONFIG_OPEN = "{~exons.config~}";
|
|
5605
|
-
var UTF8_BOM = "\uFEFF";
|
|
5606
|
-
var MAX_ALIAS_COUNT2 = 100;
|
|
5607
|
-
var UNREAD = {
|
|
5608
|
-
parseError: null,
|
|
5609
|
-
specRead: false,
|
|
5610
|
-
hasSpec: false,
|
|
5611
|
-
provider: "",
|
|
5612
|
-
toolsAllowDeclared: false,
|
|
5613
|
-
toolsAllow: [],
|
|
5614
|
-
resources: []
|
|
5615
|
-
};
|
|
5616
|
-
var GO_SPACE2 = "\\t\\n\\v\\f\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000";
|
|
5617
|
-
var GO_TRIM_SPACE = new RegExp(`^[${GO_SPACE2}]+|[${GO_SPACE2}]+$`, "g");
|
|
5618
|
-
function goTrimSpace2(s) {
|
|
5619
|
-
return s.replace(GO_TRIM_SPACE, "");
|
|
5620
|
-
}
|
|
5621
|
-
function extractExonsFrontmatter(source) {
|
|
5622
|
-
const delim = EXONS.frontmatterDelimiter;
|
|
5623
|
-
let start = source.startsWith(UTF8_BOM) ? source.slice(UTF8_BOM.length) : source;
|
|
5624
|
-
start = start.replace(/^[ \t]+/, "");
|
|
5625
|
-
if (start.startsWith(EXONS_LEGACY_CONFIG_OPEN)) {
|
|
5626
|
-
return {
|
|
5627
|
-
frontmatter: "",
|
|
5628
|
-
hasFrontmatter: false,
|
|
5629
|
-
parseError: "legacy JSON config block detected; use YAML frontmatter"
|
|
5630
|
-
};
|
|
5631
|
-
}
|
|
5632
|
-
if (!start.startsWith(delim)) return { frontmatter: "", hasFrontmatter: false, parseError: null };
|
|
5633
|
-
let rest = start.slice(delim.length);
|
|
5634
|
-
if (rest.startsWith("\n")) rest = rest.slice(1);
|
|
5635
|
-
else if (rest.startsWith("\r\n")) rest = rest.slice(2);
|
|
5636
|
-
else return { frontmatter: "", hasFrontmatter: false, parseError: null };
|
|
5637
|
-
let pos = 0;
|
|
5638
|
-
while (pos <= rest.length) {
|
|
5639
|
-
const line = rest.slice(pos);
|
|
5640
|
-
if (line.startsWith(delim)) {
|
|
5641
|
-
const after = line.slice(delim.length);
|
|
5642
|
-
if (after === "" || after[0] === "\n" || after[0] === "\r") {
|
|
5643
|
-
return {
|
|
5644
|
-
frontmatter: goTrimSpace2(rest.slice(0, pos)),
|
|
5645
|
-
hasFrontmatter: true,
|
|
5646
|
-
parseError: null
|
|
5647
|
-
};
|
|
5648
|
-
}
|
|
5649
|
-
}
|
|
5650
|
-
const nl = line.indexOf("\n");
|
|
5651
|
-
if (nl < 0) break;
|
|
5652
|
-
pos += nl + 1;
|
|
5653
|
-
}
|
|
5654
|
-
return {
|
|
5655
|
-
frontmatter: "",
|
|
5656
|
-
hasFrontmatter: true,
|
|
5657
|
-
parseError: "YAML frontmatter not properly closed"
|
|
5658
|
-
};
|
|
5659
|
-
}
|
|
5660
|
-
function retagScalars(node) {
|
|
5661
|
-
if (yaml.isMap(node)) {
|
|
5662
|
-
for (const pair of node.items) {
|
|
5663
|
-
retagScalars(pair.key);
|
|
5664
|
-
retagScalars(pair.value);
|
|
5665
|
-
}
|
|
5666
|
-
} else if (yaml.isSeq(node)) {
|
|
5667
|
-
for (const item of node.items) retagScalars(item);
|
|
5668
|
-
} else if (yaml.isScalar(node) && (typeof node.value === "number" || typeof node.value === "boolean") && typeof node.source === "string") {
|
|
5669
|
-
node.value = node.source;
|
|
5670
|
-
}
|
|
5671
|
-
}
|
|
5672
|
-
var DecodeError = class extends Error {
|
|
5673
|
-
};
|
|
5674
|
-
function goString(v) {
|
|
5675
|
-
if (v === null || v === void 0) return "";
|
|
5676
|
-
if (isString(v)) return v;
|
|
5677
|
-
throw new DecodeError("not a string");
|
|
5678
|
-
}
|
|
5679
|
-
function goStringList(v) {
|
|
5680
|
-
if (v === null || v === void 0) return null;
|
|
5681
|
-
if (!isArray(v)) throw new DecodeError("not a list");
|
|
5682
|
-
return v.filter((item) => item !== null).map(goString);
|
|
5683
|
-
}
|
|
5684
|
-
function goStruct(v) {
|
|
5685
|
-
if (v === null || v === void 0) return null;
|
|
5686
|
-
if (!isRecord(v)) throw new DecodeError("not a mapping");
|
|
5687
|
-
return v;
|
|
5688
|
-
}
|
|
5689
|
-
function decodeSpecFields(frontmatter) {
|
|
5690
|
-
let value;
|
|
5691
|
-
try {
|
|
5692
|
-
const docs = yaml.parseAllDocuments(frontmatter, { merge: true });
|
|
5693
|
-
const doc = Array.isArray(docs) ? docs[0] : void 0;
|
|
5694
|
-
if (doc === void 0) {
|
|
5695
|
-
value = null;
|
|
5696
|
-
} else {
|
|
5697
|
-
if (doc.errors.length > 0) return null;
|
|
5698
|
-
retagScalars(doc.contents);
|
|
5699
|
-
value = doc.toJS({ maxAliasCount: MAX_ALIAS_COUNT2 });
|
|
5700
|
-
}
|
|
5701
|
-
} catch {
|
|
5702
|
-
return null;
|
|
5703
|
-
}
|
|
5704
|
-
try {
|
|
5705
|
-
const root = goStruct(value) ?? {};
|
|
5706
|
-
const execution = goStruct(root.execution);
|
|
5707
|
-
const tools = goStruct(root.tools);
|
|
5708
|
-
const requirements = goStruct(root.requirements);
|
|
5709
|
-
const allow = tools === null ? null : goStringList(tools.allow);
|
|
5710
|
-
let resources = [];
|
|
5711
|
-
if (requirements !== null) {
|
|
5712
|
-
const raw = requirements.resources;
|
|
5713
|
-
if (raw !== null && raw !== void 0) {
|
|
5714
|
-
if (!isArray(raw)) throw new DecodeError("not a list");
|
|
5715
|
-
resources = raw.filter((entry) => entry !== null).map((entry) => {
|
|
5716
|
-
const r = goStruct(entry) ?? {};
|
|
5717
|
-
return { ref: goString(r.ref), kind: goString(r.kind), scope: goString(r.scope) };
|
|
5718
|
-
});
|
|
5719
|
-
}
|
|
5720
|
-
}
|
|
5721
|
-
return {
|
|
5722
|
-
provider: execution === null ? "" : goString(execution.provider),
|
|
5723
|
-
toolsAllowDeclared: allow !== null,
|
|
5724
|
-
toolsAllow: allow ?? [],
|
|
5725
|
-
resources
|
|
5726
|
-
};
|
|
5727
|
-
} catch (e) {
|
|
5728
|
-
if (e instanceof DecodeError) return null;
|
|
5729
|
-
throw e;
|
|
5730
|
-
}
|
|
5731
|
-
}
|
|
5732
|
-
function inspectExonsFrontmatter(source) {
|
|
5733
|
-
const fm = extractExonsFrontmatter(source);
|
|
5734
|
-
if (fm.parseError !== null) return { ...UNREAD, parseError: fm.parseError };
|
|
5735
|
-
if (!fm.hasFrontmatter || fm.frontmatter === "") return { ...UNREAD, specRead: true };
|
|
5736
|
-
if (fm.frontmatter.includes(EXONS_OPEN_DELIM)) return { ...UNREAD };
|
|
5737
|
-
const fields = decodeSpecFields(fm.frontmatter);
|
|
5738
|
-
if (fields === null) return { ...UNREAD };
|
|
5739
|
-
return { parseError: null, specRead: true, hasSpec: true, ...fields };
|
|
5740
|
-
}
|
|
5741
|
-
function validateOrchestratorExons(flow, issues) {
|
|
5742
|
-
const orch = flow.orchestrator;
|
|
5743
|
-
if (!isRecord(orch)) return;
|
|
5744
|
-
const source = issues.stringOf(orch, "exons", "orchestrator");
|
|
5745
|
-
if (source === null || source === "") return;
|
|
5746
|
-
const field = "orchestrator.exons";
|
|
5747
|
-
const report = inspectExonsFrontmatter(source);
|
|
5748
|
-
if (report.parseError !== null) {
|
|
5749
|
-
issues.error({
|
|
5750
|
-
field,
|
|
5751
|
-
code: CODE_PARSE_FAILED,
|
|
5752
|
-
message: `orchestrator: failed to parse exons spec: ${report.parseError}`
|
|
5753
|
-
});
|
|
5754
|
-
return;
|
|
5755
|
-
}
|
|
5756
|
-
if (!report.specRead) return;
|
|
5757
|
-
if (!report.hasSpec) {
|
|
5758
|
-
issues.error({
|
|
5759
|
-
field,
|
|
5760
|
-
code: CODE_PARSE_FAILED,
|
|
5761
|
-
message: "orchestrator: failed to parse exons spec: the document has no frontmatter spec",
|
|
5762
|
-
suggestion: "Start the document with a --- frontmatter block that sets execution.provider"
|
|
5763
|
-
});
|
|
5764
|
-
return;
|
|
5765
|
-
}
|
|
5766
|
-
if (report.provider === "") {
|
|
5767
|
-
issues.error({
|
|
5768
|
-
field,
|
|
5769
|
-
code: CODE_NO_PROVIDER,
|
|
5770
|
-
message: "orchestrator: exons spec must have execution.provider set"
|
|
5771
|
-
});
|
|
5772
|
-
}
|
|
5773
|
-
if (report.resources.length > 0) {
|
|
5774
|
-
const refs = report.resources.map((r) => `${JSON.stringify(r.ref)} (kind ${r.kind})`);
|
|
5775
|
-
issues.error({
|
|
5776
|
-
field,
|
|
5777
|
-
code: CODE_RESOURCES,
|
|
5778
|
-
message: "the orchestrator's exons spec declares requirements.resources that AIgentFlow cannot honour, so it is refused rather than run without the narrowing it asks for: " + refs.join("; "),
|
|
5779
|
-
suggestion: "Remove requirements.resources, and narrow the agent with tools.mcp_servers and tools.allow"
|
|
5780
|
-
});
|
|
5781
|
-
}
|
|
5782
|
-
warnOrchestratorToolWithheld(flow, orch, report, issues);
|
|
5783
|
-
}
|
|
5784
|
-
function warnOrchestratorToolWithheld(flow, orch, report, issues) {
|
|
5785
|
-
if (!report.toolsAllowDeclared) return;
|
|
5786
|
-
const cfg = ORCHESTRATOR_TOOL_ALLOW;
|
|
5787
|
-
const exempt = new Set(cfg.lifecycleTools);
|
|
5788
|
-
const signals = typeof orch.enable_signals === "boolean" ? orch.enable_signals : cfg.enableSignalsDefault;
|
|
5789
|
-
if (signals) for (const t of cfg.signalTools) exempt.add(t);
|
|
5790
|
-
const allow = new Set(report.toolsAllow);
|
|
5791
|
-
const withheld = (name) => !allow.has(name) && !exempt.has(name);
|
|
5792
|
-
const warn = (message) => issues.warn({ field: cfg.warningField, code: CODE_TOOL_WITHHELD, message });
|
|
5793
|
-
const named = [];
|
|
5794
|
-
if (isArray(orch.tools)) {
|
|
5795
|
-
orch.tools.forEach((raw, i) => {
|
|
5796
|
-
const name = issues.stringAt(raw, `orchestrator.tools[${i}]`);
|
|
5797
|
-
if (name !== null) named.push(name);
|
|
5798
|
-
});
|
|
5799
|
-
}
|
|
5800
|
-
if (named.length > 0) {
|
|
5801
|
-
for (const name of named) {
|
|
5802
|
-
if (!withheld(name)) continue;
|
|
5803
|
-
warn(
|
|
5804
|
-
`orchestrator tool '${name}' is named in orchestrator.tools but the orchestrator's exons definition's tools.allow does not name it, so it is never offered: a tool must pass both lists. Add '${name}' to tools.allow, or remove it from orchestrator.tools.`
|
|
5805
|
-
);
|
|
5806
|
-
}
|
|
5807
|
-
return;
|
|
5808
|
-
}
|
|
5809
|
-
if (withheld(cfg.askHumanTool)) {
|
|
5810
|
-
warn(
|
|
5811
|
-
`the orchestrator's exons definition has a tools.allow list that does not name ${cfg.askHumanTool}, so this orchestrator can never ask a person a question: human intervention is off. Add ${cfg.askHumanTool} to tools.allow if a human should be reachable, or ignore this if the orchestrator is meant to run unattended.`
|
|
5812
|
-
);
|
|
5813
|
-
}
|
|
5814
|
-
if (isRecord(flow.campaign) && !cfg.campaignTools.some((name) => !withheld(name))) {
|
|
5815
|
-
warn(
|
|
5816
|
-
`this flow declares a campaign, but the orchestrator's exons definition has a tools.allow list that names none of the campaign tools (${cfg.campaignTools.join(", ")}), so no child mission can be spawned or tracked. Add the campaign tools the orchestrator needs to tools.allow.`
|
|
5817
|
-
);
|
|
5818
|
-
}
|
|
5819
|
-
}
|
|
5820
5911
|
|
|
5821
5912
|
// src/validators/complianceResidency.ts
|
|
5822
5913
|
function own2(m, key) {
|
|
@@ -5831,7 +5922,7 @@ function sprintf(format, ...args) {
|
|
|
5831
5922
|
});
|
|
5832
5923
|
}
|
|
5833
5924
|
function literalOrEmpty(s) {
|
|
5834
|
-
const t =
|
|
5925
|
+
const t = goTrimSpace(s);
|
|
5835
5926
|
return t.includes(TEMPLATE_ACTION_OPEN) ? "" : t;
|
|
5836
5927
|
}
|
|
5837
5928
|
function euOnlyResidency(flow, issues) {
|
|
@@ -5846,7 +5937,7 @@ function euOnlyResidency(flow, issues) {
|
|
|
5846
5937
|
}
|
|
5847
5938
|
if (codes.length === 0) return null;
|
|
5848
5939
|
const eu = new Set(COMPLIANCE_RESIDENCY.euResidencyCodes);
|
|
5849
|
-
return codes.every((c) => eu.has(
|
|
5940
|
+
return codes.every((c) => eu.has(goTrimSpace(c).toUpperCase())) ? codes : null;
|
|
5850
5941
|
}
|
|
5851
5942
|
function aiProvider(executor) {
|
|
5852
5943
|
const prefix = COMPLIANCE_RESIDENCY.aiProtocol + COMPLIANCE_RESIDENCY.protocolSeparator;
|
|
@@ -5862,7 +5953,7 @@ function isRegionLane(provider) {
|
|
|
5862
5953
|
}
|
|
5863
5954
|
function nonBlankString(m, key) {
|
|
5864
5955
|
const v = own2(m, key);
|
|
5865
|
-
return typeof v === "string" &&
|
|
5956
|
+
return typeof v === "string" && goTrimSpace(v) !== "";
|
|
5866
5957
|
}
|
|
5867
5958
|
function queryEnforcesRegion(query) {
|
|
5868
5959
|
const v = own2(query, COMPLIANCE_RESIDENCY.enforceRegionParam);
|
|
@@ -5874,7 +5965,7 @@ function flowDirectsEndpoint(flow, provider, issues) {
|
|
|
5874
5965
|
const inst = own2(own2(flow, "executor_config"), key);
|
|
5875
5966
|
if (!isRecord(inst)) return false;
|
|
5876
5967
|
const baseUrl = issues.stringOf(inst, "base_url", `executor_config.${key}`);
|
|
5877
|
-
return baseUrl !== null &&
|
|
5968
|
+
return baseUrl !== null && goTrimSpace(baseUrl) !== "";
|
|
5878
5969
|
}
|
|
5879
5970
|
function callPinsEndpoint(flow, provider, query, issues) {
|
|
5880
5971
|
if (nonBlankString(query, COMPLIANCE_RESIDENCY.regionParam)) return true;
|
|
@@ -5947,7 +6038,7 @@ function warnOrchestrator(flow, issues, warn) {
|
|
|
5947
6038
|
const provider = literalOrEmpty(report.provider);
|
|
5948
6039
|
if (!isRegionLane(provider)) return;
|
|
5949
6040
|
const region = issues.stringOf(orch, "region", "orchestrator");
|
|
5950
|
-
if (region !== null &&
|
|
6041
|
+
if (region !== null && goTrimSpace(region) !== "") return;
|
|
5951
6042
|
if (orch.enforce_region === true) return;
|
|
5952
6043
|
if (flowDirectsEndpoint(flow, provider, issues)) return;
|
|
5953
6044
|
warn(COMPLIANCE_RESIDENCY.siteOrchestrator, COMPLIANCE_RESIDENCY.orchestratorField, void 0, provider);
|
|
@@ -6113,7 +6204,7 @@ function asFileRef(v) {
|
|
|
6113
6204
|
};
|
|
6114
6205
|
}
|
|
6115
6206
|
function isTextualMediaType(mediaType) {
|
|
6116
|
-
const base = goToLower(
|
|
6207
|
+
const base = goToLower(goTrimSpace2((mediaType.split(";")[0] ?? "").toString()));
|
|
6117
6208
|
if (base.startsWith("text/")) return true;
|
|
6118
6209
|
if (base.endsWith("+json") || base.endsWith("+xml")) return true;
|
|
6119
6210
|
return TEXTUAL_MEDIA_BASES.has(base);
|
|
@@ -6217,7 +6308,7 @@ var Walker = class _Walker {
|
|
|
6217
6308
|
}
|
|
6218
6309
|
}
|
|
6219
6310
|
const title = this.s(ex, "title", path);
|
|
6220
|
-
if (
|
|
6311
|
+
if (goTrimSpace2(title) === "") {
|
|
6221
6312
|
this.err(
|
|
6222
6313
|
"example_title_missing",
|
|
6223
6314
|
`${path}.title`,
|
|
@@ -6234,7 +6325,7 @@ var Walker = class _Walker {
|
|
|
6234
6325
|
}
|
|
6235
6326
|
}
|
|
6236
6327
|
const guidance = this.s(ex, "guidance", path);
|
|
6237
|
-
if (
|
|
6328
|
+
if (goTrimSpace2(guidance) === "") {
|
|
6238
6329
|
this.warn(
|
|
6239
6330
|
"example_guidance_missing",
|
|
6240
6331
|
`${path}.guidance`,
|
|
@@ -6661,7 +6752,7 @@ var Walker = class _Walker {
|
|
|
6661
6752
|
}
|
|
6662
6753
|
const rubric = this.s(e, "rubric", path);
|
|
6663
6754
|
if (rubric !== "") {
|
|
6664
|
-
if (
|
|
6755
|
+
if (goTrimSpace2(rubric) === "") {
|
|
6665
6756
|
this.err(
|
|
6666
6757
|
"example_rubric_empty",
|
|
6667
6758
|
`${path}.rubric`,
|
|
@@ -6716,7 +6807,7 @@ var Walker = class _Walker {
|
|
|
6716
6807
|
this.err("example_must_not_invalid", `${path}.must_not`, mustNotMsg);
|
|
6717
6808
|
}
|
|
6718
6809
|
mustNot.forEach((item, j) => {
|
|
6719
|
-
if (
|
|
6810
|
+
if (goTrimSpace2(item) === "" || runeCount(item) > L.maxMustNotRunes) {
|
|
6720
6811
|
this.err("example_must_not_invalid", `${path}.must_not[${j}]`, mustNotMsg);
|
|
6721
6812
|
}
|
|
6722
6813
|
});
|
|
@@ -7274,6 +7365,7 @@ function validateParsed(flow, opts, sources) {
|
|
|
7274
7365
|
const f = flow;
|
|
7275
7366
|
validateBasicStructure(f, issues);
|
|
7276
7367
|
validateRetiredKeys(f, issues);
|
|
7368
|
+
validateRemovedFlowKeys(f, issues);
|
|
7277
7369
|
validateExecutors(f, issues);
|
|
7278
7370
|
validateQuerySchema(f, issues);
|
|
7279
7371
|
validateResponseExpectations(f, issues);
|