jetic-cli 0.2.6 → 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
@@ -272674,6 +272674,21 @@ var require_workflow_simulator = __commonJS({
272674
272674
  var memory_1 = require_dist12();
272675
272675
  var faker_1 = require("@faker-js/faker");
272676
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
+ }
272677
272692
  function deepGet2(obj, dotPath) {
272678
272693
  if (obj === null || obj === void 0)
272679
272694
  return void 0;
@@ -272690,6 +272705,8 @@ var require_workflow_simulator = __commonJS({
272690
272705
  model;
272691
272706
  environment;
272692
272707
  runtime;
272708
+ strictTemplates = true;
272709
+ requestTimeoutMs = 3e4;
272693
272710
  constructor(model, environment, runtime = {}) {
272694
272711
  this.model = model;
272695
272712
  this.environment = environment;
@@ -272702,7 +272719,36 @@ var require_workflow_simulator = __commonJS({
272702
272719
  if (options?.clearMemory) {
272703
272720
  memory_1.JeticMemory.clearAllMemory(this.runtime.projectRoot);
272704
272721
  }
272722
+ this.strictTemplates = options?.strictTemplates ?? true;
272723
+ this.requestTimeoutMs = options?.requestTimeoutMs ?? 3e4;
272705
272724
  const startTime = Date.now();
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
+ }
272706
272752
  const state = { results: [], allPassed: true, halted: false };
272707
272753
  await this.runSteps(workflow, options, state, [workflow.name]);
272708
272754
  const stepResults = state.results;
@@ -272725,7 +272771,8 @@ var require_workflow_simulator = __commonJS({
272725
272771
  failedSteps: stepResults.filter((r) => !r.passed).length,
272726
272772
  totalTimeMs
272727
272773
  },
272728
- results: stepResults
272774
+ results: stepResults,
272775
+ ...preflight ? { preflight } : {}
272729
272776
  };
272730
272777
  }
272731
272778
  /**
@@ -272735,7 +272782,7 @@ var require_workflow_simulator = __commonJS({
272735
272782
  */
272736
272783
  async runSteps(workflow, options, state, lineage) {
272737
272784
  const baseUrl = this.environment.baseUrl;
272738
- const resolve2 = (value) => this.resolveTemplateString(value);
272785
+ const resolve2 = (value) => this.resolveTemplateString(value, false);
272739
272786
  for (let i = 0; i < workflow.steps.length; i++) {
272740
272787
  const step = workflow.steps[i];
272741
272788
  const stepStartTime = Date.now();
@@ -272789,12 +272836,44 @@ var require_workflow_simulator = __commonJS({
272789
272836
  continue;
272790
272837
  }
272791
272838
  }
272792
- const { headers: injectedHeaders, body: injectedBody } = await this.resolveInjections(step.inject);
272793
- const resolvedBody = await this.resolveBodyTemplates(step.body || {});
272794
- const finalBody = { ...injectedBody, ...resolvedBody };
272795
- const inputCaptured = await this.captureInputToMemory(step.captureInput, finalBody);
272796
- const resolvedPath = await this.resolvePathParams(step.path, finalBody);
272797
- 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
+ }
272798
272877
  const expectedStatus = step.expectStatus ?? 200;
272799
272878
  const maxAttempts = Math.max(1, step.retry?.times ?? workflow.retry?.times ?? 1);
272800
272879
  const retryDelayMs = step.retry?.delayMs ?? workflow.retry?.delayMs ?? 0;
@@ -272803,6 +272882,7 @@ var require_workflow_simulator = __commonJS({
272803
272882
  let responseBody = null;
272804
272883
  let stepPassed = false;
272805
272884
  let errorMsg;
272885
+ let connectionCode;
272806
272886
  while (attempts < maxAttempts) {
272807
272887
  attempts++;
272808
272888
  if (attempts > 1 && retryDelayMs > 0) {
@@ -272811,22 +272891,42 @@ var require_workflow_simulator = __commonJS({
272811
272891
  responseStatus = 0;
272812
272892
  responseBody = null;
272813
272893
  errorMsg = void 0;
272894
+ connectionCode = void 0;
272895
+ const timeoutMs = step.timeoutMs ?? this.requestTimeoutMs;
272814
272896
  try {
272897
+ const upperMethod = step.method.toUpperCase();
272815
272898
  const fetchOptions = {
272816
- method: step.method.toUpperCase(),
272899
+ method: upperMethod,
272817
272900
  headers: { "Content-Type": "application/json", ...injectedHeaders }
272818
272901
  };
272819
- if (!["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
272902
+ if (!["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0) {
272820
272903
  fetchOptions.body = JSON.stringify(finalBody);
272821
272904
  }
272905
+ if (timeoutMs > 0) {
272906
+ fetchOptions.signal = AbortSignal.timeout(timeoutMs);
272907
+ }
272822
272908
  let finalUrl = fullUrl;
272823
- 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) {
272824
272911
  const params = new URLSearchParams();
272825
- for (const [k, v] of Object.entries(finalBody)) {
272826
- if (v !== void 0 && v !== null)
272827
- params.set(k, String(v));
272828
- }
272829
- 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}`;
272830
272930
  }
272831
272931
  const resp = await fetch(finalUrl, fetchOptions);
272832
272932
  responseStatus = resp.status;
@@ -272846,7 +272946,14 @@ var require_workflow_simulator = __commonJS({
272846
272946
  break;
272847
272947
  } catch (err) {
272848
272948
  stepPassed = false;
272849
- 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
+ }
272850
272957
  }
272851
272958
  }
272852
272959
  const responseCaptured = stepPassed ? await this.captureToMemory(step.capture, responseBody) : [];
@@ -272865,6 +272972,7 @@ var require_workflow_simulator = __commonJS({
272865
272972
  retryCount: attempts - 1,
272866
272973
  requestHeaders: injectedHeaders,
272867
272974
  requestBody: finalBody,
272975
+ ...Object.keys(resolvedQuery).length > 0 ? { requestQuery: resolvedQuery } : {},
272868
272976
  responseBody,
272869
272977
  capturedMemory: stepPassed ? [...inputCaptured, ...responseCaptured] : [...inputCaptured],
272870
272978
  ...stepPassed ? {} : {
@@ -272875,6 +272983,13 @@ var require_workflow_simulator = __commonJS({
272875
272983
  state.results.push(result);
272876
272984
  if (!stepPassed) {
272877
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
+ }
272878
272993
  if (onFailure === "abort") {
272879
272994
  state.halted = true;
272880
272995
  return "stop";
@@ -272917,55 +273032,120 @@ var require_workflow_simulator = __commonJS({
272917
273032
  }
272918
273033
  return "done";
272919
273034
  }
272920
- async resolveTemplateString(value) {
272921
- const templateRe = /\{\{([^}]+)\}\}/g;
272922
- let match;
272923
- const replacements = [];
272924
- while ((match = templateRe.exec(value)) !== null) {
272925
- const expr = match[1].trim();
272926
- if (expr.includes(":") && !expr.startsWith("faker.")) {
272927
- const colonIdx = expr.indexOf(":");
272928
- const scope = expr.slice(0, colonIdx);
272929
- const key = expr.slice(colonIdx + 1);
272930
- if (scope === "human") {
272931
- const envVal = process.env[`JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`];
272932
- if (envVal !== void 0 && envVal !== "") {
272933
- replacements.push({ placeholder: match[0], resolved: envVal });
272934
- continue;
272935
- }
272936
- }
272937
- const mem = this.memory(scope);
272938
- const memVal = await mem.get(key);
272939
- replacements.push({ placeholder: match[0], resolved: memVal != null ? String(memVal) : "" });
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++;
272940
273041
  continue;
272941
273042
  }
272942
- if (expr.startsWith("faker.")) {
272943
- const parts = expr.split(".");
272944
- try {
272945
- let fn = faker_1.faker;
272946
- for (const part of parts.slice(1))
272947
- fn = fn[part];
272948
- const generated = typeof fn === "function" ? fn() : fn;
272949
- replacements.push({ placeholder: match[0], resolved: String(generated) });
272950
- } catch {
272951
- 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
+ }
272952
273057
  }
272953
- continue;
273058
+ j++;
272954
273059
  }
272955
- 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;
272956
273077
  }
272957
- let result = value;
272958
- for (const { placeholder, resolved } of replacements) {
272959
- 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`);
272960
273127
  }
272961
- return result;
272962
273128
  }
272963
273129
  async resolveBodyTemplates(body) {
272964
- const resolved = {};
272965
- for (const [key, value] of Object.entries(body)) {
272966
- 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;
272967
273147
  }
272968
- return resolved;
273148
+ return value;
272969
273149
  }
272970
273150
  async resolveInjections(inject) {
272971
273151
  const headers = {};
@@ -272980,10 +273160,18 @@ var require_workflow_simulator = __commonJS({
272980
273160
  const [scope, key] = memKeyOrTemplate.includes(":") ? memKeyOrTemplate.split(":", 2) : ["workflow", memKeyOrTemplate];
272981
273161
  const scopedMemory = this.memory(scope);
272982
273162
  const value = await scopedMemory.get(key);
272983
- 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
+ }
272984
273167
  continue;
273168
+ }
272985
273169
  strValue = typeof value === "string" ? value : JSON.stringify(value);
272986
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
+ }
272987
273175
  if (target.startsWith("body:")) {
272988
273176
  body[target.slice(5)] = strValue;
272989
273177
  } else if (target.startsWith("header:")) {
@@ -273025,24 +273213,57 @@ var require_workflow_simulator = __commonJS({
273025
273213
  return captured;
273026
273214
  }
273027
273215
  async resolvePathParams(urlPath, body) {
273028
- let result = urlPath;
273029
- 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);
273030
273218
  if (!paramMatches)
273031
273219
  return result;
273032
- for (const p of paramMatches) {
273220
+ for (const p of [...new Set(paramMatches)].sort((a, b) => b.length - a.length)) {
273033
273221
  const paramName = p.substring(1);
273034
- if (body[paramName] !== void 0) {
273035
- result = result.replace(p, String(body[paramName]));
273036
- } else {
273037
- const memory = this.memory("workflow");
273038
- const memVal = await memory.get(paramName);
273039
- if (memVal !== null) {
273040
- result = result.replace(p, String(memVal));
273041
- }
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}"`);
273042
273232
  }
273043
273233
  }
273044
273234
  return result;
273045
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
+ }
273046
273267
  };
273047
273268
  exports2.WorkflowSimulator = WorkflowSimulator2;
273048
273269
  }
@@ -273242,6 +273463,243 @@ var require_environment_tools = __commonJS({
273242
273463
  }
273243
273464
  });
273244
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
+
273245
273703
  // ../../packages/mcp-server/dist/tools/workflow-tools.js
273246
273704
  var require_workflow_tools = __commonJS({
273247
273705
  "../../packages/mcp-server/dist/tools/workflow-tools.js"(exports2) {
@@ -273294,6 +273752,7 @@ var require_workflow_tools = __commonJS({
273294
273752
  var types_1 = require_types();
273295
273753
  var model_1 = require_dist2();
273296
273754
  var simulator_1 = require_dist13();
273755
+ var run_store_1 = require_run_store();
273297
273756
  exports2.listWorkflowsSchema = zod_1.z.object({
273298
273757
  projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder")
273299
273758
  });
@@ -273385,7 +273844,12 @@ var require_workflow_tools = __commonJS({
273385
273844
  workflow: zod_1.z.string().optional().describe("Workflow slug, name, or JSON file path to execute. If omitted, defaults to available workflow"),
273386
273845
  envName: zod_1.z.string().optional().describe("Target environment name (e.g., local, staging)"),
273387
273846
  baseUrl: zod_1.z.string().optional().describe("Override target base URL (e.g. http://localhost:3000)"),
273388
- 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).")
273389
273853
  });
273390
273854
  async function handleSimulateWorkflow(args) {
273391
273855
  const { model, filePath } = (0, types_1.loadModel)(args.projectPath);
@@ -273472,14 +273936,62 @@ var require_workflow_tools = __commonJS({
273472
273936
  baseUrl: args.baseUrl || targetEnv.baseUrl
273473
273937
  };
273474
273938
  const projectRoot = (0, types_1.projectRootOfModel)(filePath);
273475
- const workflowsDir = path15.join(path15.dirname(filePath), "workflows");
273939
+ const jeticDir = path15.dirname(filePath);
273940
+ const workflowsDir = path15.join(jeticDir, "workflows");
273476
273941
  const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv, { projectRoot });
273477
- const result = await simulator.simulateWorkflow(workflowDef, {
273478
- clearMemory: args.clearMemory ?? false,
273479
- loadWorkflow: (slugOrName) => (0, model_1.loadWorkflowByName)(workflowsDir, slugOrName)
273480
- });
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
+ }
273481
273993
  return {
273482
- ...result,
273994
+ ...(0, run_store_1.compactSimulationResult)(result, runId, reportPath),
273483
273995
  filePath,
273484
273996
  workflowFile: selectedItem.filePath
273485
273997
  };
@@ -273576,9 +274088,11 @@ var require_workflow_authoring_tools = __commonJS({
273576
274088
  exports2.workflowStepSchema = zod_1.z.object({
273577
274089
  name: zod_1.z.string().min(1).describe("Step name (must be unique within the workflow)"),
273578
274090
  method: zod_1.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]).describe("HTTP method"),
273579
- 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."),
273580
274092
  description: zod_1.z.string().optional().describe("What this step does"),
273581
- 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)."),
273582
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}}"'),
273583
274097
  capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" \u2192 "dot.path.in.response"'),
273584
274098
  captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" \u2192 "bodyField"'),
@@ -273598,7 +274112,6 @@ var require_workflow_authoring_tools = __commonJS({
273598
274112
  retry: stepRetrySchema.optional().describe("Global retry default applied to all steps (overridden per step)"),
273599
274113
  steps: zod_1.z.array(exports2.workflowStepSchema).min(1).describe("Ordered steps; later steps can use memory captured by earlier ones")
273600
274114
  }).passthrough();
273601
- var TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
273602
274115
  var KNOWN_WORKFLOW_KEYS = /* @__PURE__ */ new Set(["name", "description", "environment", "steps", "generatedAt", "retry"]);
273603
274116
  var KNOWN_STEP_KEYS = /* @__PURE__ */ new Set([
273604
274117
  "name",
@@ -273610,6 +274123,8 @@ var require_workflow_authoring_tools = __commonJS({
273610
274123
  "captureInput",
273611
274124
  "expectStatus",
273612
274125
  "body",
274126
+ "query",
274127
+ "timeoutMs",
273613
274128
  "condition",
273614
274129
  "onFailure",
273615
274130
  "continueOnStatus",
@@ -273647,10 +274162,35 @@ var require_workflow_authoring_tools = __commonJS({
273647
274162
  }
273648
274163
  function collectTemplateRefs(value, out) {
273649
274164
  if (typeof value === "string") {
273650
- TEMPLATE_RE.lastIndex = 0;
273651
- let m;
273652
- while ((m = TEMPLATE_RE.exec(value)) !== null)
273653
- 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
+ }
273654
274194
  } else if (Array.isArray(value)) {
273655
274195
  for (const v of value)
273656
274196
  collectTemplateRefs(v, out);
@@ -273746,6 +274286,25 @@ var require_workflow_authoring_tools = __commonJS({
273746
274286
  const seenNames = /* @__PURE__ */ new Set();
273747
274287
  const defined = /* @__PURE__ */ new Set();
273748
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
+ }
273749
274308
  wf.steps.forEach((rawStep, i) => {
273750
274309
  const stepNo = i + 1;
273751
274310
  const sName = typeof rawStep?.name === "string" ? rawStep.name : void 0;
@@ -273759,7 +274318,7 @@ var require_workflow_authoring_tools = __commonJS({
273759
274318
  if (MODEL_DIALECT_KEYS.has(key)) {
273760
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.');
273761
274320
  } else if (!KNOWN_STEP_KEYS.has(key)) {
273762
- 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).");
273763
274322
  }
273764
274323
  }
273765
274324
  if ("condition" in step && step.condition !== void 0) {
@@ -273839,30 +274398,46 @@ var require_workflow_authoring_tools = __commonJS({
273839
274398
  if (step.body !== void 0 && (typeof step.body !== "object" || step.body === null || Array.isArray(step.body))) {
273840
274399
  err(errors, stepNo, sName, "BAD_BODY", `Step ${stepNo} "body" must be an object of field -> value.`, 'Use { "email": "{{faker.internet.email}}" }.');
273841
274400
  }
273842
- if ((method === "GET" || method === "HEAD") && step.body && Object.keys(step.body).length > 0) {
273843
- 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
+ }
273844
274409
  }
273845
- 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") {
273846
274411
  for (const field of Object.keys(step.body)) {
273847
274412
  if (!(field in endpoint.requestBody.fields)) {
273848
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.");
273849
274414
  }
273850
274415
  }
273851
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
+ }
273852
274427
  const pathParams = wfPath.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g) || [];
273853
274428
  for (const p of pathParams) {
273854
274429
  const pname = p.substring(1);
273855
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}`);
273856
274432
  const inMemory = [...defined].some((d) => d.split(":")[1] === pname) || [...persistedMemory].some((d) => d.split(":")[1] === pname);
273857
- if (!inBody && !inMemory) {
273858
- 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}}}".`);
273859
274435
  }
273860
274436
  }
273861
- if (/\{\{[^}]+\}\}/.test(wfPath)) {
273862
- 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.');
273863
- }
273864
274437
  const refs = [];
273865
274438
  collectTemplateRefs(step.body, refs);
274439
+ collectTemplateRefs(step.query, refs);
274440
+ collectTemplateRefs(step.path, refs);
273866
274441
  if (step.inject && typeof step.inject === "object") {
273867
274442
  for (const v of Object.values(step.inject))
273868
274443
  collectTemplateRefs(v, refs);
@@ -273871,6 +274446,8 @@ var require_workflow_authoring_tools = __commonJS({
273871
274446
  if (ref.startsWith("faker.")) {
273872
274447
  if (ref.split(".").length < 3) {
273873
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.");
273874
274451
  }
273875
274452
  continue;
273876
274453
  }
@@ -273929,7 +274506,13 @@ var require_workflow_authoring_tools = __commonJS({
273929
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).');
273930
274507
  }
273931
274508
  if (step.onFailure === "continue" && step.capture && Object.keys(step.capture).length > 0) {
273932
- 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
+ }
273933
274516
  }
273934
274517
  }
273935
274518
  if (step.continueOnStatus !== void 0) {
@@ -288652,6 +289235,7 @@ var require_response_envelope = __commonJS({
288652
289235
  "WRITE_DISABLED",
288653
289236
  "INTERNAL"
288654
289237
  ];
289238
+ var MAX_MESSAGE_LENGTH = 500;
288655
289239
  function errorCodeFrom(error) {
288656
289240
  if (typeof error !== "object" || error === null || !("code" in error)) {
288657
289241
  return "INTERNAL";
@@ -288659,6 +289243,28 @@ var require_response_envelope = __commonJS({
288659
289243
  const code = error.code;
288660
289244
  return typeof code === "string" && ERROR_CODES.includes(code) ? code : "INTERNAL";
288661
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
+ }
288662
289268
  function toTextResult(data) {
288663
289269
  return {
288664
289270
  content: [{
@@ -288668,6 +289274,7 @@ var require_response_envelope = __commonJS({
288668
289274
  };
288669
289275
  }
288670
289276
  function toErrorResult(error) {
289277
+ const { message, hint } = describeError(error);
288671
289278
  return {
288672
289279
  isError: true,
288673
289280
  content: [{
@@ -288676,8 +289283,8 @@ var require_response_envelope = __commonJS({
288676
289283
  ok: false,
288677
289284
  error: {
288678
289285
  code: errorCodeFrom(error),
288679
- message: "Tool execution failed.",
288680
- hint: "Check the project configuration and arguments, then retry."
289286
+ message,
289287
+ hint
288681
289288
  }
288682
289289
  }, null, 2)
288683
289290
  }]
@@ -289020,6 +289627,47 @@ var require_workflow_designer_tools = __commonJS({
289020
289627
  if (Object.keys(inject).length > 0)
289021
289628
  step.inject = inject;
289022
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
+ }
289023
289671
  const capture = {};
289024
289672
  if (endpoint.produces) {
289025
289673
  for (const p of endpoint.produces) {
@@ -289070,6 +289718,109 @@ var require_workflow_designer_tools = __commonJS({
289070
289718
  }
289071
289719
  });
289072
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
+
289073
289824
  // ../../packages/mcp-server/dist/server.js
289074
289825
  var require_server3 = __commonJS({
289075
289826
  "../../packages/mcp-server/dist/server.js"(exports2) {
@@ -289134,6 +289885,7 @@ var require_server3 = __commonJS({
289134
289885
  var session_tools_1 = require_session_tools();
289135
289886
  var workflow_designer_tools_1 = require_workflow_designer_tools();
289136
289887
  var agent_tools_1 = require_agent_tools();
289888
+ var run_tools_1 = require_run_tools();
289137
289889
  exports2.JETIC_MCP_SERVER_NAME = "jetic-mcp-server";
289138
289890
  function getJeticMcpVersion() {
289139
289891
  try {
@@ -289222,6 +289974,9 @@ var require_server3 = __commonJS({
289222
289974
  "",
289223
289975
  "STEP 4g \u2014 RUN",
289224
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.",
289225
289980
  "",
289226
289981
  "\u2501\u2501 GENERAL RULES \u2501\u2501",
289227
289982
  "No AI-provider setup is needed: never ask for OpenRouter/OpenAI keys and never use `jetic config ai`.",
@@ -289289,6 +290044,8 @@ var require_server3 = __commonJS({
289289
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);
289290
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);
289291
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);
289292
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);
289293
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);
289294
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);
@@ -292564,7 +293321,7 @@ var mcpCommand = new import_commander18.Command("mcp").description("Launch Model
292564
293321
 
292565
293322
  // src/index.ts
292566
293323
  var program = new import_commander19.Command();
292567
- 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");
292568
293325
  program.addCommand(initCommand);
292569
293326
  program.addCommand(scanCommand);
292570
293327
  program.addCommand(inspectCommand);