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