jetic-cli 0.2.6 → 0.2.8

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
@@ -272670,10 +272670,27 @@ var require_workflow_simulator = __commonJS({
272670
272670
  "use strict";
272671
272671
  init_cjs_shims();
272672
272672
  Object.defineProperty(exports2, "__esModule", { value: true });
272673
- exports2.WorkflowSimulator = void 0;
272673
+ exports2.WorkflowSimulator = exports2.CONNECTION_ERROR_CODES = void 0;
272674
+ exports2.networkErrorCode = networkErrorCode2;
272675
+ exports2.probeTarget = probeTarget2;
272674
272676
  var memory_1 = require_dist12();
272675
272677
  var faker_1 = require("@faker-js/faker");
272676
272678
  var conditions_1 = require_conditions();
272679
+ exports2.CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
272680
+ "ECONNREFUSED",
272681
+ "ECONNRESET",
272682
+ "ENOTFOUND",
272683
+ "EAI_AGAIN",
272684
+ "ETIMEDOUT",
272685
+ "EHOSTUNREACH",
272686
+ "ENETUNREACH",
272687
+ "UND_ERR_CONNECT_TIMEOUT",
272688
+ "UND_ERR_SOCKET"
272689
+ ]);
272690
+ function networkErrorCode2(error) {
272691
+ const raw = error?.cause?.code ?? error?.cause?.errno ?? error?.code ?? error?.errno;
272692
+ return typeof raw === "string" ? raw : void 0;
272693
+ }
272677
272694
  function deepGet2(obj, dotPath) {
272678
272695
  if (obj === null || obj === void 0)
272679
272696
  return void 0;
@@ -272690,6 +272707,8 @@ var require_workflow_simulator = __commonJS({
272690
272707
  model;
272691
272708
  environment;
272692
272709
  runtime;
272710
+ strictTemplates = true;
272711
+ requestTimeoutMs = 3e4;
272693
272712
  constructor(model, environment, runtime = {}) {
272694
272713
  this.model = model;
272695
272714
  this.environment = environment;
@@ -272702,7 +272721,36 @@ var require_workflow_simulator = __commonJS({
272702
272721
  if (options?.clearMemory) {
272703
272722
  memory_1.JeticMemory.clearAllMemory(this.runtime.projectRoot);
272704
272723
  }
272724
+ this.strictTemplates = options?.strictTemplates ?? true;
272725
+ this.requestTimeoutMs = options?.requestTimeoutMs ?? 3e4;
272705
272726
  const startTime = Date.now();
272727
+ const preflightMode = options?.preflight ?? "off";
272728
+ const preflight = preflightMode !== "off" && workflow.steps.length > 0 ? await probeTarget2(this.environment.baseUrl, options?.preflightTimeoutMs ?? 3e3) : void 0;
272729
+ if (preflight && preflightMode === "abort" && preflight.verdict === "unreachable") {
272730
+ preflight.aborted = true;
272731
+ return {
272732
+ success: false,
272733
+ workflow: {
272734
+ name: workflow.name,
272735
+ description: workflow.description,
272736
+ stepsCount: workflow.steps.length
272737
+ },
272738
+ environment: {
272739
+ name: this.environment.name,
272740
+ baseUrl: this.environment.baseUrl
272741
+ },
272742
+ stats: {
272743
+ totalSteps: workflow.steps.length,
272744
+ executedSteps: 0,
272745
+ passedSteps: 0,
272746
+ failedSteps: 0,
272747
+ totalTimeMs: Date.now() - startTime
272748
+ },
272749
+ results: [],
272750
+ preflight,
272751
+ abortReason: preflight.message
272752
+ };
272753
+ }
272706
272754
  const state = { results: [], allPassed: true, halted: false };
272707
272755
  await this.runSteps(workflow, options, state, [workflow.name]);
272708
272756
  const stepResults = state.results;
@@ -272725,7 +272773,8 @@ var require_workflow_simulator = __commonJS({
272725
272773
  failedSteps: stepResults.filter((r) => !r.passed).length,
272726
272774
  totalTimeMs
272727
272775
  },
272728
- results: stepResults
272776
+ results: stepResults,
272777
+ ...preflight ? { preflight } : {}
272729
272778
  };
272730
272779
  }
272731
272780
  /**
@@ -272735,7 +272784,22 @@ var require_workflow_simulator = __commonJS({
272735
272784
  */
272736
272785
  async runSteps(workflow, options, state, lineage) {
272737
272786
  const baseUrl = this.environment.baseUrl;
272738
- const resolve2 = (value) => this.resolveTemplateString(value);
272787
+ const resolve2 = (value) => this.resolveTemplateString(value, false);
272788
+ const record = async (result) => {
272789
+ state.results.push(result);
272790
+ if (!options?.onStepResult)
272791
+ return;
272792
+ try {
272793
+ await options.onStepResult(result, {
272794
+ totalSteps: workflow.steps.length,
272795
+ executedSteps: state.results.length,
272796
+ passedSteps: state.results.filter((r) => r.passed).length,
272797
+ failedSteps: state.results.filter((r) => !r.passed).length,
272798
+ skippedSteps: state.results.filter((r) => r.skipped).length
272799
+ });
272800
+ } catch {
272801
+ }
272802
+ };
272739
272803
  for (let i = 0; i < workflow.steps.length; i++) {
272740
272804
  const step = workflow.steps[i];
272741
272805
  const stepStartTime = Date.now();
@@ -272743,7 +272807,7 @@ var require_workflow_simulator = __commonJS({
272743
272807
  const kind = step.condition === void 0 ? void 0 : (0, conditions_1.conditionKind)(step.condition);
272744
272808
  if (kind === "invalid") {
272745
272809
  const reason = (0, conditions_1.describeInvalidCondition)(step.condition);
272746
- state.results.push({
272810
+ await record({
272747
272811
  stepIndex: i + 1,
272748
272812
  name: stepName,
272749
272813
  method: step.method,
@@ -272752,6 +272816,8 @@ var require_workflow_simulator = __commonJS({
272752
272816
  expectedStatus: step.expectStatus ?? 200,
272753
272817
  actualStatus: 0,
272754
272818
  durationMs: Date.now() - stepStartTime,
272819
+ startedAt: new Date(stepStartTime).toISOString(),
272820
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272755
272821
  passed: false,
272756
272822
  skipped: false,
272757
272823
  retryCount: 0,
@@ -272768,7 +272834,7 @@ var require_workflow_simulator = __commonJS({
272768
272834
  if (kind === "legacy") {
272769
272835
  const condResult = await (0, conditions_1.evaluateLegacyCondition)(step.condition, resolve2);
272770
272836
  if (!condResult.matched) {
272771
- state.results.push({
272837
+ await record({
272772
272838
  stepIndex: i + 1,
272773
272839
  name: stepName,
272774
272840
  method: step.method,
@@ -272777,6 +272843,8 @@ var require_workflow_simulator = __commonJS({
272777
272843
  expectedStatus: step.expectStatus ?? 200,
272778
272844
  actualStatus: 0,
272779
272845
  durationMs: Date.now() - stepStartTime,
272846
+ startedAt: new Date(stepStartTime).toISOString(),
272847
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272780
272848
  passed: true,
272781
272849
  skipped: true,
272782
272850
  retryCount: 0,
@@ -272789,20 +272857,86 @@ var require_workflow_simulator = __commonJS({
272789
272857
  continue;
272790
272858
  }
272791
272859
  }
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}`;
272860
+ let injectedHeaders = {};
272861
+ let finalBody = {};
272862
+ let resolvedQuery = {};
272863
+ let resolvedPath = step.path;
272864
+ let fullUrl = "";
272865
+ let inputCaptured = [];
272866
+ try {
272867
+ const injections = await this.resolveInjections(step.inject);
272868
+ injectedHeaders = injections.headers;
272869
+ const resolvedBody = await this.resolveBodyTemplates(step.body || {});
272870
+ finalBody = { ...injections.body, ...resolvedBody };
272871
+ resolvedQuery = await this.resolveBodyTemplates(step.query || {});
272872
+ inputCaptured = await this.captureInputToMemory(step.captureInput, finalBody);
272873
+ resolvedPath = await this.resolvePathParams(step.path, finalBody);
272874
+ fullUrl = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
272875
+ } catch (err) {
272876
+ await record({
272877
+ stepIndex: i + 1,
272878
+ name: stepName,
272879
+ method: step.method,
272880
+ path: step.path,
272881
+ targetUrl: "",
272882
+ expectedStatus: step.expectStatus ?? 200,
272883
+ actualStatus: 0,
272884
+ durationMs: Date.now() - stepStartTime,
272885
+ startedAt: new Date(stepStartTime).toISOString(),
272886
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272887
+ passed: false,
272888
+ skipped: false,
272889
+ retryCount: 0,
272890
+ requestHeaders: {},
272891
+ requestBody: {},
272892
+ responseBody: null,
272893
+ capturedMemory: [],
272894
+ error: `Step ${i + 1} "${stepName}": ${err?.message || err}`
272895
+ });
272896
+ state.allPassed = false;
272897
+ state.halted = true;
272898
+ return "stop";
272899
+ }
272798
272900
  const expectedStatus = step.expectStatus ?? 200;
272901
+ let requestUrl = fullUrl;
272902
+ {
272903
+ const upperMethod = step.method.toUpperCase();
272904
+ const bodyAsQuery = ["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0;
272905
+ if (Object.keys(resolvedQuery).length > 0 || bodyAsQuery) {
272906
+ const params = new URLSearchParams();
272907
+ const append = (source) => {
272908
+ for (const [k, v] of Object.entries(source)) {
272909
+ if (v === void 0 || v === null)
272910
+ continue;
272911
+ if (Array.isArray(v)) {
272912
+ for (const item of v)
272913
+ params.append(k, typeof item === "object" ? JSON.stringify(item) : String(item));
272914
+ } else {
272915
+ params.append(k, typeof v === "object" ? JSON.stringify(v) : String(v));
272916
+ }
272917
+ }
272918
+ };
272919
+ append(resolvedQuery);
272920
+ if (bodyAsQuery)
272921
+ append(finalBody);
272922
+ const qs = params.toString();
272923
+ if (qs)
272924
+ requestUrl = `${fullUrl}${fullUrl.includes("?") ? "&" : "?"}${qs}`;
272925
+ }
272926
+ }
272927
+ const actualRequestHeaders = {
272928
+ "Content-Type": "application/json",
272929
+ ...injectedHeaders
272930
+ };
272799
272931
  const maxAttempts = Math.max(1, step.retry?.times ?? workflow.retry?.times ?? 1);
272800
272932
  const retryDelayMs = step.retry?.delayMs ?? workflow.retry?.delayMs ?? 0;
272801
272933
  let attempts = 0;
272802
272934
  let responseStatus = 0;
272803
272935
  let responseBody = null;
272936
+ let responseHeaders;
272804
272937
  let stepPassed = false;
272805
272938
  let errorMsg;
272939
+ let connectionCode;
272806
272940
  while (attempts < maxAttempts) {
272807
272941
  attempts++;
272808
272942
  if (attempts > 1 && retryDelayMs > 0) {
@@ -272810,26 +272944,25 @@ var require_workflow_simulator = __commonJS({
272810
272944
  }
272811
272945
  responseStatus = 0;
272812
272946
  responseBody = null;
272947
+ responseHeaders = void 0;
272813
272948
  errorMsg = void 0;
272949
+ connectionCode = void 0;
272950
+ const timeoutMs = step.timeoutMs ?? this.requestTimeoutMs;
272814
272951
  try {
272952
+ const upperMethod = step.method.toUpperCase();
272815
272953
  const fetchOptions = {
272816
- method: step.method.toUpperCase(),
272817
- headers: { "Content-Type": "application/json", ...injectedHeaders }
272954
+ method: upperMethod,
272955
+ headers: actualRequestHeaders
272818
272956
  };
272819
- if (!["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
272957
+ if (!["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0) {
272820
272958
  fetchOptions.body = JSON.stringify(finalBody);
272821
272959
  }
272822
- let finalUrl = fullUrl;
272823
- if (["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
272824
- 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()}`;
272960
+ if (timeoutMs > 0) {
272961
+ fetchOptions.signal = AbortSignal.timeout(timeoutMs);
272830
272962
  }
272831
- const resp = await fetch(finalUrl, fetchOptions);
272963
+ const resp = await fetch(requestUrl, fetchOptions);
272832
272964
  responseStatus = resp.status;
272965
+ responseHeaders = Object.fromEntries(resp.headers.entries());
272833
272966
  const ct = resp.headers.get("content-type") || "";
272834
272967
  if (ct.includes("application/json")) {
272835
272968
  try {
@@ -272846,7 +272979,14 @@ var require_workflow_simulator = __commonJS({
272846
272979
  break;
272847
272980
  } catch (err) {
272848
272981
  stepPassed = false;
272849
- errorMsg = err.message || String(err);
272982
+ connectionCode = networkErrorCode2(err);
272983
+ if (err?.name === "TimeoutError" || connectionCode === "ETIMEDOUT") {
272984
+ errorMsg = `Request timed out after ${timeoutMs}ms (${step.method} ${resolvedPath}).`;
272985
+ } else if (connectionCode === "ECONNREFUSED") {
272986
+ errorMsg = `Connection refused (ECONNREFUSED) at ${baseUrl} \u2014 the server is not accepting connections.`;
272987
+ } else {
272988
+ errorMsg = err?.message || String(err);
272989
+ }
272850
272990
  }
272851
272991
  }
272852
272992
  const responseCaptured = stepPassed ? await this.captureToMemory(step.capture, responseBody) : [];
@@ -272857,24 +272997,36 @@ var require_workflow_simulator = __commonJS({
272857
272997
  method: step.method,
272858
272998
  path: step.path,
272859
272999
  targetUrl: fullUrl,
273000
+ resolvedUrl: requestUrl,
272860
273001
  expectedStatus,
272861
273002
  actualStatus: responseStatus,
272862
273003
  durationMs: Date.now() - stepStartTime,
273004
+ startedAt: new Date(stepStartTime).toISOString(),
273005
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272863
273006
  passed: stepPassed,
272864
273007
  skipped: false,
272865
273008
  retryCount: attempts - 1,
272866
- requestHeaders: injectedHeaders,
273009
+ requestHeaders: actualRequestHeaders,
272867
273010
  requestBody: finalBody,
273011
+ ...Object.keys(resolvedQuery).length > 0 ? { requestQuery: resolvedQuery } : {},
272868
273012
  responseBody,
273013
+ ...responseHeaders ? { responseHeaders } : {},
272869
273014
  capturedMemory: stepPassed ? [...inputCaptured, ...responseCaptured] : [...inputCaptured],
272870
273015
  ...stepPassed ? {} : {
272871
273016
  failureHandling: onFailure === "continue" ? "continued" : "aborted",
272872
273017
  error: errorMsg
272873
273018
  }
272874
273019
  };
272875
- state.results.push(result);
273020
+ await record(result);
272876
273021
  if (!stepPassed) {
272877
273022
  state.allPassed = false;
273023
+ if (connectionCode && exports2.CONNECTION_ERROR_CODES.has(connectionCode)) {
273024
+ const remaining = workflow.steps.length - (i + 1);
273025
+ result.failureHandling = "aborted";
273026
+ result.error = `${result.error || "Connection failed"}; aborting early because the target is unreachable${remaining > 0 ? ` (${remaining} step(s) not executed)` : ""}.`;
273027
+ state.halted = true;
273028
+ return "stop";
273029
+ }
272878
273030
  if (onFailure === "abort") {
272879
273031
  state.halted = true;
272880
273032
  return "stop";
@@ -272917,55 +273069,120 @@ var require_workflow_simulator = __commonJS({
272917
273069
  }
272918
273070
  return "done";
272919
273071
  }
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) : "" });
273072
+ findPlaceholders(value) {
273073
+ const placeholders = [];
273074
+ let i = 0;
273075
+ while (i < value.length - 1) {
273076
+ if (value[i] !== "{" || value[i + 1] !== "{") {
273077
+ i++;
272940
273078
  continue;
272941
273079
  }
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 });
273080
+ let depth = 0;
273081
+ let closed = -1;
273082
+ let j = i + 2;
273083
+ while (j < value.length) {
273084
+ const ch = value[j];
273085
+ if (ch === "{")
273086
+ depth++;
273087
+ else if (ch === "}") {
273088
+ if (depth > 0)
273089
+ depth--;
273090
+ else if (value[j + 1] === "}") {
273091
+ closed = j;
273092
+ break;
273093
+ }
272952
273094
  }
272953
- continue;
273095
+ j++;
273096
+ }
273097
+ if (closed === -1)
273098
+ break;
273099
+ placeholders.push({ raw: value.slice(i, closed + 2), expr: value.slice(i + 2, closed).trim(), index: i });
273100
+ i = closed + 2;
273101
+ }
273102
+ return placeholders;
273103
+ }
273104
+ async resolveTemplateString(value, strict = this.strictTemplates) {
273105
+ const placeholders = this.findPlaceholders(value);
273106
+ if (placeholders.length === 0)
273107
+ return value;
273108
+ let result = "";
273109
+ let cursor = 0;
273110
+ for (const placeholder of placeholders) {
273111
+ result += value.slice(cursor, placeholder.index);
273112
+ result += await this.resolveExpression(placeholder.expr, strict, placeholder.raw);
273113
+ cursor = placeholder.index + placeholder.raw.length;
273114
+ }
273115
+ return result + value.slice(cursor);
273116
+ }
273117
+ async resolveExpression(expr, strict, raw) {
273118
+ if (expr.startsWith("faker.")) {
273119
+ return this.resolveFakerExpression(expr, strict, raw);
273120
+ }
273121
+ if (expr.includes(":")) {
273122
+ const colonIdx = expr.indexOf(":");
273123
+ const scope = expr.slice(0, colonIdx);
273124
+ const key = expr.slice(colonIdx + 1);
273125
+ const envKey = `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
273126
+ if (scope === "human") {
273127
+ const envVal = process.env[envKey];
273128
+ if (envVal !== void 0 && envVal !== "")
273129
+ return envVal;
273130
+ }
273131
+ const memVal = await this.memory(scope).get(key);
273132
+ if (memVal !== null && memVal !== void 0) {
273133
+ return typeof memVal === "string" ? memVal : JSON.stringify(memVal);
273134
+ }
273135
+ if (!strict)
273136
+ return "";
273137
+ if (scope === "human") {
273138
+ throw new Error(`missing human input ${raw} \u2014 set ${envKey} or seed "human:${key}" memory (non-interactive runners cannot prompt)`);
272954
273139
  }
272955
- replacements.push({ placeholder: match[0], resolved: match[0] });
273140
+ throw new Error(`unresolved template ${raw} \u2014 no value for "${scope}:${key}"; capture it in an earlier step or seed it first`);
272956
273141
  }
272957
- let result = value;
272958
- for (const { placeholder, resolved } of replacements) {
272959
- result = result.replace(placeholder, resolved);
273142
+ return raw;
273143
+ }
273144
+ resolveFakerExpression(expr, strict, raw) {
273145
+ if (/[({]/.test(expr)) {
273146
+ if (!strict)
273147
+ return raw;
273148
+ 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`);
273149
+ }
273150
+ let fn = faker_1.faker;
273151
+ for (const part of expr.split(".").slice(1))
273152
+ fn = fn?.[part];
273153
+ if (typeof fn !== "function") {
273154
+ if (!strict)
273155
+ return raw;
273156
+ throw new Error(`unknown faker path ${raw} \u2014 use a complete parameterless path like {{faker.internet.email}} or {{faker.string.uuid}}`);
273157
+ }
273158
+ try {
273159
+ return String(fn());
273160
+ } catch (err) {
273161
+ if (!strict)
273162
+ return raw;
273163
+ throw new Error(`faker expression ${raw} failed: ${err?.message || err} \u2014 this faker path needs arguments; parameterized faker calls are not supported`);
272960
273164
  }
272961
- return result;
272962
273165
  }
272963
273166
  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;
273167
+ const resolved = await this.resolveDeep(body);
273168
+ return resolved && typeof resolved === "object" && !Array.isArray(resolved) ? resolved : {};
273169
+ }
273170
+ async resolveDeep(value) {
273171
+ if (typeof value === "string")
273172
+ return this.resolveTemplateString(value);
273173
+ if (Array.isArray(value)) {
273174
+ const out = [];
273175
+ for (const item of value)
273176
+ out.push(await this.resolveDeep(item));
273177
+ return out;
273178
+ }
273179
+ if (value && typeof value === "object") {
273180
+ const out = {};
273181
+ for (const [key, item] of Object.entries(value))
273182
+ out[key] = await this.resolveDeep(item);
273183
+ return out;
272967
273184
  }
272968
- return resolved;
273185
+ return value;
272969
273186
  }
272970
273187
  async resolveInjections(inject) {
272971
273188
  const headers = {};
@@ -272980,10 +273197,18 @@ var require_workflow_simulator = __commonJS({
272980
273197
  const [scope, key] = memKeyOrTemplate.includes(":") ? memKeyOrTemplate.split(":", 2) : ["workflow", memKeyOrTemplate];
272981
273198
  const scopedMemory = this.memory(scope);
272982
273199
  const value = await scopedMemory.get(key);
272983
- if (value === null)
273200
+ if (value === null || value === void 0) {
273201
+ if (this.strictTemplates) {
273202
+ throw new Error(`inject target "${target}" references "${scope}:${key}", which is not in memory \u2014 capture it earlier or seed it first`);
273203
+ }
272984
273204
  continue;
273205
+ }
272985
273206
  strValue = typeof value === "string" ? value : JSON.stringify(value);
272986
273207
  }
273208
+ const headerName = target.startsWith("header:") ? target.slice("header:".length) : target.startsWith("body:") ? null : target;
273209
+ if (headerName && headerName.toLowerCase() === "authorization" && strValue && !/^[A-Za-z][A-Za-z0-9-]*\s/.test(strValue)) {
273210
+ strValue = `Bearer ${strValue}`;
273211
+ }
272987
273212
  if (target.startsWith("body:")) {
272988
273213
  body[target.slice(5)] = strValue;
272989
273214
  } else if (target.startsWith("header:")) {
@@ -273025,26 +273250,59 @@ var require_workflow_simulator = __commonJS({
273025
273250
  return captured;
273026
273251
  }
273027
273252
  async resolvePathParams(urlPath, body) {
273028
- let result = urlPath;
273029
- const paramMatches = urlPath.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
273253
+ let result = await this.resolveTemplateString(urlPath);
273254
+ const paramMatches = result.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
273030
273255
  if (!paramMatches)
273031
273256
  return result;
273032
- for (const p of paramMatches) {
273257
+ for (const p of [...new Set(paramMatches)].sort((a, b) => b.length - a.length)) {
273033
273258
  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
- }
273259
+ let value = body[paramName];
273260
+ if (value === void 0 || value === null) {
273261
+ value = await this.memory("workflow").get(paramName);
273262
+ }
273263
+ if (value !== void 0 && value !== null) {
273264
+ result = result.split(p).join(typeof value === "string" ? value : String(value));
273265
+ continue;
273266
+ }
273267
+ if (this.strictTemplates) {
273268
+ 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
273269
  }
273043
273270
  }
273044
273271
  return result;
273045
273272
  }
273046
273273
  };
273047
273274
  exports2.WorkflowSimulator = WorkflowSimulator2;
273275
+ async function probeTarget2(baseUrl, timeoutMs = 3e3) {
273276
+ const probe = { url: baseUrl, verdict: "unreachable", message: "", aborted: false };
273277
+ try {
273278
+ const resp = await fetch(baseUrl, {
273279
+ method: "GET",
273280
+ redirect: "manual",
273281
+ signal: AbortSignal.timeout(timeoutMs)
273282
+ });
273283
+ probe.status = resp.status;
273284
+ if (resp.status >= 500) {
273285
+ probe.verdict = "server-error";
273286
+ 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.`;
273287
+ } else {
273288
+ probe.verdict = "healthy";
273289
+ probe.message = `Server reachable at ${baseUrl} (HTTP ${resp.status}).`;
273290
+ }
273291
+ } catch (err) {
273292
+ const code = networkErrorCode2(err);
273293
+ probe.status = void 0;
273294
+ if (err?.name === "TimeoutError" || code === "ETIMEDOUT") {
273295
+ probe.message = `No response from ${baseUrl} within ${timeoutMs}ms \u2014 the server may be down or hanging.`;
273296
+ } else if (code === "ECONNREFUSED") {
273297
+ probe.message = `Connection refused at ${baseUrl} \u2014 nothing is listening there. Start the app (check its PORT/.env) and retry.`;
273298
+ } else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
273299
+ probe.message = `DNS lookup failed for ${baseUrl}${code ? ` (${code})` : ""} \u2014 check the host name.`;
273300
+ } else {
273301
+ probe.message = `Cannot reach ${baseUrl}${code ? ` (${code})` : ""}: ${err?.message || err}`;
273302
+ }
273303
+ }
273304
+ return probe;
273305
+ }
273048
273306
  }
273049
273307
  });
273050
273308
 
@@ -273070,7 +273328,7 @@ var require_dist13 = __commonJS({
273070
273328
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
273071
273329
  };
273072
273330
  Object.defineProperty(exports2, "__esModule", { value: true });
273073
- exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
273331
+ exports2.CONNECTION_ERROR_CODES = exports2.networkErrorCode = exports2.probeTarget = exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
273074
273332
  var simulator_1 = require_simulator();
273075
273333
  Object.defineProperty(exports2, "EndpointSimulator", { enumerable: true, get: function() {
273076
273334
  return simulator_1.EndpointSimulator;
@@ -273087,6 +273345,15 @@ var require_dist13 = __commonJS({
273087
273345
  Object.defineProperty(exports2, "WorkflowSimulator", { enumerable: true, get: function() {
273088
273346
  return workflow_simulator_1.WorkflowSimulator;
273089
273347
  } });
273348
+ Object.defineProperty(exports2, "probeTarget", { enumerable: true, get: function() {
273349
+ return workflow_simulator_1.probeTarget;
273350
+ } });
273351
+ Object.defineProperty(exports2, "networkErrorCode", { enumerable: true, get: function() {
273352
+ return workflow_simulator_1.networkErrorCode;
273353
+ } });
273354
+ Object.defineProperty(exports2, "CONNECTION_ERROR_CODES", { enumerable: true, get: function() {
273355
+ return workflow_simulator_1.CONNECTION_ERROR_CODES;
273356
+ } });
273090
273357
  __exportStar(require_conditions(), exports2);
273091
273358
  }
273092
273359
  });
@@ -273242,6 +273509,314 @@ var require_environment_tools = __commonJS({
273242
273509
  }
273243
273510
  });
273244
273511
 
273512
+ // ../../packages/mcp-server/dist/run-store.js
273513
+ var require_run_store = __commonJS({
273514
+ "../../packages/mcp-server/dist/run-store.js"(exports2) {
273515
+ "use strict";
273516
+ init_cjs_shims();
273517
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
273518
+ if (k2 === void 0) k2 = k;
273519
+ var desc = Object.getOwnPropertyDescriptor(m, k);
273520
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
273521
+ desc = { enumerable: true, get: function() {
273522
+ return m[k];
273523
+ } };
273524
+ }
273525
+ Object.defineProperty(o, k2, desc);
273526
+ }) : (function(o, m, k, k2) {
273527
+ if (k2 === void 0) k2 = k;
273528
+ o[k2] = m[k];
273529
+ }));
273530
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
273531
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
273532
+ }) : function(o, v) {
273533
+ o["default"] = v;
273534
+ });
273535
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
273536
+ var ownKeys = function(o) {
273537
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
273538
+ var ar = [];
273539
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
273540
+ return ar;
273541
+ };
273542
+ return ownKeys(o);
273543
+ };
273544
+ return function(mod) {
273545
+ if (mod && mod.__esModule) return mod;
273546
+ var result = {};
273547
+ if (mod != null) {
273548
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
273549
+ }
273550
+ __setModuleDefault(result, mod);
273551
+ return result;
273552
+ };
273553
+ })();
273554
+ Object.defineProperty(exports2, "__esModule", { value: true });
273555
+ exports2.MAX_PERSISTED_BODY_CHARS = void 0;
273556
+ exports2.newRunId = newRunId;
273557
+ exports2.runsDir = runsDir;
273558
+ exports2.runDir = runDir;
273559
+ exports2.deriveRunStatus = deriveRunStatus;
273560
+ exports2.stepToProgress = stepToProgress;
273561
+ exports2.statsFromSteps = statsFromSteps;
273562
+ exports2.truncateResultForStorage = truncateResultForStorage;
273563
+ exports2.writeSimulationRun = writeSimulationRun;
273564
+ exports2.readSimulationRun = readSimulationRun;
273565
+ exports2.readRunStatus = readRunStatus;
273566
+ exports2.writeRunningStatus = writeRunningStatus;
273567
+ exports2.updateRunStatus = updateRunStatus;
273568
+ exports2.writeRunError = writeRunError;
273569
+ exports2.listRuns = listRuns;
273570
+ exports2.compactSimulationResult = compactSimulationResult;
273571
+ var fs10 = __importStar(require("fs"));
273572
+ var path15 = __importStar(require("path"));
273573
+ var core_1 = require_dist();
273574
+ function newRunId() {
273575
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
273576
+ }
273577
+ function runsDir(jeticDir) {
273578
+ return path15.join(jeticDir, "runs");
273579
+ }
273580
+ function runDir(jeticDir, runId) {
273581
+ return path15.join(runsDir(jeticDir), runId);
273582
+ }
273583
+ function reportPath(jeticDir, runId) {
273584
+ return path15.join(runDir(jeticDir, runId), "simulation-report.json");
273585
+ }
273586
+ function statusPath(jeticDir, runId) {
273587
+ return path15.join(runDir(jeticDir, runId), "simulation-status.json");
273588
+ }
273589
+ function deriveRunStatus(result) {
273590
+ if (result.success)
273591
+ return "passed";
273592
+ if (result.abortReason)
273593
+ return "aborted";
273594
+ return "failed";
273595
+ }
273596
+ function stepToProgress(r) {
273597
+ return {
273598
+ index: r.stepIndex,
273599
+ name: r.name,
273600
+ method: r.method,
273601
+ path: r.path,
273602
+ status: r.skipped ? "skipped" : r.passed ? "passed" : "failed",
273603
+ expectedStatus: r.expectedStatus,
273604
+ ...r.skipped ? {} : { actualStatus: r.actualStatus },
273605
+ passed: r.passed,
273606
+ skipped: r.skipped,
273607
+ ...r.retryCount > 0 ? { retryCount: r.retryCount } : {},
273608
+ durationMs: r.durationMs,
273609
+ startedAt: r.startedAt,
273610
+ finishedAt: r.finishedAt,
273611
+ ...r.error ? { error: r.error } : {}
273612
+ };
273613
+ }
273614
+ function statsFromSteps(steps) {
273615
+ const completed = steps.filter((s) => s.status !== "pending");
273616
+ return {
273617
+ totalSteps: steps.length,
273618
+ executedSteps: completed.length,
273619
+ passedSteps: completed.filter((s) => s.status === "passed" || s.status === "skipped").length,
273620
+ failedSteps: completed.filter((s) => s.status === "failed").length,
273621
+ skippedSteps: completed.filter((s) => s.status === "skipped").length
273622
+ };
273623
+ }
273624
+ exports2.MAX_PERSISTED_BODY_CHARS = 16384;
273625
+ function truncateResultForStorage(result, maxChars = exports2.MAX_PERSISTED_BODY_CHARS) {
273626
+ const clone = JSON.parse(JSON.stringify(result));
273627
+ for (const step of clone.results) {
273628
+ if (step.responseBody === void 0 || step.responseBody === null)
273629
+ continue;
273630
+ const serialized = typeof step.responseBody === "string" ? step.responseBody : JSON.stringify(step.responseBody);
273631
+ if (serialized.length <= maxChars)
273632
+ continue;
273633
+ const removed = serialized.length - maxChars;
273634
+ step.responseBody = typeof step.responseBody === "string" ? `${step.responseBody.slice(0, maxChars)}
273635
+ \u2026[truncated: ${removed} more characters]` : { _truncated: true, preview: serialized.slice(0, maxChars), fullLengthChars: serialized.length };
273636
+ step.responseBodyTruncated = true;
273637
+ }
273638
+ return clone;
273639
+ }
273640
+ function writeSimulationRun(jeticDir, runId, result, meta) {
273641
+ const status = deriveRunStatus(result);
273642
+ const durationMs = result.stats?.totalTimeMs ?? 0;
273643
+ const report = {
273644
+ type: "simulation",
273645
+ runId,
273646
+ workflowName: result.workflow?.name || "workflow",
273647
+ workflowFile: meta.workflowFile,
273648
+ environment: result.environment?.name || "unknown",
273649
+ baseUrl: result.environment?.baseUrl || "",
273650
+ status,
273651
+ startedAt: meta.startedAt,
273652
+ finishedAt: meta.finishedAt,
273653
+ durationMs,
273654
+ result: truncateResultForStorage(result)
273655
+ };
273656
+ const rPath = reportPath(jeticDir, runId);
273657
+ (0, core_1.writeJsonSync)(rPath, report);
273658
+ const sPath = statusPath(jeticDir, runId);
273659
+ const statusDoc = {
273660
+ type: "simulation",
273661
+ runId,
273662
+ workflowName: report.workflowName,
273663
+ environment: report.environment,
273664
+ baseUrl: report.baseUrl,
273665
+ status,
273666
+ startedAt: meta.startedAt,
273667
+ finishedAt: meta.finishedAt,
273668
+ durationMs,
273669
+ stats: {
273670
+ totalSteps: result.stats?.totalSteps ?? result.results.length,
273671
+ executedSteps: result.stats?.executedSteps ?? 0,
273672
+ passedSteps: result.stats?.passedSteps ?? 0,
273673
+ failedSteps: result.stats?.failedSteps ?? 0,
273674
+ skippedSteps: result.results.filter((r) => r.skipped).length
273675
+ },
273676
+ steps: result.results.map(stepToProgress),
273677
+ reportPath: rPath
273678
+ };
273679
+ (0, core_1.writeJsonSync)(sPath, statusDoc);
273680
+ return { runDir: runDir(jeticDir, runId), reportPath: rPath };
273681
+ }
273682
+ function readSimulationRun(jeticDir, runId) {
273683
+ return (0, core_1.readJsonSync)(reportPath(jeticDir, runId));
273684
+ }
273685
+ function readRunStatus(jeticDir, runId) {
273686
+ return (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
273687
+ }
273688
+ function writeRunningStatus(jeticDir, runId, meta) {
273689
+ const steps = meta.steps?.map((s, i) => ({
273690
+ index: i + 1,
273691
+ name: s.name,
273692
+ method: s.method,
273693
+ path: s.path,
273694
+ status: "pending"
273695
+ }));
273696
+ const doc = {
273697
+ type: "simulation",
273698
+ runId,
273699
+ workflowName: meta.workflowName,
273700
+ environment: meta.environment,
273701
+ baseUrl: meta.baseUrl,
273702
+ status: "running",
273703
+ startedAt: meta.startedAt,
273704
+ stats: {
273705
+ totalSteps: steps?.length ?? 0,
273706
+ executedSteps: 0,
273707
+ passedSteps: 0,
273708
+ failedSteps: 0,
273709
+ skippedSteps: 0
273710
+ },
273711
+ ...steps ? { steps } : {},
273712
+ reportPath: reportPath(jeticDir, runId)
273713
+ };
273714
+ (0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
273715
+ }
273716
+ function updateRunStatus(jeticDir, runId, update) {
273717
+ const doc = (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
273718
+ if (!doc)
273719
+ return;
273720
+ update(doc);
273721
+ (0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
273722
+ }
273723
+ function writeRunError(jeticDir, runId, error, meta) {
273724
+ const doc = {
273725
+ type: "simulation",
273726
+ runId,
273727
+ workflowName: meta.workflowName,
273728
+ environment: meta.environment,
273729
+ baseUrl: meta.baseUrl,
273730
+ status: "error",
273731
+ startedAt: meta.startedAt,
273732
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
273733
+ error: error instanceof Error ? error.message : String(error),
273734
+ stats: { totalSteps: 0, executedSteps: 0, passedSteps: 0, failedSteps: 0, skippedSteps: 0 },
273735
+ reportPath: reportPath(jeticDir, runId)
273736
+ };
273737
+ (0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
273738
+ }
273739
+ function listRuns(jeticDir, limit = 20) {
273740
+ const dir = runsDir(jeticDir);
273741
+ if (!fs10.existsSync(dir))
273742
+ return [];
273743
+ const items = [];
273744
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
273745
+ if (!entry.isDirectory())
273746
+ continue;
273747
+ const runId = entry.name;
273748
+ const simStatus = (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
273749
+ if (simStatus) {
273750
+ items.push({
273751
+ runId,
273752
+ type: "simulation",
273753
+ name: simStatus.workflowName,
273754
+ status: simStatus.status,
273755
+ startedAt: simStatus.startedAt,
273756
+ durationMs: simStatus.durationMs,
273757
+ reportPath: simStatus.reportPath
273758
+ });
273759
+ continue;
273760
+ }
273761
+ const agentReport = (0, core_1.readJsonSync)(path15.join(runDir(jeticDir, runId), "agent-report.json"));
273762
+ if (agentReport) {
273763
+ items.push({
273764
+ runId,
273765
+ type: "agent",
273766
+ name: agentReport.agentId || "agent",
273767
+ status: agentReport.status || "completed",
273768
+ reportPath: path15.join(runDir(jeticDir, runId), "agent-report.json")
273769
+ });
273770
+ }
273771
+ }
273772
+ items.sort((a, b) => b.runId.localeCompare(a.runId));
273773
+ return items.slice(0, limit);
273774
+ }
273775
+ function compactSimulationResult(result, runId, reportFilePath) {
273776
+ const steps = result.results.map((r) => {
273777
+ const summary = {
273778
+ // stepIndex is already 1-based (set as i + 1 by the simulator).
273779
+ step: r.stepIndex,
273780
+ name: r.name,
273781
+ method: r.method,
273782
+ path: r.path,
273783
+ targetUrl: r.targetUrl,
273784
+ status: r.skipped ? "skipped" : r.passed ? "passed" : "failed",
273785
+ expectedStatus: r.expectedStatus,
273786
+ durationMs: r.durationMs
273787
+ };
273788
+ if (!r.skipped)
273789
+ summary.actualStatus = r.actualStatus;
273790
+ if (r.retryCount > 0)
273791
+ summary.retryCount = r.retryCount;
273792
+ if (r.conditionResult) {
273793
+ summary.condition = `${r.conditionResult.evaluated} \u2192 ${r.conditionResult.matched ? "matched" : "not matched"}`;
273794
+ }
273795
+ if (r.error)
273796
+ summary.error = r.error;
273797
+ return summary;
273798
+ });
273799
+ return {
273800
+ ok: result.success,
273801
+ success: result.success,
273802
+ runId,
273803
+ status: deriveRunStatus(result),
273804
+ workflow: result.workflow?.name || "workflow",
273805
+ environment: {
273806
+ name: result.environment?.name || "unknown",
273807
+ baseUrl: result.environment?.baseUrl || ""
273808
+ },
273809
+ stats: result.stats,
273810
+ ...result.preflight ? { preflight: result.preflight } : {},
273811
+ ...result.abortReason ? { abortReason: result.abortReason } : {},
273812
+ steps,
273813
+ reportPath: reportFilePath,
273814
+ next: result.success ? `Full request/response detail: jetic_get_run { "runId": "${runId}" }` : `Inspect the failing step in full detail: jetic_get_run { "runId": "${runId}" }`
273815
+ };
273816
+ }
273817
+ }
273818
+ });
273819
+
273245
273820
  // ../../packages/mcp-server/dist/tools/workflow-tools.js
273246
273821
  var require_workflow_tools = __commonJS({
273247
273822
  "../../packages/mcp-server/dist/tools/workflow-tools.js"(exports2) {
@@ -273294,6 +273869,7 @@ var require_workflow_tools = __commonJS({
273294
273869
  var types_1 = require_types();
273295
273870
  var model_1 = require_dist2();
273296
273871
  var simulator_1 = require_dist13();
273872
+ var run_store_1 = require_run_store();
273297
273873
  exports2.listWorkflowsSchema = zod_1.z.object({
273298
273874
  projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder")
273299
273875
  });
@@ -273385,7 +273961,12 @@ var require_workflow_tools = __commonJS({
273385
273961
  workflow: zod_1.z.string().optional().describe("Workflow slug, name, or JSON file path to execute. If omitted, defaults to available workflow"),
273386
273962
  envName: zod_1.z.string().optional().describe("Target environment name (e.g., local, staging)"),
273387
273963
  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")
273964
+ clearMemory: zod_1.z.boolean().optional().default(false).describe("If true, clears Jetic memory before execution"),
273965
+ 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."),
273966
+ 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."),
273967
+ 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.'),
273968
+ 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."),
273969
+ requestTimeoutMs: zod_1.z.number().int().positive().max(12e4).optional().default(3e4).describe("Per-request timeout in ms (default 30000, max 120000).")
273389
273970
  });
273390
273971
  async function handleSimulateWorkflow(args) {
273391
273972
  const { model, filePath } = (0, types_1.loadModel)(args.projectPath);
@@ -273472,14 +274053,82 @@ var require_workflow_tools = __commonJS({
273472
274053
  baseUrl: args.baseUrl || targetEnv.baseUrl
273473
274054
  };
273474
274055
  const projectRoot = (0, types_1.projectRootOfModel)(filePath);
273475
- const workflowsDir = path15.join(path15.dirname(filePath), "workflows");
274056
+ const jeticDir = path15.dirname(filePath);
274057
+ const workflowsDir = path15.join(jeticDir, "workflows");
273476
274058
  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
- });
274059
+ const runId = (0, run_store_1.newRunId)();
274060
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
274061
+ const plannedSteps = workflowDef.steps.map((s) => ({
274062
+ name: s.name || `${s.method} ${s.path}`,
274063
+ method: s.method,
274064
+ path: s.path
274065
+ }));
274066
+ const runMeta = {
274067
+ workflowName: workflowDef.name || selectedItem.name,
274068
+ environment: effectiveEnv.name,
274069
+ baseUrl: effectiveEnv.baseUrl,
274070
+ startedAt,
274071
+ steps: plannedSteps
274072
+ };
274073
+ const execute = async () => {
274074
+ const result2 = await simulator.simulateWorkflow(workflowDef, {
274075
+ clearMemory: args.clearMemory ?? false,
274076
+ loadWorkflow: (slugOrName) => (0, model_1.loadWorkflowByName)(workflowsDir, slugOrName),
274077
+ strictTemplates: args.strictTemplates ?? true,
274078
+ preflight: args.preflight ?? "abort",
274079
+ requestTimeoutMs: args.requestTimeoutMs ?? 3e4,
274080
+ // Persist per-step progress so jetic_get_run can be polled while the run is in flight.
274081
+ onStepResult: (step) => {
274082
+ (0, run_store_1.updateRunStatus)(jeticDir, runId, (doc) => {
274083
+ if (!doc.steps)
274084
+ doc.steps = [];
274085
+ const progress = (0, run_store_1.stepToProgress)(step);
274086
+ const at = doc.steps.findIndex((s) => s.index === step.stepIndex && s.name === step.name);
274087
+ if (at >= 0)
274088
+ doc.steps[at] = progress;
274089
+ else
274090
+ doc.steps.push(progress);
274091
+ doc.stats = (0, run_store_1.statsFromSteps)(doc.steps);
274092
+ });
274093
+ }
274094
+ });
274095
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
274096
+ const { reportPath: reportPath2 } = (0, run_store_1.writeSimulationRun)(jeticDir, runId, result2, {
274097
+ startedAt,
274098
+ finishedAt,
274099
+ workflowFile: selectedItem.filePath
274100
+ });
274101
+ return { result: result2, reportPath: reportPath2 };
274102
+ };
274103
+ if (args.async) {
274104
+ (0, run_store_1.writeRunningStatus)(jeticDir, runId, runMeta);
274105
+ execute().then(() => void 0).catch((err) => {
274106
+ try {
274107
+ (0, run_store_1.writeRunError)(jeticDir, runId, err, runMeta);
274108
+ } catch {
274109
+ }
274110
+ });
274111
+ return {
274112
+ status: "running",
274113
+ runId,
274114
+ workflowName: runMeta.workflowName,
274115
+ environment: { name: effectiveEnv.name, baseUrl: effectiveEnv.baseUrl },
274116
+ message: `Run started in the background against ${effectiveEnv.baseUrl}. Poll with jetic_get_run { "runId": "${runId}" } or jetic_list_runs.`
274117
+ };
274118
+ }
274119
+ const { result, reportPath } = await execute();
274120
+ if (args.includeStepResults) {
274121
+ return {
274122
+ runId,
274123
+ status: (0, run_store_1.deriveRunStatus)(result),
274124
+ reportPath,
274125
+ filePath,
274126
+ workflowFile: selectedItem.filePath,
274127
+ ...result
274128
+ };
274129
+ }
273481
274130
  return {
273482
- ...result,
274131
+ ...(0, run_store_1.compactSimulationResult)(result, runId, reportPath),
273483
274132
  filePath,
273484
274133
  workflowFile: selectedItem.filePath
273485
274134
  };
@@ -273576,9 +274225,11 @@ var require_workflow_authoring_tools = __commonJS({
273576
274225
  exports2.workflowStepSchema = zod_1.z.object({
273577
274226
  name: zod_1.z.string().min(1).describe("Step name (must be unique within the workflow)"),
273578
274227
  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)"),
274228
+ 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
274229
  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"),
274230
+ 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"),
274231
+ 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."),
274232
+ 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
274233
  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
274234
  capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" \u2192 "dot.path.in.response"'),
273584
274235
  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 +274249,6 @@ var require_workflow_authoring_tools = __commonJS({
273598
274249
  retry: stepRetrySchema.optional().describe("Global retry default applied to all steps (overridden per step)"),
273599
274250
  steps: zod_1.z.array(exports2.workflowStepSchema).min(1).describe("Ordered steps; later steps can use memory captured by earlier ones")
273600
274251
  }).passthrough();
273601
- var TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
273602
274252
  var KNOWN_WORKFLOW_KEYS = /* @__PURE__ */ new Set(["name", "description", "environment", "steps", "generatedAt", "retry"]);
273603
274253
  var KNOWN_STEP_KEYS = /* @__PURE__ */ new Set([
273604
274254
  "name",
@@ -273610,6 +274260,8 @@ var require_workflow_authoring_tools = __commonJS({
273610
274260
  "captureInput",
273611
274261
  "expectStatus",
273612
274262
  "body",
274263
+ "query",
274264
+ "timeoutMs",
273613
274265
  "condition",
273614
274266
  "onFailure",
273615
274267
  "continueOnStatus",
@@ -273647,10 +274299,35 @@ var require_workflow_authoring_tools = __commonJS({
273647
274299
  }
273648
274300
  function collectTemplateRefs(value, out) {
273649
274301
  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());
274302
+ let i = 0;
274303
+ while (i < value.length) {
274304
+ const open = value.indexOf("{{", i);
274305
+ if (open === -1)
274306
+ break;
274307
+ let depth = 0;
274308
+ let end = -1;
274309
+ for (let j = open + 2; j < value.length; j++) {
274310
+ const ch = value[j];
274311
+ if (ch === "{") {
274312
+ depth++;
274313
+ } else if (ch === "}") {
274314
+ if (depth > 0) {
274315
+ depth--;
274316
+ } else if (value[j + 1] === "}") {
274317
+ end = j;
274318
+ break;
274319
+ } else {
274320
+ break;
274321
+ }
274322
+ }
274323
+ }
274324
+ if (end === -1) {
274325
+ i = open + 2;
274326
+ continue;
274327
+ }
274328
+ out.push(value.slice(open + 2, end).trim());
274329
+ i = end + 2;
274330
+ }
273654
274331
  } else if (Array.isArray(value)) {
273655
274332
  for (const v of value)
273656
274333
  collectTemplateRefs(v, out);
@@ -273746,6 +274423,25 @@ var require_workflow_authoring_tools = __commonJS({
273746
274423
  const seenNames = /* @__PURE__ */ new Set();
273747
274424
  const defined = /* @__PURE__ */ new Set();
273748
274425
  const humanKeys = /* @__PURE__ */ new Set();
274426
+ const refsUsedAnywhere = /* @__PURE__ */ new Set();
274427
+ for (const rawStep of wf.steps) {
274428
+ const stepRefs = [];
274429
+ collectTemplateRefs(rawStep?.body, stepRefs);
274430
+ collectTemplateRefs(rawStep?.query, stepRefs);
274431
+ collectTemplateRefs(rawStep?.path, stepRefs);
274432
+ const inject = rawStep?.inject;
274433
+ if (inject && typeof inject === "object" && !Array.isArray(inject)) {
274434
+ for (const v of Object.values(inject))
274435
+ collectTemplateRefs(v, stepRefs);
274436
+ }
274437
+ for (const ref of stepRefs) {
274438
+ if (ref.startsWith("faker.") || !ref.includes(":"))
274439
+ continue;
274440
+ const { scope, key } = splitMemKey(ref);
274441
+ if (key)
274442
+ refsUsedAnywhere.add(`${scope}:${key}`);
274443
+ }
274444
+ }
273749
274445
  wf.steps.forEach((rawStep, i) => {
273750
274446
  const stepNo = i + 1;
273751
274447
  const sName = typeof rawStep?.name === "string" ? rawStep.name : void 0;
@@ -273759,7 +274455,7 @@ var require_workflow_authoring_tools = __commonJS({
273759
274455
  if (MODEL_DIALECT_KEYS.has(key)) {
273760
274456
  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
274457
  } 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).");
274458
+ 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
274459
  }
273764
274460
  }
273765
274461
  if ("condition" in step && step.condition !== void 0) {
@@ -273839,30 +274535,46 @@ var require_workflow_authoring_tools = __commonJS({
273839
274535
  if (step.body !== void 0 && (typeof step.body !== "object" || step.body === null || Array.isArray(step.body))) {
273840
274536
  err(errors, stepNo, sName, "BAD_BODY", `Step ${stepNo} "body" must be an object of field -> value.`, 'Use { "email": "{{faker.internet.email}}" }.');
273841
274537
  }
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.");
274538
+ if (step.query !== void 0 && (typeof step.query !== "object" || step.query === null || Array.isArray(step.query))) {
274539
+ err(errors, stepNo, sName, "BAD_QUERY", `Step ${stepNo} "query" must be an object of param -> value.`, 'Use { "query": { "page": 1, "search": "{{workflow:term}}" } }.');
274540
+ }
274541
+ if ((method === "GET" || method === "HEAD") && step.body && typeof step.body === "object" && !Array.isArray(step.body)) {
274542
+ const nested = Object.values(step.body).some((v) => v !== null && typeof v === "object");
274543
+ if (nested) {
274544
+ 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.');
274545
+ }
273844
274546
  }
273845
- if (endpoint?.requestBody?.fields && step.body && typeof step.body === "object") {
274547
+ if (endpoint?.requestBody?.fields && step.body && typeof step.body === "object" && method !== "GET" && method !== "HEAD") {
273846
274548
  for (const field of Object.keys(step.body)) {
273847
274549
  if (!(field in endpoint.requestBody.fields)) {
273848
274550
  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
274551
  }
273850
274552
  }
273851
274553
  }
274554
+ if (step.query && typeof step.query === "object" && !Array.isArray(step.query) && endpoint) {
274555
+ const declaredQuery = (endpoint.parameters || []).filter((p) => p.in === "query");
274556
+ if (declaredQuery.length > 0) {
274557
+ for (const param of Object.keys(step.query)) {
274558
+ if (!declaredQuery.some((p) => p.name === param)) {
274559
+ 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.");
274560
+ }
274561
+ }
274562
+ }
274563
+ }
273852
274564
  const pathParams = wfPath.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g) || [];
273853
274565
  for (const p of pathParams) {
273854
274566
  const pname = p.substring(1);
273855
274567
  const inBody = step.body && typeof step.body === "object" && pname in step.body;
274568
+ const inInjectBody = step.inject && typeof step.inject === "object" && !Array.isArray(step.inject) && Object.keys(step.inject).includes(`body:${pname}`);
273856
274569
  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}" }).`);
274570
+ if (!inBody && !inInjectBody && !inMemory) {
274571
+ 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
274572
  }
273860
274573
  }
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
274574
  const refs = [];
273865
274575
  collectTemplateRefs(step.body, refs);
274576
+ collectTemplateRefs(step.query, refs);
274577
+ collectTemplateRefs(step.path, refs);
273866
274578
  if (step.inject && typeof step.inject === "object") {
273867
274579
  for (const v of Object.values(step.inject))
273868
274580
  collectTemplateRefs(v, refs);
@@ -273871,6 +274583,8 @@ var require_workflow_authoring_tools = __commonJS({
273871
274583
  if (ref.startsWith("faker.")) {
273872
274584
  if (ref.split(".").length < 3) {
273873
274585
  warn(warnings, stepNo, sName, "SUSPICIOUS_FAKER", `"{{${ref}}}" looks like an incomplete faker path.`, "Use full paths like {{faker.internet.email}} or {{faker.string.uuid}}.");
274586
+ } else if (ref.includes("(") || ref.includes("{") || ref.includes("[")) {
274587
+ 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
274588
  }
273875
274589
  continue;
273876
274590
  }
@@ -273929,7 +274643,13 @@ var require_workflow_authoring_tools = __commonJS({
273929
274643
  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
274644
  }
273931
274645
  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.");
274646
+ const consumedDownstream = Object.keys(step.capture).some((k) => {
274647
+ const { scope, key } = splitMemKey(k);
274648
+ return key && refsUsedAnywhere.has(`${scope}:${key}`);
274649
+ });
274650
+ if (consumedDownstream) {
274651
+ 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.");
274652
+ }
273933
274653
  }
273934
274654
  }
273935
274655
  if (step.continueOnStatus !== void 0) {
@@ -288652,6 +289372,7 @@ var require_response_envelope = __commonJS({
288652
289372
  "WRITE_DISABLED",
288653
289373
  "INTERNAL"
288654
289374
  ];
289375
+ var MAX_MESSAGE_LENGTH = 500;
288655
289376
  function errorCodeFrom(error) {
288656
289377
  if (typeof error !== "object" || error === null || !("code" in error)) {
288657
289378
  return "INTERNAL";
@@ -288659,6 +289380,28 @@ var require_response_envelope = __commonJS({
288659
289380
  const code = error.code;
288660
289381
  return typeof code === "string" && ERROR_CODES.includes(code) ? code : "INTERNAL";
288661
289382
  }
289383
+ function describeError(error) {
289384
+ const record = typeof error === "object" && error !== null ? error : {};
289385
+ const issues = Array.isArray(record.issues) ? record.issues : void 0;
289386
+ if (issues && issues.length > 0) {
289387
+ const parts = issues.slice(0, 5).map((issue) => {
289388
+ const path15 = Array.isArray(issue?.path) ? issue.path.join(".") : "";
289389
+ return `${path15 ? `${path15}: ` : ""}${issue?.message ?? "invalid"}`;
289390
+ });
289391
+ const more = issues.length > 5 ? ` (+${issues.length - 5} more)` : "";
289392
+ return {
289393
+ message: `Invalid input \u2014 ${parts.join("; ")}${more}`,
289394
+ hint: "Fix the flagged argument(s) and retry. Use the tool schema for exact field types."
289395
+ };
289396
+ }
289397
+ const rawMessage = typeof record.message === "string" ? record.message.trim() : "";
289398
+ const message = rawMessage ? rawMessage.length > MAX_MESSAGE_LENGTH ? `${rawMessage.slice(0, MAX_MESSAGE_LENGTH)}\u2026` : rawMessage : "Tool execution failed.";
289399
+ const customHint = typeof record.hint === "string" && record.hint.trim() ? record.hint.trim() : "";
289400
+ return {
289401
+ message,
289402
+ hint: customHint || "Check the project configuration and arguments, then retry."
289403
+ };
289404
+ }
288662
289405
  function toTextResult(data) {
288663
289406
  return {
288664
289407
  content: [{
@@ -288668,6 +289411,7 @@ var require_response_envelope = __commonJS({
288668
289411
  };
288669
289412
  }
288670
289413
  function toErrorResult(error) {
289414
+ const { message, hint } = describeError(error);
288671
289415
  return {
288672
289416
  isError: true,
288673
289417
  content: [{
@@ -288676,8 +289420,8 @@ var require_response_envelope = __commonJS({
288676
289420
  ok: false,
288677
289421
  error: {
288678
289422
  code: errorCodeFrom(error),
288679
- message: "Tool execution failed.",
288680
- hint: "Check the project configuration and arguments, then retry."
289423
+ message,
289424
+ hint
288681
289425
  }
288682
289426
  }, null, 2)
288683
289427
  }]
@@ -289020,6 +289764,47 @@ var require_workflow_designer_tools = __commonJS({
289020
289764
  if (Object.keys(inject).length > 0)
289021
289765
  step.inject = inject;
289022
289766
  }
289767
+ const pathParams = [...new Set(endpoint.path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g) || [])].map((p) => p.slice(1));
289768
+ for (const pname of pathParams) {
289769
+ const inBody = step.body && typeof step.body === "object" && pname in step.body;
289770
+ if (inBody)
289771
+ continue;
289772
+ const memKey = [...producedKeys].find((k) => (k.split(":").pop() || "").toLowerCase() === pname.toLowerCase());
289773
+ if (memKey) {
289774
+ step.path = step.path.split(`:${pname}`).join(`{{${memKey}}}`);
289775
+ } else {
289776
+ 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.`);
289777
+ }
289778
+ }
289779
+ const queryParams = (endpoint.parameters || []).filter((p) => p.in === "query");
289780
+ if (queryParams.length > 0) {
289781
+ const query = {};
289782
+ for (const p of queryParams) {
289783
+ const memKey = [...producedKeys].find((k) => (k.split(":").pop() || "").toLowerCase() === p.name.toLowerCase());
289784
+ if (memKey) {
289785
+ query[p.name] = `{{${memKey}}}`;
289786
+ continue;
289787
+ }
289788
+ if (p.required !== true)
289789
+ continue;
289790
+ const val = fieldToFakerValue(p.name, {
289791
+ type: p.type || "string",
289792
+ format: p.format,
289793
+ min: p.min,
289794
+ max: p.max,
289795
+ enum: p.enum
289796
+ });
289797
+ if (val !== null) {
289798
+ query[p.name] = val;
289799
+ } else {
289800
+ query[p.name] = `{{human:${p.name}}}`;
289801
+ allHumanKeys.add(p.name);
289802
+ 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).`);
289803
+ }
289804
+ }
289805
+ if (Object.keys(query).length > 0)
289806
+ step.query = query;
289807
+ }
289023
289808
  const capture = {};
289024
289809
  if (endpoint.produces) {
289025
289810
  for (const p of endpoint.produces) {
@@ -289070,6 +289855,115 @@ var require_workflow_designer_tools = __commonJS({
289070
289855
  }
289071
289856
  });
289072
289857
 
289858
+ // ../../packages/mcp-server/dist/tools/run-tools.js
289859
+ var require_run_tools = __commonJS({
289860
+ "../../packages/mcp-server/dist/tools/run-tools.js"(exports2) {
289861
+ "use strict";
289862
+ init_cjs_shims();
289863
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
289864
+ if (k2 === void 0) k2 = k;
289865
+ var desc = Object.getOwnPropertyDescriptor(m, k);
289866
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
289867
+ desc = { enumerable: true, get: function() {
289868
+ return m[k];
289869
+ } };
289870
+ }
289871
+ Object.defineProperty(o, k2, desc);
289872
+ }) : (function(o, m, k, k2) {
289873
+ if (k2 === void 0) k2 = k;
289874
+ o[k2] = m[k];
289875
+ }));
289876
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
289877
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
289878
+ }) : function(o, v) {
289879
+ o["default"] = v;
289880
+ });
289881
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
289882
+ var ownKeys = function(o) {
289883
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
289884
+ var ar = [];
289885
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
289886
+ return ar;
289887
+ };
289888
+ return ownKeys(o);
289889
+ };
289890
+ return function(mod) {
289891
+ if (mod && mod.__esModule) return mod;
289892
+ var result = {};
289893
+ if (mod != null) {
289894
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
289895
+ }
289896
+ __setModuleDefault(result, mod);
289897
+ return result;
289898
+ };
289899
+ })();
289900
+ Object.defineProperty(exports2, "__esModule", { value: true });
289901
+ exports2.listRunsSchema = exports2.getRunSchema = void 0;
289902
+ exports2.handleGetRun = handleGetRun;
289903
+ exports2.handleListRuns = handleListRuns;
289904
+ var zod_1 = require("zod");
289905
+ var path15 = __importStar(require("path"));
289906
+ var core_1 = require_dist();
289907
+ var types_1 = require_types();
289908
+ var run_store_1 = require_run_store();
289909
+ exports2.getRunSchema = zod_1.z.object({
289910
+ projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder"),
289911
+ 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')
289912
+ });
289913
+ exports2.listRunsSchema = zod_1.z.object({
289914
+ projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder"),
289915
+ limit: zod_1.z.number().int().positive().max(100).optional().default(20).describe("Max runs to return, newest first (default 20)")
289916
+ });
289917
+ function handleGetRun(args) {
289918
+ const projectRoot = path15.resolve((0, types_1.resolveProjectInput)(args.projectPath));
289919
+ const config = (0, core_1.loadConfig)(projectRoot);
289920
+ const report = (0, run_store_1.readSimulationRun)(config.jeticDir, args.runId);
289921
+ if (report) {
289922
+ return {
289923
+ found: true,
289924
+ ...report,
289925
+ steps: report.result?.results ? report.result.results.map(run_store_1.stepToProgress) : []
289926
+ };
289927
+ }
289928
+ const status = (0, run_store_1.readRunStatus)(config.jeticDir, args.runId);
289929
+ if (status) {
289930
+ return {
289931
+ found: true,
289932
+ type: "simulation",
289933
+ runId: args.runId,
289934
+ status: status.status,
289935
+ workflowName: status.workflowName,
289936
+ environment: status.environment,
289937
+ baseUrl: status.baseUrl,
289938
+ startedAt: status.startedAt,
289939
+ finishedAt: status.finishedAt,
289940
+ stats: status.stats,
289941
+ ...status.steps ? { steps: status.steps } : {},
289942
+ reportPath: status.reportPath,
289943
+ ...status.error ? { error: status.error } : {},
289944
+ ...status.status === "running" ? { message: "Run still in progress \u2014 poll again shortly." } : {}
289945
+ };
289946
+ }
289947
+ const agentReport = (0, core_1.readJsonSync)(path15.join(config.jeticDir, "runs", args.runId, "agent-report.json"));
289948
+ if (agentReport)
289949
+ return { found: true, type: "agent", runId: args.runId, ...agentReport };
289950
+ const available = (0, run_store_1.listRuns)(config.jeticDir, 10).map((r) => `${r.runId} (${r.type}: ${r.status})`);
289951
+ const detail = available.length > 0 ? ` Recent runs: ${available.join(", ")}.` : " No runs found yet.";
289952
+ throw new Error(`Run "${args.runId}" not found under .jetic/runs/.${detail}`);
289953
+ }
289954
+ function handleListRuns(args) {
289955
+ const projectRoot = path15.resolve((0, types_1.resolveProjectInput)(args.projectPath));
289956
+ const config = (0, core_1.loadConfig)(projectRoot);
289957
+ const runs = (0, run_store_1.listRuns)(config.jeticDir, args.limit ?? 20);
289958
+ return {
289959
+ runsDir: path15.join(config.jeticDir, "runs"),
289960
+ totalCount: runs.length,
289961
+ runs
289962
+ };
289963
+ }
289964
+ }
289965
+ });
289966
+
289073
289967
  // ../../packages/mcp-server/dist/server.js
289074
289968
  var require_server3 = __commonJS({
289075
289969
  "../../packages/mcp-server/dist/server.js"(exports2) {
@@ -289134,6 +290028,7 @@ var require_server3 = __commonJS({
289134
290028
  var session_tools_1 = require_session_tools();
289135
290029
  var workflow_designer_tools_1 = require_workflow_designer_tools();
289136
290030
  var agent_tools_1 = require_agent_tools();
290031
+ var run_tools_1 = require_run_tools();
289137
290032
  exports2.JETIC_MCP_SERVER_NAME = "jetic-mcp-server";
289138
290033
  function getJeticMcpVersion() {
289139
290034
  try {
@@ -289222,6 +290117,9 @@ var require_server3 = __commonJS({
289222
290117
  "",
289223
290118
  "STEP 4g \u2014 RUN",
289224
290119
  ' jetic_simulate_workflow { "workflow": "<slug>" }',
290120
+ " It preflights the target first and fails fast with a clear reason when the app is unreachable.",
290121
+ ' The response is a compact summary + runId. Full step detail: jetic_get_run { "runId": "..." };',
290122
+ " recent runs: jetic_list_runs. For long journeys pass async:true, then poll jetic_get_run.",
289225
290123
  "",
289226
290124
  "\u2501\u2501 GENERAL RULES \u2501\u2501",
289227
290125
  "No AI-provider setup is needed: never ask for OpenRouter/OpenAI keys and never use `jetic config ai`.",
@@ -289289,6 +290187,8 @@ var require_server3 = __commonJS({
289289
290187
  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
290188
  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
290189
  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);
290190
+ 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);
290191
+ 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
290192
  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
290193
  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
290194
  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);
@@ -290240,6 +291140,14 @@ function deepGet(obj, dotPath) {
290240
291140
  }
290241
291141
  return current;
290242
291142
  }
291143
+ var TRACE_BODY_CAP = 16384;
291144
+ function truncateTraceBody(value) {
291145
+ if (value === void 0 || value === null) return value;
291146
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
291147
+ if (serialized.length <= TRACE_BODY_CAP) return value;
291148
+ return typeof value === "string" ? `${value.slice(0, TRACE_BODY_CAP)}
291149
+ \u2026[truncated: ${serialized.length - TRACE_BODY_CAP} more characters]` : { _truncated: true, preview: serialized.slice(0, TRACE_BODY_CAP), fullLengthChars: serialized.length };
291150
+ }
290243
291151
  async function resolveConditionRef(ref, context = "condition") {
290244
291152
  return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref, context);
290245
291153
  }
@@ -290249,7 +291157,21 @@ async function evaluateStepCondition(condition, context = "condition") {
290249
291157
  async function preStepCondition(step) {
290250
291158
  if (!step.condition) return null;
290251
291159
  const label = step.name || `${step.method} ${step.path}`;
290252
- const base = { step, status: 0, responseBody: null, durationMs: 0, captured: [], injected: {} };
291160
+ const now = (/* @__PURE__ */ new Date()).toISOString();
291161
+ const base = {
291162
+ step,
291163
+ status: 0,
291164
+ responseBody: null,
291165
+ durationMs: 0,
291166
+ startedAt: now,
291167
+ finishedAt: now,
291168
+ passed: false,
291169
+ captured: [],
291170
+ injected: {},
291171
+ requestHeaders: {},
291172
+ requestBody: {},
291173
+ resolvedUrl: ""
291174
+ };
290253
291175
  const kind = (0, import_simulator2.conditionKind)(step.condition);
290254
291176
  if (kind === "invalid") {
290255
291177
  return { ...base, passed: false, error: `Step "${label}": ${(0, import_simulator2.describeInvalidCondition)(step.condition)}` };
@@ -290382,10 +291304,24 @@ async function resolveTemplateString(value, context) {
290382
291304
  }
290383
291305
  return result;
290384
291306
  }
291307
+ async function resolveDeepValue(value, context) {
291308
+ if (typeof value === "string") return resolveTemplateString(value, context);
291309
+ if (Array.isArray(value)) {
291310
+ const out = [];
291311
+ for (const item of value) out.push(await resolveDeepValue(item, context));
291312
+ return out;
291313
+ }
291314
+ if (value && typeof value === "object") {
291315
+ const out = {};
291316
+ for (const [key, item] of Object.entries(value)) out[key] = await resolveDeepValue(item, context);
291317
+ return out;
291318
+ }
291319
+ return value;
291320
+ }
290385
291321
  async function resolveBodyTemplates(body, context) {
290386
291322
  const resolved = {};
290387
291323
  for (const [key, value] of Object.entries(body)) {
290388
- resolved[key] = typeof value === "string" ? await resolveTemplateString(value, context) : value;
291324
+ resolved[key] = await resolveDeepValue(value, context);
290389
291325
  }
290390
291326
  return resolved;
290391
291327
  }
@@ -290404,6 +291340,10 @@ async function resolveInjections(inject, memory, allMemory, context) {
290404
291340
  if (value === null) continue;
290405
291341
  strValue = typeof value === "string" ? value : JSON.stringify(value);
290406
291342
  }
291343
+ const headerName = target.startsWith("header:") ? target.slice(7) : target.startsWith("body:") ? null : target;
291344
+ if (headerName && headerName.toLowerCase() === "authorization" && strValue && !/^[A-Za-z][A-Za-z0-9-]*\s/.test(strValue)) {
291345
+ strValue = `Bearer ${strValue}`;
291346
+ }
290407
291347
  if (target.startsWith("body:")) {
290408
291348
  body[target.slice(5)] = strValue;
290409
291349
  } else if (target.startsWith("header:")) {
@@ -290446,8 +291386,10 @@ async function resolvePathParams(urlPath, body) {
290446
291386
  return `:${param}`;
290447
291387
  });
290448
291388
  }
291389
+ var REQUEST_TIMEOUT_MS = 3e4;
290449
291390
  async function executeStep(step, baseUrl, defaultBody) {
290450
291391
  const startTime = Date.now();
291392
+ const startedAt = new Date(startTime).toISOString();
290451
291393
  const memory = new import_memory2.JeticMemory({ scope: "workflow" });
290452
291394
  const stepLabel = step.name || `${step.method} ${step.path}`;
290453
291395
  const { headers, body: injectedBody } = await resolveInjections(step.inject, memory, memory, stepLabel);
@@ -290457,13 +291399,12 @@ async function executeStep(step, baseUrl, defaultBody) {
290457
291399
  const resolvedPath = await resolvePathParams(step.path, requestBody);
290458
291400
  const url = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
290459
291401
  const expectedStatus = step.expectStatus ?? 200;
291402
+ const requestHeaders = { "Content-Type": "application/json", ...headers };
290460
291403
  try {
290461
291404
  const fetchOptions = {
290462
291405
  method: step.method.toUpperCase(),
290463
- headers: {
290464
- "Content-Type": "application/json",
290465
- ...headers
290466
- }
291406
+ headers: requestHeaders,
291407
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
290467
291408
  };
290468
291409
  if (!["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(requestBody).length > 0) {
290469
291410
  fetchOptions.body = JSON.stringify(requestBody);
@@ -290496,20 +291437,39 @@ async function executeStep(step, baseUrl, defaultBody) {
290496
291437
  status: response.status,
290497
291438
  responseBody,
290498
291439
  durationMs,
291440
+ startedAt,
291441
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
290499
291442
  passed,
290500
291443
  captured: [...inputCaptured, ...captured],
290501
- injected: headers
291444
+ injected: headers,
291445
+ requestHeaders,
291446
+ requestBody,
291447
+ resolvedUrl: finalUrl,
291448
+ responseHeaders: Object.fromEntries(response.headers.entries())
290502
291449
  };
290503
291450
  } catch (err) {
291451
+ const code = (0, import_simulator2.networkErrorCode)(err);
291452
+ let message = err.message || String(err);
291453
+ if (err?.name === "TimeoutError" || code === "ETIMEDOUT") {
291454
+ message = `Request timed out after ${REQUEST_TIMEOUT_MS}ms (${step.method} ${resolvedPath}).`;
291455
+ } else if (code === "ECONNREFUSED") {
291456
+ message = `Connection refused (ECONNREFUSED) at ${baseUrl} \u2014 the server is not accepting connections.`;
291457
+ }
290504
291458
  return {
290505
291459
  step,
290506
291460
  status: 0,
290507
291461
  responseBody: null,
290508
291462
  durationMs: Date.now() - startTime,
291463
+ startedAt,
291464
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
290509
291465
  passed: false,
290510
291466
  captured: inputCaptured,
290511
- error: err.message || String(err),
290512
- injected: headers
291467
+ error: message,
291468
+ injected: headers,
291469
+ requestHeaders,
291470
+ requestBody,
291471
+ resolvedUrl: url,
291472
+ connectionError: code ? import_simulator2.CONNECTION_ERROR_CODES.has(code) : false
290513
291473
  };
290514
291474
  }
290515
291475
  }
@@ -290757,17 +291717,23 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290757
291717
  }
290758
291718
  const checkSpinner = new Spinner();
290759
291719
  checkSpinner.start(`Checking if backend is active at ${baseUrl}...`);
290760
- try {
290761
- await fetch(baseUrl);
290762
- checkSpinner.stop(` ${c.green}\u2713${c.reset} Backend is active`);
290763
- console.log("");
290764
- } catch (e) {
291720
+ const probe = await (0, import_simulator2.probeTarget)(baseUrl, 3e3);
291721
+ if (probe.verdict === "unreachable") {
290765
291722
  checkSpinner.stop();
290766
291723
  console.log(` ${c.red}\u2717${c.reset} Backend is unreachable at ${c.bold}${baseUrl}${c.reset}`);
290767
- console.log(` ${c.yellow}\u26A0${c.reset} Please run/initialize your backend project and try again.
291724
+ console.log(` ${c.red}${probe.message}${c.reset}`);
291725
+ console.log(` ${c.yellow}\u26A0${c.reset} Start your backend project and try again.
290768
291726
  `);
290769
291727
  process.exit(1);
290770
291728
  }
291729
+ if (probe.verdict === "server-error") {
291730
+ checkSpinner.stop(` ${c.yellow}\u26A0${c.reset} Backend reachable but failing (HTTP ${probe.status}) at ${baseUrl}`);
291731
+ console.log(` ${c.yellow}${probe.message}${c.reset}
291732
+ `);
291733
+ } else {
291734
+ checkSpinner.stop(` ${c.green}\u2713${c.reset} Backend is active (HTTP ${probe.status})`);
291735
+ console.log("");
291736
+ }
290771
291737
  renderWorkflowHeader(workflow);
290772
291738
  console.log(` ${c.magenta}\u{1F680}${c.reset} Executing workflow steps...
290773
291739
  `);
@@ -290881,6 +291847,12 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290881
291847
  const icon = result.skipped ? SKIP : result.passed ? TICK : CROSS;
290882
291848
  spinner.stop(` ${icon} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
290883
291849
  renderStepResult(result, i, workflow.steps.length);
291850
+ if (result.connectionError) {
291851
+ console.log(` ${c.red}${c.bold}\u26A0 Target unreachable \u2014 aborting early.${c.reset}
291852
+ `);
291853
+ stopOnFailure = true;
291854
+ break;
291855
+ }
290884
291856
  if (!result.passed && result.error && !result.status && (0, import_simulator2.conditionKind)(step.condition) === "invalid") {
290885
291857
  console.log(` ${c.red}${c.bold}\u26A0 Unrecognized condition \u2014 stopping workflow.${c.reset}
290886
291858
  `);
@@ -290933,12 +291905,19 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290933
291905
  path: r.step?.path,
290934
291906
  description: r.step?.description,
290935
291907
  status: r.status,
291908
+ responseStatus: r.status,
290936
291909
  passed: r.passed,
290937
291910
  durationMs: r.durationMs,
291911
+ startedAt: r.startedAt,
291912
+ finishedAt: r.finishedAt,
291913
+ resolvedUrl: r.resolvedUrl,
291914
+ requestHeaders: r.requestHeaders,
291915
+ requestBody: r.requestBody,
291916
+ requestBodySpec: r.step?.body,
291917
+ responseHeaders: r.responseHeaders,
291918
+ responseBody: truncateTraceBody(r.responseBody),
290938
291919
  captured: Object.fromEntries((r.captured ?? []).map((cap) => [cap, "(captured)"])),
290939
291920
  injected: r.injected ?? {},
290940
- requestBody: r.step?.body,
290941
- responseBody: r.responseBody,
290942
291921
  error: r.error ?? null,
290943
291922
  expectStatus: r.step?.expectStatus,
290944
291923
  captureSpec: r.step?.capture,
@@ -292564,7 +293543,7 @@ var mcpCommand = new import_commander18.Command("mcp").description("Launch Model
292564
293543
 
292565
293544
  // src/index.ts
292566
293545
  var program = new import_commander19.Command();
292567
- program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.5");
293546
+ program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.7");
292568
293547
  program.addCommand(initCommand);
292569
293548
  program.addCommand(scanCommand);
292570
293549
  program.addCommand(inspectCommand);