jetic-cli 0.2.5 → 0.2.7

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.js CHANGED
@@ -269150,6 +269150,7 @@ var require_yaml_io = __commonJS({
269150
269150
  exports2.loadModelFromDir = loadModelFromDir5;
269151
269151
  exports2.readWorkflowDefinitionFile = readWorkflowDefinitionFile2;
269152
269152
  exports2.listWorkflowFiles = listWorkflowFiles2;
269153
+ exports2.loadWorkflowByName = loadWorkflowByName3;
269153
269154
  exports2.hasYamlModel = hasYamlModel5;
269154
269155
  exports2.loadModelCompat = loadModelCompat;
269155
269156
  var fs10 = __importStar(require("fs"));
@@ -269245,6 +269246,29 @@ var require_yaml_io = __commonJS({
269245
269246
  return [];
269246
269247
  return fs10.readdirSync(workflowsDir).filter((f) => f.endsWith(".json") || f.endsWith(".yaml") || f.endsWith(".yml")).sort();
269247
269248
  }
269249
+ function loadWorkflowByName3(workflowsDir, slugOrName) {
269250
+ const wanted = slugOrName.trim().toLowerCase();
269251
+ if (!wanted)
269252
+ return null;
269253
+ const files = listWorkflowFiles2(workflowsDir);
269254
+ const bySlug = files.find((f) => f.replace(/.(json|ya?ml)$/, "").toLowerCase() === wanted);
269255
+ if (bySlug) {
269256
+ try {
269257
+ return readWorkflowDefinitionFile2(path15.join(workflowsDir, bySlug));
269258
+ } catch {
269259
+ return null;
269260
+ }
269261
+ }
269262
+ for (const file of files) {
269263
+ try {
269264
+ const def = readWorkflowDefinitionFile2(path15.join(workflowsDir, file));
269265
+ if (def?.name && def.name.trim().toLowerCase() === wanted)
269266
+ return def;
269267
+ } catch {
269268
+ }
269269
+ }
269270
+ return null;
269271
+ }
269248
269272
  function hasYamlModel5(modelDir) {
269249
269273
  return fs10.existsSync(path15.join(modelDir, "api.yaml"));
269250
269274
  }
@@ -272353,12 +272377,15 @@ var require_dist12 = __commonJS({
272353
272377
  exports2.JeticMemory = void 0;
272354
272378
  var fs10 = __importStar(require("fs"));
272355
272379
  var path15 = __importStar(require("path"));
272380
+ function memoryFilePath(projectRoot) {
272381
+ return path15.join(projectRoot ?? process.cwd(), ".jetic", "memory.json");
272382
+ }
272356
272383
  var JeticMemory4 = class {
272357
272384
  scope;
272358
272385
  filePath;
272359
272386
  constructor(options) {
272360
272387
  this.scope = options.scope;
272361
- this.filePath = path15.join(process.cwd(), ".jetic", "memory.json");
272388
+ this.filePath = memoryFilePath(options.projectRoot);
272362
272389
  }
272363
272390
  ensureDirectory() {
272364
272391
  const dir = path15.dirname(this.filePath);
@@ -272451,8 +272478,8 @@ var require_dist12 = __commonJS({
272451
272478
  this.writeData(data);
272452
272479
  }
272453
272480
  }
272454
- static getAllMemory() {
272455
- const filePath = path15.join(process.cwd(), ".jetic", "memory.json");
272481
+ static getAllMemory(projectRoot) {
272482
+ const filePath = memoryFilePath(projectRoot);
272456
272483
  if (!fs10.existsSync(filePath))
272457
272484
  return {};
272458
272485
  try {
@@ -272474,8 +272501,8 @@ var require_dist12 = __commonJS({
272474
272501
  return {};
272475
272502
  }
272476
272503
  }
272477
- static clearAllMemory() {
272478
- const filePath = path15.join(process.cwd(), ".jetic", "memory.json");
272504
+ static clearAllMemory(projectRoot) {
272505
+ const filePath = memoryFilePath(projectRoot);
272479
272506
  if (fs10.existsSync(filePath)) {
272480
272507
  fs10.unlinkSync(filePath);
272481
272508
  }
@@ -272485,6 +272512,158 @@ var require_dist12 = __commonJS({
272485
272512
  }
272486
272513
  });
272487
272514
 
272515
+ // ../../packages/simulator/dist/conditions.js
272516
+ var require_conditions = __commonJS({
272517
+ "../../packages/simulator/dist/conditions.js"(exports2) {
272518
+ "use strict";
272519
+ init_cjs_shims();
272520
+ Object.defineProperty(exports2, "__esModule", { value: true });
272521
+ exports2.UNARY_OPERATORS = exports2.CONDITION_OPERATORS = void 0;
272522
+ exports2.conditionKind = conditionKind3;
272523
+ exports2.isRulesCondition = isRulesCondition3;
272524
+ exports2.isLegacyCondition = isLegacyCondition;
272525
+ exports2.describeInvalidCondition = describeInvalidCondition3;
272526
+ exports2.evaluateRules = evaluateRules3;
272527
+ exports2.evaluateLegacyCondition = evaluateLegacyCondition3;
272528
+ exports2.CONDITION_OPERATORS = [
272529
+ "equals",
272530
+ "not_equals",
272531
+ "greater_than",
272532
+ "greater_than_or_equal",
272533
+ "less_than",
272534
+ "less_than_or_equal",
272535
+ "exists",
272536
+ "not_exists",
272537
+ "is_empty",
272538
+ "is_not_empty",
272539
+ "contains",
272540
+ "not_contains",
272541
+ "starts_with",
272542
+ "ends_with"
272543
+ ];
272544
+ exports2.UNARY_OPERATORS = ["exists", "not_exists", "is_empty", "is_not_empty"];
272545
+ function isObject(value) {
272546
+ return typeof value === "object" && value !== null && !Array.isArray(value);
272547
+ }
272548
+ function conditionKind3(condition) {
272549
+ if (!isObject(condition))
272550
+ return "invalid";
272551
+ if (isObject(condition.rules))
272552
+ return "rules";
272553
+ if (typeof condition.if === "string")
272554
+ return "legacy";
272555
+ return "invalid";
272556
+ }
272557
+ function isRulesCondition3(condition) {
272558
+ return conditionKind3(condition) === "rules";
272559
+ }
272560
+ function isLegacyCondition(condition) {
272561
+ return conditionKind3(condition) === "legacy";
272562
+ }
272563
+ function describeInvalidCondition3(condition) {
272564
+ if (!isObject(condition))
272565
+ return "condition must be an object";
272566
+ const keys = Object.keys(condition);
272567
+ return `unrecognized condition (keys: ${keys.join(", ") || "none"}). Use { "rules": { "all": [{ "left", "operator", "right" }] }, "onFail": "abort|continue|switch" }`;
272568
+ }
272569
+ async function evaluateRule(rule, resolve2) {
272570
+ const left = await resolve2(String(rule.left ?? ""));
272571
+ const right = rule.right !== void 0 ? await resolve2(String(rule.right)) : void 0;
272572
+ switch (rule.operator) {
272573
+ case "equals":
272574
+ return { ok: left === right };
272575
+ case "not_equals":
272576
+ return { ok: left !== right };
272577
+ case "greater_than":
272578
+ return { ok: Number(left) > Number(right) };
272579
+ case "greater_than_or_equal":
272580
+ return { ok: Number(left) >= Number(right) };
272581
+ case "less_than":
272582
+ return { ok: Number(left) < Number(right) };
272583
+ case "less_than_or_equal":
272584
+ return { ok: Number(left) <= Number(right) };
272585
+ case "exists":
272586
+ return { ok: left !== "" && left !== void 0 };
272587
+ case "not_exists":
272588
+ return { ok: left === "" || left === void 0 };
272589
+ case "is_empty":
272590
+ return { ok: left === "" };
272591
+ case "is_not_empty":
272592
+ return { ok: left !== "" };
272593
+ case "contains":
272594
+ return { ok: right !== void 0 && left.includes(right) };
272595
+ case "not_contains":
272596
+ return { ok: right !== void 0 && !left.includes(right) };
272597
+ case "starts_with":
272598
+ return { ok: right !== void 0 && left.startsWith(right) };
272599
+ case "ends_with":
272600
+ return { ok: right !== void 0 && left.endsWith(right) };
272601
+ default:
272602
+ return { ok: false, unknownOperator: true };
272603
+ }
272604
+ }
272605
+ function describeRule(rule) {
272606
+ return `${rule.left} ${rule.operator}${rule.right !== void 0 ? ` ${rule.right}` : ""}`;
272607
+ }
272608
+ async function evaluateRules3(group, resolve2) {
272609
+ const all = Array.isArray(group.all) ? group.all : void 0;
272610
+ const any = Array.isArray(group.any) ? group.any : void 0;
272611
+ if ((!all || all.length === 0) && (!any || any.length === 0)) {
272612
+ return { passed: true, reason: "No rules defined" };
272613
+ }
272614
+ const failing = [];
272615
+ let passed = true;
272616
+ if (all && all.length > 0) {
272617
+ const results = [];
272618
+ for (const rule of all)
272619
+ results.push({ rule, ...await evaluateRule(rule, resolve2) });
272620
+ const bad = results.filter((r) => !r.ok);
272621
+ if (bad.length > 0) {
272622
+ passed = false;
272623
+ failing.push(...bad.map((r) => r.unknownOperator ? `${describeRule(r.rule)} (unknown operator)` : describeRule(r.rule)));
272624
+ }
272625
+ }
272626
+ if (any && any.length > 0) {
272627
+ const results = [];
272628
+ for (const rule of any)
272629
+ results.push({ rule, ...await evaluateRule(rule, resolve2) });
272630
+ if (!results.some((r) => r.ok)) {
272631
+ passed = false;
272632
+ failing.push(`none of: ${results.map((r) => describeRule(r.rule)).join(" | ")}`);
272633
+ }
272634
+ }
272635
+ const mode = all && any ? "AND+OR" : all ? "AND" : "OR";
272636
+ return {
272637
+ passed,
272638
+ reason: passed ? `All conditions met (${mode})` : `Failed: ${failing.join(", ")}`
272639
+ };
272640
+ }
272641
+ async function evaluateLegacyCondition3(condition, resolve2) {
272642
+ const evaluated = await resolve2(condition.if);
272643
+ let matched;
272644
+ if (condition.exists !== void 0) {
272645
+ const hasValue = evaluated !== "" && evaluated !== "null" && evaluated !== "undefined";
272646
+ matched = condition.exists ? hasValue : !hasValue;
272647
+ } else if (condition.equals !== void 0) {
272648
+ matched = evaluated === condition.equals;
272649
+ } else if (condition.notEquals !== void 0) {
272650
+ matched = evaluated !== condition.notEquals;
272651
+ } else if (condition.contains !== void 0) {
272652
+ matched = evaluated.includes(condition.contains);
272653
+ } else if (condition.greaterThan !== void 0) {
272654
+ matched = Number(evaluated) > condition.greaterThan;
272655
+ } else if (condition.lessThan !== void 0) {
272656
+ matched = Number(evaluated) < condition.lessThan;
272657
+ } else if (condition.in !== void 0) {
272658
+ matched = condition.in.includes(evaluated);
272659
+ } else {
272660
+ matched = evaluated !== "" && evaluated !== "false" && evaluated !== "0" && evaluated !== "null" && evaluated !== "undefined";
272661
+ }
272662
+ return { matched, evaluated };
272663
+ }
272664
+ }
272665
+ });
272666
+
272488
272667
  // ../../packages/simulator/dist/workflow-simulator.js
272489
272668
  var require_workflow_simulator = __commonJS({
272490
272669
  "../../packages/simulator/dist/workflow-simulator.js"(exports2) {
@@ -272494,6 +272673,22 @@ var require_workflow_simulator = __commonJS({
272494
272673
  exports2.WorkflowSimulator = void 0;
272495
272674
  var memory_1 = require_dist12();
272496
272675
  var faker_1 = require("@faker-js/faker");
272676
+ var conditions_1 = require_conditions();
272677
+ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
272678
+ "ECONNREFUSED",
272679
+ "ECONNRESET",
272680
+ "ENOTFOUND",
272681
+ "EAI_AGAIN",
272682
+ "ETIMEDOUT",
272683
+ "EHOSTUNREACH",
272684
+ "ENETUNREACH",
272685
+ "UND_ERR_CONNECT_TIMEOUT",
272686
+ "UND_ERR_SOCKET"
272687
+ ]);
272688
+ function networkErrorCode(error) {
272689
+ const raw = error?.cause?.code ?? error?.cause?.errno ?? error?.code ?? error?.errno;
272690
+ return typeof raw === "string" ? raw : void 0;
272691
+ }
272497
272692
  function deepGet2(obj, dotPath) {
272498
272693
  if (obj === null || obj === void 0)
272499
272694
  return void 0;
@@ -272509,27 +272704,120 @@ var require_workflow_simulator = __commonJS({
272509
272704
  var WorkflowSimulator2 = class {
272510
272705
  model;
272511
272706
  environment;
272512
- constructor(model, environment) {
272707
+ runtime;
272708
+ strictTemplates = true;
272709
+ requestTimeoutMs = 3e4;
272710
+ constructor(model, environment, runtime = {}) {
272513
272711
  this.model = model;
272514
272712
  this.environment = environment;
272713
+ this.runtime = runtime;
272714
+ }
272715
+ memory(scope) {
272716
+ return new memory_1.JeticMemory({ scope, projectRoot: this.runtime.projectRoot });
272515
272717
  }
272516
272718
  async simulateWorkflow(workflow, options) {
272517
272719
  if (options?.clearMemory) {
272518
- memory_1.JeticMemory.clearAllMemory();
272720
+ memory_1.JeticMemory.clearAllMemory(this.runtime.projectRoot);
272519
272721
  }
272520
- const baseUrl = this.environment.baseUrl;
272722
+ this.strictTemplates = options?.strictTemplates ?? true;
272723
+ this.requestTimeoutMs = options?.requestTimeoutMs ?? 3e4;
272521
272724
  const startTime = Date.now();
272522
- const stepResults = [];
272523
- let allPassed = true;
272725
+ const preflightMode = options?.preflight ?? "off";
272726
+ const preflight = preflightMode !== "off" && workflow.steps.length > 0 ? await this.runPreflight(this.environment.baseUrl, options?.preflightTimeoutMs ?? 3e3) : void 0;
272727
+ if (preflight && preflightMode === "abort" && preflight.verdict === "unreachable") {
272728
+ preflight.aborted = true;
272729
+ return {
272730
+ success: false,
272731
+ workflow: {
272732
+ name: workflow.name,
272733
+ description: workflow.description,
272734
+ stepsCount: workflow.steps.length
272735
+ },
272736
+ environment: {
272737
+ name: this.environment.name,
272738
+ baseUrl: this.environment.baseUrl
272739
+ },
272740
+ stats: {
272741
+ totalSteps: workflow.steps.length,
272742
+ executedSteps: 0,
272743
+ passedSteps: 0,
272744
+ failedSteps: 0,
272745
+ totalTimeMs: Date.now() - startTime
272746
+ },
272747
+ results: [],
272748
+ preflight,
272749
+ abortReason: preflight.message
272750
+ };
272751
+ }
272752
+ const state = { results: [], allPassed: true, halted: false };
272753
+ await this.runSteps(workflow, options, state, [workflow.name]);
272754
+ const stepResults = state.results;
272755
+ const totalTimeMs = Date.now() - startTime;
272756
+ return {
272757
+ success: state.allPassed,
272758
+ workflow: {
272759
+ name: workflow.name,
272760
+ description: workflow.description,
272761
+ stepsCount: workflow.steps.length
272762
+ },
272763
+ environment: {
272764
+ name: this.environment.name,
272765
+ baseUrl: this.environment.baseUrl
272766
+ },
272767
+ stats: {
272768
+ totalSteps: workflow.steps.length,
272769
+ executedSteps: stepResults.length,
272770
+ passedSteps: stepResults.filter((r) => r.passed).length,
272771
+ failedSteps: stepResults.filter((r) => !r.passed).length,
272772
+ totalTimeMs
272773
+ },
272774
+ results: stepResults,
272775
+ ...preflight ? { preflight } : {}
272776
+ };
272777
+ }
272778
+ /**
272779
+ * Runs one workflow's steps, appending to `state`. Returns `'stop'` when the remaining steps
272780
+ * of THIS workflow must not run (failure, `onFail: continue|switch`), else `'done'`.
272781
+ * `state.halted` is set only for failures, so a caller resuming after a switch knows whether to go on.
272782
+ */
272783
+ async runSteps(workflow, options, state, lineage) {
272784
+ const baseUrl = this.environment.baseUrl;
272785
+ const resolve2 = (value) => this.resolveTemplateString(value, false);
272524
272786
  for (let i = 0; i < workflow.steps.length; i++) {
272525
272787
  const step = workflow.steps[i];
272526
272788
  const stepStartTime = Date.now();
272527
- if (step.condition) {
272528
- const condResult = await this.evaluateCondition(step.condition);
272789
+ const stepName = step.name || `${step.method} ${step.path}`;
272790
+ const kind = step.condition === void 0 ? void 0 : (0, conditions_1.conditionKind)(step.condition);
272791
+ if (kind === "invalid") {
272792
+ const reason = (0, conditions_1.describeInvalidCondition)(step.condition);
272793
+ state.results.push({
272794
+ stepIndex: i + 1,
272795
+ name: stepName,
272796
+ method: step.method,
272797
+ path: step.path,
272798
+ targetUrl: "",
272799
+ expectedStatus: step.expectStatus ?? 200,
272800
+ actualStatus: 0,
272801
+ durationMs: Date.now() - stepStartTime,
272802
+ passed: false,
272803
+ skipped: false,
272804
+ retryCount: 0,
272805
+ requestHeaders: {},
272806
+ requestBody: {},
272807
+ responseBody: null,
272808
+ capturedMemory: [],
272809
+ error: `Step ${i + 1} "${stepName}": ${reason}`
272810
+ });
272811
+ state.allPassed = false;
272812
+ state.halted = true;
272813
+ return "stop";
272814
+ }
272815
+ if (kind === "legacy") {
272816
+ const condResult = await (0, conditions_1.evaluateLegacyCondition)(step.condition, resolve2);
272529
272817
  if (!condResult.matched) {
272530
- stepResults.push({
272818
+ state.results.push({
272531
272819
  stepIndex: i + 1,
272532
- name: step.name || `${step.method} ${step.path}`,
272820
+ name: stepName,
272533
272821
  method: step.method,
272534
272822
  path: step.path,
272535
272823
  targetUrl: "",
@@ -272548,12 +272836,44 @@ var require_workflow_simulator = __commonJS({
272548
272836
  continue;
272549
272837
  }
272550
272838
  }
272551
- const { headers: injectedHeaders, body: injectedBody } = await this.resolveInjections(step.inject);
272552
- const resolvedBody = await this.resolveBodyTemplates(step.body || {});
272553
- const finalBody = { ...injectedBody, ...resolvedBody };
272554
- const inputCaptured = await this.captureInputToMemory(step.captureInput, finalBody);
272555
- const resolvedPath = await this.resolvePathParams(step.path, finalBody);
272556
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
272839
+ let injectedHeaders = {};
272840
+ let finalBody = {};
272841
+ let resolvedQuery = {};
272842
+ let resolvedPath = step.path;
272843
+ let fullUrl = "";
272844
+ let inputCaptured = [];
272845
+ try {
272846
+ const injections = await this.resolveInjections(step.inject);
272847
+ injectedHeaders = injections.headers;
272848
+ const resolvedBody = await this.resolveBodyTemplates(step.body || {});
272849
+ finalBody = { ...injections.body, ...resolvedBody };
272850
+ resolvedQuery = await this.resolveBodyTemplates(step.query || {});
272851
+ inputCaptured = await this.captureInputToMemory(step.captureInput, finalBody);
272852
+ resolvedPath = await this.resolvePathParams(step.path, finalBody);
272853
+ fullUrl = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
272854
+ } catch (err) {
272855
+ state.results.push({
272856
+ stepIndex: i + 1,
272857
+ name: stepName,
272858
+ method: step.method,
272859
+ path: step.path,
272860
+ targetUrl: "",
272861
+ expectedStatus: step.expectStatus ?? 200,
272862
+ actualStatus: 0,
272863
+ durationMs: Date.now() - stepStartTime,
272864
+ passed: false,
272865
+ skipped: false,
272866
+ retryCount: 0,
272867
+ requestHeaders: {},
272868
+ requestBody: {},
272869
+ responseBody: null,
272870
+ capturedMemory: [],
272871
+ error: `Step ${i + 1} "${stepName}": ${err?.message || err}`
272872
+ });
272873
+ state.allPassed = false;
272874
+ state.halted = true;
272875
+ return "stop";
272876
+ }
272557
272877
  const expectedStatus = step.expectStatus ?? 200;
272558
272878
  const maxAttempts = Math.max(1, step.retry?.times ?? workflow.retry?.times ?? 1);
272559
272879
  const retryDelayMs = step.retry?.delayMs ?? workflow.retry?.delayMs ?? 0;
@@ -272562,6 +272882,7 @@ var require_workflow_simulator = __commonJS({
272562
272882
  let responseBody = null;
272563
272883
  let stepPassed = false;
272564
272884
  let errorMsg;
272885
+ let connectionCode;
272565
272886
  while (attempts < maxAttempts) {
272566
272887
  attempts++;
272567
272888
  if (attempts > 1 && retryDelayMs > 0) {
@@ -272570,22 +272891,42 @@ var require_workflow_simulator = __commonJS({
272570
272891
  responseStatus = 0;
272571
272892
  responseBody = null;
272572
272893
  errorMsg = void 0;
272894
+ connectionCode = void 0;
272895
+ const timeoutMs = step.timeoutMs ?? this.requestTimeoutMs;
272573
272896
  try {
272897
+ const upperMethod = step.method.toUpperCase();
272574
272898
  const fetchOptions = {
272575
- method: step.method.toUpperCase(),
272899
+ method: upperMethod,
272576
272900
  headers: { "Content-Type": "application/json", ...injectedHeaders }
272577
272901
  };
272578
- if (!["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
272902
+ if (!["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0) {
272579
272903
  fetchOptions.body = JSON.stringify(finalBody);
272580
272904
  }
272905
+ if (timeoutMs > 0) {
272906
+ fetchOptions.signal = AbortSignal.timeout(timeoutMs);
272907
+ }
272581
272908
  let finalUrl = fullUrl;
272582
- if (["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
272909
+ const bodyAsQuery = ["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0;
272910
+ if (Object.keys(resolvedQuery).length > 0 || bodyAsQuery) {
272583
272911
  const params = new URLSearchParams();
272584
- for (const [k, v] of Object.entries(finalBody)) {
272585
- if (v !== void 0 && v !== null)
272586
- params.set(k, String(v));
272587
- }
272588
- finalUrl = `${fullUrl}?${params.toString()}`;
272912
+ const append = (source) => {
272913
+ for (const [k, v] of Object.entries(source)) {
272914
+ if (v === void 0 || v === null)
272915
+ continue;
272916
+ if (Array.isArray(v)) {
272917
+ for (const item of v)
272918
+ params.append(k, typeof item === "object" ? JSON.stringify(item) : String(item));
272919
+ } else {
272920
+ params.append(k, typeof v === "object" ? JSON.stringify(v) : String(v));
272921
+ }
272922
+ }
272923
+ };
272924
+ append(resolvedQuery);
272925
+ if (bodyAsQuery)
272926
+ append(finalBody);
272927
+ const qs = params.toString();
272928
+ if (qs)
272929
+ finalUrl = `${fullUrl}${fullUrl.includes("?") ? "&" : "?"}${qs}`;
272589
272930
  }
272590
272931
  const resp = await fetch(finalUrl, fetchOptions);
272591
272932
  responseStatus = resp.status;
@@ -272605,151 +272946,206 @@ var require_workflow_simulator = __commonJS({
272605
272946
  break;
272606
272947
  } catch (err) {
272607
272948
  stepPassed = false;
272608
- errorMsg = err.message || String(err);
272949
+ connectionCode = networkErrorCode(err);
272950
+ if (err?.name === "TimeoutError" || connectionCode === "ETIMEDOUT") {
272951
+ errorMsg = `Request timed out after ${timeoutMs}ms (${step.method} ${resolvedPath}).`;
272952
+ } else if (connectionCode === "ECONNREFUSED") {
272953
+ errorMsg = `Connection refused (ECONNREFUSED) at ${baseUrl} \u2014 the server is not accepting connections.`;
272954
+ } else {
272955
+ errorMsg = err?.message || String(err);
272956
+ }
272609
272957
  }
272610
272958
  }
272611
272959
  const responseCaptured = stepPassed ? await this.captureToMemory(step.capture, responseBody) : [];
272612
- if (!stepPassed) {
272613
- allPassed = false;
272614
- const onFailure = step.onFailure ?? "abort";
272615
- stepResults.push({
272616
- stepIndex: i + 1,
272617
- name: step.name || `${step.method} ${step.path}`,
272618
- method: step.method,
272619
- path: step.path,
272620
- targetUrl: fullUrl,
272621
- expectedStatus,
272622
- actualStatus: responseStatus,
272623
- durationMs: Date.now() - stepStartTime,
272624
- passed: false,
272625
- skipped: false,
272626
- retryCount: attempts - 1,
272627
- failureHandling: onFailure === "continue" ? "continued" : "aborted",
272628
- requestHeaders: injectedHeaders,
272629
- requestBody: finalBody,
272630
- responseBody,
272631
- capturedMemory: [...inputCaptured],
272632
- error: errorMsg
272633
- });
272634
- if (onFailure === "abort")
272635
- break;
272636
- continue;
272637
- }
272638
- stepResults.push({
272960
+ const onFailure = step.onFailure ?? "abort";
272961
+ const result = {
272639
272962
  stepIndex: i + 1,
272640
- name: step.name || `${step.method} ${step.path}`,
272963
+ name: stepName,
272641
272964
  method: step.method,
272642
272965
  path: step.path,
272643
272966
  targetUrl: fullUrl,
272644
272967
  expectedStatus,
272645
272968
  actualStatus: responseStatus,
272646
272969
  durationMs: Date.now() - stepStartTime,
272647
- passed: true,
272970
+ passed: stepPassed,
272648
272971
  skipped: false,
272649
272972
  retryCount: attempts - 1,
272650
272973
  requestHeaders: injectedHeaders,
272651
272974
  requestBody: finalBody,
272975
+ ...Object.keys(resolvedQuery).length > 0 ? { requestQuery: resolvedQuery } : {},
272652
272976
  responseBody,
272653
- capturedMemory: [...inputCaptured, ...responseCaptured]
272654
- });
272655
- }
272656
- const totalTimeMs = Date.now() - startTime;
272657
- return {
272658
- success: allPassed,
272659
- workflow: {
272660
- name: workflow.name,
272661
- description: workflow.description,
272662
- stepsCount: workflow.steps.length
272663
- },
272664
- environment: {
272665
- name: this.environment.name,
272666
- baseUrl
272667
- },
272668
- stats: {
272669
- totalSteps: workflow.steps.length,
272670
- executedSteps: stepResults.length,
272671
- passedSteps: stepResults.filter((r) => r.passed).length,
272672
- failedSteps: stepResults.filter((r) => !r.passed).length,
272673
- totalTimeMs
272674
- },
272675
- results: stepResults
272676
- };
272677
- }
272678
- // ── Condition evaluator ────────────────────────────────────────────────────
272679
- // Resolves the `if` template and applies the declared operator. Returns
272680
- // `matched: true` when the condition passes (step should execute).
272681
- async evaluateCondition(condition) {
272682
- const evaluated = await this.resolveTemplateString(condition.if);
272683
- let matched;
272684
- if (condition.exists !== void 0) {
272685
- const hasValue = evaluated !== "" && evaluated !== "null" && evaluated !== "undefined";
272686
- matched = condition.exists ? hasValue : !hasValue;
272687
- } else if (condition.equals !== void 0) {
272688
- matched = evaluated === condition.equals;
272689
- } else if (condition.notEquals !== void 0) {
272690
- matched = evaluated !== condition.notEquals;
272691
- } else if (condition.contains !== void 0) {
272692
- matched = evaluated.includes(condition.contains);
272693
- } else if (condition.greaterThan !== void 0) {
272694
- matched = Number(evaluated) > condition.greaterThan;
272695
- } else if (condition.lessThan !== void 0) {
272696
- matched = Number(evaluated) < condition.lessThan;
272697
- } else if (condition.in !== void 0) {
272698
- matched = condition.in.includes(evaluated);
272699
- } else {
272700
- matched = evaluated !== "" && evaluated !== "false" && evaluated !== "0" && evaluated !== "null" && evaluated !== "undefined";
272701
- }
272702
- return { matched, evaluated };
272703
- }
272704
- async resolveTemplateString(value) {
272705
- const templateRe = /\{\{([^}]+)\}\}/g;
272706
- let match;
272707
- const replacements = [];
272708
- while ((match = templateRe.exec(value)) !== null) {
272709
- const expr = match[1].trim();
272710
- if (expr.includes(":") && !expr.startsWith("faker.")) {
272711
- const colonIdx = expr.indexOf(":");
272712
- const scope = expr.slice(0, colonIdx);
272713
- const key = expr.slice(colonIdx + 1);
272714
- if (scope === "human") {
272715
- const envVal = process.env[`JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`];
272716
- if (envVal !== void 0 && envVal !== "") {
272717
- replacements.push({ placeholder: match[0], resolved: envVal });
272718
- continue;
272719
- }
272977
+ capturedMemory: stepPassed ? [...inputCaptured, ...responseCaptured] : [...inputCaptured],
272978
+ ...stepPassed ? {} : {
272979
+ failureHandling: onFailure === "continue" ? "continued" : "aborted",
272980
+ error: errorMsg
272720
272981
  }
272721
- const mem = new memory_1.JeticMemory({ scope });
272722
- const memVal = await mem.get(key);
272723
- replacements.push({ placeholder: match[0], resolved: memVal != null ? String(memVal) : "" });
272982
+ };
272983
+ state.results.push(result);
272984
+ if (!stepPassed) {
272985
+ state.allPassed = false;
272986
+ if (connectionCode && CONNECTION_ERROR_CODES.has(connectionCode)) {
272987
+ const remaining = workflow.steps.length - (i + 1);
272988
+ result.failureHandling = "aborted";
272989
+ result.error = `${result.error || "Connection failed"}; aborting early because the target is unreachable${remaining > 0 ? ` (${remaining} step(s) not executed)` : ""}.`;
272990
+ state.halted = true;
272991
+ return "stop";
272992
+ }
272993
+ if (onFailure === "abort") {
272994
+ state.halted = true;
272995
+ return "stop";
272996
+ }
272997
+ }
272998
+ if (kind === "rules") {
272999
+ const condition = step.condition;
273000
+ const outcome = await (0, conditions_1.evaluateRules)(condition.rules, resolve2);
273001
+ result.conditionResult = { evaluated: outcome.reason, matched: outcome.passed };
273002
+ if (outcome.passed)
273003
+ continue;
273004
+ result.conditionOutcome = condition.onFail;
273005
+ const fail = (message) => {
273006
+ result.passed = false;
273007
+ result.error = result.error ? `${result.error}; ${message}` : message;
273008
+ state.allPassed = false;
273009
+ state.halted = true;
273010
+ return "stop";
273011
+ };
273012
+ if (condition.onFail === "continue")
273013
+ return "stop";
273014
+ if (condition.onFail !== "switch")
273015
+ return fail(`Condition failed at step ${i + 1}: ${outcome.reason}`);
273016
+ const target = condition.switchToWorkflow;
273017
+ if (!target)
273018
+ return fail(`Condition failed at step ${i + 1} (${outcome.reason}) and onFail "switch" has no switchToWorkflow`);
273019
+ if (!options?.loadWorkflow) {
273020
+ return fail(`Condition failed at step ${i + 1} (${outcome.reason}); cannot switch to "${target}": this runner was not given a workflow loader`);
273021
+ }
273022
+ const switched = await options.loadWorkflow(target);
273023
+ if (!switched)
273024
+ return fail(`Condition failed at step ${i + 1} (${outcome.reason}); switch target workflow "${target}" was not found`);
273025
+ if (lineage.includes(switched.name)) {
273026
+ return fail(`Condition failed at step ${i + 1}; switching to "${switched.name}" would loop (${[...lineage, switched.name].join(" \u2192 ")})`);
273027
+ }
273028
+ await this.runSteps(switched, options, state, [...lineage, switched.name]);
273029
+ if (state.halted || !condition.returnOnComplete)
273030
+ return "stop";
273031
+ }
273032
+ }
273033
+ return "done";
273034
+ }
273035
+ findPlaceholders(value) {
273036
+ const placeholders = [];
273037
+ let i = 0;
273038
+ while (i < value.length - 1) {
273039
+ if (value[i] !== "{" || value[i + 1] !== "{") {
273040
+ i++;
272724
273041
  continue;
272725
273042
  }
272726
- if (expr.startsWith("faker.")) {
272727
- const parts = expr.split(".");
272728
- try {
272729
- let fn = faker_1.faker;
272730
- for (const part of parts.slice(1))
272731
- fn = fn[part];
272732
- const generated = typeof fn === "function" ? fn() : fn;
272733
- replacements.push({ placeholder: match[0], resolved: String(generated) });
272734
- } catch {
272735
- replacements.push({ placeholder: match[0], resolved: expr });
273043
+ let depth = 0;
273044
+ let closed = -1;
273045
+ let j = i + 2;
273046
+ while (j < value.length) {
273047
+ const ch = value[j];
273048
+ if (ch === "{")
273049
+ depth++;
273050
+ else if (ch === "}") {
273051
+ if (depth > 0)
273052
+ depth--;
273053
+ else if (value[j + 1] === "}") {
273054
+ closed = j;
273055
+ break;
273056
+ }
272736
273057
  }
272737
- continue;
273058
+ j++;
272738
273059
  }
272739
- replacements.push({ placeholder: match[0], resolved: match[0] });
273060
+ if (closed === -1)
273061
+ break;
273062
+ placeholders.push({ raw: value.slice(i, closed + 2), expr: value.slice(i + 2, closed).trim(), index: i });
273063
+ i = closed + 2;
273064
+ }
273065
+ return placeholders;
273066
+ }
273067
+ async resolveTemplateString(value, strict = this.strictTemplates) {
273068
+ const placeholders = this.findPlaceholders(value);
273069
+ if (placeholders.length === 0)
273070
+ return value;
273071
+ let result = "";
273072
+ let cursor = 0;
273073
+ for (const placeholder of placeholders) {
273074
+ result += value.slice(cursor, placeholder.index);
273075
+ result += await this.resolveExpression(placeholder.expr, strict, placeholder.raw);
273076
+ cursor = placeholder.index + placeholder.raw.length;
272740
273077
  }
272741
- let result = value;
272742
- for (const { placeholder, resolved } of replacements) {
272743
- result = result.replace(placeholder, resolved);
273078
+ return result + value.slice(cursor);
273079
+ }
273080
+ async resolveExpression(expr, strict, raw) {
273081
+ if (expr.startsWith("faker.")) {
273082
+ return this.resolveFakerExpression(expr, strict, raw);
273083
+ }
273084
+ if (expr.includes(":")) {
273085
+ const colonIdx = expr.indexOf(":");
273086
+ const scope = expr.slice(0, colonIdx);
273087
+ const key = expr.slice(colonIdx + 1);
273088
+ const envKey = `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
273089
+ if (scope === "human") {
273090
+ const envVal = process.env[envKey];
273091
+ if (envVal !== void 0 && envVal !== "")
273092
+ return envVal;
273093
+ }
273094
+ const memVal = await this.memory(scope).get(key);
273095
+ if (memVal !== null && memVal !== void 0) {
273096
+ return typeof memVal === "string" ? memVal : JSON.stringify(memVal);
273097
+ }
273098
+ if (!strict)
273099
+ return "";
273100
+ if (scope === "human") {
273101
+ throw new Error(`missing human input ${raw} \u2014 set ${envKey} or seed "human:${key}" memory (non-interactive runners cannot prompt)`);
273102
+ }
273103
+ throw new Error(`unresolved template ${raw} \u2014 no value for "${scope}:${key}"; capture it in an earlier step or seed it first`);
273104
+ }
273105
+ return raw;
273106
+ }
273107
+ resolveFakerExpression(expr, strict, raw) {
273108
+ if (/[({]/.test(expr)) {
273109
+ if (!strict)
273110
+ return raw;
273111
+ throw new Error(`unsupported faker expression ${raw} \u2014 parameterized calls are not supported; use a parameterless path like {{faker.internet.email}} or a literal value`);
273112
+ }
273113
+ let fn = faker_1.faker;
273114
+ for (const part of expr.split(".").slice(1))
273115
+ fn = fn?.[part];
273116
+ if (typeof fn !== "function") {
273117
+ if (!strict)
273118
+ return raw;
273119
+ throw new Error(`unknown faker path ${raw} \u2014 use a complete parameterless path like {{faker.internet.email}} or {{faker.string.uuid}}`);
273120
+ }
273121
+ try {
273122
+ return String(fn());
273123
+ } catch (err) {
273124
+ if (!strict)
273125
+ return raw;
273126
+ throw new Error(`faker expression ${raw} failed: ${err?.message || err} \u2014 this faker path needs arguments; parameterized faker calls are not supported`);
272744
273127
  }
272745
- return result;
272746
273128
  }
272747
273129
  async resolveBodyTemplates(body) {
272748
- const resolved = {};
272749
- for (const [key, value] of Object.entries(body)) {
272750
- resolved[key] = typeof value === "string" ? await this.resolveTemplateString(value) : value;
273130
+ const resolved = await this.resolveDeep(body);
273131
+ return resolved && typeof resolved === "object" && !Array.isArray(resolved) ? resolved : {};
273132
+ }
273133
+ async resolveDeep(value) {
273134
+ if (typeof value === "string")
273135
+ return this.resolveTemplateString(value);
273136
+ if (Array.isArray(value)) {
273137
+ const out = [];
273138
+ for (const item of value)
273139
+ out.push(await this.resolveDeep(item));
273140
+ return out;
273141
+ }
273142
+ if (value && typeof value === "object") {
273143
+ const out = {};
273144
+ for (const [key, item] of Object.entries(value))
273145
+ out[key] = await this.resolveDeep(item);
273146
+ return out;
272751
273147
  }
272752
- return resolved;
273148
+ return value;
272753
273149
  }
272754
273150
  async resolveInjections(inject) {
272755
273151
  const headers = {};
@@ -272762,12 +273158,20 @@ var require_workflow_simulator = __commonJS({
272762
273158
  strValue = await this.resolveTemplateString(memKeyOrTemplate);
272763
273159
  } else {
272764
273160
  const [scope, key] = memKeyOrTemplate.includes(":") ? memKeyOrTemplate.split(":", 2) : ["workflow", memKeyOrTemplate];
272765
- const scopedMemory = new memory_1.JeticMemory({ scope });
273161
+ const scopedMemory = this.memory(scope);
272766
273162
  const value = await scopedMemory.get(key);
272767
- if (value === null)
273163
+ if (value === null || value === void 0) {
273164
+ if (this.strictTemplates) {
273165
+ throw new Error(`inject target "${target}" references "${scope}:${key}", which is not in memory \u2014 capture it earlier or seed it first`);
273166
+ }
272768
273167
  continue;
273168
+ }
272769
273169
  strValue = typeof value === "string" ? value : JSON.stringify(value);
272770
273170
  }
273171
+ const headerName = target.startsWith("header:") ? target.slice("header:".length) : target.startsWith("body:") ? null : target;
273172
+ if (headerName && headerName.toLowerCase() === "authorization" && strValue && !/^[A-Za-z][A-Za-z0-9-]*\s/.test(strValue)) {
273173
+ strValue = `Bearer ${strValue}`;
273174
+ }
272771
273175
  if (target.startsWith("body:")) {
272772
273176
  body[target.slice(5)] = strValue;
272773
273177
  } else if (target.startsWith("header:")) {
@@ -272787,7 +273191,7 @@ var require_workflow_simulator = __commonJS({
272787
273191
  if (value === void 0 || value === null)
272788
273192
  continue;
272789
273193
  const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
272790
- const memory = new memory_1.JeticMemory({ scope });
273194
+ const memory = this.memory(scope);
272791
273195
  await memory.set(key, value);
272792
273196
  captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
272793
273197
  }
@@ -272802,31 +273206,64 @@ var require_workflow_simulator = __commonJS({
272802
273206
  if (value === void 0 || value === null)
272803
273207
  continue;
272804
273208
  const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
272805
- const memory = new memory_1.JeticMemory({ scope });
273209
+ const memory = this.memory(scope);
272806
273210
  await memory.set(key, value);
272807
273211
  captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
272808
273212
  }
272809
273213
  return captured;
272810
273214
  }
272811
273215
  async resolvePathParams(urlPath, body) {
272812
- let result = urlPath;
272813
- const paramMatches = urlPath.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
273216
+ let result = await this.resolveTemplateString(urlPath);
273217
+ const paramMatches = result.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
272814
273218
  if (!paramMatches)
272815
273219
  return result;
272816
- for (const p of paramMatches) {
273220
+ for (const p of [...new Set(paramMatches)].sort((a, b) => b.length - a.length)) {
272817
273221
  const paramName = p.substring(1);
272818
- if (body[paramName] !== void 0) {
272819
- result = result.replace(p, String(body[paramName]));
272820
- } else {
272821
- const memory = new memory_1.JeticMemory({ scope: "workflow" });
272822
- const memVal = await memory.get(paramName);
272823
- if (memVal !== null) {
272824
- result = result.replace(p, String(memVal));
272825
- }
273222
+ let value = body[paramName];
273223
+ if (value === void 0 || value === null) {
273224
+ value = await this.memory("workflow").get(paramName);
273225
+ }
273226
+ if (value !== void 0 && value !== null) {
273227
+ result = result.split(p).join(typeof value === "string" ? value : String(value));
273228
+ continue;
273229
+ }
273230
+ if (this.strictTemplates) {
273231
+ throw new Error(`unresolved path parameter "${p}" in "${urlPath}" \u2014 provide it through a body field, a {{template}}, or a memory key named "${paramName}"`);
272826
273232
  }
272827
273233
  }
272828
273234
  return result;
272829
273235
  }
273236
+ async runPreflight(baseUrl, timeoutMs) {
273237
+ const probe = { url: baseUrl, verdict: "unreachable", message: "", aborted: false };
273238
+ try {
273239
+ const resp = await fetch(baseUrl, {
273240
+ method: "GET",
273241
+ redirect: "manual",
273242
+ signal: AbortSignal.timeout(timeoutMs)
273243
+ });
273244
+ probe.status = resp.status;
273245
+ if (resp.status >= 500) {
273246
+ probe.verdict = "server-error";
273247
+ probe.message = `Server is reachable at ${baseUrl} but responded HTTP ${resp.status} \u2014 the app is likely failing (database/init error). Check the server logs before simulating.`;
273248
+ } else {
273249
+ probe.verdict = "healthy";
273250
+ probe.message = `Server reachable at ${baseUrl} (HTTP ${resp.status}).`;
273251
+ }
273252
+ } catch (err) {
273253
+ const code = networkErrorCode(err);
273254
+ probe.status = void 0;
273255
+ if (err?.name === "TimeoutError" || code === "ETIMEDOUT") {
273256
+ probe.message = `No response from ${baseUrl} within ${timeoutMs}ms \u2014 the server may be down or hanging.`;
273257
+ } else if (code === "ECONNREFUSED") {
273258
+ probe.message = `Connection refused at ${baseUrl} \u2014 nothing is listening there. Start the app (check its PORT/.env) and retry.`;
273259
+ } else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
273260
+ probe.message = `DNS lookup failed for ${baseUrl}${code ? ` (${code})` : ""} \u2014 check the host name.`;
273261
+ } else {
273262
+ probe.message = `Cannot reach ${baseUrl}${code ? ` (${code})` : ""}: ${err?.message || err}`;
273263
+ }
273264
+ }
273265
+ return probe;
273266
+ }
272830
273267
  };
272831
273268
  exports2.WorkflowSimulator = WorkflowSimulator2;
272832
273269
  }
@@ -272837,6 +273274,22 @@ var require_dist13 = __commonJS({
272837
273274
  "../../packages/simulator/dist/index.js"(exports2) {
272838
273275
  "use strict";
272839
273276
  init_cjs_shims();
273277
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
273278
+ if (k2 === void 0) k2 = k;
273279
+ var desc = Object.getOwnPropertyDescriptor(m, k);
273280
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
273281
+ desc = { enumerable: true, get: function() {
273282
+ return m[k];
273283
+ } };
273284
+ }
273285
+ Object.defineProperty(o, k2, desc);
273286
+ }) : (function(o, m, k, k2) {
273287
+ if (k2 === void 0) k2 = k;
273288
+ o[k2] = m[k];
273289
+ }));
273290
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
273291
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
273292
+ };
272840
273293
  Object.defineProperty(exports2, "__esModule", { value: true });
272841
273294
  exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
272842
273295
  var simulator_1 = require_simulator();
@@ -272855,6 +273308,7 @@ var require_dist13 = __commonJS({
272855
273308
  Object.defineProperty(exports2, "WorkflowSimulator", { enumerable: true, get: function() {
272856
273309
  return workflow_simulator_1.WorkflowSimulator;
272857
273310
  } });
273311
+ __exportStar(require_conditions(), exports2);
272858
273312
  }
272859
273313
  });
272860
273314
 
@@ -273009,6 +273463,243 @@ var require_environment_tools = __commonJS({
273009
273463
  }
273010
273464
  });
273011
273465
 
273466
+ // ../../packages/mcp-server/dist/run-store.js
273467
+ var require_run_store = __commonJS({
273468
+ "../../packages/mcp-server/dist/run-store.js"(exports2) {
273469
+ "use strict";
273470
+ init_cjs_shims();
273471
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
273472
+ if (k2 === void 0) k2 = k;
273473
+ var desc = Object.getOwnPropertyDescriptor(m, k);
273474
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
273475
+ desc = { enumerable: true, get: function() {
273476
+ return m[k];
273477
+ } };
273478
+ }
273479
+ Object.defineProperty(o, k2, desc);
273480
+ }) : (function(o, m, k, k2) {
273481
+ if (k2 === void 0) k2 = k;
273482
+ o[k2] = m[k];
273483
+ }));
273484
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
273485
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
273486
+ }) : function(o, v) {
273487
+ o["default"] = v;
273488
+ });
273489
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
273490
+ var ownKeys = function(o) {
273491
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
273492
+ var ar = [];
273493
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
273494
+ return ar;
273495
+ };
273496
+ return ownKeys(o);
273497
+ };
273498
+ return function(mod) {
273499
+ if (mod && mod.__esModule) return mod;
273500
+ var result = {};
273501
+ if (mod != null) {
273502
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
273503
+ }
273504
+ __setModuleDefault(result, mod);
273505
+ return result;
273506
+ };
273507
+ })();
273508
+ Object.defineProperty(exports2, "__esModule", { value: true });
273509
+ exports2.newRunId = newRunId;
273510
+ exports2.runsDir = runsDir;
273511
+ exports2.runDir = runDir;
273512
+ exports2.deriveRunStatus = deriveRunStatus;
273513
+ exports2.writeSimulationRun = writeSimulationRun;
273514
+ exports2.readSimulationRun = readSimulationRun;
273515
+ exports2.readRunStatus = readRunStatus;
273516
+ exports2.writeRunningStatus = writeRunningStatus;
273517
+ exports2.writeRunError = writeRunError;
273518
+ exports2.listRuns = listRuns;
273519
+ exports2.compactSimulationResult = compactSimulationResult;
273520
+ var fs10 = __importStar(require("fs"));
273521
+ var path15 = __importStar(require("path"));
273522
+ var core_1 = require_dist();
273523
+ function newRunId() {
273524
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
273525
+ }
273526
+ function runsDir(jeticDir) {
273527
+ return path15.join(jeticDir, "runs");
273528
+ }
273529
+ function runDir(jeticDir, runId) {
273530
+ return path15.join(runsDir(jeticDir), runId);
273531
+ }
273532
+ function reportPath(jeticDir, runId) {
273533
+ return path15.join(runDir(jeticDir, runId), "simulation-report.json");
273534
+ }
273535
+ function statusPath(jeticDir, runId) {
273536
+ return path15.join(runDir(jeticDir, runId), "simulation-status.json");
273537
+ }
273538
+ function deriveRunStatus(result) {
273539
+ if (result.success)
273540
+ return "passed";
273541
+ if (result.abortReason)
273542
+ return "aborted";
273543
+ return "failed";
273544
+ }
273545
+ function writeSimulationRun(jeticDir, runId, result, meta) {
273546
+ const status = deriveRunStatus(result);
273547
+ const durationMs = result.stats?.totalTimeMs ?? 0;
273548
+ const report = {
273549
+ type: "simulation",
273550
+ runId,
273551
+ workflowName: result.workflow?.name || "workflow",
273552
+ workflowFile: meta.workflowFile,
273553
+ environment: result.environment?.name || "unknown",
273554
+ baseUrl: result.environment?.baseUrl || "",
273555
+ status,
273556
+ startedAt: meta.startedAt,
273557
+ finishedAt: meta.finishedAt,
273558
+ durationMs,
273559
+ result
273560
+ };
273561
+ const rPath = reportPath(jeticDir, runId);
273562
+ (0, core_1.writeJsonSync)(rPath, report);
273563
+ const sPath = statusPath(jeticDir, runId);
273564
+ const statusDoc = {
273565
+ type: "simulation",
273566
+ runId,
273567
+ workflowName: report.workflowName,
273568
+ environment: report.environment,
273569
+ baseUrl: report.baseUrl,
273570
+ status,
273571
+ startedAt: meta.startedAt,
273572
+ finishedAt: meta.finishedAt,
273573
+ durationMs,
273574
+ stats: {
273575
+ totalSteps: result.stats?.totalSteps ?? result.results.length,
273576
+ executedSteps: result.stats?.executedSteps ?? 0,
273577
+ passedSteps: result.stats?.passedSteps ?? 0,
273578
+ failedSteps: result.stats?.failedSteps ?? 0,
273579
+ skippedSteps: result.results.filter((r) => r.skipped).length
273580
+ },
273581
+ reportPath: rPath
273582
+ };
273583
+ (0, core_1.writeJsonSync)(sPath, statusDoc);
273584
+ return { runDir: runDir(jeticDir, runId), reportPath: rPath };
273585
+ }
273586
+ function readSimulationRun(jeticDir, runId) {
273587
+ return (0, core_1.readJsonSync)(reportPath(jeticDir, runId));
273588
+ }
273589
+ function readRunStatus(jeticDir, runId) {
273590
+ return (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
273591
+ }
273592
+ function writeRunningStatus(jeticDir, runId, meta) {
273593
+ const doc = {
273594
+ type: "simulation",
273595
+ runId,
273596
+ workflowName: meta.workflowName,
273597
+ environment: meta.environment,
273598
+ baseUrl: meta.baseUrl,
273599
+ status: "running",
273600
+ startedAt: meta.startedAt,
273601
+ stats: { totalSteps: 0, executedSteps: 0, passedSteps: 0, failedSteps: 0, skippedSteps: 0 },
273602
+ reportPath: reportPath(jeticDir, runId)
273603
+ };
273604
+ (0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
273605
+ }
273606
+ function writeRunError(jeticDir, runId, error, meta) {
273607
+ const doc = {
273608
+ type: "simulation",
273609
+ runId,
273610
+ workflowName: meta.workflowName,
273611
+ environment: meta.environment,
273612
+ baseUrl: meta.baseUrl,
273613
+ status: "error",
273614
+ startedAt: meta.startedAt,
273615
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
273616
+ error: error instanceof Error ? error.message : String(error),
273617
+ stats: { totalSteps: 0, executedSteps: 0, passedSteps: 0, failedSteps: 0, skippedSteps: 0 },
273618
+ reportPath: reportPath(jeticDir, runId)
273619
+ };
273620
+ (0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
273621
+ }
273622
+ function listRuns(jeticDir, limit = 20) {
273623
+ const dir = runsDir(jeticDir);
273624
+ if (!fs10.existsSync(dir))
273625
+ return [];
273626
+ const items = [];
273627
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
273628
+ if (!entry.isDirectory())
273629
+ continue;
273630
+ const runId = entry.name;
273631
+ const simStatus = (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
273632
+ if (simStatus) {
273633
+ items.push({
273634
+ runId,
273635
+ type: "simulation",
273636
+ name: simStatus.workflowName,
273637
+ status: simStatus.status,
273638
+ startedAt: simStatus.startedAt,
273639
+ durationMs: simStatus.durationMs,
273640
+ reportPath: simStatus.reportPath
273641
+ });
273642
+ continue;
273643
+ }
273644
+ const agentReport = (0, core_1.readJsonSync)(path15.join(runDir(jeticDir, runId), "agent-report.json"));
273645
+ if (agentReport) {
273646
+ items.push({
273647
+ runId,
273648
+ type: "agent",
273649
+ name: agentReport.agentId || "agent",
273650
+ status: agentReport.status || "completed",
273651
+ reportPath: path15.join(runDir(jeticDir, runId), "agent-report.json")
273652
+ });
273653
+ }
273654
+ }
273655
+ items.sort((a, b) => b.runId.localeCompare(a.runId));
273656
+ return items.slice(0, limit);
273657
+ }
273658
+ function compactSimulationResult(result, runId, reportFilePath) {
273659
+ const steps = result.results.map((r) => {
273660
+ const summary = {
273661
+ // stepIndex is already 1-based (set as i + 1 by the simulator).
273662
+ step: r.stepIndex,
273663
+ name: r.name,
273664
+ method: r.method,
273665
+ path: r.path,
273666
+ targetUrl: r.targetUrl,
273667
+ status: r.skipped ? "skipped" : r.passed ? "passed" : "failed",
273668
+ expectedStatus: r.expectedStatus,
273669
+ durationMs: r.durationMs
273670
+ };
273671
+ if (!r.skipped)
273672
+ summary.actualStatus = r.actualStatus;
273673
+ if (r.retryCount > 0)
273674
+ summary.retryCount = r.retryCount;
273675
+ if (r.conditionResult) {
273676
+ summary.condition = `${r.conditionResult.evaluated} \u2192 ${r.conditionResult.matched ? "matched" : "not matched"}`;
273677
+ }
273678
+ if (r.error)
273679
+ summary.error = r.error;
273680
+ return summary;
273681
+ });
273682
+ return {
273683
+ ok: result.success,
273684
+ success: result.success,
273685
+ runId,
273686
+ status: deriveRunStatus(result),
273687
+ workflow: result.workflow?.name || "workflow",
273688
+ environment: {
273689
+ name: result.environment?.name || "unknown",
273690
+ baseUrl: result.environment?.baseUrl || ""
273691
+ },
273692
+ stats: result.stats,
273693
+ ...result.preflight ? { preflight: result.preflight } : {},
273694
+ ...result.abortReason ? { abortReason: result.abortReason } : {},
273695
+ steps,
273696
+ reportPath: reportFilePath,
273697
+ next: result.success ? `Full request/response detail: jetic_get_run { "runId": "${runId}" }` : `Inspect the failing step in full detail: jetic_get_run { "runId": "${runId}" }`
273698
+ };
273699
+ }
273700
+ }
273701
+ });
273702
+
273012
273703
  // ../../packages/mcp-server/dist/tools/workflow-tools.js
273013
273704
  var require_workflow_tools = __commonJS({
273014
273705
  "../../packages/mcp-server/dist/tools/workflow-tools.js"(exports2) {
@@ -273061,6 +273752,7 @@ var require_workflow_tools = __commonJS({
273061
273752
  var types_1 = require_types();
273062
273753
  var model_1 = require_dist2();
273063
273754
  var simulator_1 = require_dist13();
273755
+ var run_store_1 = require_run_store();
273064
273756
  exports2.listWorkflowsSchema = zod_1.z.object({
273065
273757
  projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder")
273066
273758
  });
@@ -273152,7 +273844,12 @@ var require_workflow_tools = __commonJS({
273152
273844
  workflow: zod_1.z.string().optional().describe("Workflow slug, name, or JSON file path to execute. If omitted, defaults to available workflow"),
273153
273845
  envName: zod_1.z.string().optional().describe("Target environment name (e.g., local, staging)"),
273154
273846
  baseUrl: zod_1.z.string().optional().describe("Override target base URL (e.g. http://localhost:3000)"),
273155
- clearMemory: zod_1.z.boolean().optional().default(false).describe("If true, clears Jetic memory before execution")
273847
+ clearMemory: zod_1.z.boolean().optional().default(false).describe("If true, clears Jetic memory before execution"),
273848
+ includeStepResults: zod_1.z.boolean().optional().default(false).describe("Return every step's full request/response detail (large payload). Default false returns a compact per-step summary plus a runId \u2014 use jetic_get_run for full detail."),
273849
+ async: zod_1.z.boolean().optional().default(false).describe("Start the run in the background and return immediately with a runId. Poll it with jetic_get_run or jetic_list_runs."),
273850
+ preflight: zod_1.z.enum(["abort", "warn", "off"]).optional().default("abort").describe('Probe the target baseUrl before step 1. "abort" (default) fails fast when the server is unreachable; "warn" records the probe and continues; "off" skips it.'),
273851
+ strictTemplates: zod_1.z.boolean().optional().default(true).describe("Fail a step before sending when a {{template}} or :path param has no value (default true). Set false to restore legacy empty-string behaviour."),
273852
+ requestTimeoutMs: zod_1.z.number().int().positive().max(12e4).optional().default(3e4).describe("Per-request timeout in ms (default 30000, max 120000).")
273156
273853
  });
273157
273854
  async function handleSimulateWorkflow(args) {
273158
273855
  const { model, filePath } = (0, types_1.loadModel)(args.projectPath);
@@ -273238,10 +273935,63 @@ var require_workflow_tools = __commonJS({
273238
273935
  ...targetEnv,
273239
273936
  baseUrl: args.baseUrl || targetEnv.baseUrl
273240
273937
  };
273241
- const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv);
273242
- const result = await simulator.simulateWorkflow(workflowDef, { clearMemory: args.clearMemory ?? false });
273938
+ const projectRoot = (0, types_1.projectRootOfModel)(filePath);
273939
+ const jeticDir = path15.dirname(filePath);
273940
+ const workflowsDir = path15.join(jeticDir, "workflows");
273941
+ const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv, { projectRoot });
273942
+ const runId = (0, run_store_1.newRunId)();
273943
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
273944
+ const runMeta = {
273945
+ workflowName: workflowDef.name || selectedItem.name,
273946
+ environment: effectiveEnv.name,
273947
+ baseUrl: effectiveEnv.baseUrl,
273948
+ startedAt
273949
+ };
273950
+ const execute = async () => {
273951
+ const result2 = await simulator.simulateWorkflow(workflowDef, {
273952
+ clearMemory: args.clearMemory ?? false,
273953
+ loadWorkflow: (slugOrName) => (0, model_1.loadWorkflowByName)(workflowsDir, slugOrName),
273954
+ strictTemplates: args.strictTemplates ?? true,
273955
+ preflight: args.preflight ?? "abort",
273956
+ requestTimeoutMs: args.requestTimeoutMs ?? 3e4
273957
+ });
273958
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
273959
+ const { reportPath: reportPath2 } = (0, run_store_1.writeSimulationRun)(jeticDir, runId, result2, {
273960
+ startedAt,
273961
+ finishedAt,
273962
+ workflowFile: selectedItem.filePath
273963
+ });
273964
+ return { result: result2, reportPath: reportPath2 };
273965
+ };
273966
+ if (args.async) {
273967
+ (0, run_store_1.writeRunningStatus)(jeticDir, runId, runMeta);
273968
+ execute().then(() => void 0).catch((err) => {
273969
+ try {
273970
+ (0, run_store_1.writeRunError)(jeticDir, runId, err, runMeta);
273971
+ } catch {
273972
+ }
273973
+ });
273974
+ return {
273975
+ status: "running",
273976
+ runId,
273977
+ workflowName: runMeta.workflowName,
273978
+ environment: { name: effectiveEnv.name, baseUrl: effectiveEnv.baseUrl },
273979
+ message: `Run started in the background against ${effectiveEnv.baseUrl}. Poll with jetic_get_run { "runId": "${runId}" } or jetic_list_runs.`
273980
+ };
273981
+ }
273982
+ const { result, reportPath } = await execute();
273983
+ if (args.includeStepResults) {
273984
+ return {
273985
+ runId,
273986
+ status: (0, run_store_1.deriveRunStatus)(result),
273987
+ reportPath,
273988
+ filePath,
273989
+ workflowFile: selectedItem.filePath,
273990
+ ...result
273991
+ };
273992
+ }
273243
273993
  return {
273244
- ...result,
273994
+ ...(0, run_store_1.compactSimulationResult)(result, runId, reportPath),
273245
273995
  filePath,
273246
273996
  workflowFile: selectedItem.filePath
273247
273997
  };
@@ -273304,7 +274054,23 @@ var require_workflow_authoring_tools = __commonJS({
273304
274054
  var path15 = __importStar(require("path"));
273305
274055
  var types_1 = require_types();
273306
274056
  var workflow_tools_1 = require_workflow_tools();
273307
- var stepConditionSchema = zod_1.z.object({
274057
+ var simulator_1 = require_dist13();
274058
+ var model_1 = require_dist2();
274059
+ var conditionRuleSchema = zod_1.z.object({
274060
+ left: zod_1.z.string().min(1).describe('Value to test, usually a template: "{{workflow:role}}"'),
274061
+ operator: zod_1.z.enum(simulator_1.CONDITION_OPERATORS).describe("equals, not_equals, greater_than(_or_equal), less_than(_or_equal), exists, not_exists, is_empty, is_not_empty, contains, not_contains, starts_with, ends_with"),
274062
+ right: zod_1.z.string().optional().describe("Value to compare against (omit for exists/not_exists/is_empty/is_not_empty)")
274063
+ });
274064
+ var rulesConditionSchema = zod_1.z.object({
274065
+ rules: zod_1.z.object({
274066
+ all: zod_1.z.array(conditionRuleSchema).optional().describe("AND \u2014 every rule must hold"),
274067
+ any: zod_1.z.array(conditionRuleSchema).optional().describe("OR \u2014 at least one rule must hold")
274068
+ }).describe("Rule group evaluated after the step runs"),
274069
+ onFail: zod_1.z.enum(["abort", "continue", "switch"]).describe("When the rules do not hold: abort = fail the workflow; continue = stop remaining steps without failing; switch = run switchToWorkflow"),
274070
+ switchToWorkflow: zod_1.z.string().optional().describe('Workflow slug or name to run when onFail is "switch"'),
274071
+ returnOnComplete: zod_1.z.boolean().optional().describe('With onFail "switch": resume this workflow after the target finishes')
274072
+ }).describe("Canonical condition: evaluated after the step; onFail steers the run");
274073
+ var legacyConditionSchema = zod_1.z.object({
273308
274074
  if: zod_1.z.string().min(1).describe('Template expression to resolve, e.g. "{{workflow:role}}"'),
273309
274075
  equals: zod_1.z.string().optional().describe("Resolved value must equal this string"),
273310
274076
  notEquals: zod_1.z.string().optional().describe("Resolved value must NOT equal this string"),
@@ -273313,7 +274079,8 @@ var require_workflow_authoring_tools = __commonJS({
273313
274079
  greaterThan: zod_1.z.number().optional().describe("Numeric greater-than comparison"),
273314
274080
  lessThan: zod_1.z.number().optional().describe("Numeric less-than comparison"),
273315
274081
  in: zod_1.z.array(zod_1.z.string()).optional().describe("Resolved value must be one of these strings")
273316
- }).describe("Skip this step when the condition is not met");
274082
+ }).describe("Legacy guard: evaluated before the step; if not met the step is skipped");
274083
+ var stepConditionSchema = zod_1.z.union([rulesConditionSchema, legacyConditionSchema]);
273317
274084
  var stepRetrySchema = zod_1.z.object({
273318
274085
  times: zod_1.z.number().int().positive().max(10).describe("Max retry attempts (1\u201310)"),
273319
274086
  delayMs: zod_1.z.number().int().nonnegative().optional().describe("Delay between retries in ms (default 0)")
@@ -273321,14 +274088,16 @@ var require_workflow_authoring_tools = __commonJS({
273321
274088
  exports2.workflowStepSchema = zod_1.z.object({
273322
274089
  name: zod_1.z.string().min(1).describe("Step name (must be unique within the workflow)"),
273323
274090
  method: zod_1.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]).describe("HTTP method"),
273324
- path: zod_1.z.string().min(1).describe("Route path; :params resolve from body/memory (NOT from {{templates}} \u2014 those only work in body/inject)"),
274091
+ path: zod_1.z.string().min(1).describe("Route path. :params resolve from a body field, or a memory key of the same name; {{templates}} are also resolved in the path."),
273325
274092
  description: zod_1.z.string().optional().describe("What this step does"),
273326
- body: zod_1.z.record(zod_1.z.any()).optional().describe("Request payload. Values support {{faker.*}}, {{scope:key}}, {{workflow:key}} and {{human:key}} templates"),
274093
+ body: zod_1.z.record(zod_1.z.any()).optional().describe("Request payload (nested objects/arrays are templated too). Values support {{faker.*}}, {{scope:key}}, {{workflow:key}} and {{human:key}} templates"),
274094
+ query: zod_1.z.record(zod_1.z.any()).optional().describe("URL query parameters (deep-resolved templates). Use this instead of a body on GET/HEAD steps."),
274095
+ timeoutMs: zod_1.z.number().int().positive().optional().describe("Per-step request timeout in ms (overrides the run default, which is 30000; max 120000)."),
273327
274096
  inject: zod_1.z.record(zod_1.z.string()).optional().describe('Header/body injection: "header:Name" or "body:field" \u2192 "scope:key" or "{{template}}"'),
273328
274097
  capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" \u2192 "dot.path.in.response"'),
273329
274098
  captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" \u2192 "bodyField"'),
273330
274099
  expectStatus: zod_1.z.number().int().positive().optional().describe("Expected HTTP status (default 200; any 2xx passes when expecting 2xx)"),
273331
- condition: stepConditionSchema.optional().describe('Skip this step when condition is not met. Use {{workflow:key}} in the "if" field.'),
274100
+ condition: stepConditionSchema.optional().describe("Preferred: { rules: { all|any: [{ left, operator, right }] }, onFail: abort|continue|switch } \u2014 evaluated after the step. Legacy: { if, equals\u2026 } skips the step when not met."),
273332
274101
  onFailure: zod_1.z.enum(["abort", "continue"]).optional().describe('"abort" (default): stop the workflow on failure. "continue": log failure and proceed (captures skipped).'),
273333
274102
  continueOnStatus: zod_1.z.array(zod_1.z.number().int()).optional().describe("Additional HTTP status codes treated as pass for this step (e.g. [404])."),
273334
274103
  retry: stepRetrySchema.optional().describe("Retry on failure \u2014 overrides the workflow-level global retry.")
@@ -273343,7 +274112,6 @@ var require_workflow_authoring_tools = __commonJS({
273343
274112
  retry: stepRetrySchema.optional().describe("Global retry default applied to all steps (overridden per step)"),
273344
274113
  steps: zod_1.z.array(exports2.workflowStepSchema).min(1).describe("Ordered steps; later steps can use memory captured by earlier ones")
273345
274114
  }).passthrough();
273346
- var TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
273347
274115
  var KNOWN_WORKFLOW_KEYS = /* @__PURE__ */ new Set(["name", "description", "environment", "steps", "generatedAt", "retry"]);
273348
274116
  var KNOWN_STEP_KEYS = /* @__PURE__ */ new Set([
273349
274117
  "name",
@@ -273355,6 +274123,8 @@ var require_workflow_authoring_tools = __commonJS({
273355
274123
  "captureInput",
273356
274124
  "expectStatus",
273357
274125
  "body",
274126
+ "query",
274127
+ "timeoutMs",
273358
274128
  "condition",
273359
274129
  "onFailure",
273360
274130
  "continueOnStatus",
@@ -273392,10 +274162,35 @@ var require_workflow_authoring_tools = __commonJS({
273392
274162
  }
273393
274163
  function collectTemplateRefs(value, out) {
273394
274164
  if (typeof value === "string") {
273395
- TEMPLATE_RE.lastIndex = 0;
273396
- let m;
273397
- while ((m = TEMPLATE_RE.exec(value)) !== null)
273398
- out.push(m[1].trim());
274165
+ let i = 0;
274166
+ while (i < value.length) {
274167
+ const open = value.indexOf("{{", i);
274168
+ if (open === -1)
274169
+ break;
274170
+ let depth = 0;
274171
+ let end = -1;
274172
+ for (let j = open + 2; j < value.length; j++) {
274173
+ const ch = value[j];
274174
+ if (ch === "{") {
274175
+ depth++;
274176
+ } else if (ch === "}") {
274177
+ if (depth > 0) {
274178
+ depth--;
274179
+ } else if (value[j + 1] === "}") {
274180
+ end = j;
274181
+ break;
274182
+ } else {
274183
+ break;
274184
+ }
274185
+ }
274186
+ }
274187
+ if (end === -1) {
274188
+ i = open + 2;
274189
+ continue;
274190
+ }
274191
+ out.push(value.slice(open + 2, end).trim());
274192
+ i = end + 2;
274193
+ }
273399
274194
  } else if (Array.isArray(value)) {
273400
274195
  for (const v of value)
273401
274196
  collectTemplateRefs(v, out);
@@ -273411,6 +274206,58 @@ var require_workflow_authoring_tools = __commonJS({
273411
274206
  const prefix = wfPath.split("/").slice(0, 3).join("/");
273412
274207
  return model.filter((e) => e.path.startsWith(prefix)).map((e) => `${e.method} ${e.path}`).slice(0, 3);
273413
274208
  }
274209
+ function checkRulesCondition(cond, stepNo, sName, errors, warnings, ownName, workflowsDir2) {
274210
+ const refs = [];
274211
+ const rules = cond.rules;
274212
+ let ruleCount = 0;
274213
+ for (const group of ["all", "any"]) {
274214
+ const list = rules[group];
274215
+ if (list === void 0)
274216
+ continue;
274217
+ if (!Array.isArray(list)) {
274218
+ err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition.rules.${group}" must be an array of rules.`, `Use "${group}": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }].`);
274219
+ continue;
274220
+ }
274221
+ list.forEach((rule, ri) => {
274222
+ ruleCount++;
274223
+ const where = `Step ${stepNo} condition.rules.${group}[${ri}]`;
274224
+ if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
274225
+ err(errors, stepNo, sName, "BAD_CONDITION", `${where} must be an object { left, operator, right? }.`, 'Use { "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }.');
274226
+ return;
274227
+ }
274228
+ if (typeof rule.left !== "string" || !rule.left.trim()) {
274229
+ err(errors, stepNo, sName, "BAD_CONDITION", `${where} needs a non-empty "left".`, 'Set "left" to a value or template like "{{workflow:role}}".');
274230
+ } else if (!/\{\{[^}]+\}\}/.test(rule.left)) {
274231
+ warn(warnings, stepNo, sName, "CONDITION_BARE_IF", `${where} "left" ("${rule.left}") contains no {{template}} and is compared literally.`, "Use {{workflow:key}} or {{scope:key}} to reference captured memory values.");
274232
+ }
274233
+ if (!simulator_1.CONDITION_OPERATORS.includes(rule.operator)) {
274234
+ err(errors, stepNo, sName, "BAD_CONDITION_OPERATOR", `${where} has unknown operator ${JSON.stringify(rule.operator)}.`, `Use one of: ${simulator_1.CONDITION_OPERATORS.join(", ")}.`);
274235
+ } else if (!simulator_1.UNARY_OPERATORS.includes(rule.operator) && rule.right === void 0) {
274236
+ err(errors, stepNo, sName, "CONDITION_MISSING_RIGHT", `${where} uses "${rule.operator}" but has no "right" operand.`, 'Add "right", or use exists / not_exists / is_empty / is_not_empty.');
274237
+ }
274238
+ collectTemplateRefs(rule.left, refs);
274239
+ collectTemplateRefs(rule.right, refs);
274240
+ });
274241
+ }
274242
+ if (ruleCount === 0 && !errors.some((e) => e.stepIndex === stepNo && e.code === "BAD_CONDITION")) {
274243
+ warn(warnings, stepNo, sName, "CONDITION_NO_RULES", `Step ${stepNo} condition has no rules, so it always passes.`, 'Add rules under "rules.all" or "rules.any".');
274244
+ }
274245
+ if (!["abort", "continue", "switch"].includes(cond.onFail)) {
274246
+ err(errors, stepNo, sName, "BAD_CONDITION_ONFAIL", `Step ${stepNo} condition needs "onFail" of "abort", "continue" or "switch" (got ${JSON.stringify(cond.onFail)}).`, '"abort" fails the workflow, "continue" stops the remaining steps quietly, "switch" runs switchToWorkflow.');
274247
+ } else if (cond.onFail === "switch") {
274248
+ const target = typeof cond.switchToWorkflow === "string" ? cond.switchToWorkflow.trim() : "";
274249
+ if (!target) {
274250
+ err(errors, stepNo, sName, "CONDITION_SWITCH_NO_TARGET", `Step ${stepNo} uses onFail "switch" but has no "switchToWorkflow".`, 'Set "switchToWorkflow" to the slug or name of the workflow to run.');
274251
+ } else if (ownName && target.toLowerCase() === ownName.trim().toLowerCase()) {
274252
+ err(errors, stepNo, sName, "CONDITION_SWITCH_SELF", `Step ${stepNo} switches to this same workflow ("${target}"), which would loop.`, 'Point "switchToWorkflow" at a different workflow.');
274253
+ } else if (!(0, model_1.loadWorkflowByName)(workflowsDir2, target)) {
274254
+ warn(warnings, stepNo, sName, "CONDITION_SWITCH_TARGET_MISSING", `Step ${stepNo} switches to "${target}", which does not exist in .jetic/workflows yet.`, "Create that workflow before running this one, or the run fails when the condition triggers.");
274255
+ }
274256
+ } else if (cond.returnOnComplete !== void 0 || cond.switchToWorkflow !== void 0) {
274257
+ warn(warnings, stepNo, sName, "CONDITION_UNUSED_SWITCH_FIELDS", `Step ${stepNo} sets switchToWorkflow/returnOnComplete but onFail is "${cond.onFail}", so they are ignored.`, 'Use onFail "switch" or remove those fields.');
274258
+ }
274259
+ return refs;
274260
+ }
273414
274261
  function validateWorkflowDefinition2(wf, opts) {
273415
274262
  const errors = [];
273416
274263
  const warnings = [];
@@ -273439,6 +274286,25 @@ var require_workflow_authoring_tools = __commonJS({
273439
274286
  const seenNames = /* @__PURE__ */ new Set();
273440
274287
  const defined = /* @__PURE__ */ new Set();
273441
274288
  const humanKeys = /* @__PURE__ */ new Set();
274289
+ const refsUsedAnywhere = /* @__PURE__ */ new Set();
274290
+ for (const rawStep of wf.steps) {
274291
+ const stepRefs = [];
274292
+ collectTemplateRefs(rawStep?.body, stepRefs);
274293
+ collectTemplateRefs(rawStep?.query, stepRefs);
274294
+ collectTemplateRefs(rawStep?.path, stepRefs);
274295
+ const inject = rawStep?.inject;
274296
+ if (inject && typeof inject === "object" && !Array.isArray(inject)) {
274297
+ for (const v of Object.values(inject))
274298
+ collectTemplateRefs(v, stepRefs);
274299
+ }
274300
+ for (const ref of stepRefs) {
274301
+ if (ref.startsWith("faker.") || !ref.includes(":"))
274302
+ continue;
274303
+ const { scope, key } = splitMemKey(ref);
274304
+ if (key)
274305
+ refsUsedAnywhere.add(`${scope}:${key}`);
274306
+ }
274307
+ }
273442
274308
  wf.steps.forEach((rawStep, i) => {
273443
274309
  const stepNo = i + 1;
273444
274310
  const sName = typeof rawStep?.name === "string" ? rawStep.name : void 0;
@@ -273447,19 +274313,22 @@ var require_workflow_authoring_tools = __commonJS({
273447
274313
  return;
273448
274314
  }
273449
274315
  const step = rawStep;
274316
+ let rulesRefs = [];
273450
274317
  for (const key of Object.keys(step)) {
273451
274318
  if (MODEL_DIALECT_KEYS.has(key)) {
273452
274319
  err(errors, stepNo, sName, "MODEL_DIALECT_KEY", `Step ${stepNo} uses "${key}" from the model-embedded workflow dialect, which file workflows ignore.`, 'File-workflow equivalent: split "call" into method+path; replace auth/bind with inject/capture.');
273453
274320
  } else if (!KNOWN_STEP_KEYS.has(key)) {
273454
- warn(warnings, stepNo, sName, "UNKNOWN_STEP_KEY", `Step ${stepNo} has unknown key "${key}" which the simulator ignores.`, "Remove it or check the field name (valid: name, method, path, description, body, inject, capture, captureInput, expectStatus).");
274321
+ warn(warnings, stepNo, sName, "UNKNOWN_STEP_KEY", `Step ${stepNo} has unknown key "${key}" which the simulator ignores.`, "Remove it or check the field name (valid: name, method, path, description, body, query, timeoutMs, inject, capture, captureInput, expectStatus, condition, onFailure, continueOnStatus, retry).");
273455
274322
  }
273456
274323
  }
273457
274324
  if ("condition" in step && step.condition !== void 0) {
273458
274325
  const cond = step.condition;
273459
274326
  if (!cond || typeof cond !== "object" || Array.isArray(cond)) {
273460
- err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "if": "{{workflow:role}}", "equals": "admin" } }.');
274327
+ err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "rules": { "all": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }] }, "onFail": "abort" } }.');
274328
+ } else if ((0, simulator_1.conditionKind)(cond) === "rules") {
274329
+ rulesRefs = checkRulesCondition(cond, stepNo, sName, errors, warnings, wf.name, path15.join(projectRoot, ".jetic", "workflows"));
273461
274330
  } else if (!cond.if || typeof cond.if !== "string" || !cond.if.trim()) {
273462
- err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must have a non-empty "if" field.`, 'Set "if" to a template expression like "{{workflow:role}}".');
274331
+ err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" is neither a rules condition (needs "rules" + "onFail") nor a legacy guard (needs a non-empty "if"). The runners fail steps with an unrecognized condition.`, 'Use { "rules": { "all": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }] }, "onFail": "abort" }.');
273463
274332
  } else {
273464
274333
  const operators = ["equals", "notEquals", "contains", "exists", "greaterThan", "lessThan", "in"];
273465
274334
  const usedOps = operators.filter((op) => op in cond && cond[op] !== void 0);
@@ -273529,30 +274398,46 @@ var require_workflow_authoring_tools = __commonJS({
273529
274398
  if (step.body !== void 0 && (typeof step.body !== "object" || step.body === null || Array.isArray(step.body))) {
273530
274399
  err(errors, stepNo, sName, "BAD_BODY", `Step ${stepNo} "body" must be an object of field -> value.`, 'Use { "email": "{{faker.internet.email}}" }.');
273531
274400
  }
273532
- if ((method === "GET" || method === "HEAD") && step.body && Object.keys(step.body).length > 0) {
273533
- warn(warnings, stepNo, sName, "GET_WITH_BODY", `Step ${stepNo} is ${method} with a body \u2014 it will be sent as URL query params, not a JSON body.`, "This is usually what you want for filters; otherwise switch to POST.");
274401
+ if (step.query !== void 0 && (typeof step.query !== "object" || step.query === null || Array.isArray(step.query))) {
274402
+ err(errors, stepNo, sName, "BAD_QUERY", `Step ${stepNo} "query" must be an object of param -> value.`, 'Use { "query": { "page": 1, "search": "{{workflow:term}}" } }.');
274403
+ }
274404
+ if ((method === "GET" || method === "HEAD") && step.body && typeof step.body === "object" && !Array.isArray(step.body)) {
274405
+ const nested = Object.values(step.body).some((v) => v !== null && typeof v === "object");
274406
+ if (nested) {
274407
+ warn(warnings, stepNo, sName, "GET_WITH_BODY", `Step ${stepNo} is ${method} with nested body values \u2014 they are stringified into the URL query string.`, 'Move flat filters to the "query" field instead.');
274408
+ }
273534
274409
  }
273535
- if (endpoint?.requestBody?.fields && step.body && typeof step.body === "object") {
274410
+ if (endpoint?.requestBody?.fields && step.body && typeof step.body === "object" && method !== "GET" && method !== "HEAD") {
273536
274411
  for (const field of Object.keys(step.body)) {
273537
274412
  if (!(field in endpoint.requestBody.fields)) {
273538
274413
  warn(warnings, stepNo, sName, "UNKNOWN_BODY_FIELD", `Step ${stepNo} sends body field "${field}" not declared on ${method} ${wfPath}.`, "Check the spelling, or extend the endpoint with jetic_update_endpoint.");
273539
274414
  }
273540
274415
  }
273541
274416
  }
274417
+ if (step.query && typeof step.query === "object" && !Array.isArray(step.query) && endpoint) {
274418
+ const declaredQuery = (endpoint.parameters || []).filter((p) => p.in === "query");
274419
+ if (declaredQuery.length > 0) {
274420
+ for (const param of Object.keys(step.query)) {
274421
+ if (!declaredQuery.some((p) => p.name === param)) {
274422
+ warn(warnings, stepNo, sName, "UNKNOWN_QUERY_PARAM", `Step ${stepNo} sends query param "${param}" not declared on ${method} ${wfPath}.`, "Check the spelling, or extend the endpoint with jetic_update_endpoint.");
274423
+ }
274424
+ }
274425
+ }
274426
+ }
273542
274427
  const pathParams = wfPath.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g) || [];
273543
274428
  for (const p of pathParams) {
273544
274429
  const pname = p.substring(1);
273545
274430
  const inBody = step.body && typeof step.body === "object" && pname in step.body;
274431
+ const inInjectBody = step.inject && typeof step.inject === "object" && !Array.isArray(step.inject) && Object.keys(step.inject).includes(`body:${pname}`);
273546
274432
  const inMemory = [...defined].some((d) => d.split(":")[1] === pname) || [...persistedMemory].some((d) => d.split(":")[1] === pname);
273547
- if (!inBody && !inMemory) {
273548
- err(errors, stepNo, sName, "UNRESOLVED_PATH_PARAM", `Path param ":${pname}" in step ${stepNo} has no body field or memory source \u2014 the URL will literally contain ":${pname}" and fail.`, `Add "${pname}" to this step's body, or capture it as memory in an earlier step (capture: { "workflow:${pname}": "data.${pname}" }).`);
274433
+ if (!inBody && !inInjectBody && !inMemory) {
274434
+ err(errors, stepNo, sName, "UNRESOLVED_PATH_PARAM", `Path param ":${pname}" in step ${stepNo} has no body field or memory source \u2014 the URL will literally contain ":${pname}" and fail.`, `Add "${pname}" to this step's body (or inject { "body:${pname}": "<scope:key>" }), capture it as memory in an earlier step (capture: { "workflow:${pname}": "data.${pname}" }), or inline it as "/users/{{workflow:${pname}}}".`);
273549
274435
  }
273550
274436
  }
273551
- if (/\{\{[^}]+\}\}/.test(wfPath)) {
273552
- warn(warnings, stepNo, sName, "PATH_TEMPLATE", `Step ${stepNo} path contains a {{template}} \u2014 paths only resolve :params, templates work in body/inject.`, 'Use "/api/users/:id" with an "id" body field or captured memory key.');
273553
- }
273554
274437
  const refs = [];
273555
274438
  collectTemplateRefs(step.body, refs);
274439
+ collectTemplateRefs(step.query, refs);
274440
+ collectTemplateRefs(step.path, refs);
273556
274441
  if (step.inject && typeof step.inject === "object") {
273557
274442
  for (const v of Object.values(step.inject))
273558
274443
  collectTemplateRefs(v, refs);
@@ -273561,6 +274446,8 @@ var require_workflow_authoring_tools = __commonJS({
273561
274446
  if (ref.startsWith("faker.")) {
273562
274447
  if (ref.split(".").length < 3) {
273563
274448
  warn(warnings, stepNo, sName, "SUSPICIOUS_FAKER", `"{{${ref}}}" looks like an incomplete faker path.`, "Use full paths like {{faker.internet.email}} or {{faker.string.uuid}}.");
274449
+ } else if (ref.includes("(") || ref.includes("{") || ref.includes("[")) {
274450
+ err(errors, stepNo, sName, "UNSUPPORTED_FAKER_CALL", `"{{${ref}}}" in step ${stepNo} passes arguments \u2014 the simulator only supports parameterless faker paths.`, "Use a plain path like {{faker.internet.email}} or {{faker.number.int}}, or hard-code the value.");
273564
274451
  }
273565
274452
  continue;
273566
274453
  }
@@ -273619,7 +274506,13 @@ var require_workflow_authoring_tools = __commonJS({
273619
274506
  err(errors, stepNo, sName, "BAD_ON_FAILURE", `Step ${stepNo} "onFailure" must be "abort" or "continue" (got "${step.onFailure}").`, 'Use "abort" (default, stops the workflow) or "continue" (logs failure, proceeds).');
273620
274507
  }
273621
274508
  if (step.onFailure === "continue" && step.capture && Object.keys(step.capture).length > 0) {
273622
- warn(warnings, stepNo, sName, "CONTINUE_WITH_CAPTURE", `Step ${stepNo} uses onFailure:"continue" but has captures \u2014 captures are only saved when the step passes.`, "This is usually fine for optional captures; ensure later steps handle missing memory gracefully.");
274509
+ const consumedDownstream = Object.keys(step.capture).some((k) => {
274510
+ const { scope, key } = splitMemKey(k);
274511
+ return key && refsUsedAnywhere.has(`${scope}:${key}`);
274512
+ });
274513
+ if (consumedDownstream) {
274514
+ warn(warnings, stepNo, sName, "CONTINUE_WITH_CAPTURE", `Step ${stepNo} uses onFailure:"continue" but later steps depend on its captures \u2014 if the request fails, those captures are skipped and downstream steps will fail or be skipped.`, "Make the run conditional on the capture, or move the capture to a step that must pass.");
274515
+ }
273623
274516
  }
273624
274517
  }
273625
274518
  if (step.continueOnStatus !== void 0) {
@@ -273658,6 +274551,25 @@ var require_workflow_authoring_tools = __commonJS({
273658
274551
  }
273659
274552
  }
273660
274553
  }
274554
+ for (const ref of rulesRefs) {
274555
+ if (ref.startsWith("faker."))
274556
+ continue;
274557
+ if (!ref.includes(":")) {
274558
+ err(errors, stepNo, sName, "BAD_TEMPLATE", `"{{${ref}}}" in the condition of step ${stepNo} needs a scope ("{{workflow:key}}").`, "Bare {{key}} is left literal \u2014 add the scope prefix.");
274559
+ continue;
274560
+ }
274561
+ const { scope, key } = splitMemKey(ref);
274562
+ if (!key)
274563
+ continue;
274564
+ if (scope === "human") {
274565
+ humanKeys.add(key);
274566
+ continue;
274567
+ }
274568
+ const full = `${scope}:${key}`;
274569
+ if (!defined.has(full) && !persistedMemory.has(full)) {
274570
+ err(errors, stepNo, sName, "UNDEFINED_MEMORY_REF", `"{{${ref}}}" in the condition of step ${stepNo} is not captured by this or an earlier step and is not in memory.json.`, `Capture "${full}" in this step or an earlier one before referencing it in a condition.`);
274571
+ }
274572
+ }
273661
274573
  });
273662
274574
  return toReport(errors, warnings, humanKeys);
273663
274575
  }
@@ -288323,6 +289235,7 @@ var require_response_envelope = __commonJS({
288323
289235
  "WRITE_DISABLED",
288324
289236
  "INTERNAL"
288325
289237
  ];
289238
+ var MAX_MESSAGE_LENGTH = 500;
288326
289239
  function errorCodeFrom(error) {
288327
289240
  if (typeof error !== "object" || error === null || !("code" in error)) {
288328
289241
  return "INTERNAL";
@@ -288330,6 +289243,28 @@ var require_response_envelope = __commonJS({
288330
289243
  const code = error.code;
288331
289244
  return typeof code === "string" && ERROR_CODES.includes(code) ? code : "INTERNAL";
288332
289245
  }
289246
+ function describeError(error) {
289247
+ const record = typeof error === "object" && error !== null ? error : {};
289248
+ const issues = Array.isArray(record.issues) ? record.issues : void 0;
289249
+ if (issues && issues.length > 0) {
289250
+ const parts = issues.slice(0, 5).map((issue) => {
289251
+ const path15 = Array.isArray(issue?.path) ? issue.path.join(".") : "";
289252
+ return `${path15 ? `${path15}: ` : ""}${issue?.message ?? "invalid"}`;
289253
+ });
289254
+ const more = issues.length > 5 ? ` (+${issues.length - 5} more)` : "";
289255
+ return {
289256
+ message: `Invalid input \u2014 ${parts.join("; ")}${more}`,
289257
+ hint: "Fix the flagged argument(s) and retry. Use the tool schema for exact field types."
289258
+ };
289259
+ }
289260
+ const rawMessage = typeof record.message === "string" ? record.message.trim() : "";
289261
+ const message = rawMessage ? rawMessage.length > MAX_MESSAGE_LENGTH ? `${rawMessage.slice(0, MAX_MESSAGE_LENGTH)}\u2026` : rawMessage : "Tool execution failed.";
289262
+ const customHint = typeof record.hint === "string" && record.hint.trim() ? record.hint.trim() : "";
289263
+ return {
289264
+ message,
289265
+ hint: customHint || "Check the project configuration and arguments, then retry."
289266
+ };
289267
+ }
288333
289268
  function toTextResult(data) {
288334
289269
  return {
288335
289270
  content: [{
@@ -288339,6 +289274,7 @@ var require_response_envelope = __commonJS({
288339
289274
  };
288340
289275
  }
288341
289276
  function toErrorResult(error) {
289277
+ const { message, hint } = describeError(error);
288342
289278
  return {
288343
289279
  isError: true,
288344
289280
  content: [{
@@ -288347,8 +289283,8 @@ var require_response_envelope = __commonJS({
288347
289283
  ok: false,
288348
289284
  error: {
288349
289285
  code: errorCodeFrom(error),
288350
- message: "Tool execution failed.",
288351
- hint: "Check the project configuration and arguments, then retry."
289286
+ message,
289287
+ hint
288352
289288
  }
288353
289289
  }, null, 2)
288354
289290
  }]
@@ -288691,6 +289627,47 @@ var require_workflow_designer_tools = __commonJS({
288691
289627
  if (Object.keys(inject).length > 0)
288692
289628
  step.inject = inject;
288693
289629
  }
289630
+ const pathParams = [...new Set(endpoint.path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g) || [])].map((p) => p.slice(1));
289631
+ for (const pname of pathParams) {
289632
+ const inBody = step.body && typeof step.body === "object" && pname in step.body;
289633
+ if (inBody)
289634
+ continue;
289635
+ const memKey = [...producedKeys].find((k) => (k.split(":").pop() || "").toLowerCase() === pname.toLowerCase());
289636
+ if (memKey) {
289637
+ step.path = step.path.split(`:${pname}`).join(`{{${memKey}}}`);
289638
+ } else {
289639
+ missing.push(`Path param ":${pname}" has no source \u2014 add a body field "${pname}", capture a memory key named "${pname}" in an earlier step, or inline {{workflow:${pname}}} in the path.`);
289640
+ }
289641
+ }
289642
+ const queryParams = (endpoint.parameters || []).filter((p) => p.in === "query");
289643
+ if (queryParams.length > 0) {
289644
+ const query = {};
289645
+ for (const p of queryParams) {
289646
+ const memKey = [...producedKeys].find((k) => (k.split(":").pop() || "").toLowerCase() === p.name.toLowerCase());
289647
+ if (memKey) {
289648
+ query[p.name] = `{{${memKey}}}`;
289649
+ continue;
289650
+ }
289651
+ if (p.required !== true)
289652
+ continue;
289653
+ const val = fieldToFakerValue(p.name, {
289654
+ type: p.type || "string",
289655
+ format: p.format,
289656
+ min: p.min,
289657
+ max: p.max,
289658
+ enum: p.enum
289659
+ });
289660
+ if (val !== null) {
289661
+ query[p.name] = val;
289662
+ } else {
289663
+ query[p.name] = `{{human:${p.name}}}`;
289664
+ allHumanKeys.add(p.name);
289665
+ missing.push(`Query param "${p.name}" (required, type: ${p.type || "string"}) \u2014 no auto-mapping. Using {{human:${p.name}}} (will pause for interactive input at runtime).`);
289666
+ }
289667
+ }
289668
+ if (Object.keys(query).length > 0)
289669
+ step.query = query;
289670
+ }
288694
289671
  const capture = {};
288695
289672
  if (endpoint.produces) {
288696
289673
  for (const p of endpoint.produces) {
@@ -288741,6 +289718,109 @@ var require_workflow_designer_tools = __commonJS({
288741
289718
  }
288742
289719
  });
288743
289720
 
289721
+ // ../../packages/mcp-server/dist/tools/run-tools.js
289722
+ var require_run_tools = __commonJS({
289723
+ "../../packages/mcp-server/dist/tools/run-tools.js"(exports2) {
289724
+ "use strict";
289725
+ init_cjs_shims();
289726
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
289727
+ if (k2 === void 0) k2 = k;
289728
+ var desc = Object.getOwnPropertyDescriptor(m, k);
289729
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
289730
+ desc = { enumerable: true, get: function() {
289731
+ return m[k];
289732
+ } };
289733
+ }
289734
+ Object.defineProperty(o, k2, desc);
289735
+ }) : (function(o, m, k, k2) {
289736
+ if (k2 === void 0) k2 = k;
289737
+ o[k2] = m[k];
289738
+ }));
289739
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
289740
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
289741
+ }) : function(o, v) {
289742
+ o["default"] = v;
289743
+ });
289744
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
289745
+ var ownKeys = function(o) {
289746
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
289747
+ var ar = [];
289748
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
289749
+ return ar;
289750
+ };
289751
+ return ownKeys(o);
289752
+ };
289753
+ return function(mod) {
289754
+ if (mod && mod.__esModule) return mod;
289755
+ var result = {};
289756
+ if (mod != null) {
289757
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
289758
+ }
289759
+ __setModuleDefault(result, mod);
289760
+ return result;
289761
+ };
289762
+ })();
289763
+ Object.defineProperty(exports2, "__esModule", { value: true });
289764
+ exports2.listRunsSchema = exports2.getRunSchema = void 0;
289765
+ exports2.handleGetRun = handleGetRun;
289766
+ exports2.handleListRuns = handleListRuns;
289767
+ var zod_1 = require("zod");
289768
+ var path15 = __importStar(require("path"));
289769
+ var core_1 = require_dist();
289770
+ var types_1 = require_types();
289771
+ var run_store_1 = require_run_store();
289772
+ exports2.getRunSchema = zod_1.z.object({
289773
+ projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder"),
289774
+ runId: zod_1.z.string().describe('Run id returned by jetic_simulate_workflow (e.g. "2026-10-19T12-34-56-789Z") or jetic_run_agent')
289775
+ });
289776
+ exports2.listRunsSchema = zod_1.z.object({
289777
+ projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder"),
289778
+ limit: zod_1.z.number().int().positive().max(100).optional().default(20).describe("Max runs to return, newest first (default 20)")
289779
+ });
289780
+ function handleGetRun(args) {
289781
+ const projectRoot = path15.resolve((0, types_1.resolveProjectInput)(args.projectPath));
289782
+ const config = (0, core_1.loadConfig)(projectRoot);
289783
+ const report = (0, run_store_1.readSimulationRun)(config.jeticDir, args.runId);
289784
+ if (report)
289785
+ return { found: true, ...report };
289786
+ const status = (0, run_store_1.readRunStatus)(config.jeticDir, args.runId);
289787
+ if (status) {
289788
+ return {
289789
+ found: true,
289790
+ type: "simulation",
289791
+ runId: args.runId,
289792
+ status: status.status,
289793
+ workflowName: status.workflowName,
289794
+ environment: status.environment,
289795
+ baseUrl: status.baseUrl,
289796
+ startedAt: status.startedAt,
289797
+ finishedAt: status.finishedAt,
289798
+ stats: status.stats,
289799
+ reportPath: status.reportPath,
289800
+ ...status.error ? { error: status.error } : {},
289801
+ ...status.status === "running" ? { message: "Run still in progress \u2014 poll again shortly." } : {}
289802
+ };
289803
+ }
289804
+ const agentReport = (0, core_1.readJsonSync)(path15.join(config.jeticDir, "runs", args.runId, "agent-report.json"));
289805
+ if (agentReport)
289806
+ return { found: true, type: "agent", runId: args.runId, ...agentReport };
289807
+ const available = (0, run_store_1.listRuns)(config.jeticDir, 10).map((r) => `${r.runId} (${r.type}: ${r.status})`);
289808
+ const detail = available.length > 0 ? ` Recent runs: ${available.join(", ")}.` : " No runs found yet.";
289809
+ throw new Error(`Run "${args.runId}" not found under .jetic/runs/.${detail}`);
289810
+ }
289811
+ function handleListRuns(args) {
289812
+ const projectRoot = path15.resolve((0, types_1.resolveProjectInput)(args.projectPath));
289813
+ const config = (0, core_1.loadConfig)(projectRoot);
289814
+ const runs = (0, run_store_1.listRuns)(config.jeticDir, args.limit ?? 20);
289815
+ return {
289816
+ runsDir: path15.join(config.jeticDir, "runs"),
289817
+ totalCount: runs.length,
289818
+ runs
289819
+ };
289820
+ }
289821
+ }
289822
+ });
289823
+
288744
289824
  // ../../packages/mcp-server/dist/server.js
288745
289825
  var require_server3 = __commonJS({
288746
289826
  "../../packages/mcp-server/dist/server.js"(exports2) {
@@ -288805,6 +289885,7 @@ var require_server3 = __commonJS({
288805
289885
  var session_tools_1 = require_session_tools();
288806
289886
  var workflow_designer_tools_1 = require_workflow_designer_tools();
288807
289887
  var agent_tools_1 = require_agent_tools();
289888
+ var run_tools_1 = require_run_tools();
288808
289889
  exports2.JETIC_MCP_SERVER_NAME = "jetic-mcp-server";
288809
289890
  function getJeticMcpVersion() {
288810
289891
  try {
@@ -288859,11 +289940,20 @@ var require_server3 = __commonJS({
288859
289940
  " The scaffold tool already handles faker mapping \u2014 only override to {{human:key}} when a",
288860
289941
  " field appears in missingFields AND genuinely requires interactive user input.",
288861
289942
  "",
288862
- "STEP 4c \u2014 CONDITIONS (branching / skipping steps)",
288863
- " Add `condition` to skip a step when a memory value does not match:",
288864
- ' { "condition": { "if": "{{workflow:userRole}}", "equals": "admin" } }',
288865
- " Operators: equals, notEquals, contains, exists (bool), greaterThan, lessThan, in (string[]).",
288866
- " Skipped steps count as passed \u2014 the workflow continues.",
289943
+ "STEP 4c \u2014 CONDITIONS (branching / assertions on memory)",
289944
+ " Preferred form \u2014 checked AFTER the step runs, so it can use values that step just captured:",
289945
+ ' { "condition": { "rules": { "all": [ { "left": "{{workflow:userRole}}", "operator": "equals", "right": "admin" } ] },',
289946
+ ' "onFail": "abort" } }',
289947
+ " rules.all = AND, rules.any = OR. Operators: equals, not_equals, greater_than(_or_equal), less_than(_or_equal),",
289948
+ " exists, not_exists, is_empty, is_not_empty, contains, not_contains, starts_with, ends_with.",
289949
+ " onFail when the rules do NOT hold:",
289950
+ ' \u2022 "abort" \u2014 fail the workflow',
289951
+ ' \u2022 "continue" \u2014 stop the remaining steps, workflow still passes (branch not applicable)',
289952
+ ' \u2022 "switch" \u2014 run another saved workflow: add "switchToWorkflow": "<slug or name>";',
289953
+ ' add "returnOnComplete": true to resume this workflow afterwards',
289954
+ " Legacy form (still supported) \u2014 a guard checked BEFORE the step; a miss skips just that step (counts as passed):",
289955
+ ' { "condition": { "if": "{{workflow:userRole}}", "equals": "admin" } } // equals, notEquals, contains, exists, greaterThan, lessThan, in',
289956
+ " A condition that is neither shape FAILS the run \u2014 it is never ignored.",
288867
289957
  ' Pair with onFailure:"continue" for negative/branching flows.',
288868
289958
  "",
288869
289959
  "STEP 4d \u2014 FAILURE MODES & RETRY",
@@ -288884,6 +289974,9 @@ var require_server3 = __commonJS({
288884
289974
  "",
288885
289975
  "STEP 4g \u2014 RUN",
288886
289976
  ' jetic_simulate_workflow { "workflow": "<slug>" }',
289977
+ " It preflights the target first and fails fast with a clear reason when the app is unreachable.",
289978
+ ' The response is a compact summary + runId. Full step detail: jetic_get_run { "runId": "..." };',
289979
+ " recent runs: jetic_list_runs. For long journeys pass async:true, then poll jetic_get_run.",
288887
289980
  "",
288888
289981
  "\u2501\u2501 GENERAL RULES \u2501\u2501",
288889
289982
  "No AI-provider setup is needed: never ask for OpenRouter/OpenAI keys and never use `jetic config ai`.",
@@ -288951,9 +290044,11 @@ var require_server3 = __commonJS({
288951
290044
  register("jetic_list_workflows", "List workflows", "Lists multi-step integration test workflows from .jetic/workflows/*.json, .jetic/workflow.json, or embedded model.json workflows.", workflow_tools_1.listWorkflowsSchema.shape, READ_ONLY, workflow_tools_1.handleListWorkflows);
288952
290045
  register("jetic_simulate_workflow", "Simulate workflow", "Executes a full end-to-end multi-step workflow simulation with dynamic memory capture, header injection, and schema validation against the live server.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
288953
290046
  register("jetic_simulate", "Simulate workflow (alias)", "Alias of jetic_simulate_workflow: runs a multi-step user-journey workflow against the live server. Prefer jetic_simulate_workflow in new prompts.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
290047
+ register("jetic_get_run", "Get run report", "Returns the full stored report of a previous run \u2014 every step's request, response, captures and errors \u2014 from .jetic/runs/<runId>/. Use the runId returned by jetic_simulate_workflow or jetic_list_runs.", run_tools_1.getRunSchema.shape, READ_ONLY, run_tools_1.handleGetRun);
290048
+ register("jetic_list_runs", "List runs", "Lists recent simulation and agent runs from .jetic/runs/, newest first, with status, step counts and duration. Use jetic_get_run for the full report of one run.", run_tools_1.listRunsSchema.shape, READ_ONLY, run_tools_1.handleListRuns);
288954
290049
  register("jetic_scaffold_workflow", "Scaffold workflow steps from model", "PHASE 4a \u2014 FIRST STEP when building a workflow. Given an ordered endpoint list, reads model.json and returns ready-made `draftSteps`: body fields faker-mapped by type/format/name, inject derived from securitySchemes.obtainedFrom token-producer chains, capture from endpoint.produces, captureInput for reusable credentials. Returns missingFields (fields needing human input or manual fix) and securityChain analysis. Does NOT save. Review draftSteps, then call jetic_validate_workflow \u2192 jetic_create_workflow.", workflow_designer_tools_1.scaffoldWorkflowSchema.shape, READ_ONLY, workflow_designer_tools_1.handleScaffoldWorkflow);
288955
- register("jetic_validate_workflow", "Validate workflow (dry run)", "Deep-validates a workflow WITHOUT saving: step structure, endpoint cross-checks against model.json, full memory data-flow analysis. Now also validates condition (including condition.if template refs), onFailure, continueOnStatus, and retry fields. Accepts a saved workflow slug/name or an inline definition.", workflow_authoring_tools_1.validateWorkflowSchema.shape, READ_ONLY, workflow_authoring_tools_1.handleValidateWorkflow);
288956
- register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow. Supports: condition (skip steps based on memory), onFailure (abort|continue), continueOnStatus (extra pass codes), retry (per-step or global default). Refuses to save on validation errors.", workflow_authoring_tools_1.createWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleCreateWorkflow);
290050
+ register("jetic_validate_workflow", "Validate workflow (dry run)", "Deep-validates a workflow WITHOUT saving: step structure, endpoint cross-checks against model.json, full memory data-flow analysis. Also validates conditions (rules + onFail/switch targets, or legacy if guards; template refs), onFailure, continueOnStatus, and retry fields. Accepts a saved workflow slug/name or an inline definition.", workflow_authoring_tools_1.validateWorkflowSchema.shape, READ_ONLY, workflow_authoring_tools_1.handleValidateWorkflow);
290051
+ register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow. Supports: condition (rules with onFail abort|continue|switch, or legacy if-guards), onFailure (abort|continue), continueOnStatus (extra pass codes), retry (per-step or global default). Refuses to save on validation errors.", workflow_authoring_tools_1.createWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleCreateWorkflow);
288957
290052
  register("jetic_update_workflow", "Update workflow", "Updates a saved file workflow: rename, description/environment, full steps replacement, or appendSteps. Validates before writing and refuses to save on errors.", workflow_authoring_tools_1.updateWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleUpdateWorkflow);
288958
290053
  register("jetic_delete_workflow", "Delete workflow", "Deletes a saved .jetic/workflows/*.json workflow file by slug, name, or path. Destructive: confirm with the user before calling. Cannot delete model.json-embedded workflows.", workflow_authoring_tools_1.deleteWorkflowSchema.shape, DESTRUCTIVE, workflow_authoring_tools_1.handleDeleteWorkflow);
288959
290054
  register("jetic_list_agents", "List agents", "Lists registered deterministic agents (model-watcher, workflow-impact) available to jetic_run_agent.", agent_tools_1.listAgentsSchema.shape, READ_ONLY, agent_tools_1.handleListAgents);
@@ -289697,9 +290792,12 @@ Running ${files.length} workflow(s)...
289697
290792
  for (const file of files) {
289698
290793
  const workflow = (0, import_model2.readWorkflowDefinitionFile)(path9.join(config.workflowsDir, file));
289699
290794
  import_core11.jeticEvents.emit("workflow.started", { workflowId: workflow.name });
289700
- const simulator = new import_simulator.WorkflowSimulator(model, targetEnv);
289701
- const result = await simulator.simulateWorkflow(workflow);
289702
- results.push({ name: workflow.name, passed: result.success, durationMs: result.stats.totalTimeMs });
290795
+ const simulator = new import_simulator.WorkflowSimulator(model, targetEnv, { projectRoot: config.projectRoot });
290796
+ const result = await simulator.simulateWorkflow(workflow, {
290797
+ loadWorkflow: (slugOrName) => (0, import_model2.loadWorkflowByName)(config.workflowsDir, slugOrName)
290798
+ });
290799
+ const failures = result.results.filter((r) => !r.passed).map((r) => ({ step: r.name, status: r.actualStatus, error: r.error }));
290800
+ results.push({ name: workflow.name, passed: result.success, durationMs: result.stats.totalTimeMs, failures });
289703
290801
  if (result.success) {
289704
290802
  import_core11.jeticEvents.emit("workflow.completed", { workflowId: workflow.name, passed: true });
289705
290803
  } else {
@@ -289707,6 +290805,10 @@ Running ${files.length} workflow(s)...
289707
290805
  }
289708
290806
  if (!options.ci) {
289709
290807
  console.log(` ${result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m"} ${workflow.name}`);
290808
+ for (const failure of failures) {
290809
+ const detail = failure.error ?? (failure.status ? `HTTP ${failure.status}` : "failed");
290810
+ console.log(` \x1B[2m\u21B3 ${failure.step}: ${detail}\x1B[0m`);
290811
+ }
289710
290812
  }
289711
290813
  }
289712
290814
  const passed = results.filter((r) => r.passed).length;
@@ -289830,7 +290932,7 @@ memoryCommand.command("clear").description("Clear memory").action(() => {
289830
290932
  init_cjs_shims();
289831
290933
  var import_commander15 = require("commander");
289832
290934
  var import_core14 = __toESM(require_dist());
289833
- var import_simulator2 = __toESM(require_dist13());
290935
+ var import_simulator3 = __toESM(require_dist13());
289834
290936
  var path12 = __toESM(require("path"));
289835
290937
 
289836
290938
  // src/commands/simulate-workflow.ts
@@ -289839,7 +290941,9 @@ var path11 = __toESM(require("path"));
289839
290941
  var fs7 = __toESM(require("fs"));
289840
290942
  var import_commander14 = require("commander");
289841
290943
  var import_core13 = __toESM(require_dist());
290944
+ var import_model5 = __toESM(require_dist2());
289842
290945
  var import_memory2 = __toESM(require_dist12());
290946
+ var import_simulator2 = __toESM(require_dist13());
289843
290947
  var import_faker = require("@faker-js/faker");
289844
290948
  var c = {
289845
290949
  reset: "\x1B[0m",
@@ -289896,58 +291000,32 @@ function deepGet(obj, dotPath) {
289896
291000
  async function resolveConditionRef(ref, context = "condition") {
289897
291001
  return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref, context);
289898
291002
  }
289899
- async function evalConditionRule(rule, context = "condition") {
289900
- const left = await resolveConditionRef(rule.left, context);
289901
- const right = rule.right !== void 0 ? await resolveConditionRef(rule.right, context) : void 0;
289902
- const op = rule.operator;
289903
- switch (op) {
289904
- case "equals":
289905
- return left === right;
289906
- case "not_equals":
289907
- return left !== right;
289908
- case "greater_than":
289909
- return Number(left) > Number(right);
289910
- case "greater_than_or_equal":
289911
- return Number(left) >= Number(right);
289912
- case "less_than":
289913
- return Number(left) < Number(right);
289914
- case "less_than_or_equal":
289915
- return Number(left) <= Number(right);
289916
- case "exists":
289917
- return left !== "" && left !== void 0;
289918
- case "not_exists":
289919
- return left === "" || left === void 0;
289920
- case "is_empty":
289921
- return left === "";
289922
- case "is_not_empty":
289923
- return left !== "";
289924
- case "contains":
289925
- return right !== void 0 && left.includes(right);
289926
- case "not_contains":
289927
- return right !== void 0 && !left.includes(right);
289928
- case "starts_with":
289929
- return right !== void 0 && left.startsWith(right);
289930
- case "ends_with":
289931
- return right !== void 0 && left.endsWith(right);
289932
- default:
289933
- return false;
289934
- }
289935
- }
289936
291003
  async function evaluateStepCondition(condition, context = "condition") {
289937
- const { rules } = condition;
289938
- const ruleList = rules.all ?? rules.any ?? [];
289939
- const isAnd = !!rules.all;
289940
- if (ruleList.length === 0) return { passed: true, reason: "No rules defined" };
289941
- const results = [];
289942
- for (const rule of ruleList) {
289943
- results.push({ rule, ok: await evalConditionRule(rule, context) });
291004
+ return (0, import_simulator2.evaluateRules)(condition.rules, (value) => resolveConditionRef(value, context));
291005
+ }
291006
+ async function preStepCondition(step) {
291007
+ if (!step.condition) return null;
291008
+ const label = step.name || `${step.method} ${step.path}`;
291009
+ const base = { step, status: 0, responseBody: null, durationMs: 0, captured: [], injected: {} };
291010
+ const kind = (0, import_simulator2.conditionKind)(step.condition);
291011
+ if (kind === "invalid") {
291012
+ return { ...base, passed: false, error: `Step "${label}": ${(0, import_simulator2.describeInvalidCondition)(step.condition)}` };
291013
+ }
291014
+ if (kind === "legacy") {
291015
+ const outcome = await (0, import_simulator2.evaluateLegacyCondition)(
291016
+ step.condition,
291017
+ (value) => resolveConditionRef(value, `condition of step "${label}"`)
291018
+ );
291019
+ if (!outcome.matched) {
291020
+ return {
291021
+ ...base,
291022
+ passed: true,
291023
+ skipped: true,
291024
+ conditionResult: { passed: false, reason: `legacy guard not met (resolved "${outcome.evaluated}")` }
291025
+ };
291026
+ }
289944
291027
  }
289945
- const passed = isAnd ? results.every((r) => r.ok) : results.some((r) => r.ok);
289946
- const failing = results.filter((r) => !r.ok);
289947
- const reason = passed ? `All conditions met (${isAnd ? "AND" : "OR"})` : `Failed: ${failing.map(
289948
- (r) => `${r.rule.left} ${r.rule.operator}${r.rule.right !== void 0 ? " " + r.rule.right : ""}`
289949
- ).join(", ")}`;
289950
- return { passed, reason };
291028
+ return null;
289951
291029
  }
289952
291030
  function envKeyForHuman(key) {
289953
291031
  return `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
@@ -290236,12 +291314,14 @@ function renderStepResult(result, index, total) {
290236
291314
  console.log(` ${CHAIN} ${c.green}\u{1F4BE} captured ${cap}${c.reset}`);
290237
291315
  }
290238
291316
  }
290239
- if (result.conditionResult) {
291317
+ if (result.skipped) {
291318
+ console.log(` ${CHAIN} ${c.yellow}\u2298 skipped${c.reset} ${c.dim}${result.conditionResult?.reason ?? ""}${c.reset}`);
291319
+ } else if (result.conditionResult) {
290240
291320
  const { passed: condPassed, reason } = result.conditionResult;
290241
291321
  const condIcon = condPassed ? `${c.magenta}\u25C7${c.reset}` : `${c.yellow}\u25C8${c.reset}`;
290242
291322
  const condColor = condPassed ? c.magenta : c.yellow;
290243
291323
  console.log(` ${CHAIN} ${condIcon} ${condColor}condition ${condPassed ? "passed" : "failed"}${c.reset} ${c.dim}${reason}${c.reset}`);
290244
- if (!condPassed && result.step.condition) {
291324
+ if (!condPassed && (0, import_simulator2.isRulesCondition)(result.step.condition)) {
290245
291325
  const { onFail, switchToWorkflow } = result.step.condition;
290246
291326
  const actionStr = onFail === "switch" ? `switching to workflow: ${switchToWorkflow ?? "unknown"}` : onFail === "continue" ? "continuing without remaining steps" : "aborting workflow";
290247
291327
  console.log(` ${CHAIN} ${c.yellow} \u2192 ${actionStr}${c.reset}`);
@@ -290458,12 +291538,17 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290458
291538
  spinner.start(`${stepLabel} ${c.dim}${step.name}${c.reset}...`);
290459
291539
  let result;
290460
291540
  try {
290461
- result = await executeStep(step, baseUrl, {});
290462
- if (step.condition) {
291541
+ const preempted = await preStepCondition(step);
291542
+ result = preempted ?? await executeStep(step, baseUrl, {});
291543
+ if (!preempted && (0, import_simulator2.isRulesCondition)(step.condition)) {
290463
291544
  const condResult = await evaluateStepCondition(step.condition, `condition of step "${step.name || step.path}"`);
290464
291545
  result.conditionResult = condResult;
290465
291546
  if (!condResult.passed) {
290466
291547
  const { onFail, switchToWorkflow } = step.condition;
291548
+ if (onFail === "abort") {
291549
+ result.passed = false;
291550
+ result.error = `Condition failed: ${condResult.reason}`;
291551
+ }
290467
291552
  results.push(result);
290468
291553
  const icon2 = result.passed ? TICK : CROSS;
290469
291554
  spinner.stop(` ${icon2} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
@@ -290481,10 +291566,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290481
291566
  } else if (onFail === "switch" && switchToWorkflow) {
290482
291567
  console.log(` ${ARROW} Condition failed \u2014 switching to workflow: ${c.bold}${switchToWorkflow}${c.reset}
290483
291568
  `);
290484
- const switchPath = path11.join(workflowsDir, `${switchToWorkflow}.json`);
290485
- if (fs7.existsSync(switchPath)) {
291569
+ const switchedWorkflow = (0, import_model5.loadWorkflowByName)(workflowsDir, switchToWorkflow);
291570
+ if (switchedWorkflow) {
290486
291571
  try {
290487
- const switchedWorkflow = JSON.parse(fs7.readFileSync(switchPath, "utf-8"));
290488
291572
  console.log(` ${TICK} Loaded switch workflow: ${c.bold}${switchedWorkflow.name}${c.reset}
290489
291573
  `);
290490
291574
  renderWorkflowHeader(switchedWorkflow);
@@ -290495,8 +291579,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290495
291579
  const switchLabel = `${c.dim}${si + 1}/${switchedWorkflow.steps.length}${c.reset} ${getMethodColor(switchStep.method)}${switchStep.method}${c.reset} ${switchStep.path}`;
290496
291580
  const switchSpinner = new Spinner();
290497
291581
  switchSpinner.start(`${switchLabel} ${c.dim}${switchStep.name}${c.reset}...`);
290498
- const switchResult = await executeStep(switchStep, baseUrl, {});
290499
- if (switchStep.condition) {
291582
+ const switchPre = await preStepCondition(switchStep);
291583
+ const switchResult = switchPre ?? await executeStep(switchStep, baseUrl, {});
291584
+ if (!switchPre && (0, import_simulator2.isRulesCondition)(switchStep.condition)) {
290500
291585
  switchResult.conditionResult = await evaluateStepCondition(
290501
291586
  switchStep.condition,
290502
291587
  `condition of step "${switchStep.name || switchStep.path}"`
@@ -290508,12 +291593,16 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290508
291593
  renderStepResult(switchResult, si, switchedWorkflow.steps.length);
290509
291594
  }
290510
291595
  } catch (e) {
290511
- console.log(` ${c.red}\u2717 Failed to load switch workflow: ${e.message}${c.reset}
291596
+ console.log(` ${c.red}\u2717 Failed to run switch workflow: ${e.message}${c.reset}
290512
291597
  `);
291598
+ result.passed = false;
291599
+ result.error = `Switch workflow "${switchToWorkflow}" failed: ${e.message}`;
290513
291600
  }
290514
291601
  } else {
290515
- console.log(` ${c.yellow}\u26A0 Switch workflow not found: ${switchPath}${c.reset}
291602
+ console.log(` ${c.red}\u2717 Switch workflow not found: ${switchToWorkflow}${c.reset}
290516
291603
  `);
291604
+ result.passed = false;
291605
+ result.error = `Switch workflow "${switchToWorkflow}" was not found in ${workflowsDir}`;
290517
291606
  }
290518
291607
  if (step.condition.returnOnComplete) {
290519
291608
  console.log(` ${c.green}\u21A9 Sub-workflow complete. Returning to main workflow...${c.reset}
@@ -290524,6 +291613,14 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290524
291613
  break;
290525
291614
  }
290526
291615
  }
291616
+ if (onFail === "switch" && !switchToWorkflow) {
291617
+ console.log(` ${c.red}\u2717 Condition failed and onFail "switch" has no switchToWorkflow.${c.reset}
291618
+ `);
291619
+ result.passed = false;
291620
+ result.error = 'onFail "switch" has no switchToWorkflow';
291621
+ stopOnFailure = true;
291622
+ break;
291623
+ }
290527
291624
  continue;
290528
291625
  }
290529
291626
  }
@@ -290538,9 +291635,15 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290538
291635
  throw err;
290539
291636
  }
290540
291637
  results.push(result);
290541
- const icon = result.passed ? TICK : CROSS;
291638
+ const icon = result.skipped ? SKIP : result.passed ? TICK : CROSS;
290542
291639
  spinner.stop(` ${icon} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
290543
291640
  renderStepResult(result, i, workflow.steps.length);
291641
+ if (!result.passed && result.error && !result.status && (0, import_simulator2.conditionKind)(step.condition) === "invalid") {
291642
+ console.log(` ${c.red}${c.bold}\u26A0 Unrecognized condition \u2014 stopping workflow.${c.reset}
291643
+ `);
291644
+ stopOnFailure = true;
291645
+ break;
291646
+ }
290544
291647
  if (!result.passed && (step.path.includes("/login") || step.path.includes("/register") || step.path.includes("/auth"))) {
290545
291648
  console.log(` ${c.red}${c.bold}\u26A0 Auth step failed \u2014 stopping workflow to prevent cascading failures.${c.reset}
290546
291649
  `);
@@ -290663,15 +291766,19 @@ var Spinner2 = class {
290663
291766
  }
290664
291767
  }
290665
291768
  };
290666
- async function selectEnvironment(environments) {
291769
+ function defaultEnvironment(model, environments) {
291770
+ const defaultName = model.defaultEnvironment ?? environments.find((e) => e.default)?.name;
291771
+ return environments.find((e) => e.name === defaultName) ?? environments[0];
291772
+ }
291773
+ async function selectEnvironment(model, environments) {
290667
291774
  if (environments.length === 0) {
290668
291775
  console.log(` ${c2.yellow}\u26A0${c2.reset} No environments defined in model.json`);
290669
291776
  console.log(` ${c2.dim}Using default: http://localhost:3000${c2.reset}
290670
291777
  `);
290671
291778
  return { name: "default", baseUrl: "http://localhost:3000" };
290672
291779
  }
290673
- if (environments.length === 1) {
290674
- const env = environments[0];
291780
+ if (environments.length === 1 || !process.stdin.isTTY) {
291781
+ const env = defaultEnvironment(model, environments);
290675
291782
  console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${env.name}${c2.reset} ${c2.dim}\u2192 ${env.baseUrl}${c2.reset}
290676
291783
  `);
290677
291784
  return env;
@@ -290914,7 +292021,7 @@ function loadModel3() {
290914
292021
  }
290915
292022
  var simulateCommand = new import_commander15.Command("simulate").description("Simulate API endpoints against a live server");
290916
292023
  simulateCommand.addCommand(simulateWorkflowCommand);
290917
- simulateCommand.command("endpoint [method] [endpointPath]").description("Simulate a specific endpoint or all endpoints").option("--all", "Simulate all endpoints").option("--verbose", "Show detailed request/response info", false).action(async (method, endpointPath, options) => {
292024
+ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulate a specific endpoint or all endpoints").option("--all", "Simulate all endpoints").option("--verbose", "Show detailed request/response info", false).option("--env <name>", "Environment name to use from model.json (skips the interactive picker)").action(async (method, endpointPath, options) => {
290918
292025
  console.log("");
290919
292026
  console.log(` ${c2.bgCyan}${c2.black}${c2.bold} JETIC ${c2.reset} ${c2.cyan}${c2.bold}Endpoint Simulator${c2.reset}`);
290920
292027
  console.log("");
@@ -290928,7 +292035,20 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
290928
292035
  console.log(` ${c2.dim}Model v${model.version} \u2022 ${model.endpoints.length} endpoints${c2.reset}
290929
292036
  `);
290930
292037
  const environments = model.environments || [];
290931
- const selectedEnv = await selectEnvironment(environments);
292038
+ let selectedEnv;
292039
+ if (options.env) {
292040
+ const named = environments.find((e) => e.name === options.env);
292041
+ if (!named) {
292042
+ console.log(` ${c2.red}\u2717${c2.reset} Environment ${c2.bold}${options.env}${c2.reset} not found. Available: ${environments.map((e) => e.name).join(", ") || "(none)"}
292043
+ `);
292044
+ process.exit(2);
292045
+ }
292046
+ selectedEnv = named;
292047
+ console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${named.name}${c2.reset} ${c2.dim}\u2192 ${named.baseUrl}${c2.reset}
292048
+ `);
292049
+ } else {
292050
+ selectedEnv = await selectEnvironment(model, environments);
292051
+ }
290932
292052
  const checkSpinner = new Spinner2();
290933
292053
  checkSpinner.start(`Checking if backend is active at ${selectedEnv.baseUrl}...`);
290934
292054
  try {
@@ -290942,7 +292062,7 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
290942
292062
  `);
290943
292063
  process.exit(1);
290944
292064
  }
290945
- const simulator = new import_simulator2.EndpointSimulator(model, selectedEnv);
292065
+ const simulator = new import_simulator3.EndpointSimulator(model, selectedEnv);
290946
292066
  const spinner = new Spinner2();
290947
292067
  if (options.all) {
290948
292068
  console.log(` ${c2.magenta}\u{1F680}${c2.reset} Simulating ${c2.bold}${model.endpoints.length}${c2.reset} endpoints...
@@ -291005,8 +292125,9 @@ var import_commander16 = require("commander");
291005
292125
  var import_express = __toESM(require("express"));
291006
292126
  var import_path = __toESM(require("path"));
291007
292127
  var import_fs = __toESM(require("fs"));
291008
- var import_model5 = __toESM(require_dist2());
292128
+ var import_model6 = __toESM(require_dist2());
291009
292129
  var import_memory3 = __toESM(require_dist12());
292130
+ var import_simulator4 = __toESM(require_dist13());
291010
292131
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".cache", "__pycache__", ".next", ".nuxt", ".venv"]);
291011
292132
  var IGNORED_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".mp4", ".webm", ".woff", ".woff2", ".ttf", ".eot", ".map"]);
291012
292133
  var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".kt", ".cs", ".rb", ".php", ".json", ".yaml", ".yml", ".toml", ".env", ".graphql", ".gql", ".sql", ".prisma", ".proto"]);
@@ -291155,7 +292276,7 @@ var ChangeScanner = class {
291155
292276
  this.saveChanges(data);
291156
292277
  }
291157
292278
  };
291158
- var devCommand = new import_commander16.Command("dev").description("Start the Jetic local dashboard").option("-p, --port <number>", "Port to run the dashboard on", "8787").action(async (options) => {
292279
+ var devCommand = new import_commander16.Command("dev").description("Start the Jetic local dashboard").option("-p, --port <number>", "Port to run the dashboard on", "8787").option("--no-open", "Do not open the dashboard in a browser (also: JETIC_NO_OPEN=1)").action(async (options) => {
291159
292280
  const port = parseInt(options.port, 10);
291160
292281
  const app = (0, import_express.default)();
291161
292282
  const projectRoot = process.cwd();
@@ -291183,7 +292304,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
291183
292304
  const jeticDir2 = import_path.default.join(process.cwd(), ".jetic");
291184
292305
  const modelDir = import_path.default.join(jeticDir2, "model");
291185
292306
  try {
291186
- if ((0, import_model5.hasYamlModel)(modelDir)) return res.json((0, import_model5.loadModelFromDir)(modelDir));
292307
+ if ((0, import_model6.hasYamlModel)(modelDir)) return res.json((0, import_model6.loadModelFromDir)(modelDir));
291187
292308
  const modelPath = import_path.default.join(jeticDir2, "model.json");
291188
292309
  if (!import_fs.default.existsSync(modelPath)) return res.json(null);
291189
292310
  res.json(JSON.parse(import_fs.default.readFileSync(modelPath, "utf8")));
@@ -291793,57 +292914,43 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
291793
292914
  }
291794
292915
  return result;
291795
292916
  }
291796
- async function evalCondRule(rule, stepIndex, stepName) {
291797
- const left = await resolveCondRef(rule.left, stepIndex, stepName);
291798
- const right = rule.right !== void 0 ? await resolveCondRef(rule.right, stepIndex, stepName) : void 0;
291799
- switch (rule.operator) {
291800
- case "equals":
291801
- return left === right;
291802
- case "not_equals":
291803
- return left !== right;
291804
- case "greater_than":
291805
- return Number(left) > Number(right);
291806
- case "greater_than_or_equal":
291807
- return Number(left) >= Number(right);
291808
- case "less_than":
291809
- return Number(left) < Number(right);
291810
- case "less_than_or_equal":
291811
- return Number(left) <= Number(right);
291812
- case "exists":
291813
- return left !== "" && left !== void 0;
291814
- case "not_exists":
291815
- return left === "" || left === void 0;
291816
- case "is_empty":
291817
- return left === "";
291818
- case "is_not_empty":
291819
- return left !== "";
291820
- case "contains":
291821
- return right !== void 0 && left.includes(right);
291822
- case "not_contains":
291823
- return right !== void 0 && !left.includes(right);
291824
- case "starts_with":
291825
- return right !== void 0 && left.startsWith(right);
291826
- case "ends_with":
291827
- return right !== void 0 && left.endsWith(right);
291828
- default:
291829
- return false;
291830
- }
291831
- }
291832
292917
  async function evalCondition(condition, stepIndex, stepName) {
291833
- const rules = condition.rules?.all ?? condition.rules?.any ?? [];
291834
- const isAnd = !!condition.rules?.all;
291835
- if (rules.length === 0) return { passed: true, reason: "No rules" };
291836
- const results = [];
291837
- for (const rule of rules) results.push({ rule, ok: await evalCondRule(rule, stepIndex, stepName) });
291838
- const passed2 = isAnd ? results.every((r) => r.ok) : results.some((r) => r.ok);
291839
- const failing = results.filter((r) => !r.ok);
291840
- const reason = passed2 ? `All conditions met (${isAnd ? "AND" : "OR"})` : `Failed: ${failing.map((r) => `${r.rule.left} ${r.rule.operator}${r.rule.right !== void 0 ? " " + r.rule.right : ""}`).join(", ")}`;
291841
- return { passed: passed2, reason };
292918
+ return (0, import_simulator4.evaluateRules)(condition.rules ?? {}, (value) => resolveCondRef(value, stepIndex, stepName));
291842
292919
  }
291843
292920
  for (let i = 0; i < workflow.steps.length; i++) {
291844
292921
  if (aborted) break;
291845
292922
  const step = workflow.steps[i];
291846
292923
  send("step_start", { index: i, step: { name: step.name, method: step.method, path: step.path } });
292924
+ const condKind = step.condition ? (0, import_simulator4.conditionKind)(step.condition) : void 0;
292925
+ const stepSummary = { name: step.name, method: step.method, path: step.path, description: step.description };
292926
+ if (condKind === "invalid") {
292927
+ const reason = `Step ${i + 1} "${step.name || `${step.method} ${step.path}`}": ${(0, import_simulator4.describeInvalidCondition)(step.condition)}`;
292928
+ failed++;
292929
+ send("step_result", { index: i, step: stepSummary, status: 0, passed: false, durationMs: 0, captured: [], injected: {}, responseBody: null, error: reason });
292930
+ send("aborted", { index: i, reason });
292931
+ res.end();
292932
+ return;
292933
+ }
292934
+ if (condKind === "legacy") {
292935
+ const guard = await (0, import_simulator4.evaluateLegacyCondition)(step.condition, (value) => resolveCondRef(value, i, step.name || ""));
292936
+ if (!guard.matched) {
292937
+ passed++;
292938
+ send("step_result", {
292939
+ index: i,
292940
+ step: stepSummary,
292941
+ status: 0,
292942
+ passed: true,
292943
+ skipped: true,
292944
+ durationMs: 0,
292945
+ captured: [],
292946
+ injected: {},
292947
+ responseBody: null,
292948
+ error: null,
292949
+ skipReason: `legacy guard not met (resolved "${guard.evaluated}")`
292950
+ });
292951
+ continue;
292952
+ }
292953
+ }
291847
292954
  let result;
291848
292955
  try {
291849
292956
  result = await executeStep2(step, i);
@@ -291868,7 +292975,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
291868
292975
  responseBody: result.responseBody,
291869
292976
  error: result.error
291870
292977
  });
291871
- if (step.condition && !aborted) {
292978
+ if ((0, import_simulator4.isRulesCondition)(step.condition) && !aborted) {
291872
292979
  const condResult = await evalCondition(step.condition, i, step.name || `${step.method} ${step.path}`);
291873
292980
  send("condition_result", {
291874
292981
  index: i,
@@ -292050,6 +293157,8 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
292050
293157
  console.log(`
292051
293158
  \u{1F680} Jetic Studio is running at ${url}
292052
293159
  `);
293160
+ const noOpenEnv = process.env.JETIC_NO_OPEN;
293161
+ if (!options.open || noOpenEnv && noOpenEnv !== "0" && noOpenEnv.toLowerCase() !== "false") return;
292053
293162
  const { exec } = require("child_process");
292054
293163
  const start = process.platform == "darwin" ? "open" : process.platform == "win32" ? "start" : "xdg-open";
292055
293164
  exec(`${start} ${url}`);
@@ -292212,7 +293321,7 @@ var mcpCommand = new import_commander18.Command("mcp").description("Launch Model
292212
293321
 
292213
293322
  // src/index.ts
292214
293323
  var program = new import_commander19.Command();
292215
- program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.5");
293324
+ program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.6");
292216
293325
  program.addCommand(initCommand);
292217
293326
  program.addCommand(scanCommand);
292218
293327
  program.addCommand(inspectCommand);