@vaudience/aigentflow-flow-validator 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -304,6 +304,17 @@ function isValidGoDuration(input) {
304
304
  var RESERVED_STEP_ID_CHAR = ".";
305
305
  var RESERVED_STEP_ID_ORCHESTRATOR = "orchestrator";
306
306
  var DISPLAY_NAME_MAX_LEN = 80;
307
+ var YAML11_BOOL_WORDS = new Set(
308
+ ["y", "yes", "on", "n", "no", "off"].flatMap((w) => [
309
+ w,
310
+ w.toUpperCase(),
311
+ w[0].toUpperCase() + w.slice(1)
312
+ ])
313
+ );
314
+ function isBoolLike(v) {
315
+ if (v === null || typeof v === "boolean") return true;
316
+ return typeof v === "string" && YAML11_BOOL_WORDS.has(v);
317
+ }
307
318
  function validateBasicStructure(flow, issues) {
308
319
  if (!issues.nonEmptyStringOf(flow, "aigentflow_version", "")) {
309
320
  issues.error({
@@ -333,6 +344,14 @@ function validateBasicStructure(flow, issues) {
333
344
  });
334
345
  }
335
346
  }
347
+ if (flow.strict_templates !== void 0 && !isBoolLike(flow.strict_templates)) {
348
+ issues.error({
349
+ field: "strict_templates",
350
+ message: "strict_templates must be a boolean (true or false)",
351
+ code: "invalid_type",
352
+ suggestion: "Write 'strict_templates: true' or remove the key"
353
+ });
354
+ }
336
355
  const start = issues.stringOf(flow, "start", "");
337
356
  if (start === null || start === "") {
338
357
  issues.error({
@@ -432,7 +451,7 @@ function validateBasicStructure(flow, issues) {
432
451
 
433
452
  // src/spec/aigentflow-spec.json
434
453
  var aigentflow_spec_default = {
435
- specVersion: "2.821.0",
454
+ specVersion: "2.828.0",
436
455
  inputSchemaVersion: 1,
437
456
  executorSchemes: [
438
457
  "a2a",
@@ -456,6 +475,7 @@ var aigentflow_spec_default = {
456
475
  "geocoding",
457
476
  "geovisualize",
458
477
  "getmd",
478
+ "here",
459
479
  "homeassistant",
460
480
  "hosting",
461
481
  "http",
@@ -928,6 +948,33 @@ var aigentflow_spec_default = {
928
948
  stepFieldFormat: "steps.%s.query.%s",
929
949
  loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
930
950
  },
951
+ flowDataReservedKeys: {
952
+ code: "flow_data_key_reserved",
953
+ fieldFormat: "data.%s",
954
+ messageFormat: "data: key %s is reserved: %s. It would write into the engine's own state, so it is not seeded.",
955
+ suggestion: "Rename the key (no leading `_` or `.`, and not one of the engine namespaces memory., orchestrator., campaign., voiceagent.).",
956
+ exact: {
957
+ org_id: "the mission's organization, set by the engine for Thalamus/Engram calls",
958
+ memory: "the memory scope namespace",
959
+ orchestrator: "the orchestrator's state namespace",
960
+ campaign: "the campaign's state namespace",
961
+ voiceagent: "the voice agent's state namespace",
962
+ current_flow: "engine bookkeeping (the current flow definition)",
963
+ modified_flow: "engine bookkeeping (dynamic flow modification)",
964
+ modification_history: "engine bookkeeping (dynamic flow modification)",
965
+ modification_pending: "engine bookkeeping (dynamic flow modification)",
966
+ modification_requests: "engine bookkeeping (dynamic flow modification)",
967
+ bottleneck_state: "engine bookkeeping (bottleneck analysis)"
968
+ },
969
+ prefixes: [
970
+ { prefix: "_", reason: "keys starting with `_` are engine bookkeeping (current flow, loop and for_each context, taint lists such as `__untrusted_*`)" },
971
+ { prefix: ".", reason: "keys starting with `.` are engine step-response slots" },
972
+ { prefix: "memory.", reason: "the `memory.` namespace belongs to the engine" },
973
+ { prefix: "orchestrator.", reason: "the `orchestrator.` namespace belongs to the orchestrator" },
974
+ { prefix: "campaign.", reason: "the `campaign.` namespace belongs to the campaign engine" },
975
+ { prefix: "voiceagent.", reason: "the `voiceagent.` namespace belongs to the voice agent" }
976
+ ]
977
+ },
931
978
  credentialEndpointPairing: {
932
979
  stepFieldFormat: "steps.%s.query.%s",
933
980
  loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s",
@@ -1257,6 +1304,11 @@ var aigentflow_spec_default = {
1257
1304
  }
1258
1305
  }
1259
1306
  },
1307
+ templateDefaultPiped: {
1308
+ code: "template_default_piped",
1309
+ messageFormat: "`default` takes the value FIRST, and a pipe passes the piped value LAST: `%s` is `default <fallback> <value>`, so the fallback is always the result",
1310
+ suggestion: 'write the value first and the fallback second, as a call: `{{ default .x "fallback" }}`'
1311
+ },
1260
1312
  complianceResidency: {
1261
1313
  code: "compliance_residency_unenforced",
1262
1314
  euResidencyCodes: ["EU", "DE", "FR", "IE", "NL", "IT", "ES"],
@@ -1555,6 +1607,7 @@ var aigentflow_spec_default = {
1555
1607
  rate_limit: "FlowRateLimitConfig",
1556
1608
  start: "scalar",
1557
1609
  steps: "map<StepDefinition>",
1610
+ strict_templates: "scalar",
1558
1611
  tags: "list<scalar>",
1559
1612
  template: "FlowTemplateMetadata",
1560
1613
  tool_discovery: "scalar",
@@ -1859,6 +1912,14 @@ var SERVER_OWNED_QUERY_KEYS = {
1859
1912
  stepFieldFormat: aigentflow_spec_default.serverOwnedQueryKeys.stepFieldFormat,
1860
1913
  loopStepFieldFormat: aigentflow_spec_default.serverOwnedQueryKeys.loopStepFieldFormat
1861
1914
  };
1915
+ var FLOW_DATA_RESERVED_KEYS = {
1916
+ code: aigentflow_spec_default.flowDataReservedKeys.code,
1917
+ fieldFormat: aigentflow_spec_default.flowDataReservedKeys.fieldFormat,
1918
+ messageFormat: aigentflow_spec_default.flowDataReservedKeys.messageFormat,
1919
+ suggestion: aigentflow_spec_default.flowDataReservedKeys.suggestion,
1920
+ exact: aigentflow_spec_default.flowDataReservedKeys.exact,
1921
+ prefixes: aigentflow_spec_default.flowDataReservedKeys.prefixes
1922
+ };
1862
1923
  var CREDENTIAL_ENDPOINT_PAIRING = {
1863
1924
  stepFieldFormat: aigentflow_spec_default.credentialEndpointPairing.stepFieldFormat,
1864
1925
  loopStepFieldFormat: aigentflow_spec_default.credentialEndpointPairing.loopStepFieldFormat,
@@ -1916,6 +1977,11 @@ var EXAMPLES = {
1916
1977
  name: p.name,
1917
1978
  re: new RegExp(p.expr, p.flags)
1918
1979
  }))};
1980
+ var TEMPLATE_DEFAULT_PIPED = {
1981
+ code: aigentflow_spec_default.templateDefaultPiped.code,
1982
+ messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
1983
+ suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
1984
+ };
1919
1985
 
1920
1986
  // src/validators/executors.ts
1921
1987
  var SCHEME_SEPARATOR = "://";
@@ -3773,16 +3839,16 @@ function skipSpaces(src, idx) {
3773
3839
  }
3774
3840
  return i;
3775
3841
  }
3776
- function scanString(src, start, quote) {
3842
+ function scanString(src, start, quote2) {
3777
3843
  let i = start + 1;
3778
- const raw = quote === "`";
3844
+ const raw = quote2 === "`";
3779
3845
  while (i < src.length) {
3780
3846
  const c = src[i];
3781
3847
  if (!raw && c === "\\") {
3782
3848
  i += 2;
3783
3849
  continue;
3784
3850
  }
3785
- if (c === quote) return i;
3851
+ if (c === quote2) return i;
3786
3852
  i += 1;
3787
3853
  }
3788
3854
  return -1;
@@ -3879,6 +3945,117 @@ function splitPipeline(body) {
3879
3945
  return stages;
3880
3946
  }
3881
3947
 
3948
+ // src/template/gotmpl-default.ts
3949
+ var FUNCTION_NAME = "default";
3950
+ var PIPE = "|";
3951
+ var OPEN_PAREN = "(";
3952
+ var CLOSE_PAREN = ")";
3953
+ var MAX_NESTING_DEPTH = 100;
3954
+ var PIPELINE_KEYWORDS = /^(?:else\s+if|else\s+with|if|range|with|template|block)(?![\w$.])/;
3955
+ var NO_PIPELINE = /^(?:define|else|end|break|continue)(?![\w$.])/;
3956
+ var DECLARATION = /^\$\w*(?:\s*,\s*\$\w*)*\s*:?=/;
3957
+ function pipedDefaultPipelines(src) {
3958
+ const { actions, fatal } = lexActions(src);
3959
+ if (fatal) return [];
3960
+ const found = /* @__PURE__ */ new Set();
3961
+ try {
3962
+ for (const action of actions) {
3963
+ if (action.isComment || action.body === "") continue;
3964
+ scanAction(action.body, found);
3965
+ }
3966
+ } catch (err) {
3967
+ if (!(err instanceof RangeError)) throw err;
3968
+ return [];
3969
+ }
3970
+ return [...found].sort();
3971
+ }
3972
+ function scanAction(body, found) {
3973
+ let rest = body;
3974
+ const kw = PIPELINE_KEYWORDS.exec(rest);
3975
+ if (!kw && NO_PIPELINE.test(rest)) return;
3976
+ if (kw) {
3977
+ rest = rest.slice(kw[0].length).trimStart();
3978
+ const keyword = kw[0].replace(/\s+/g, " ");
3979
+ if (keyword === "template" || keyword === "block") {
3980
+ const q2 = rest[0];
3981
+ if (q2 === '"' || q2 === "`") {
3982
+ const end = scanString(rest, 0, q2);
3983
+ if (end === -1) return;
3984
+ rest = rest.slice(end + 1).trimStart();
3985
+ }
3986
+ }
3987
+ }
3988
+ if (rest === "") return;
3989
+ parsePipeline(rest, 0, false, found, 0);
3990
+ }
3991
+ function parsePipeline(src, start, nested, found, depth) {
3992
+ if (depth > MAX_NESTING_DEPTH) return { text: src.slice(start), end: src.length };
3993
+ let i = start;
3994
+ const n = src.length;
3995
+ let decl = "";
3996
+ const decMatch = DECLARATION.exec(src.slice(i));
3997
+ if (decMatch) {
3998
+ decl = decMatch[0].replace(/\s+/g, " ") + " ";
3999
+ i += decMatch[0].length;
4000
+ }
4001
+ const commands = [];
4002
+ let current = [];
4003
+ let firstWords = [];
4004
+ let pipedDefault = false;
4005
+ const pushCommand = () => {
4006
+ if (current.length > 0) {
4007
+ commands.push(current);
4008
+ firstWords.push(current[0]);
4009
+ }
4010
+ current = [];
4011
+ };
4012
+ while (i < n) {
4013
+ const c = src[i];
4014
+ if (c === " " || c === " " || c === "\n" || c === "\r") {
4015
+ i += 1;
4016
+ continue;
4017
+ }
4018
+ if (c === '"' || c === "`" || c === "'") {
4019
+ const end = scanString(src, i, c);
4020
+ const stop = end === -1 ? n - 1 : end;
4021
+ current.push(src.slice(i, stop + 1));
4022
+ i = stop + 1;
4023
+ continue;
4024
+ }
4025
+ if (c === OPEN_PAREN) {
4026
+ const inner = parsePipeline(src, i + 1, true, found, depth + 1);
4027
+ current.push(`(${inner.text})`);
4028
+ i = inner.end;
4029
+ continue;
4030
+ }
4031
+ if (c === CLOSE_PAREN) {
4032
+ if (nested) {
4033
+ i += 1;
4034
+ break;
4035
+ }
4036
+ i += 1;
4037
+ continue;
4038
+ }
4039
+ if (c === PIPE) {
4040
+ pushCommand();
4041
+ i += 1;
4042
+ continue;
4043
+ }
4044
+ let j = i;
4045
+ while (j < n && !/[\s|()"`']/.test(src[j])) j += 1;
4046
+ if (j === i) j = i + 1;
4047
+ current.push(src.slice(i, j));
4048
+ i = j;
4049
+ }
4050
+ pushCommand();
4051
+ firstWords.forEach((word, idx) => {
4052
+ if (idx > 0 && word === FUNCTION_NAME) pipedDefault = true;
4053
+ });
4054
+ const text = decl + commands.map((cmd) => cmd.join(" ")).join(` ${PIPE} `);
4055
+ if (pipedDefault) found.add(text);
4056
+ return { text, end: i };
4057
+ }
4058
+
3882
4059
  // src/validators/processingOperations.ts
3883
4060
  var PROCESSING_OP_GUARD_KEY = PROCESSING_OPERATIONS.guardKey;
3884
4061
  var PROCESSING_PHASES = ["pre_processing", "post_processing"];
@@ -4025,6 +4202,17 @@ function checkTemplateString(value, field, stepID, issues, stats, opts, demote =
4025
4202
  });
4026
4203
  }
4027
4204
  }
4205
+ if (errors.length > 0) return;
4206
+ for (const pipe of pipedDefaultPipelines(value)) {
4207
+ report({
4208
+ field,
4209
+ message: TEMPLATE_DEFAULT_PIPED.messageFormat.replace("%s", () => pipe),
4210
+ code: TEMPLATE_DEFAULT_PIPED.code,
4211
+ stepId: stepID,
4212
+ context: `Template: ${value}`,
4213
+ suggestion: TEMPLATE_DEFAULT_PIPED.suggestion
4214
+ });
4215
+ }
4028
4216
  }
4029
4217
  function walkStrings2(basePath, value, stepID, issues, stats, opts, check, demote = false) {
4030
4218
  if (isString(value)) {
@@ -4263,6 +4451,35 @@ function validateRetiredKeys(flow, issues) {
4263
4451
  }
4264
4452
  }
4265
4453
 
4454
+ // src/validators/flowDataKeys.ts
4455
+ var KEY_DATA = "data";
4456
+ function quote(key) {
4457
+ return JSON.stringify(key);
4458
+ }
4459
+ function flowDataKeyReservation(key) {
4460
+ if (Object.prototype.hasOwnProperty.call(FLOW_DATA_RESERVED_KEYS.exact, key)) {
4461
+ return FLOW_DATA_RESERVED_KEYS.exact[key];
4462
+ }
4463
+ for (const p of FLOW_DATA_RESERVED_KEYS.prefixes) {
4464
+ if (key.startsWith(p.prefix)) return p.reason;
4465
+ }
4466
+ return void 0;
4467
+ }
4468
+ function validateFlowDataKeys(flow, issues) {
4469
+ const data = flow[KEY_DATA];
4470
+ if (!isRecord(data)) return;
4471
+ for (const key of Object.keys(data).sort()) {
4472
+ const reason = flowDataKeyReservation(key);
4473
+ if (reason === void 0) continue;
4474
+ issues.error({
4475
+ field: FLOW_DATA_RESERVED_KEYS.fieldFormat.replace("%s", () => key),
4476
+ message: FLOW_DATA_RESERVED_KEYS.messageFormat.replace("%s", () => quote(key)).replace("%s", () => reason),
4477
+ code: FLOW_DATA_RESERVED_KEYS.code,
4478
+ suggestion: FLOW_DATA_RESERVED_KEYS.suggestion
4479
+ });
4480
+ }
4481
+ }
4482
+
4266
4483
  // src/validators/saveDoor.ts
4267
4484
  var TOOL_DISCOVERY_MODES = /* @__PURE__ */ new Set(["eager", "lazy", "off"]);
4268
4485
  var TEMPLATE_ACTION_OPEN2 = "{{";
@@ -6725,6 +6942,7 @@ function validateParsed(flow, opts, sources) {
6725
6942
  validateSaveDoorExtras(f, issues);
6726
6943
  validateExecutorConfigEnvScopes(f, issues);
6727
6944
  validateServerOwnedQueryKeys(f, issues);
6945
+ validateFlowDataKeys(f, issues);
6728
6946
  validateCredentialEndpointPairing(f, issues);
6729
6947
  validateExamples(f, issues);
6730
6948
  validateOrchestratorExons(f, issues);